diff --git a/public/assets/event-manage/details/Clock.svg b/public/assets/event-manage/details/Clock.svg new file mode 100644 index 00000000..fdb1b20d --- /dev/null +++ b/public/assets/event-manage/details/Clock.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/entities/host/ui/HostEditForm.tsx b/src/entities/host/ui/HostEditForm.tsx new file mode 100644 index 00000000..700bbef3 --- /dev/null +++ b/src/entities/host/ui/HostEditForm.tsx @@ -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 ( +
+
+ setEmail(e.target.value)} + className="h-12" + labelClassName="sm:text-base md:text-lg" + errorMessage={!emailValidation.success ? emailValidation.error.errors[0].message : ''} + /> + +
+
+ setChannelDescription(e.target.value)} + className="h-24 mb-8" + /> + +
+
+ ); +}; + +export default HostEditForm; diff --git a/src/entities/host/ui/HostInfo.tsx b/src/entities/host/ui/HostInfo.tsx new file mode 100644 index 00000000..e49c290a --- /dev/null +++ b/src/entities/host/ui/HostInfo.tsx @@ -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 ( +
+
+

대표 이메일

+

{hostInfo?.result.email}

+
+
+

멤버 목록

+
+ {hostInfo?.result.hostChannelMembers.map(user => ( + + {user.memberName} + + ))} +
+
+
+ ); +}; + +export default HostInfo; diff --git a/src/entities/host/ui/MemberInvite.tsx b/src/entities/host/ui/MemberInvite.tsx new file mode 100644 index 00000000..5f78c94a --- /dev/null +++ b/src/entities/host/ui/MemberInvite.tsx @@ -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>; + handleInviteMembers: () => void; +}) => { + return ( +
+
+

멤버 등록

+

+ 추가할 회원의 이메일을 입력한 뒤, 엔터를 눌러 검색해 주세요. +
+ 삭제하려면 추가된 이메일 아이콘의 x를 눌러주세요. +

+
+ + +
+ ); +}; + +export default MemberInvite; diff --git a/src/entities/user/ui/BookmarkList.tsx b/src/entities/user/ui/BookmarkList.tsx index 83fc2d91..bdadab6a 100644 --- a/src/entities/user/ui/BookmarkList.tsx +++ b/src/entities/user/ui/BookmarkList.tsx @@ -8,8 +8,8 @@ const BookmarkList = () => { const { data } = useBookmarks(); const navigate = useNavigate(); - const sortedData = data ? [...data].sort((a, b) => a.id - b.id) : []; - const visibleEvents = sortedData.slice(0, 2); + if (!data) return null; + const visibleEvents = data.slice(0, 2); return (
diff --git a/src/features/host/hook/useHostInfoHook.ts b/src/features/host/hook/useHostInfoHook.ts index 98dab3ce..4e3d6e43 100644 --- a/src/features/host/hook/useHostInfoHook.ts +++ b/src/features/host/hook/useHostInfoHook.ts @@ -5,6 +5,7 @@ import { HostChannelInfoResponse } from '../../../entities/host/model/hostChanne export const useHostInfoSave = ( hostChannelId: number, hostInfo: HostChannelInfoResponse, + email: string, channelDescription: string ) => { const queryClient = useQueryClient(); @@ -17,7 +18,7 @@ export const useHostInfoSave = ( hostChannelId, profileImageUrl: hostInfo.result.profileImageUrl, hostChannelName: hostInfo.result.hostChannelName, - hostEmail: hostInfo.result.email, + hostEmail: email, channelDescription, }; diff --git a/src/features/ticket/model/ticketInformation.ts b/src/features/ticket/model/ticketInformation.ts index 6fa2153e..4dded4b0 100644 --- a/src/features/ticket/model/ticketInformation.ts +++ b/src/features/ticket/model/ticketInformation.ts @@ -17,6 +17,8 @@ export interface ReadTicketResponse { ticketDescription: string; ticketPrice: number; availableQuantity: number; + startDate: string; + endDate: string; } export interface TicketOptionChoice { diff --git a/src/pages/event/ui/host/HostCreationPage.tsx b/src/pages/event/ui/host/HostCreationPage.tsx index 22285d0d..cb308e49 100644 --- a/src/pages/event/ui/host/HostCreationPage.tsx +++ b/src/pages/event/ui/host/HostCreationPage.tsx @@ -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 (
- 기본 프로필 이미지 + 기본 프로필 이미지
- {selectedHost && ( -
-
-

대표 이메일

-

{hostInfo?.result.email}

-
-
-

멤버 목록

-
- {hostInfo?.result.hostChannelMembers.map(user => ( - - {user.memberName} - - ))} -
-
-
- )} + {selectedHost && } {selectedInfo && ( -
- -
- setChannelDescription(e.target.value)} - className="h-24 mb-8" - /> - -
-
-
-

멤버 등록

-

- 이메일로 회원을 검색해 추가 할 수 있습니다. 삭제 하려면 추가된 이메일 아이콘의 x를 눌러주세요.{' '} -

-
- - -
-
+ <> + + + )}
diff --git a/src/shared/hooks/useImageUpload.ts b/src/shared/hooks/useImageUpload.ts index 5345b386..d615d721 100644 --- a/src/shared/hooks/useImageUpload.ts +++ b/src/shared/hooks/useImageUpload.ts @@ -1,7 +1,7 @@ import { useRef, useState, useCallback, useEffect } from 'react'; import { uploadFile } from '../../features/event/hooks/usePresignedUrlHook'; import { convertImageToWebP } from '../lib/convertImageToWebP'; -export const DEFAULT_BASIC_PROFILE = 'https://gotogetherbucket.s3.ap-northeast-2.amazonaws.com/default.png'; +import basicProfile from '../../../public/assets/event-manage/creation/BasicProfile.png'; const useImageUpload = ({ value, @@ -19,9 +19,9 @@ const useImageUpload = ({ useEffect(() => { if (value) { setPreviewUrl(value); - } else if (useDefaultImage && previewUrl !== DEFAULT_BASIC_PROFILE) { - setPreviewUrl(DEFAULT_BASIC_PROFILE); - onSuccess?.(DEFAULT_BASIC_PROFILE); + } else if (useDefaultImage && previewUrl !== basicProfile) { + setPreviewUrl(basicProfile); + onSuccess?.(basicProfile); } }, [value, onSuccess, useDefaultImage, previewUrl]); diff --git a/src/shared/lib/formValidation.ts b/src/shared/lib/formValidation.ts index 100b88f5..869d1df6 100644 --- a/src/shared/lib/formValidation.ts +++ b/src/shared/lib/formValidation.ts @@ -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 }); export type FormData = z.infer; export type OrganizerFormData = z.infer; diff --git a/src/widgets/event/ui/TicketInfo.tsx b/src/widgets/event/ui/TicketInfo.tsx index 7791207f..a12d86ae 100644 --- a/src/widgets/event/ui/TicketInfo.tsx +++ b/src/widgets/event/ui/TicketInfo.tsx @@ -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 => (
-
{/* w-[230px] */} +
+ {' '} + {/* w-[230px] */}
{ticket.ticketName} {/* w-[170px] */}
@@ -123,6 +126,15 @@ const TicketInfo = ({ eventId }: { eventId: number }) => { />
+
+
+ 시계 아이콘 + + 구매 가능기간: {new Date(ticket.startDate).toLocaleDateString()} ~{' '} + {new Date(ticket.endDate).toLocaleDateString()} + +
+
))}