-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: 호스트&티켓 페이지 리팩토링 #219
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 all commits
07226a8
e67ba96
5fa98b9
d93f646
24b3b46
e8fad7b
b14b873
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 |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import { useEffect, useState } from 'react'; | ||
| import { useParams } from 'react-router-dom'; | ||
| import useHostChannelInfo from '../hook/useHostChannelInfoHook'; | ||
| import { useHostInfoSave } from '../../../features/host/hook/useHostInfoHook'; | ||
| import { hostInfoSchema } from '../../../shared/lib/formValidation'; | ||
| import DefaultTextField from '../../../../design-system/ui/textFields/DefaultTextField'; | ||
| import TertiaryButton from '../../../../design-system/ui/buttons/TertiaryButton'; | ||
| import MultilineTextField from '../../../../design-system/ui/textFields/MultilineTextField'; | ||
|
|
||
| const HostEditForm = () => { | ||
| const { id } = useParams<{ id: string }>(); | ||
| const hostChannelId = Number(id); | ||
|
|
||
| const [email, setEmail] = useState(''); | ||
| const [channelDescription, setChannelDescription] = useState(''); | ||
|
|
||
| const { data: hostInfo } = useHostChannelInfo(hostChannelId); | ||
| const { handleSave } = useHostInfoSave(hostChannelId, hostInfo!, email, channelDescription); | ||
|
|
||
| const emailValidation = hostInfoSchema.safeParse({ email }); | ||
|
|
||
| useEffect(() => { | ||
| if (hostInfo?.result.email) setEmail(hostInfo.result.email); | ||
| if (hostInfo?.result.channelDescription) setChannelDescription(hostInfo.result.channelDescription); | ||
| }, [hostInfo]); | ||
|
|
||
| return ( | ||
| <div className="flex flex-col gap-4 md:gap-8 px-8 md:px-6 mb-4 md:mb-8"> | ||
| <div className="flex flex-col gap-2"> | ||
| <DefaultTextField | ||
| label="대표 이메일" | ||
| detail="채널 혹은, 채널에서 주최하는 이벤트에 대해 문의 할 수 있는 메일로 작성해주세요." | ||
| value={email} | ||
| onChange={e => setEmail(e.target.value)} | ||
| className="h-12" | ||
| labelClassName="sm:text-base md:text-lg" | ||
| errorMessage={!emailValidation.success ? emailValidation.error.errors[0].message : ''} | ||
| /> | ||
| <TertiaryButton | ||
| type="button" | ||
| label="수정하기" | ||
| size="large" | ||
| color="pink" | ||
| onClick={handleSave} | ||
| disabled={!emailValidation.success} | ||
| /> | ||
|
Comment on lines
+39
to
+46
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 중복된 저장 기능 개선 필요 두 개의 저장 버튼이 동일한 이메일 필드에만 유효성 검사를 적용하고 있는 것도 일관성 측면에서 검토가 필요합니다. Also applies to: 55-55 🤖 Prompt for AI Agents |
||
| </div> | ||
| <div className="flex flex-col gap-2"> | ||
| <MultilineTextField | ||
| label="채널에 대한 설명" | ||
| value={channelDescription} | ||
| onChange={e => setChannelDescription(e.target.value)} | ||
| className="h-24 mb-8" | ||
| /> | ||
| <TertiaryButton type="button" label="수정하기" size="large" color="pink" onClick={handleSave} /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default HostEditForm; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import ProfileCircle from '../../../../design-system/ui/Profile'; | ||
| import { formatProfilName } from '../../../shared/lib/formatProfileName'; | ||
| import { HostChannelInfoResponse } from '../model/hostChannelInfo'; | ||
|
|
||
| const HostInfo = ({ hostInfo }: { hostInfo?: HostChannelInfoResponse }) => { | ||
| return ( | ||
| <div className="flex flex-col px-8 md:px-12 gap-6"> | ||
| <div className="flex flex-col gap-4 py-4"> | ||
| <p className="text-xl text-black font-semibold">대표 이메일</p> | ||
| <p>{hostInfo?.result.email}</p> | ||
| </div> | ||
| <div className="flex flex-col gap-4 lg:gap-6"> | ||
| <p className="text-xl text-black font-semibold">멤버 목록</p> | ||
| <div className="flex flex-wrap gap-x-5 gap-y-4 lg:gap-x-10 lg:gap-y-6 text-sm md:text-16 lg:text-base"> | ||
| {hostInfo?.result.hostChannelMembers.map(user => ( | ||
| <ProfileCircle | ||
| key={user.id} | ||
| id={user.id} | ||
| profile="userProfile" | ||
| name={formatProfilName(user.memberName)} | ||
| className="w-12 h-12 md:w-13 md:h-13 lg:w-14 lg:h-14" | ||
| > | ||
| {user.memberName} | ||
| </ProfileCircle> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default HostInfo; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { Dispatch, SetStateAction } from 'react'; | ||
| import TertiaryButton from '../../../../design-system/ui/buttons/TertiaryButton'; | ||
| import MemberEmailInput from '../../../features/menu/ui/MemberEmailInput'; | ||
|
|
||
| const MemberInvite = ({ | ||
| emails, | ||
| setEmails, | ||
| handleInviteMembers, | ||
| }: { | ||
| emails: string[]; | ||
| setEmails: Dispatch<SetStateAction<string[]>>; | ||
| handleInviteMembers: () => void; | ||
| }) => { | ||
| return ( | ||
| <div className="flex flex-col gap-2 px-8 md:px-6 mb-4"> | ||
| <div className="flex flex-col"> | ||
| <h1 className="font-bold text-black text-lg">멤버 등록</h1> | ||
| <p className="text-placeholderText text-10 md:text-13"> | ||
| 추가할 회원의 이메일을 입력한 뒤, 엔터를 눌러 검색해 주세요. | ||
| <br /> | ||
| 삭제하려면 추가된 이메일 아이콘의 x를 눌러주세요. | ||
| </p> | ||
| </div> | ||
| <MemberEmailInput emails={emails} setEmails={setEmails} /> | ||
| <TertiaryButton type="button" label="전송" size="large" color="pink" onClick={handleInviteMembers} /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default MemberInvite; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,3 @@ | ||||||||||||||||||||||||||
| import basicProfile from '../../../../../public/assets/event-manage/creation/BasicProfile.png'; | ||||||||||||||||||||||||||
| import addImage from '../../../../../public/assets/event-manage/creation/AddImage.svg'; | ||||||||||||||||||||||||||
| import DefaultTextField from '../../../../../design-system/ui/textFields/DefaultTextField'; | ||||||||||||||||||||||||||
| import MultilineTextField from '../../../../../design-system/ui/textFields/MultilineTextField'; | ||||||||||||||||||||||||||
|
|
@@ -57,14 +56,21 @@ const HostCreationPage = ({ onValidationChange }: HostCreationPageProps) => { | |||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||
| }, [hostChannelName, hostEmail, channelDescription, setHostState]); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||
| return () => { | ||||||||||||||||||||||||||
| setHostState({ | ||||||||||||||||||||||||||
| profileImageUrl: '', | ||||||||||||||||||||||||||
| hostChannelName: '', | ||||||||||||||||||||||||||
| hostEmail: '', | ||||||||||||||||||||||||||
| channelDescription: '', | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
| }, [setHostState]); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||
| <div className="flex flex-col gap-5 px-4"> | ||||||||||||||||||||||||||
| <div className="relative flex items-center justify-center mb-4"> | ||||||||||||||||||||||||||
| <img | ||||||||||||||||||||||||||
| src={previewUrl || basicProfile} | ||||||||||||||||||||||||||
| alt="기본 프로필 이미지" | ||||||||||||||||||||||||||
| className="w-24 h-24 object-cover rounded-full" | ||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||
| <img src={previewUrl || ''} alt="기본 프로필 이미지" className="w-24 h-24 object-cover rounded-full" /> | ||||||||||||||||||||||||||
|
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 프로필 이미지 처리 일관성 문제 빈 문자열을 사용하면 깨진 이미지 아이콘이 표시될 수 있습니다. 다음 중 하나의 방식으로 해결하세요:
+import basicProfile from '../../../../../public/assets/event-manage/creation/BasicProfile.png';
-<img src={previewUrl || ''} alt="기본 프로필 이미지" className="w-24 h-24 object-cover rounded-full" />
+<img src={previewUrl || basicProfile} alt="기본 프로필 이미지" className="w-24 h-24 object-cover rounded-full" />
-<img src={previewUrl || ''} alt="기본 프로필 이미지" className="w-24 h-24 object-cover rounded-full" />
+{previewUrl ? (
+ <img src={previewUrl} alt="기본 프로필 이미지" className="w-24 h-24 object-cover rounded-full" />
+) : (
+ <div className="w-24 h-24 bg-gray-200 rounded-full flex items-center justify-center">
+ <span className="text-gray-500">이미지</span>
+ </div>
+)}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||
| onClick={() => fileInputRef.current?.click()} | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,7 +9,7 @@ export const formSchema = z.object({ | |||||||||||||
| .regex(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/, '올바른 이메일 형식이어야 합니다.'), | ||||||||||||||
| phone: z | ||||||||||||||
| .string() | ||||||||||||||
| .regex(/^[0-9]{3}-[0-9]{3,4}-[0-9]{4}$/, '연락처는 휴대전화 번호 형식(예: 010-1234-5678)이어야 합니다.') | ||||||||||||||
| .regex(/^[0-9]{3}-[0-9]{3,4}-[0-9]{4}$/, '연락처는 휴대전화 번호 형식(예: 010-1234-5678)이어야 합니다.'), | ||||||||||||||
| }); | ||||||||||||||
| export const organizerFormSchema = formSchema.pick({ email: true, phone: true }); | ||||||||||||||
| export const eventTitleSchema = z.object({ | ||||||||||||||
|
|
@@ -21,6 +21,7 @@ export const hostCreationSchema = z.object({ | |||||||||||||
| channelDescription: z.string().min(5, '채널 설명은 최소 두 글자 이상이어야 합니다.'), | ||||||||||||||
| }); | ||||||||||||||
| export const myPageSchema = formSchema.pick({ name: true, phone: true }); | ||||||||||||||
| export const hostInfoSchema = formSchema.pick({ email: true }); | ||||||||||||||
|
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 타입 정의 누락 새로운 다음 diff를 적용하여 타입 정의를 추가하세요: export type FormData = z.infer<typeof formSchema>;
export type OrganizerFormData = z.infer<typeof organizerFormSchema>;
export type EventTitleFormData = z.infer<typeof eventTitleSchema>;
export type HostCreationFormData = z.infer<typeof hostCreationSchema>;
+export type HostInfoFormData = z.infer<typeof hostInfoSchema>;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| export type FormData = z.infer<typeof formSchema>; | ||||||||||||||
| export type OrganizerFormData = z.infer<typeof organizerFormSchema>; | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import { useTickets } from '../../../features/ticket/hooks/useTicketHook'; | |
| import { useOrderTicket } from '../../../features/ticket/hooks/useOrderHook'; | ||
| import { readTicketOptions } from '../../../features/ticket/api/ticketOption'; | ||
| import useAuthStore from '../../../app/provider/authStore'; | ||
| import clock from '../../../../public/assets/event-manage/details/Clock.svg'; | ||
|
|
||
| const TicketInfo = ({ eventId }: { eventId: number }) => { | ||
| const limitNum = 4; | ||
|
|
@@ -84,7 +85,9 @@ const TicketInfo = ({ eventId }: { eventId: number }) => { | |
| {data.result.map(ticket => ( | ||
| <div key={ticket.ticketId} className="bg-gray1 px-3 py-3 md:px-6 md:py-4 rounded-[10px] mb-3"> | ||
| <div className="flex justify-between items-center"> | ||
| <div className="flex flex-col gap-2"> {/* w-[230px] */} | ||
| <div className="flex flex-col gap-2"> | ||
| {' '} | ||
| {/* w-[230px] */} | ||
| <div className="flex items-center gap-2 md:gap-4"> | ||
| <span className="font-bold text-base md:text-md">{ticket.ticketName}</span> {/* w-[170px] */} | ||
| </div> | ||
|
|
@@ -123,6 +126,15 @@ const TicketInfo = ({ eventId }: { eventId: number }) => { | |
| /> | ||
| </div> | ||
| </div> | ||
| <div className="w-full bg-[#E4EFFF] border px-4 py-3 rounded-[5px] mt-2"> | ||
| <div className="flex items-center gap-2"> | ||
| <img src={clock} alt="시계 아이콘" className="w-4 h-4 md:w-5 md:h-5" /> | ||
| <span className="text-sm font-semibold"> | ||
| 구매 가능기간: {new Date(ticket.startDate).toLocaleDateString()} ~{' '} | ||
| {new Date(ticket.endDate).toLocaleDateString()} | ||
|
Comment on lines
+133
to
+134
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 날짜 로케일 설정을 명시적으로 지정해주세요.
다음과 같이 수정하는 것을 권장합니다: - 구매 가능기간: {new Date(ticket.startDate).toLocaleDateString()} ~{' '}
- {new Date(ticket.endDate).toLocaleDateString()}
+ 구매 가능기간: {new Date(ticket.startDate).toLocaleDateString('ko-KR')} ~{' '}
+ {new Date(ticket.endDate).toLocaleDateString('ko-KR')}🤖 Prompt for AI Agents |
||
| </span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </div> | ||
|
|
||
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.
위험한 Non-null assertion 사용
hostInfo!와 같은 non-null assertion 사용은hostInfo가undefined일 때 런타임 오류를 발생시킬 수 있습니다. 조건부 렌더링이나 적절한 기본값 처리를 고려해보세요.또는 컴포넌트 전체를 조건부로 렌더링하는 방식을 고려하세요.
🤖 Prompt for AI Agents