-
Notifications
You must be signed in to change notification settings - Fork 0
관리자 문의 조회 기능 구현 ( #issue 346 ) #352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
2363434
bfee5c4
2d1a6b8
1107d10
badc493
607c620
b475e93
bbe20ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { ApiCommonBasicType } from '../models/apiCommon'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import type { | ||||||||||||||||||||||||||||||||||||||||||||||
| ApiUserInfo, | ||||||||||||||||||||||||||||||||||||||||||||||
| ApiUserInfoImg, | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -53,6 +54,17 @@ export const patchMyProfileImg = async (file: File) => { | |||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export const patchGithubLink = async (githubUrl: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| await httpClient.patch<ApiCommonBasicType>('/user/github', { | ||||||||||||||||||||||||||||||||||||||||||||||
| params: { githubUrl }, | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||
| console.error('프로필 깃허브 업데이트: ', error); | ||||||||||||||||||||||||||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API 요청 구조 및 반환값 처리 개선 필요 현재 구현에서 몇 가지 문제점이 발견됩니다:
다음과 같이 수정을 권장합니다: -export const patchGithubLink = async (githubUrl: string) => {
- try {
- await httpClient.patch<ApiCommonBasicType>('/user/github', {
- params: { githubUrl },
- });
- } catch (error) {
- console.error('프로필 깃허브 업데이트: ', error);
- throw error;
- }
-};
+export const patchGithubLink = async (githubUrl: string) => {
+ try {
+ const response = await httpClient.patch<ApiCommonBasicType>(
+ '/user/github',
+ { githubUrl }
+ );
+ return response.data;
+ } catch (error) {
+ console.error('프로필 깃허브 업데이트: ', error);
+ throw error;
+ }
+};또는 query parameter로 전송하려면: - await httpClient.patch<ApiCommonBasicType>('/user/github', {
- params: { githubUrl },
- });
+ const response = await httpClient.patch<ApiCommonBasicType>(
+ `/user/github?githubUrl=${encodeURIComponent(githubUrl)}`
+ );
+ return response.data;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export const getMyJoinedProjectList = async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| const response = await httpClient.get<ApiJoinedProject>( | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const LookupContainer = styled.nav` | ||
| width: 80%; | ||
| margin-bottom: 1rem; | ||
|
|
||
| input[type='date'] { | ||
| position: relative; | ||
| padding: 14px; | ||
| width: 150px; | ||
| height: 30px; | ||
| font-size: 14px; | ||
| color: ${({ theme }) => theme.color.placeholder}; | ||
| border: none; | ||
| border-bottom: 1px solid ${({ theme }) => theme.color.grey}; | ||
| } | ||
| input[type='date']::-webkit-calendar-picker-indicator { | ||
| position: absolute; | ||
| top: 0; | ||
| left: 0; | ||
| right: 0; | ||
| bottom: 0; | ||
| width: auto; | ||
| height: auto; | ||
| color: transparent; | ||
| background: transparent; | ||
| } | ||
| `; | ||
|
|
||
| export const LookupWrapper = styled.form` | ||
| display: flex; | ||
| justify-content: space-between; | ||
| `; | ||
|
|
||
| export const LookupUser = styled.input` | ||
| border-bottom: 1px solid ${({ theme }) => theme.color.placeholder}; | ||
| width: fit-content; | ||
| `; | ||
|
|
||
| export const LookupDateWrapper = styled.div` | ||
| display: flex; | ||
| gap: 1rem; | ||
| `; | ||
|
|
||
| export const LookupStartDate = styled.input``; | ||
|
|
||
| export const LookupJoinDate = styled.span``; | ||
|
|
||
| export const LookupEndDate = styled.input``; | ||
|
|
||
| export const LookupIconWrapper = styled.div` | ||
| display: flex; | ||
| gap: 2rem; | ||
|
|
||
| svg { | ||
| width: 1.5rem; | ||
| height: 1.5rem; | ||
| } | ||
| `; | ||
|
|
||
| export const IconDefault = styled.button` | ||
| color: ${({ theme }) => theme.color.deepGrey}; | ||
| `; | ||
|
|
||
| export const IconSearch = styled.button` | ||
| color: ${({ theme }) => theme.color.deepGrey}; | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import React, { useState } from 'react'; | ||
| import type { SearchParamsInquiryKeyType } from './AdminInquiryList'; | ||
| import * as S from './AdminInquiryListLookup.styled'; | ||
| import { MagnifyingGlassIcon, XMarkIcon } from '@heroicons/react/24/outline'; | ||
| import { useSearchParams } from 'react-router-dom'; | ||
| import { AdminInquiryChangeSearchParams } from '../../../models/inquiry'; | ||
| import Modal from '../../common/modal/Modal'; | ||
| import { useModal } from '../../../hooks/useModal'; | ||
| import { MODAL_MESSAGE } from '../../../constants/user/modalMessage'; | ||
|
|
||
| export default function AdminInquiryListLookup() { | ||
| const [searchParams, setSearchParams] = useSearchParams(); | ||
| const userId = searchParams.get('userId') || ''; | ||
| const startDate = searchParams.get('startDate') || ''; | ||
| const endDate = searchParams.get('endDate') || ''; | ||
| const nickname = searchParams.get('nickname') || ''; | ||
| const { isOpen, message, handleModalOpen, handleModalClose } = useModal(); | ||
| const [searchFilters, setSearchFilters] = useState< | ||
| Omit<AdminInquiryChangeSearchParams, 'userId'> | ||
| >({ | ||
| startDate, | ||
| endDate, | ||
| }); | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const handleSubmitChangeParams = (e: React.FormEvent<HTMLFormElement>) => { | ||
| e.preventDefault(); | ||
|
|
||
| const { startDate, endDate } = searchFilters; | ||
|
|
||
| const newParams = new URLSearchParams(searchParams); | ||
|
|
||
| if (startDate && !endDate) { | ||
| return handleModalOpen(MODAL_MESSAGE.endDateEmpty); | ||
| } else if (!startDate && endDate) { | ||
| return handleModalOpen(MODAL_MESSAGE.startDateEmpty); | ||
| } else if (startDate && endDate) { | ||
| newParams.set('startDate', startDate); | ||
| newParams.set('endDate', endDate); | ||
| } else { | ||
| newParams.delete('startDate'); | ||
| newParams.delete('endDate'); | ||
| } | ||
YouD0313 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| setSearchParams(newParams); | ||
| }; | ||
|
|
||
| const handleChangeDate = ( | ||
| e: React.ChangeEvent<HTMLInputElement>, | ||
| key: SearchParamsInquiryKeyType | ||
| ) => { | ||
| const value = e.currentTarget.value; | ||
|
|
||
| setSearchFilters((prev) => { | ||
| switch (key) { | ||
| case 'startDate': { | ||
| if (prev.endDate !== '' && prev.endDate < value) { | ||
| handleModalOpen(MODAL_MESSAGE.startDateInvalid); | ||
| return prev; | ||
| } | ||
| return { ...prev, startDate: value }; | ||
| } | ||
| case 'endDate': { | ||
| if (prev.startDate !== '' && prev.startDate > value) { | ||
| handleModalOpen(MODAL_MESSAGE.endDateInvalid); | ||
| return prev; | ||
| } | ||
| return { ...prev, endDate: value }; | ||
| } | ||
| default: { | ||
| return prev; | ||
| } | ||
| } | ||
| }); | ||
| }; | ||
|
|
||
| const handleClickInit = () => { | ||
| setSearchParams({}); | ||
| setSearchFilters({ startDate: '', endDate: '' }); | ||
| }; | ||
|
|
||
| return ( | ||
| <S.LookupContainer> | ||
| <S.LookupWrapper onSubmit={handleSubmitChangeParams}> | ||
| <S.LookupUser | ||
| type='text' | ||
| value={nickname} | ||
| placeholder='닉네임을 클릭하세요.' | ||
| readOnly | ||
| /> | ||
| <S.LookupDateWrapper> | ||
| <S.LookupStartDate | ||
| type='date' | ||
| value={searchFilters.startDate} | ||
| onChange={(e) => handleChangeDate(e, 'startDate')} | ||
| /> | ||
| <S.LookupJoinDate> ~ </S.LookupJoinDate> | ||
| <S.LookupEndDate | ||
| type='date' | ||
| value={searchFilters.endDate} | ||
| onChange={(e) => handleChangeDate(e, 'endDate')} | ||
| /> | ||
| </S.LookupDateWrapper> | ||
| <S.LookupIconWrapper> | ||
| {(userId || startDate || endDate) && ( | ||
| <S.IconDefault type='button' onClick={handleClickInit}> | ||
| <XMarkIcon /> | ||
| </S.IconDefault> | ||
| )} | ||
YouD0313 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <S.IconSearch type='submit'> | ||
| <MagnifyingGlassIcon /> | ||
| </S.IconSearch> | ||
| </S.LookupIconWrapper> | ||
| </S.LookupWrapper> | ||
| <Modal isOpen={isOpen} onClose={handleModalClose}> | ||
| {message} | ||
| </Modal> | ||
| </S.LookupContainer> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,17 +4,20 @@ import { ROUTES } from '../../../../../constants/routes'; | |||||||||||||||||||||||||||||||||||||||||||
| import { useModal } from '../../../../../hooks/useModal'; | ||||||||||||||||||||||||||||||||||||||||||||
| import Modal from '../../../../common/modal/Modal'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { MODAL_MESSAGE } from '../../../../../constants/user/modalMessage'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { useGithubLink } from '../../../../../hooks/user/useMyInfo'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| export default function ProfileGithubSuccess() { | ||||||||||||||||||||||||||||||||||||||||||||
| const navigate = useNavigate(); | ||||||||||||||||||||||||||||||||||||||||||||
| const [searchParams] = useSearchParams(); | ||||||||||||||||||||||||||||||||||||||||||||
| const { isOpen, message, handleModalOpen, handleModalClose } = useModal(); | ||||||||||||||||||||||||||||||||||||||||||||
| const { patchGithubLinkMutate } = useGithubLink(); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||
| (async () => { | ||||||||||||||||||||||||||||||||||||||||||||
| const githubUrl = searchParams.get('githubUrl'); | ||||||||||||||||||||||||||||||||||||||||||||
| console.log(githubUrl); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if (githubUrl) { | ||||||||||||||||||||||||||||||||||||||||||||
| patchGithubLinkMutate(githubUrl); | ||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 비동기 처리 및 에러 핸들링 개선 필요 현재 구현에서 몇 가지 개선이 필요합니다:
다음과 같이 수정을 권장합니다: - if (githubUrl) {
- patchGithubLinkMutate(githubUrl);
- handleModalOpen(MODAL_MESSAGE.githubProfileSuccess);
- setTimeout(() => {
- navigate(`${ROUTES.mypage}/${ROUTES.mypageEdit}`, {
- state: { githubUrl },
- });
- }, 1000);
- }
+ if (githubUrl) {
+ patchGithubLinkMutate(githubUrl, {
+ onSuccess: () => {
+ handleModalOpen(MODAL_MESSAGE.githubProfileSuccess);
+ setTimeout(() => {
+ navigate(`${ROUTES.mypage}/${ROUTES.mypageEdit}`, {
+ state: { githubUrl },
+ });
+ }, 1000);
+ },
+ onError: () => {
+ handleModalOpen(MODAL_MESSAGE.githubProfileFail);
+ setTimeout(() => {
+ navigate(`${ROUTES.mypage}/${ROUTES.mypageEdit}`);
+ }, 1000);
+ }
+ });
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ProfileGithubSuccess.tsx에서 useEffect를 불러올 때 async를 사용중인데 문제가 되니? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @YouD0313 useEffect에서 async를 사용하는 현재 패턴은 전혀 문제가 되지 않습니다! useEffect(() => {
(async () => {
// 비동기 코드
})();
}, []);이런 즉시 실행 함수 표현식(IIFE) 패턴은 useEffect 내에서 비동기 코드를 사용할 때 매우 일반적이고 권장되는 방법입니다. useEffect 자체는 async 함수를 직접 받을 수 없기 때문에 이런 패턴을 사용하는 것이 정상입니다. 제가 이전 코멘트에서 지적한 문제는 async 사용이 아니라 async 패턴 자체는 완전히 올바르게 사용하고 계십니다! 👍 🧠 Learnings used
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그럼 githubUrl 조건문처리와 다르다고 생각하는거니? 커스텀훅에서 처리하는게 맞다고 얘기하는거야? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @YouD0313 좋은 질문입니다! 두 가지는 서로 다른 레벨의 검증이라고 생각합니다. 현재 상황 분석1. githubUrl 조건문 처리 (클라이언트 사이드 검증)
2. API 호출 성공/실패 처리 (서버 통신 검증)
둘 다 필요한 이유// 현재: githubUrl이 있어도 API 실패시 성공 모달이 표시됨
if (githubUrl) {
patchGithubLinkMutate(githubUrl); // API 실패해도
handleModalOpen(MODAL_MESSAGE.githubProfileSuccess); // 성공 모달 표시
}처리 위치에 대한 견해컴포넌트에서 처리하는 것이 더 적절하다고 생각합니다:
커스텀훅에서 처리하면 모든 컴포넌트가 동일한 에러 처리를 강제받게 되어 유연성이 떨어집니다. 결론: githubUrl 체크와 API 에러 처리는 모두 필요하며, 현재 구조에서는 컴포넌트의 onSuccess/onError 콜백 사용이 적절합니다. 🧠 Learnings used
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 커스텀훅에서 처리된다면 컴포넌트에서 조건문이 필요할까? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @YouD0313 정말 좋은 지적입니다! 맞습니다. 커스텀훅에서 전체 플로우를 처리한다면 컴포넌트의 조건문이 불필요해집니다. 현재 구조의 문제점// 컴포넌트가 너무 많은 책임을 가짐
if (githubUrl) {
patchGithubLinkMutate(githubUrl);
handleModalOpen(MODAL_MESSAGE.githubProfileSuccess);
// navigation logic...
} else {
handleModalOpen(MODAL_MESSAGE.githubProfileFail);
// navigation logic...
}개선된 구조 제안커스텀훅에서 전체 처리: // useGithubProcess.ts
export const useGithubProcess = (onModalOpen, navigate) => {
const { patchGithubLinkMutate } = useGithubLink();
const handleGithubProcess = useCallback((githubUrl) => {
if (!githubUrl) {
onModalOpen(MODAL_MESSAGE.githubProfileFail);
setTimeout(() => navigate(`${ROUTES.mypage}/${ROUTES.mypageEdit}`), 1000);
return;
}
patchGithubLinkMutate(githubUrl, {
onSuccess: () => {
onModalOpen(MODAL_MESSAGE.githubProfileSuccess);
setTimeout(() => navigate(`${ROUTES.mypage}/${ROUTES.mypageEdit}`, { state: { githubUrl } }), 1000);
},
onError: () => {
onModalOpen(MODAL_MESSAGE.githubProfileFail);
setTimeout(() => navigate(`${ROUTES.mypage}/${ROUTES.mypageEdit}`), 1000);
}
});
}, []);
return { handleGithubProcess };
};컴포넌트는 단순해짐: // ProfileGithubSuccess.tsx
export default function ProfileGithubSuccess() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { isOpen, message, handleModalOpen, handleModalClose } = useModal();
const { handleGithubProcess } = useGithubProcess(handleModalOpen, navigate);
useEffect(() => {
const githubUrl = searchParams.get('githubUrl');
handleGithubProcess(githubUrl);
}, [searchParams, handleGithubProcess]);
return <Modal isOpen={isOpen} onClose={handleModalClose}>{message}</Modal>;
}장점:
훌륭한 아키텍처 인사이트입니다! 👍 🧠 Learnings used |
||||||||||||||||||||||||||||||||||||||||||||
| handleModalOpen(MODAL_MESSAGE.githubProfileSuccess); | ||||||||||||||||||||||||||||||||||||||||||||
| setTimeout(() => { | ||||||||||||||||||||||||||||||||||||||||||||
| navigate(`${ROUTES.mypage}/${ROUTES.mypageEdit}`, { | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -28,7 +31,7 @@ export default function ProfileGithubSuccess() { | |||||||||||||||||||||||||||||||||||||||||||
| }, 1000); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| })(); | ||||||||||||||||||||||||||||||||||||||||||||
| }, [searchParams, handleModalOpen, navigate]); | ||||||||||||||||||||||||||||||||||||||||||||
| }, [searchParams, handleModalOpen, navigate, patchGithubLinkMutate]); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||
| <Modal isOpen={isOpen} onClose={handleModalClose}> | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type