Conversation
|
""" Walkthrough이번 변경 사항에서는 예약 메일(이메일) 기능이 대대적으로 도입 및 통합되었습니다. 예약 메일의 생성, 조회, 수정, 삭제를 위한 API 모듈과 타입 정의가 추가되었고, 상태 관리를 위한 Zustand 스토어와 React Query 기반의 커스텀 훅이 구현되었습니다. 여러 UI 컴포넌트 및 페이지가 이 새로운 상태 관리와 API 연동 구조에 맞게 리팩토링되었습니다. 메일 입력, 전송, 편집, 삭제, 시간 선택 등의 기능이 중앙 집중적으로 관리되며, 기존의 로컬 상태 사용 방식이 스토어 기반으로 전환되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant EmailInput
participant EmailStore
participant TimePicker
participant useSendEmail/useEditEmail
participant API(mail.ts)
participant MailBoxPage
User->>EmailInput: 제목/내용/수신자 입력
EmailInput->>EmailStore: 상태 업데이트
User->>TimePicker: 날짜/시간 선택
TimePicker->>EmailStore: 날짜/시간 업데이트
User->>EmailInput: "보내기" 클릭
EmailInput->>useSendEmail/useEditEmail: 메일 데이터 전달
useSendEmail/useEditEmail->>API(mail.ts): HTTP 요청 (생성/수정)
API(mail.ts)-->>useSendEmail/useEditEmail: 응답 반환
useSendEmail/useEditEmail->>MailBoxPage: 성공 시 이동/알림
Suggested reviewers
Poem
""" ✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 10
🔭 Outside diff range comments (1)
src/pages/dashboard/ui/mail/MailBoxPage.tsx (1)
31-31: 💡 Verification agent🧩 Analysis chain
검색 기능이 구현되지 않은 것 같습니다.
SearchBar 컴포넌트가 있지만 실제 검색 기능이 구현되지 않은 것으로 보입니다. 필터링 로직을 추가하거나 아직 구현 예정이라면 TODO 주석을 추가하는 것이 좋습니다.
🏁 Script executed:
#!/bin/bash # SearchBar 컴포넌트의 검색 기능 구현 확인 # SearchBar 컴포넌트 코드 찾기 rg -A 5 "SearchBar" "src/shared/ui/SearchBar.tsx" || echo "SearchBar 컴포넌트 파일을 찾을 수 없습니다." # 검색어 상태 및 필터링 로직이 있는지 확인 rg -A 10 "const \[search" "src/pages/dashboard/ui/mail/MailBoxPage.tsx" || echo "검색어 상태가 정의되지 않았습니다."Length of output: 548
검색 기능 로직 미구현 – 상태 관리 및 필터링 또는 TODO 주석 추가 필요
MailBoxPage.tsx에서
SearchBar가 렌더링만 되고 실제 검색어 상태(state) 관리나 필터링 로직이 없습니다. 다음 중 하나를 적용해주세요:
search상태 및setSearch정의SearchBar에value={search}및onChange={…}전달- 메일 목록을
search값으로 필터링하는 로직 추가- 아직 구현 중이라면 TODO 주석↑ 삽입
예시:
-import <SearchBar placeholder="제목 검색" className="w-[35%] my-2" /> +const [search, setSearch] = useState(""); + +<SearchBar + placeholder="제목 검색" + className="w-[35%] my-2" + value={search} + onChange={setSearch} />
🧹 Nitpick comments (13)
src/features/event-manage/event-create/ui/TimePicker.tsx (1)
16-25: 날짜와 시간 형식 개선 가능형식화된 날짜와 시간을 상위 컴포넌트에 전달하는 방식은 좋지만, 시간 형식이 'HH:mm'으로 표준화되어 있지 않을 수 있습니다.
시간 형식을 항상 표준화된 형태로 유지하도록 수정을 제안합니다:
useEffect(() => { - const formattedTime = `${selectedHour}:${selectedMinute}`; + // HH:mm 형식 보장 + const formattedTime = `${selectedHour.padStart(2, '0')}:${selectedMinute.padStart(2, '0')}`; onTimeChange(formattedTime); }, [selectedHour, selectedMinute]);src/features/dashboard/model/EmailStore.tsx (1)
25-25: 통합 계획에 대한 설명 필요주석에 "추후 2개 통합 예정"이라고 되어 있는데, 이 내용은 코드에서 직접적으로 전달되지 않습니다.
주석을 좀 더 명확하게 작성하는 것이 좋겠습니다:
- reservationTime: '', // 추후 2개 통합 예정 + reservationTime: '', // 추후 reservationDate와 통합하여 ISO 형식의 단일 필드로 변경 예정src/pages/dashboard/ui/mail/MailBoxPage.tsx (1)
52-53: 불필요한 삼항 연산자 사용을 개선하세요.불필요한 boolean 리터럴이 있는 삼항 연산자를 사용하고 있습니다. 더 간결하게 작성할 수 있습니다.
- isPending={listType === 'pending' ? true : false} + isPending={listType === 'pending'}src/pages/dashboard/ui/mail/EmailEditPage.tsx (2)
11-59: 폼 유효성 검사 추가를 고려하세요.현재 구현에는 이메일 데이터 제출 전에 필수 필드가 입력되었는지 확인하는 유효성 검사가 없습니다. 제목, 내용, 수신자 등의 필수 필드에 대한 검증을 추가하는 것이 좋을 것 같습니다.
다음과 같은 유효성 검사 로직을 handleEdit 함수에 추가할 수 있습니다:
const handleEdit = () => { const eventId = id ? parseInt(id) : 0; + // 필수 필드 유효성 검사 + if (!title.trim()) { + alert('제목을 입력해주세요.'); + return; + } + if (!content.trim()) { + alert('내용을 입력해주세요.'); + return; + } + if (recipients.length === 0) { + alert('받는 사람을 추가해주세요.'); + return; + } + if (!reservationDate || !reservationTime) { + alert('예약 날짜와 시간을 선택해주세요.'); + return; + } + const emailData = { eventId, title, content, recipients, reservationDate, reservationTime, }; editEmail({ reservationEmailId: reservationEmailId, data: emailData,}); };🧰 Tools
🪛 ESLint
[error] 47-47: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
43-57: 로딩 상태 표시 기능을 추가하는 것이 좋을 것 같습니다.editEmail 뮤테이션이 진행 중일 때 로딩 상태를 표시하는 기능이 없습니다. 사용자 경험 개선을 위해 버튼 비활성화나 로딩 인디케이터를 추가하는 것이 좋을 것 같습니다.
다음과 같이 로딩 상태를 추가하고 버튼을 비활성화할 수 있습니다:
const { id } = useParams(); - const {mutate: editEmail} = useEditEmail(); + const {mutate: editEmail, isPending} = useEditEmail(); ... return ( <DashboardLayout centerContent="WOOACON 2024"> <div className="flex flex-col gap-5 mt-8 px-7"> <EmailInput type="이메일 내용 수정" openSelectTicket={() => setTicketModalOpen(true)} allParticipantEmails={participants.map((p: { email: string; }) => p.email)} isEdited= {true} /> {/*시간 선택 컴포넌트*/} <TimePicker onTimeChange={(time: string) => setReservationTime(time)} onDateChange={(date: string) => setReservationDate(date)} /> - <Button label="보내기" onClick={handleEdit} className="w-full h-12 rounded-full" /> + <Button + label={isPending ? "처리 중..." : "보내기"} + onClick={handleEdit} + className="w-full h-12 rounded-full" + disabled={isPending} + /> </div> {ticketModalOpen && <SelectTicketModal onClose={() => setTicketModalOpen(false)} />} </DashboardLayout> );🧰 Tools
🪛 ESLint
[error] 47-47: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
src/features/dashboard/ui/EmailInput.tsx (2)
27-33: 초기화 로직에 들여쓰기 오류가 있습니다.useEffect 내의 setContent 호출 들여쓰기가 일관되지 않습니다. 코드 가독성을 위해 수정이 필요합니다.
useEffect(() => { if (!isEdited) { setRecipients([]); setTitle(''); - setContent(''); + setContent(''); } }, [isEdited, setRecipients]);
34-40: 이메일 주소 유효성 검사 추가를 고려하세요.이메일을 추가할 때 형식 검사를 수행하는 로직이 없습니다. 잘못된 이메일 주소가 추가되는 것을 방지하기 위해 유효성 검사를 추가하는 것이 좋을 것 같습니다.
이메일 주소 검증 함수를 추가하고 이메일 추가 시 호출하는 방법을 고려해 보세요:
const isValidEmail = (email: string): boolean => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; // EmailInputBase 컴포넌트에 onAddEmail 프롭을 추가하고, // 해당 프롭을 통해 이메일 추가 전 유효성 검사를 수행하도록 수정src/features/dashboard/api/mail.ts (3)
17-20: sendEmail 함수의 반환 타입을 명시적으로 지정해주세요.다른 함수와의 일관성을 위해 반환 타입을 명시적으로 지정하는 것이 좋습니다.
-export const sendEmail = async (data: EmailRequest) => { +export const sendEmail = async (data: EmailRequest): Promise<EmailResponse> => { const response = await axiosClient.post('/reservation-emails', data); return response.data.result; };
22-25: editEmail 함수의 반환 타입을 명시적으로 지정해주세요.함수 시그니처의 일관성을 위해 반환 타입을 추가하는 것이 좋습니다.
-export const editEmail = async (reservationEmailId: number, data: EmailRequest) => { +export const editEmail = async (reservationEmailId: number, data: EmailRequest): Promise<EmailResponse> => { const response = await axiosClient.put(`/reservation-emails/${reservationEmailId}`, data); return response.data.result; };
27-30: deleteEmail 함수의 반환 타입을 명시적으로 지정해주세요.반환 타입을 명시하면 API 함수 호출 시 타입 안전성이 향상됩니다.
-export const deleteEmail = async (reservationEmailId: number) => { +export const deleteEmail = async (reservationEmailId: number): Promise<EmailResponse> => { const response = await axiosClient.delete(`/reservation-emails/${reservationEmailId}`); return response.data.result; }src/features/dashboard/hook/useEmailHook.ts (3)
15-30: 네이티브 alert 대신 더 나은 알림 컴포넌트 사용을 고려해 보세요.
alert()함수는 사용자 경험이 좋지 않습니다. Toast 또는 모달 알림 컴포넌트를 사용하는 것이 좋습니다.또한 실패 처리 시 에러 세부 정보를 보여주면 디버깅에 도움이 될 수 있습니다.
export const useSendEmail = () => { const { id } = useParams(); const navigate = useNavigate(); const { reset } = useEmailStore(); return useMutation<EmailResponse, Error, EmailRequest>({ mutationFn: sendEmail, onSuccess: () => { reset(); - alert("예약 메일이 성공적으로 발송되었습니다!"); + toast.success("예약 메일이 성공적으로 발송되었습니다!"); navigate(`/dashboard/${id}/mailBox`); }, - onError: () => { - alert("메일 전송에 실패했습니다. 다시 시도해 주세요."); + onError: (error) => { + toast.error(`메일 전송에 실패했습니다: ${error.message}. 다시 시도해 주세요.`); }, }); };
32-47: useEditEmail 훅의 에러 처리가 개선될 수 있습니다.에러 객체의 정보를 활용하여 더 구체적인 에러 메시지를 표시하는 것이 좋습니다.
onSuccess: () => { reset(); - alert("예약 메일이 성공적으로 수정되었습니다!"); + toast.success("예약 메일이 성공적으로 수정되었습니다!"); navigate(`/dashboard/${id}/mailBox`); }, - onError: () => { - alert("메일 수정에 실패했습니다. 다시 시도해 주세요."); + onError: (error) => { + toast.error(`메일 수정에 실패했습니다: ${error.message}. 다시 시도해 주세요.`); },
1-60: 재시도 로직 추가를 고려해보세요.네트워크 요청이 일시적으로 실패할 수 있으므로, React Query의 재시도 기능을 활용하는 것이 좋습니다. 특히 읽기 작업에 유용합니다.
예를 들어 useReadEmail에 retry 옵션을 추가할 수 있습니다:
export const useReadEmail = (eventId: number, status: 'PENDING' | 'SENT') => { return useQuery<ReadEmailResponse[]>({ queryKey: ['emails', eventId, status], queryFn: () => readEmail(eventId, status), enabled: !!eventId && !!status, + retry: 2, + retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 10000), }); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
src/features/dashboard/api/mail.ts(1 hunks)src/features/dashboard/hook/useEmailHook.ts(1 hunks)src/features/dashboard/model/EmailStore.tsx(1 hunks)src/features/dashboard/model/emailInformation.ts(1 hunks)src/features/dashboard/ui/EmailInput.tsx(2 hunks)src/features/event-manage/event-create/ui/TimePicker.tsx(1 hunks)src/features/join/model/userInformation.ts(0 hunks)src/pages/dashboard/ui/ParticipantsMangementPage.tsx(1 hunks)src/pages/dashboard/ui/mail/EmailEditPage.tsx(1 hunks)src/pages/dashboard/ui/mail/EmailPage.tsx(1 hunks)src/pages/dashboard/ui/mail/MailBoxPage.tsx(2 hunks)src/pages/dashboard/ui/ticket/TicketListPage.tsx(1 hunks)src/pages/join/AuthCallback.tsx(2 hunks)src/pages/join/InfoInputPage.tsx(0 hunks)src/widgets/dashboard/ui/EmailModal.tsx(2 hunks)src/widgets/dashboard/ui/SentMailCard.tsx(2 hunks)
💤 Files with no reviewable changes (2)
- src/features/join/model/userInformation.ts
- src/pages/join/InfoInputPage.tsx
🧰 Additional context used
🧬 Code Graph Analysis (8)
src/pages/join/AuthCallback.tsx (2)
src/app/provider/authStore.ts (1)
useAuthStore(17-33)src/features/join/hooks/useUserHook.ts (1)
useUserInfo(5-10)
src/pages/dashboard/ui/mail/MailBoxPage.tsx (2)
src/features/dashboard/hook/useEmailHook.ts (2)
useReadEmail(7-13)useDeleteEmail(49-60)src/features/dashboard/api/mail.ts (1)
deleteEmail(27-30)
src/pages/dashboard/ui/mail/EmailEditPage.tsx (4)
src/features/dashboard/hook/useParticipants.ts (1)
useParticipants(9-27)src/features/dashboard/api/mail.ts (1)
editEmail(22-25)src/features/dashboard/hook/useEmailHook.ts (1)
useEditEmail(32-47)src/features/dashboard/model/EmailStore.tsx (1)
useEmailStore(19-41)
src/pages/dashboard/ui/mail/EmailPage.tsx (4)
src/features/dashboard/hook/useParticipants.ts (1)
useParticipants(9-27)src/features/dashboard/api/mail.ts (1)
sendEmail(17-20)src/features/dashboard/hook/useEmailHook.ts (1)
useSendEmail(15-30)src/features/dashboard/model/EmailStore.tsx (1)
useEmailStore(19-41)
src/widgets/dashboard/ui/EmailModal.tsx (3)
src/features/dashboard/api/mail.ts (1)
sendEmail(17-20)src/features/dashboard/hook/useEmailHook.ts (1)
useSendEmail(15-30)src/features/dashboard/model/EmailStore.tsx (1)
useEmailStore(19-41)
src/features/dashboard/hook/useEmailHook.ts (3)
src/features/dashboard/model/emailInformation.ts (3)
ReadEmailResponse(15-22)EmailResponse(9-14)EmailRequest(1-8)src/features/dashboard/api/mail.ts (4)
readEmail(4-15)sendEmail(17-20)editEmail(22-25)deleteEmail(27-30)src/features/dashboard/model/EmailStore.tsx (1)
useEmailStore(19-41)
src/features/dashboard/ui/EmailInput.tsx (1)
src/features/dashboard/model/EmailStore.tsx (1)
useEmailStore(19-41)
src/features/dashboard/api/mail.ts (2)
src/features/dashboard/model/emailInformation.ts (2)
ReadEmailResponse(15-22)EmailRequest(1-8)src/shared/types/api/http-client.ts (1)
axiosClient(6-13)
🪛 ESLint
src/pages/dashboard/ui/ParticipantsMangementPage.tsx
[error] 56-56: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
src/pages/dashboard/ui/mail/EmailEditPage.tsx
[error] 47-47: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
src/pages/dashboard/ui/mail/EmailPage.tsx
[error] 47-47: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
🪛 Biome (1.9.4)
src/pages/dashboard/ui/mail/MailBoxPage.tsx
[error] 53-54: Unnecessary use of boolean literals in conditional expression.
Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with
(lint/complexity/noUselessTernary)
🔇 Additional comments (36)
src/pages/join/AuthCallback.tsx (3)
10-10: 코드 정리: 사용하지 않는 상태 제거사용하지 않는
isModalOpen상태를 제거하여 코드가 더 간결해졌습니다. 불필요한 의존성을 줄이면 유지보수성이 향상됩니다.
15-16: 데이터 유효성 검사 추가로 안정성 향상data가 없는 경우 빠르게 함수 실행을 중단하는 가드 조건을 추가한 것은 매우 좋은 방어적 프로그래밍 방식입니다. 이는 data가 로드되기 전에 접근하려는 시도를 방지하여 잠재적인 오류를 예방합니다.
31-31: 의존성 배열 업데이트 완료useEffect의 의존성 배열에서 사용하지 않는
isModalOpen을 제거하여 불필요한 리렌더링을 방지하고 컴포넌트의 성능을 개선했습니다.src/features/dashboard/model/emailInformation.ts (3)
1-8: 이메일 요청 인터페이스 구조 적절
EmailRequest인터페이스는 이메일 전송에 필요한 모든 필드를 포함하고 있습니다. 이벤트 ID, 제목, 내용, 수신자 목록, 그리고 예약 날짜와 시간을 적절히 정의했습니다.추후 개선 사항으로, 날짜와 시간 필드에 더 구체적인 포맷 패턴이나 유효성 검사를 추가하는 것을 고려해 볼 수 있습니다.
9-14: 응답 인터페이스 구조 명확
EmailResponse인터페이스는 API 응답의 표준 구조를 잘 따르고 있습니다. 성공 여부, 코드, 메시지 및 결과를 포함하여 클라이언트가 응답을 처리하기 쉽게 설계되었습니다.
15-22: 이메일 조회 응답 인터페이스 적절
ReadEmailResponse인터페이스는 기존 이메일을 조회할 때 필요한 모든 정보를 포함하고 있습니다. ID 필드가 추가된 것은 조회 데이터 구조에 적합합니다.src/pages/dashboard/ui/ticket/TicketListPage.tsx (1)
16-16: 이벤트 ID 기본값 변경 확인 필요이벤트 ID의 기본값이
1에서0으로 변경되었습니다. 이 변경이 의도적인지, 그리고 API가 이 값을 올바르게 처리할 수 있는지 확인이 필요합니다.다른 컴포넌트에서도 이벤트 ID 기본값으로
0을 사용하는지, 그리고 이 값이 백엔드 API와 호환되는지 확인해주세요. 이 변경으로 인해 티켓 데이터 조회에 문제가 없는지 테스트해볼 것을 권장합니다.src/features/event-manage/event-create/ui/TimePicker.tsx (1)
5-26: Props 인터페이스 및 콜백 구현 완료컴포넌트를 제어 가능한(controlled) 형태로 변경한 점이 훌륭합니다. 날짜와 시간 변경 시 상위 컴포넌트에 값을 전달하는 로직이 잘 구현되었습니다.
src/features/dashboard/model/EmailStore.tsx (1)
1-42: Zustand 스토어 구현 완료이메일 관련 상태를 중앙 집중적으로 관리하는 스토어 구현이 잘 되었습니다. 필요한 상태와 액션들이 모두 포함되어 있습니다.
src/pages/dashboard/ui/mail/MailBoxPage.tsx (6)
7-8: API 연동 구현 완료 👍예약 메일 조회와 삭제 기능을 API와 연동하기 위한 훅을 잘 import 했습니다.
11-12: URL 파라미터에서 이벤트 ID를 적절히 추출합니다.이벤트 ID를 URL 파라미터에서 가져와 정수로 변환하는 로직이 적절합니다.
17-19: React Query 기반 데이터 페칭 구현 👏상태에 따라 'PENDING' 또는 'SENT' 필터링을 적용하여 이메일 데이터를 가져오고, 삭제 뮤테이션을 준비하는 로직이 잘 구현되었습니다.
21-24: 이메일 삭제 핸들러 구현이 명확합니다.삭제 함수가 간결하게 구현되었으며, 모달 상태 관리도 적절합니다.
45-58: 로딩 상태 처리와 조건부 렌더링이 잘 구현되었습니다.API 로딩 상태에 따른 조건부 렌더링이 적절히 구현되었고, 이메일 목록을 매핑하여 표시하는 방식이 효율적입니다.
🧰 Tools
🪛 Biome (1.9.4)
[error] 53-54: Unnecessary use of boolean literals in conditional expression.
Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with(lint/complexity/noUselessTernary)
65-70: 모달 닫기 및 삭제 로직 개선모달을 닫을 때 selectedEmailId를 null로 리셋하고, 삭제 시 fallback 값으로 0을 사용하는 로직이 안전하게 구현되었습니다.
src/pages/dashboard/ui/mail/EmailEditPage.tsx (6)
14-15: 이벤트 ID 추출 및 수정 뮤테이션 설정이 적절합니다.URL 파라미터에서 ID를 추출하고 useEditEmail 훅을 사용하여 이메일 수정 뮤테이션을 준비하는 로직이 잘 구현되었습니다.
17-26: 중앙 집중식 상태 관리 적용이 훌륭합니다.useEmailStore를 통해 이메일 관련 상태와 상태 설정 함수들을 잘 가져왔습니다. 상태 관리가 중앙 집중화되어 코드 일관성이 향상되었습니다.
28-39: 이메일 수정 핸들러가 명확하게 구현되었습니다.handleEdit 함수가 필요한 모든 필드를 포함하여 데이터를 구성하고 editEmail 뮤테이션을 호출하는 로직이 적절합니다.
48-48: 수정 모드 플래그 추가가 적절합니다.isEdited 속성을 추가하여 이메일 수정 모드를 명확히 표시하는 것이 좋습니다.
51-54: 시간 선택 컴포넌트 콜백 구현이 잘 되었습니다.TimePicker 컴포넌트에 시간과 날짜 변경에 대한 콜백을 추가하여 중앙 상태를 업데이트하는 로직이 잘 구현되었습니다.
55-55: 버튼 클릭 핸들러가 적절히 업데이트되었습니다."보내기" 버튼의 클릭 핸들러가 handleEdit 함수를 호출하도록 변경되어 API 연동이 잘 이루어졌습니다.
src/widgets/dashboard/ui/SentMailCard.tsx (5)
6-8: 필요한 import와 타입이 적절히 추가되었습니다.라우팅, 타입 정의, 상태 관리를 위한 import가 적절히 추가되었습니다.
11-14: 타입 및 프롭 정의가 명확하게 업데이트되었습니다.mail 프롭의 타입을 ReadEmailResponse로 변경하고, 모달 상태 설정 대신 onClickDelete 콜백 함수를 사용하도록 업데이트한 것이 좋습니다.
20-31: 이메일 수정 로직이 중앙 상태 관리와 잘 연동되었습니다.useParams를 사용해 이벤트 ID를 가져오고, 스토어 상태를 업데이트한 후 편집 페이지로 이동하는 로직이 잘 구현되었습니다.
42-42: 날짜 표시 수정이 적절합니다.mail.reservationDate를 사용하여 날짜를 표시하도록 변경되었습니다.
59-60: 버튼 핸들러가 적절히 업데이트되었습니다.삭제 및 수정 버튼에 콜백 함수와 핸들러가 적절히 연결되었습니다.
Also applies to: 66-67
src/features/dashboard/ui/EmailInput.tsx (6)
1-1: 필요한 import가 적절히 추가되었습니다.useEffect와 중앙 상태 관리를 위한 useEmailStore가 잘 추가되었습니다.
Also applies to: 6-6
12-15: 수정 모드 지원을 위한 프롭 추가가 적절합니다.isEdited 옵션 프롭을 추가하여 컴포넌트가 편집 모드에서 동작할 수 있도록 확장한 점이 좋습니다.
18-25: 중앙 집중식 상태 관리 구현이 효과적입니다.useEmailStore에서 필요한 상태와 설정 함수들을 가져와 사용하는 방식이 적절합니다. 이 접근법은 컴포넌트 간 상태 일관성을 유지하는 데 도움이 됩니다.
35-40: 이메일 관리 함수가 잘 업데이트되었습니다.addAllEmails와 removeAllEmail 함수가 중앙 상태를 업데이트하도록 변경되었습니다.
56-62: 이메일 입력 컴포넌트 연동이 적절합니다.EmailInputBase 컴포넌트가 중앙 상태의 recipients를 사용하고, 적절한 플레이스홀더를 표시하도록 구현되었습니다.
64-73: 입력 필드 상태 연동이 잘 구현되었습니다.제목과 내용 입력 필드가 중앙 상태와 연동되어 값을 표시하고 업데이트하는 로직이 잘 구현되었습니다.
src/features/dashboard/api/mail.ts (2)
1-3: 적절한 타입 및 모듈 가져오기를 사용하고 있습니다.필요한 HTTP 클라이언트와 이메일 관련 타입을 올바르게 가져오고 있습니다.
4-15: readEmail 함수가 명확한 반환 타입과 매개변수 타입을 가지고 있습니다.이벤트 ID와 상태(PENDING 또는 SENT)에 따라 이메일을 조회하는 기능이 잘 구현되어 있습니다. 타입 안전성이 보장되어 있고 Promise 반환 타입이 명시적으로 지정되어 있습니다.
src/features/dashboard/hook/useEmailHook.ts (2)
1-6: 필요한 의존성과 모듈을 잘 가져오고 있습니다.React Query, 커스텀 API 함수, 타입, React Router 및 Zustand 스토어를 적절하게 가져오고 있습니다.
7-13: useReadEmail 훅이 적절하게 구현되어 있습니다.Query 키가 적절하게 설정되어 있고, 필수 파라미터가 있을 때만 쿼리가 활성화되도록
enabled옵션이 잘 설정되어 있습니다.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (5)
src/pages/dashboard/ui/mail/EmailPage.tsx (3)
15-56: 이메일 상태 관리 및 발송 로직이 효과적으로 구현되었습니다.이메일 제목, 내용, 수신자, 예약 날짜 및 시간에 대한 유효성 검사가 추가되어 데이터 무결성을 보장합니다.
63-63: 🛠️ Refactor suggestion명시적 타입 지정 필요
any타입은 타입 안전성을 저하시킵니다. 이메일 속성에 명시적인 타입을 지정해주세요.- allParticipantEmails={participants.map((p: { email: any; }) => p.email)} + allParticipantEmails={participants.map((p: { email: string; }) => p.email)}🧰 Tools
🪛 ESLint
[error] 63-63: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
71-71: 🛠️ Refactor suggestion이메일 발송 중 로딩 상태 관리 필요
이메일 발송 중에 사용자가 중복으로 버튼을 클릭하는 것을 방지하기 위한 로딩 상태 관리가 필요합니다.
- const { mutate: sendEmail } = useSendEmail(); + const { mutate: sendEmail, isLoading } = useSendEmail(); // ... - <Button label="보내기" onClick={handleSend} className="w-full h-12 rounded-full" /> + <Button + label={isLoading ? "발송 중..." : "보내기"} + onClick={handleSend} + className="w-full h-12 rounded-full" + disabled={isLoading} + />src/widgets/dashboard/ui/EmailModal.tsx (2)
27-54: handleSend 함수가 적절하게 구현되었습니다.이전 리뷰에서 제안한 대로 유효성 검사가 잘 구현되었습니다. 모든 필수 필드(제목, 내용, 수신자, 예약 날짜/시간)에 대한 검증이 적절히 포함되어 있습니다.
69-70: 🛠️ Refactor suggestion이메일 발송 중 로딩 상태 처리가 필요합니다.
이메일 발송 중에 사용자가 중복으로 버튼을 클릭하는 것을 방지하는 로딩 상태 관리가 여전히 구현되지 않았습니다. useSendEmail 훅에서 제공하는 isLoading 상태를 활용하세요.
다음과 같이 로딩 상태를 사용하고 발송 중에는 버튼이 비활성화되도록 처리하세요:
- const { mutate: sendEmail } = useSendEmail(); + const { mutate: sendEmail, isLoading } = useSendEmail(); // ... - <TertiaryButton label="취소" type="button" color="black" size="medium" onClick={onClose} /> + <TertiaryButton + label="취소" + type="button" + color="black" + size="medium" + onClick={onClose} + disabled={isLoading} + /> - <TertiaryButton label="전송" type="button" color="pink" size="medium" onClick={handleSend} /> + <TertiaryButton + label={isLoading ? "발송 중..." : "전송"} + type="button" + color="pink" + size="medium" + onClick={handleSend} + disabled={isLoading} + />
🧹 Nitpick comments (1)
src/widgets/dashboard/ui/EmailModal.tsx (1)
44-44: eventId 파싱 시 안전성 강화가 필요합니다.
parseInt함수는 문자열에 숫자가 아닌 문자가 포함되어 있을 경우 예기치 않은 결과를 반환할 수 있습니다. 파싱 오류 처리를 위한 추가 안전장치가 필요합니다.다음과 같이 개선할 수 있습니다:
- const eventId = id ? parseInt(id) : 0; + const eventId = id && !isNaN(Number(id)) ? parseInt(id) : 0;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/features/dashboard/hook/useEmailHook.ts(1 hunks)src/pages/dashboard/ui/ParticipantsMangementPage.tsx(1 hunks)src/pages/dashboard/ui/mail/EmailEditPage.tsx(1 hunks)src/pages/dashboard/ui/mail/EmailPage.tsx(1 hunks)src/widgets/dashboard/ui/EmailModal.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/pages/dashboard/ui/ParticipantsMangementPage.tsx
- src/pages/dashboard/ui/mail/EmailEditPage.tsx
- src/features/dashboard/hook/useEmailHook.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/widgets/dashboard/ui/EmailModal.tsx (3)
src/features/dashboard/api/mail.ts (1)
sendEmail(17-20)src/features/dashboard/hook/useEmailHook.ts (1)
useSendEmail(15-30)src/features/dashboard/model/EmailStore.tsx (1)
useEmailStore(19-41)
🪛 ESLint
src/pages/dashboard/ui/mail/EmailPage.tsx
[error] 63-63: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: storybook
🔇 Additional comments (5)
src/pages/dashboard/ui/mail/EmailPage.tsx (2)
7-10: React Router와 상태 관리 관련 임포트가 잘 추가되었습니다.기존
useNavigate대신useParams를 사용하여 URL 파라미터를 가져오는 방식으로 변경되었고, 이메일 상태 관리를 위한 Zustand 스토어와 이메일 전송 API 연동을 위한 커스텀 훅이 적절히 임포트되었습니다.
66-69: TimePicker 컴포넌트 연결이 적절하게 구현되었습니다.상태 관리 스토어의 함수를 활용하여 시간과 날짜 선택 이벤트를 처리하는 방식으로 잘 구현되었습니다.
src/widgets/dashboard/ui/EmailModal.tsx (3)
1-5: 적절한 의존성과 상태 관리 구조로 리팩토링되었습니다.이메일 모달이 Zustand 스토어 기반 상태 관리로 마이그레이션되어 더 나은 구조를 갖추게 되었습니다. React Router와 API 훅을 적절히 통합하여 일관된 아키텍처를 구현했습니다.
14-25: 전역 상태 관리로 적절히 마이그레이션 되었습니다.useParams와 useEmailStore를 사용하여 컴포넌트 내 로컬 상태에서 중앙 집중식 상태 관리로 마이그레이션한 것은 좋은 접근 방식입니다. 이를 통해 상태 관리가 간소화되고 다른 컴포넌트와의 일관성이 유지됩니다.
64-67: TimePicker 컴포넌트가 스토어와 잘 통합되었습니다.TimePicker의 콜백 함수가 Zustand 스토어의 상태를 직접 업데이트하도록 설정되어 있어, 상태 관리가 일관되게 유지됩니다.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/features/dashboard/hook/useEmailHook.ts (2)
59-71: 🛠️ Refactor suggestion알림 UI 개선 필요
이전 리뷰에서 제안된 대로
alert()대신 toast 알림을 사용하는 것이 사용자 경험에 더 좋습니다.queryClient.invalidateQueries는 적용되었지만, 알림 메시지는 여전히alert를 사용하고 있습니다.import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { sendEmail, editEmail, readEmail, deleteEmail, readPurchaserEmails } from '../api/mail'; import { EmailRequest, EmailResponse, ReadEmailResponse } from '../model/emailInformation'; import { useNavigate, useParams } from 'react-router-dom'; import { useEmailStore } from '../model/EmailStore'; +import { toast } from 'react-toastify'; // ... 중략 ... export const useDeleteEmail = () => { const queryClient = useQueryClient(); return useMutation<EmailResponse, Error, number>({ mutationFn: (reservationEmailId) => deleteEmail(reservationEmailId), onSuccess: () => { - alert("메일이 삭제되었습니다."); + toast.success("메일이 삭제되었습니다."); queryClient.invalidateQueries({ queryKey: ['emails'] }); }, onError: () => { - alert("메일 삭제에 실패했습니다. 다시 시도해 주세요."); + toast.error("메일 삭제에 실패했습니다. 다시 시도해 주세요."); } }); }
30-40: 🛠️ Refactor suggestion알림 UI 개선 필요
사용자 경험 향상을 위해
alert()대신 toast 알림을 사용하는 것이 더 좋습니다. 알림은 페이지 흐름을 방해하지 않고 자연스럽게 표시되어야 합니다.import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { sendEmail, editEmail, readEmail, deleteEmail, readPurchaserEmails } from '../api/mail'; import { EmailRequest, EmailResponse, ReadEmailResponse } from '../model/emailInformation'; import { useNavigate, useParams } from 'react-router-dom'; import { useEmailStore } from '../model/EmailStore'; +import { toast } from 'react-toastify'; // ... 중략 ... export const useSendEmail = () => { const { id } = useParams(); const navigate = useNavigate(); const { reset } = useEmailStore(); return useMutation<EmailResponse, Error, EmailRequest>({ mutationFn: sendEmail, onSuccess: () => { reset(); - alert("예약 메일이 성공적으로 발송되었습니다!"); + toast.success("예약 메일이 성공적으로 발송되었습니다!"); navigate(`/dashboard/${id}/mailBox`); }, onError: () => { - alert("메일 전송에 실패했습니다. 다시 시도해 주세요."); + toast.error("메일 전송에 실패했습니다. 다시 시도해 주세요."); }, }); };
🧹 Nitpick comments (3)
src/widgets/dashboard/ui/SelectTicketInfo.tsx (1)
10-10: 코드 가독성 개선이 필요합니다.콤마 뒤에 공백이 없어 가독성이 떨어집니다.
-const SelectTicketInfo = ({ tickets,onClick}: SelectTicketInfoProps) => { +const SelectTicketInfo = ({ tickets, onClick }: SelectTicketInfoProps) => {src/features/dashboard/api/mail.ts (1)
31-34: API 에러 처리 개선 필요현재 함수는 API 호출 성공 시에만 처리하고 있습니다. API 호출 실패 시 에러를 적절히 처리하는 로직을 추가하는 것이 좋습니다.
export const sendEmail = async (data: EmailRequest) => { + try { const response = await axiosClient.post('/reservation-emails', data); return response.data.result; + } catch (error) { + console.error('Failed to send email:', error); + throw error; + } };src/features/dashboard/hook/useEmailHook.ts (1)
15-23: 코드 포맷팅 개선 필요
usePurchaserEmails훅의 들여쓰기가 다른 훅들과 일관되지 않습니다. 코드 가독성을 위해 일관된 포맷팅이 필요합니다.export const usePurchaserEmails = () => { return useMutation({ mutationFn: ({ eventId, ticketId }: { eventId: number; ticketId?: number }) => - readPurchaserEmails(eventId, ticketId), + readPurchaserEmails(eventId, ticketId), onError: () => { alert('이메일을 불러오는 데 실패했습니다.'); - }, - }); - }; + }, + }); };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
src/features/dashboard/api/mail.ts(1 hunks)src/features/dashboard/hook/useEmailHook.ts(1 hunks)src/features/dashboard/model/EmailStore.tsx(1 hunks)src/features/dashboard/model/emailInformation.ts(1 hunks)src/features/dashboard/model/participantInformation.ts(1 hunks)src/features/event-manage/event-create/ui/TimePicker.tsx(1 hunks)src/pages/dashboard/ui/ParticipantsMangementPage.tsx(1 hunks)src/pages/dashboard/ui/mail/EmailPage.tsx(1 hunks)src/widgets/dashboard/ui/EmailModal.tsx(2 hunks)src/widgets/dashboard/ui/SelectTicketInfo.tsx(2 hunks)src/widgets/dashboard/ui/SelectTicketModal.tsx(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/features/dashboard/model/participantInformation.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- src/pages/dashboard/ui/ParticipantsMangementPage.tsx
- src/features/dashboard/model/EmailStore.tsx
- src/features/dashboard/model/emailInformation.ts
- src/features/event-manage/event-create/ui/TimePicker.tsx
- src/pages/dashboard/ui/mail/EmailPage.tsx
- src/widgets/dashboard/ui/EmailModal.tsx
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/features/dashboard/api/mail.ts (2)
src/features/dashboard/model/emailInformation.ts (2)
ReadEmailResponse(14-21)EmailRequest(1-7)src/shared/types/api/http-client.ts (1)
axiosClient(6-13)
src/widgets/dashboard/ui/SelectTicketInfo.tsx (1)
src/features/ticket/model/ticketInformation.ts (1)
ReadTicketResponse(21-27)
src/features/dashboard/hook/useEmailHook.ts (3)
src/features/dashboard/model/emailInformation.ts (3)
ReadEmailResponse(14-21)EmailResponse(8-13)EmailRequest(1-7)src/features/dashboard/api/mail.ts (5)
readEmail(5-16)readPurchaserEmails(19-29)sendEmail(31-34)editEmail(36-39)deleteEmail(41-44)src/features/dashboard/model/EmailStore.tsx (1)
useEmailStore(17-36)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: storybook
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/widgets/dashboard/ui/SelectTicketModal.tsx (2)
14-20: 동적 데이터 페칭과 상태 관리 구현 적절URL에서 이벤트 ID를 추출하고 이를 기반으로 티켓 정보를 불러오는 방식이 잘 구현되어 있습니다. fallback 처리(
data?.result ?? [])도 적절하게 되어 있어 데이터가 없는 경우에도 오류 없이 동작합니다.다만, 타입 안전성을 더 강화하기 위해
eventId변수에 명시적인 타입을 추가하는 것을 고려해보세요:- const eventId = id ? parseInt(id) : 0; + const eventId: number = id ? parseInt(id) : 0;
46-58: 로딩 상태 처리와 동적 데이터 렌더링 개선 가능로딩 상태를 적절히 처리하고 동적 데이터를 기반으로 티켓 목록을 렌더링하는 방식은 좋습니다. 하지만 로딩 UI가 매우 단순합니다.
사용자 경험 향상을 위해 스켈레톤 UI나 스피너 등을 활용하여 로딩 상태를 개선하는 것을 고려해보세요:
{isLoading ? ( - <div>로딩 중...</div> + <div className="flex justify-center items-center py-4"> + <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div> + <span className="ml-2">티켓 정보를 불러오는 중입니다...</span> + </div> ) : ( tickets.map((ticket) => ( // ... )) )}또한 티켓 데이터가 비어있는 경우에 대한 처리도 추가하면 좋을 것 같습니다:
{isLoading ? ( <div>로딩 중...</div> ) : ( + tickets.length === 0 ? ( + <div className="text-center py-4 text-gray-500">티켓 정보가 없습니다.</div> + ) : ( tickets.map((ticket) => ( <SelectTicketInfo key={ticket.ticketId} tickets={ticket} onClick={() => { handleClick(ticket.ticketId); }} /> )) + ) )}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/features/dashboard/api/mail.ts(1 hunks)src/widgets/dashboard/ui/SelectTicketModal.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/features/dashboard/api/mail.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/widgets/dashboard/ui/SelectTicketModal.tsx (3)
src/features/ticket/hooks/useTicketHook.ts (1)
useTickets(5-11)src/features/dashboard/model/EmailStore.tsx (1)
useEmailStore(17-36)src/features/dashboard/hook/useEmailHook.ts (1)
usePurchaserEmails(15-23)
🔇 Additional comments (3)
src/widgets/dashboard/ui/SelectTicketModal.tsx (3)
1-6: 적절한 임포트 구성으로 API 연동 준비 완료필요한 모든 훅과 컴포넌트를 임포트하여 동적 데이터 페칭과 상태 관리를 위한 준비가 잘 되어 있습니다.
useParams를 사용한 URL 파라미터 추출,useTickets를 통한 티켓 데이터 조회,useEmailStore를 활용한 이메일 상태 관리, 그리고usePurchaserEmails로 구매자 이메일 정보를 가져오는 구조가 명확합니다.
21-32: 이메일 데이터 조회 및 상태 업데이트 로직 구현 적절티켓 선택 시 해당 티켓의 구매자 이메일 정보를 조회하고 성공 시 상태를 업데이트하는 로직이 잘 구현되어 있습니다. API 호출, 상태 업데이트, 모달 닫기까지의 흐름이 명확합니다.
39-42: 이메일 모달 열기 로직 적절이전 리뷰에서 지적된
openEmailModal함수의 중복 호출 문제가 해결되었습니다. 뒤로 가기 버튼의onClick핸들러와handleClick함수 내의 호출이 각각 다른 사용자 액션에 대응하여 적절히 구현되어 있습니다.
예약 메일 발송
예약 메일 조회 (발송/미발송)
예약 메일 수정
예약 메일 삭제
티켓별 이메일 보내기 구현
Summary by CodeRabbit
신규 기능
버그 수정
리팩터링
스타일/사용성 개선
문서화
기타