From 129ebca9aa7d42cb01cefb96633d4b4dad24d356 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Wed, 4 Jun 2025 14:23:48 +0900 Subject: [PATCH 01/20] =?UTF-8?q?refact:=20=EC=A3=BC=EB=AC=B8=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/ticket/api/order.ts | 6 ++++-- src/features/ticket/hooks/useOrderHook.ts | 2 +- .../dashboard/ui/ticket/TIcketConfirmPage.tsx | 14 ++++++------- src/pages/menu/ui/MyTicketPage.tsx | 20 +++++++++++-------- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/features/ticket/api/order.ts b/src/features/ticket/api/order.ts index 5b43bfc4..339331ef 100644 --- a/src/features/ticket/api/order.ts +++ b/src/features/ticket/api/order.ts @@ -23,7 +23,9 @@ export const orderTickets = async (data: OrderTicketRequest) => { }; // 티켓 취소 -export const cancelTickets = async (orderId: number) => { - const response = await axiosClient.post(`/orders/${orderId}/cancel`); +export const cancelTickets = async (orderIds: number[]) => { + const response = await axiosClient.post('/orders/cancel', { + orderIds, + }); return response.data; }; diff --git a/src/features/ticket/hooks/useOrderHook.ts b/src/features/ticket/hooks/useOrderHook.ts index 96d367dc..26878710 100644 --- a/src/features/ticket/hooks/useOrderHook.ts +++ b/src/features/ticket/hooks/useOrderHook.ts @@ -22,7 +22,7 @@ export const useTicketOrderDetail = (orderId: number) => { // 주문 취소 export const useCancelTicket = () => { return useMutation({ - mutationFn: (orderId: number) => cancelTickets(orderId), + mutationFn: (orderIds: number[]) => cancelTickets(orderIds), onSuccess: () => { alert('티켓이 성공적으로 취소되었습니다.'); }, diff --git a/src/pages/dashboard/ui/ticket/TIcketConfirmPage.tsx b/src/pages/dashboard/ui/ticket/TIcketConfirmPage.tsx index 27335567..aee1ce04 100644 --- a/src/pages/dashboard/ui/ticket/TIcketConfirmPage.tsx +++ b/src/pages/dashboard/ui/ticket/TIcketConfirmPage.tsx @@ -23,9 +23,11 @@ const TicketConfirmPage = () => { navigate(-1); }; const cancleOrderTicket = async (orderIds: number[]) => { - for (const orderId of orderIds) { - cancelTicket(orderId); - } + cancelTicket(orderIds, { + onSuccess: () => { + navigate('/menu/myticket'); + }, + }); }; return ( <> @@ -72,11 +74,7 @@ const TicketConfirmPage = () => { approveButtonText="티켓 취소" rejectButtonText="뒤로가기" onClose={() => setIsModalOpen(false)} - onClick={() => { - cancleOrderTicket(orderIds).then(() => { - navigate('/menu/myticket'); - }); - }} + onClick={() => cancleOrderTicket(orderIds)} /> )} diff --git a/src/pages/menu/ui/MyTicketPage.tsx b/src/pages/menu/ui/MyTicketPage.tsx index 3397f7a8..cf31d0bd 100644 --- a/src/pages/menu/ui/MyTicketPage.tsx +++ b/src/pages/menu/ui/MyTicketPage.tsx @@ -104,9 +104,8 @@ const MyTicketPage = () => { location={ticket.event.address} hashtags={ticket.event.hashtags} onClick={() => handleEventCardClick(ticket)} - className={`transition-transform duration-200 ${ - isCancelMode && selectedIds.includes(ticket.id) ? 'scale-95 border-2 border-pink-400' : '' - }`} + className={`transition-transform duration-200 ${isCancelMode && selectedIds.includes(ticket.id) ? 'scale-95 border-2 border-pink-400' : '' + }`} >
티켓 @@ -157,11 +156,16 @@ const MyTicketPage = () => { rejectButtonText="뒤로가기" onClose={() => setIsDeleteModalOpen(false)} onClick={() => { - Promise.all(selectedIds.map(id => cancelTicket(id))).then(() => { - setTickets(prev => prev.filter(ticket => !selectedIds.includes(ticket.id))); - setIsDeleteModalOpen(false); - setIsCancelMode(false); - setSelectedIds([]); + cancelTicket(selectedIds, { + onSuccess: () => { + setTickets(prev => prev.filter(ticket => !selectedIds.includes(ticket.id))); + setIsDeleteModalOpen(false); + setIsCancelMode(false); + setSelectedIds([]); + }, + onError: () => { + alert('티켓 취소에 실패했습니다.'); + }, }); }} /> From fabcaa63f7710660e63c0a6a8240fc571492dea2 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Wed, 4 Jun 2025 14:55:47 +0900 Subject: [PATCH 02/20] =?UTF-8?q?refact:=20=EC=98=88=EC=95=BD=20=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EC=8B=9C=EA=B0=84=20kst=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/lib/date.ts | 20 +++++++++++++++++++ .../dashboard/ui/email/SentMailCard.tsx | 5 +++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/shared/lib/date.ts b/src/shared/lib/date.ts index 2815eca3..c5d55d2c 100644 --- a/src/shared/lib/date.ts +++ b/src/shared/lib/date.ts @@ -21,3 +21,23 @@ export const formatISO = (date: Date, time: string): string => { const kstDate = new Date(newDate.getTime() + 9 * 60 * 60 * 1000); // UTC+9 return kstDate.toISOString(); }; + +export const formatUtcToKst = (utcString: string): string => { + const date = new Date(utcString); + + const utcYear = date.getUTCFullYear(); + const utcMonth = date.getUTCMonth(); + const utcDay = date.getUTCDate(); + const utcHours = date.getUTCHours(); + const utcMinutes = date.getUTCMinutes(); + + const kstDate = new Date(Date.UTC(utcYear, utcMonth, utcDay, utcHours + 9, utcMinutes)); + + const year = kstDate.getFullYear(); + const month = (kstDate.getMonth() + 1).toString().padStart(2, '0'); + const day = kstDate.getDate().toString().padStart(2, '0'); + const hours = kstDate.getHours().toString().padStart(2, '0'); + const minutes = kstDate.getMinutes().toString().padStart(2, '0'); + + return `${year}년 ${month}월 ${day}일 ${hours}:${minutes}`; +}; diff --git a/src/widgets/dashboard/ui/email/SentMailCard.tsx b/src/widgets/dashboard/ui/email/SentMailCard.tsx index 2628482c..a1fef07e 100644 --- a/src/widgets/dashboard/ui/email/SentMailCard.tsx +++ b/src/widgets/dashboard/ui/email/SentMailCard.tsx @@ -1,7 +1,7 @@ import arrow from '../../../../../public/assets/dashboard/mail/Arrow.svg'; import { useState } from 'react'; import IconButton from '../../../../../design-system/ui/buttons/IconButton'; -import { formatDate, formatTime } from '../../../../shared/lib/date'; +import { formatUtcToKst } from '../../../../shared/lib/date'; import TertiaryButton from '../../../../../design-system/ui/buttons/TertiaryButton'; import { useNavigate, useParams } from 'react-router-dom'; import { ReadEmailResponse } from '../../../../features/dashboard/model/email'; @@ -18,6 +18,7 @@ const SentMailCard = ({ mail, isPending = false, onClickDelete }: SentMailCardPr const navigate = useNavigate(); const [isOpen, setIsOpen] = useState(false); const { id } = useParams(); + console.log(formatUtcToKst("2025-07-18T18:00")); const { setReservationEmailId, setTitle, setContent, setRecipients, setReservationDate } = useEmailStore(); const handleEditClick = () => { setReservationEmailId(mail.id); @@ -38,7 +39,7 @@ const SentMailCard = ({ mail, isPending = false, onClickDelete }: SentMailCardPr

{mail.title}

- {formatDate(mail.reservationDate)} {formatTime(mail.reservationDate)} + {formatUtcToKst(mail.reservationDate)}

Date: Wed, 4 Jun 2025 15:18:59 +0900 Subject: [PATCH 03/20] =?UTF-8?q?refact:=20=EB=A9=94=EC=9D=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=EC=8B=9C=EA=B0=84=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/event/ui/TimePicker.tsx | 29 ++++++++++++++++--- src/pages/dashboard/ui/mail/EmailEditPage.tsx | 1 + src/shared/lib/date.ts | 13 +++------ .../dashboard/ui/email/SentMailCard.tsx | 1 - 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/features/event/ui/TimePicker.tsx b/src/features/event/ui/TimePicker.tsx index ec6dae29..0ea080c7 100644 --- a/src/features/event/ui/TimePicker.tsx +++ b/src/features/event/ui/TimePicker.tsx @@ -3,13 +3,34 @@ import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; interface TimePickerProps { + value?: string; onChange: (datetime: string) => void; } -const TimePicker = ({ onChange }: TimePickerProps) => { - const [selectedDate, setSelectedDate] = useState(new Date()); - const [selectedHour, setSelectedHour] = useState('00'); - const [selectedMinute, setSelectedMinute] = useState('00'); +const parseUtcToKst = (utcString: string): Date => { + const utcDate = new Date(utcString); + const kstTimestamp = utcDate.getTime() + 9 * 60 * 60 * 1000; + return new Date(kstTimestamp); +}; + +const TimePicker = ({ value, onChange }: TimePickerProps) => { + const initialKstDate = value ? parseUtcToKst(value) : new Date(); + const [selectedDate, setSelectedDate] = useState(initialKstDate); + const [selectedHour, setSelectedHour] = useState( + initialKstDate.getHours().toString().padStart(2, '0') + ); + const [selectedMinute, setSelectedMinute] = useState( + initialKstDate.getMinutes().toString().padStart(2, '0') + ); + + useEffect(() => { + if (value) { + const date = new Date(value); + setSelectedDate(date); + setSelectedHour(date.getHours().toString().padStart(2, '0')); + setSelectedMinute(date.getMinutes().toString().padStart(2, '0')); + } + }, [value]); // 날짜, 시간 바뀔 때마다 업데이트 useEffect(() => { diff --git a/src/pages/dashboard/ui/mail/EmailEditPage.tsx b/src/pages/dashboard/ui/mail/EmailEditPage.tsx index 24b15fb4..a6ba02f8 100644 --- a/src/pages/dashboard/ui/mail/EmailEditPage.tsx +++ b/src/pages/dashboard/ui/mail/EmailEditPage.tsx @@ -46,6 +46,7 @@ const EmailEditPage = () => { /> {/*시간 선택 컴포넌트*/} { setReservationDate(isoString); }} diff --git a/src/shared/lib/date.ts b/src/shared/lib/date.ts index c5d55d2c..b54f1bd2 100644 --- a/src/shared/lib/date.ts +++ b/src/shared/lib/date.ts @@ -21,17 +21,11 @@ export const formatISO = (date: Date, time: string): string => { const kstDate = new Date(newDate.getTime() + 9 * 60 * 60 * 1000); // UTC+9 return kstDate.toISOString(); }; - export const formatUtcToKst = (utcString: string): string => { - const date = new Date(utcString); - - const utcYear = date.getUTCFullYear(); - const utcMonth = date.getUTCMonth(); - const utcDay = date.getUTCDate(); - const utcHours = date.getUTCHours(); - const utcMinutes = date.getUTCMinutes(); + const utcDate = new Date(utcString); - const kstDate = new Date(Date.UTC(utcYear, utcMonth, utcDay, utcHours + 9, utcMinutes)); + const kstTimestamp = utcDate.getTime() + 9 * 60 * 60 * 1000; + const kstDate = new Date(kstTimestamp); const year = kstDate.getFullYear(); const month = (kstDate.getMonth() + 1).toString().padStart(2, '0'); @@ -41,3 +35,4 @@ export const formatUtcToKst = (utcString: string): string => { return `${year}년 ${month}월 ${day}일 ${hours}:${minutes}`; }; + diff --git a/src/widgets/dashboard/ui/email/SentMailCard.tsx b/src/widgets/dashboard/ui/email/SentMailCard.tsx index a1fef07e..0872c19c 100644 --- a/src/widgets/dashboard/ui/email/SentMailCard.tsx +++ b/src/widgets/dashboard/ui/email/SentMailCard.tsx @@ -18,7 +18,6 @@ const SentMailCard = ({ mail, isPending = false, onClickDelete }: SentMailCardPr const navigate = useNavigate(); const [isOpen, setIsOpen] = useState(false); const { id } = useParams(); - console.log(formatUtcToKst("2025-07-18T18:00")); const { setReservationEmailId, setTitle, setContent, setRecipients, setReservationDate } = useEmailStore(); const handleEditClick = () => { setReservationEmailId(mail.id); From e693564e01e18dd3ad3c34761ae16d84114798d4 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Wed, 4 Jun 2025 15:32:40 +0900 Subject: [PATCH 04/20] =?UTF-8?q?refact:=20d-day=20false=20=EA=B0=92=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design-system/ui/texts/Countdown.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/design-system/ui/texts/Countdown.tsx b/design-system/ui/texts/Countdown.tsx index 4d947d9f..5fb3b84b 100644 --- a/design-system/ui/texts/Countdown.tsx +++ b/design-system/ui/texts/Countdown.tsx @@ -21,7 +21,10 @@ const Countdown = ({ children, isChecked }: CountdownProps) => { border-[0.1px] font-medium ${flexCenter} `; - return ; + const isEnded = children === 'false'; + const displayText = isEnded ? '종료' : children; + + return }; export default Countdown; From a4a5e3164938f33d5b4fff6479a925efa6e5c6f0 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Wed, 4 Jun 2025 15:46:33 +0900 Subject: [PATCH 05/20] =?UTF-8?q?refact:=20=EC=B2=B4=ED=81=AC=20UI=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design-system/ui/modals/QrModal.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/design-system/ui/modals/QrModal.tsx b/design-system/ui/modals/QrModal.tsx index 37d3e763..f7d9fa5b 100644 --- a/design-system/ui/modals/QrModal.tsx +++ b/design-system/ui/modals/QrModal.tsx @@ -5,7 +5,8 @@ import { flexColumnCenter, flexColumn, flexRowSpaceBetweenCenter } from '../../s import qr_calendar from '../../icons/QrCalendar.svg'; import qr_location from '../../icons/QrLocation.svg'; import qr_ticket from '../../icons/QrTicket.svg'; -import qr_check from '../../icons/QrCheck.svg'; +import qr_check from '../../../public/assets/menu/completed.svg'; +import qr_pending from '../../../public/assets/menu/pending.svg'; interface QrModalProps { isChecked: boolean; // QR 상태 @@ -88,13 +89,13 @@ const QrModal = ({
} + iconPath={qr_check} children={orderStatus === 'COMPLETED' ? '승인됨' : '대기 중'} className="text-11" > } + iconPath={qr_check} children={isCheckIn ? '체크인 완료' : '체크인 미완료'} className="text-11" > From f5054d862b85e6f85628817bb2d0a5a51721253a Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Wed, 4 Jun 2025 15:52:42 +0900 Subject: [PATCH 06/20] =?UTF-8?q?refact:=20=EB=B9=88=20QR=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design-system/ui/modals/QrModal.tsx | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/design-system/ui/modals/QrModal.tsx b/design-system/ui/modals/QrModal.tsx index f7d9fa5b..9de81ddc 100644 --- a/design-system/ui/modals/QrModal.tsx +++ b/design-system/ui/modals/QrModal.tsx @@ -58,7 +58,17 @@ const QrModal = ({
{iconPath1 &&
{iconPath1}
}
- QR Code + {ticketQrCode ? ( + QR Code + ) : ( +
+ 주최자의 승인이 완료되면
QR이 발급됩니다. +
+ )}
@@ -69,19 +79,19 @@ const QrModal = ({
} + iconPath={qr_calendar} children={formattedDate} className="text-11" > } + iconPath={qr_location} children={location} className="text-11" > } + iconPath={qr_ticket} children={ticketName} className="text-11" > @@ -89,13 +99,13 @@ const QrModal = ({
} + iconPath={qr_check} children={orderStatus === 'COMPLETED' ? '승인됨' : '대기 중'} className="text-11" > } + iconPath={qr_check} children={isCheckIn ? '체크인 완료' : '체크인 미완료'} className="text-11" > From 4a088a57bdf922f00a174440d82300d1f5349d1e Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Wed, 4 Jun 2025 16:00:29 +0900 Subject: [PATCH 07/20] =?UTF-8?q?refact:=20=ED=8B=B0=EC=BC=93=20=EC=97=86?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20=EB=AC=B8=EA=B5=AC=20=EC=9C=84=EC=B9=98?= =?UTF-8?q?=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/menu/ui/MyTicketPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/menu/ui/MyTicketPage.tsx b/src/pages/menu/ui/MyTicketPage.tsx index cf31d0bd..5a99122b 100644 --- a/src/pages/menu/ui/MyTicketPage.tsx +++ b/src/pages/menu/ui/MyTicketPage.tsx @@ -122,7 +122,10 @@ const MyTicketPage = () => { )) ) : ( -

구매하신 티켓 정보가 없습니다.

+
+

구매하신 티켓 정보가 없습니다.

+
+ )}
From 28da70c5ba325f7aac02901fa1b2e191f73bc9ab Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Wed, 4 Jun 2025 16:12:03 +0900 Subject: [PATCH 08/20] =?UTF-8?q?feat:=20=EC=9D=B4=EC=9A=A9=20=EC=95=BD?= =?UTF-8?q?=EA=B4=80=20=EB=8F=99=EC=9D=98=20api=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/join/api/user.ts | 8 ++++++- src/features/join/hooks/useUserHook.ts | 14 +++++++++++- src/features/join/model/agreementStore.ts | 15 +++++++++++++ src/features/join/model/userInformation.ts | 7 ++++++ src/pages/join/AgreementPage.tsx | 25 ++++++++++++++++++++-- 5 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/features/join/api/user.ts b/src/features/join/api/user.ts index ba992d3e..57afbf7f 100644 --- a/src/features/join/api/user.ts +++ b/src/features/join/api/user.ts @@ -1,5 +1,5 @@ import { axiosClient } from '../../../shared/types/api/http-client'; -import { UserInfoRequest, UserInfoResponse } from '../model/userInformation'; +import { TermsAgreementRequset, UserInfoRequest, UserInfoResponse } from '../model/userInformation'; export const readUser = async (): Promise => { const response = await axiosClient.get<{ result: UserInfoResponse }>('/users', { @@ -14,3 +14,9 @@ export const updateUser = async (data: UserInfoRequest): Promise { + const response = await axiosClient.post('/terms', payload); + return response.data; +}; diff --git a/src/features/join/hooks/useUserHook.ts b/src/features/join/hooks/useUserHook.ts index 1b717a8a..20ae5ca7 100644 --- a/src/features/join/hooks/useUserHook.ts +++ b/src/features/join/hooks/useUserHook.ts @@ -1,5 +1,5 @@ import { useMutation, useQuery } from '@tanstack/react-query'; -import { readUser, updateUser } from '../api/user'; +import { agreeTerms, readUser, updateUser } from '../api/user'; import { UserInfoRequest, UserInfoResponse } from '../model/userInformation'; export const useUserInfo = (enabled: boolean = true) => { @@ -15,3 +15,15 @@ export const useUserUpdate = () => { mutationFn: updateUser, }); }; + +export const useAgreeTerms = () => { + return useMutation({ + mutationFn: agreeTerms, + onSuccess: () => { + alert('이용약관 동의 완료'); + }, + onError: () => { + alert('동의 처리 실패'); + } + }); +}; \ No newline at end of file diff --git a/src/features/join/model/agreementStore.ts b/src/features/join/model/agreementStore.ts index d576dac7..4232f32a 100644 --- a/src/features/join/model/agreementStore.ts +++ b/src/features/join/model/agreementStore.ts @@ -13,6 +13,12 @@ interface AgreementState { agreements: Agreements; toggleAgreement: (key: keyof Agreements) => void; // 특정 항목 상태 토글 isAllRequiredAgreed: () => boolean; // 필수 항목 체크 여부 + getAgreementStates: () => { + serviceAgreed: boolean; + privacyPolicyAgree: boolean; + personalInfoUsageAgreed: boolean; + marketingAgreed: boolean; + }; } export const useAgreementStore = create((set, get) => ({ @@ -56,4 +62,13 @@ export const useAgreementStore = create((set, get) => ({ const { agreements } = get(); return agreements.terms && agreements.privacy && agreements.dataUsage; }, + getAgreementStates: () => { + const { agreements } = get(); + return { + serviceAgreed: agreements.terms, + privacyPolicyAgree: agreements.privacy, + personalInfoUsageAgreed: agreements.dataUsage, + marketingAgreed: agreements.marketing, + }; + }, })); diff --git a/src/features/join/model/userInformation.ts b/src/features/join/model/userInformation.ts index 14553792..2f81705f 100644 --- a/src/features/join/model/userInformation.ts +++ b/src/features/join/model/userInformation.ts @@ -9,4 +9,11 @@ export interface UserInfoRequest { name: string; phoneNumber: string; email: string; +} + +export interface TermsAgreementRequset { + serviceAgreed: boolean; + privacyPolicyAgree: boolean; + personalInfoUsageAgreed: boolean; + marketingAgreed: boolean; } \ No newline at end of file diff --git a/src/pages/join/AgreementPage.tsx b/src/pages/join/AgreementPage.tsx index ff6a31ad..65c6e31d 100644 --- a/src/pages/join/AgreementPage.tsx +++ b/src/pages/join/AgreementPage.tsx @@ -4,10 +4,31 @@ import Button from '../../../design-system/ui/Button'; import AgreementList from '../../features/join/ui/AgreementList'; import { useAgreementStore } from '../../features/join/model/agreementStore'; import { useNavigate } from 'react-router-dom'; +import { useAgreeTerms } from '../../features/join/hooks/useUserHook'; +import { TermsAgreementRequset } from '../../features/join/model/userInformation'; const AgreementPage: React.FC = () => { - const { isAllRequiredAgreed } = useAgreementStore(); + const { isAllRequiredAgreed, getAgreementStates } = useAgreementStore(); const navigate = useNavigate(); + const { mutate:agreeTerms } = useAgreeTerms(); + + const handleAgree = () => { + const agreementData: TermsAgreementRequset = { + serviceAgreed: getAgreementStates().serviceAgreed, + privacyPolicyAgree: getAgreementStates().privacyPolicyAgree, + personalInfoUsageAgreed: getAgreementStates().personalInfoUsageAgreed, + marketingAgreed: getAgreementStates().marketingAgreed, + }; + + agreeTerms(agreementData, { + onSuccess: () => { + navigate('/join/info-input'); + }, + onError: () => { + alert('약관 동의 중 오류가 발생했습니다. 다시 시도해주세요.'); + }, + }); + }; return (
@@ -32,7 +53,7 @@ const AgreementPage: React.FC = () => {
From 48d1126931fd278c042e4d4e5459deaeaefebe2d Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Wed, 4 Jun 2025 16:36:49 +0900 Subject: [PATCH 09/20] =?UTF-8?q?feat:=20=EC=9D=B4=EC=9A=A9=20=EC=95=BD?= =?UTF-8?q?=EA=B4=80=20=EC=83=81=EC=84=B8=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/join/api/user.ts | 6 ++++-- src/features/join/hooks/useUserHook.ts | 3 ++- src/features/join/ui/AgreementList.tsx | 5 +++++ src/pages/join/AgreementPage.tsx | 6 ++---- src/shared/ui/AgreementCard.tsx | 14 ++++++++++++-- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/features/join/api/user.ts b/src/features/join/api/user.ts index 57afbf7f..c1392f40 100644 --- a/src/features/join/api/user.ts +++ b/src/features/join/api/user.ts @@ -16,7 +16,9 @@ export const updateUser = async (data: UserInfoRequest): Promise { - const response = await axiosClient.post('/terms', payload); +export const agreeTerms = async (data: TermsAgreementRequset) => { + const response = await axiosClient.post('/terms', data, { + headers: { isPublicApi: true }, + }); return response.data; }; diff --git a/src/features/join/hooks/useUserHook.ts b/src/features/join/hooks/useUserHook.ts index 20ae5ca7..aa3af4f2 100644 --- a/src/features/join/hooks/useUserHook.ts +++ b/src/features/join/hooks/useUserHook.ts @@ -22,8 +22,9 @@ export const useAgreeTerms = () => { onSuccess: () => { alert('이용약관 동의 완료'); }, - onError: () => { + onError: (error) => { alert('동의 처리 실패'); + console.error('이용약관 동의 실패', error); } }); }; \ No newline at end of file diff --git a/src/features/join/ui/AgreementList.tsx b/src/features/join/ui/AgreementList.tsx index 059128cd..be517c85 100644 --- a/src/features/join/ui/AgreementList.tsx +++ b/src/features/join/ui/AgreementList.tsx @@ -3,6 +3,7 @@ import { useAgreementStore } from '../model/agreementStore'; const AgreementList = () => { const { agreements, toggleAgreement } = useAgreementStore(); + const NOTION_TERMS_LINK = 'https://namu00.notion.site/1a5eaffb9b0e8196b408f986b13aa15d?source=copy_link'; return (
@@ -11,24 +12,28 @@ const AgreementList = () => { required={true} checked={agreements.terms} onChange={() => toggleAgreement('terms')} + link={NOTION_TERMS_LINK} /> toggleAgreement('privacy')} + link={NOTION_TERMS_LINK} /> toggleAgreement('dataUsage')} + link={NOTION_TERMS_LINK} /> toggleAgreement('marketing')} + link={NOTION_TERMS_LINK} />
); diff --git a/src/pages/join/AgreementPage.tsx b/src/pages/join/AgreementPage.tsx index 65c6e31d..618473a5 100644 --- a/src/pages/join/AgreementPage.tsx +++ b/src/pages/join/AgreementPage.tsx @@ -19,14 +19,12 @@ const AgreementPage: React.FC = () => { personalInfoUsageAgreed: getAgreementStates().personalInfoUsageAgreed, marketingAgreed: getAgreementStates().marketingAgreed, }; + console.log(agreementData) agreeTerms(agreementData, { onSuccess: () => { navigate('/join/info-input'); }, - onError: () => { - alert('약관 동의 중 오류가 발생했습니다. 다시 시도해주세요.'); - }, }); }; @@ -36,7 +34,7 @@ const AgreementPage: React.FC = () => { centerContent="이용약관" leftButtonLabel="<" leftButtonClassName="text-2xl z-30 font-semibold" - leftButtonClick={() => navigate(-1)} + leftButtonClick={() => navigate('/')} color="black" />
diff --git a/src/shared/ui/AgreementCard.tsx b/src/shared/ui/AgreementCard.tsx index e9638f25..fb8be0b7 100644 --- a/src/shared/ui/AgreementCard.tsx +++ b/src/shared/ui/AgreementCard.tsx @@ -6,9 +6,10 @@ interface AgreementCardProps { required: boolean; checked: boolean; onChange: () => void; + link?: string; } -const AgreementCard: React.FC = ({ title, required, checked, onChange }) => ( +const AgreementCard: React.FC = ({ title, required, checked, onChange, link }) => (
@@ -17,7 +18,16 @@ const AgreementCard: React.FC = ({ title, required, checked, {title}
- + {link && ( + + > + + )}
); From 7c196fdfbb2ee52c92e1204472577f37a918fcd8 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Wed, 4 Jun 2025 17:02:50 +0900 Subject: [PATCH 10/20] =?UTF-8?q?refact:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=99=88=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/backgrounds/EventRegisterLayout.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/shared/ui/backgrounds/EventRegisterLayout.tsx b/src/shared/ui/backgrounds/EventRegisterLayout.tsx index 03dbbf7b..0bc7a885 100644 --- a/src/shared/ui/backgrounds/EventRegisterLayout.tsx +++ b/src/shared/ui/backgrounds/EventRegisterLayout.tsx @@ -1,6 +1,9 @@ import { ReactNode, useState, Children, isValidElement, cloneElement } from 'react'; import Button from '../../../../design-system/ui/Button'; import Header from '../../../../design-system/ui/Header'; +import IconButton from '../../../../design-system/ui/buttons/IconButton'; +import { useNavigate } from 'react-router-dom'; +import HomeButton from '../../../../public/assets/menu/HomeButton.svg'; interface EventRegisterLayoutProps { children: ReactNode; @@ -35,6 +38,7 @@ const EventRegisterLayout = ({ } return child; }); + const navigate = useNavigate(); return (
@@ -46,6 +50,13 @@ const EventRegisterLayout = ({ leftButtonClick={onPrev} color="white" leftButtonClassName="text-xl z-30" + rightContent={ + } + onClick={() => navigate('/')} + iconClassName="cursor-pointer z-30 ml-auto" + /> + } />
From c341cb4c7b0278198e489d4867385909eb30e9ff Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Wed, 4 Jun 2025 17:05:09 +0900 Subject: [PATCH 11/20] =?UTF-8?q?refact:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EC=8B=9C=20=EB=AC=B8=EA=B5=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/menu/ui/myHost/HostDetailPage.tsx | 33 +++++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/pages/menu/ui/myHost/HostDetailPage.tsx b/src/pages/menu/ui/myHost/HostDetailPage.tsx index 52f6d3aa..aafd2937 100644 --- a/src/pages/menu/ui/myHost/HostDetailPage.tsx +++ b/src/pages/menu/ui/myHost/HostDetailPage.tsx @@ -10,6 +10,7 @@ const HostDetailPage = () => { const hostChannelId = Number(id); const { data } = useHostDetail(hostChannelId); + const events = data?.result.events ?? []; return ( { } >
- {data?.result.events?.map(event => ( - - ))} + {events.length === 0 ? ( +

+ 등록된 이벤트가 없습니다. +

+ ) : ( + events.map(event => ( + + )) + )}
); From 838be6516fb998fdec8b8eec0cf97b2095a69155 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Thu, 5 Jun 2025 14:28:33 +0900 Subject: [PATCH 12/20] =?UTF-8?q?fix:=20qr=EB=AA=A8=EB=8B=AC=20=EC=97=B4?= =?UTF-8?q?=EB=A6=B4=20=EB=95=8C=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EC=9C=84=EB=A1=9C=20=EC=98=AC=EB=9D=BC=EA=B0=80?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/menu/ui/MyTicketPage.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/menu/ui/MyTicketPage.tsx b/src/pages/menu/ui/MyTicketPage.tsx index 5a99122b..2e267b41 100644 --- a/src/pages/menu/ui/MyTicketPage.tsx +++ b/src/pages/menu/ui/MyTicketPage.tsx @@ -67,7 +67,7 @@ const MyTicketPage = () => { return ( - {!isModalOpen && tickets.length > 0 && ( + {tickets.length > 0 && (
{
{isModalOpen && selectedTicket && ( -
-
+
} @@ -149,7 +148,6 @@ const MyTicketPage = () => { onClick={() => setIsModalOpen(false)} />
-
)} {isDeleteModalOpen && ( From 82f3e937e0e21a4d8e2d4b5b467d0f9378188682 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Thu, 5 Jun 2025 15:00:36 +0900 Subject: [PATCH 13/20] =?UTF-8?q?refact:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B6=88=EA=B0=80=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design-system/ui/textFields/UnderlineTextField.tsx | 4 ++-- src/pages/join/InfoInputPage.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/design-system/ui/textFields/UnderlineTextField.tsx b/design-system/ui/textFields/UnderlineTextField.tsx index c7014adc..46503e9d 100644 --- a/design-system/ui/textFields/UnderlineTextField.tsx +++ b/design-system/ui/textFields/UnderlineTextField.tsx @@ -1,6 +1,6 @@ -import { ChangeEvent, forwardRef } from 'react'; +import { ChangeEvent, forwardRef, InputHTMLAttributes } from 'react'; -interface UnderlineTextFieldProps { +interface UnderlineTextFieldProps extends InputHTMLAttributes { label: string; type?: string; value?: string; diff --git a/src/pages/join/InfoInputPage.tsx b/src/pages/join/InfoInputPage.tsx index 1418fa0f..9c591d6e 100644 --- a/src/pages/join/InfoInputPage.tsx +++ b/src/pages/join/InfoInputPage.tsx @@ -95,6 +95,7 @@ const InfoInputPage = () => { type="email" errorMessage={errors.email?.message} className="text-xl" + readOnly {...register('email')} /> From 9f0943b4549f1beba5f4776c71d738d1acd40f78 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Thu, 5 Jun 2025 15:12:08 +0900 Subject: [PATCH 14/20] =?UTF-8?q?refact:=20=EA=B4=80=EB=A0=A8=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20=EC=97=AC=EB=9F=AC=EC=A4=84=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/event/ui/EventDetailsPage.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/pages/event/ui/EventDetailsPage.tsx b/src/pages/event/ui/EventDetailsPage.tsx index cafe5920..16e500cd 100644 --- a/src/pages/event/ui/EventDetailsPage.tsx +++ b/src/pages/event/ui/EventDetailsPage.tsx @@ -138,12 +138,17 @@ const EventDetailsPage = () => {

관련 링크

-
+
{event.result.referenceLinks.map((link: { title: string; url: string }, index: number) => ( -
+
링크 이모지 {link.title} - + {link.url}
From e2264c2504608ce14ea4ac97071416f0257aedc4 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Thu, 5 Jun 2025 15:26:07 +0900 Subject: [PATCH 15/20] =?UTF-8?q?refact:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=20=EB=AF=B8=EC=9E=85=EB=A0=A5=20=EC=8B=9C=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=8C=20=EB=B2=84=ED=8A=BC=20=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/event/ui/TextEditor.tsx | 41 ++++++++----------- .../event/ui/create-event/EventInfoPage.tsx | 20 ++++++++- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/features/event/ui/TextEditor.tsx b/src/features/event/ui/TextEditor.tsx index 632ba87a..7d917cb7 100644 --- a/src/features/event/ui/TextEditor.tsx +++ b/src/features/event/ui/TextEditor.tsx @@ -8,30 +8,17 @@ interface TextEditorProps { value?: string; onChange?: (value: string) => void; setEventState?: React.Dispatch>; + onValidationChange?: (isValid: boolean) => void; } const formats = [ - 'font', - 'header', - 'bold', - 'italic', - 'underline', - 'strike', - 'blockquote', - 'list', - 'bullet', - 'indent', - 'link', - 'image', - 'align', - 'color', - 'background', - 'size', - 'h1', + 'font', 'header', 'bold', 'italic', 'underline', 'strike', 'blockquote', + 'list', 'bullet', 'indent', 'link', 'image', 'align', 'color', 'background', + 'size', 'h1', ]; -const TextEditor = ({ value, onChange, setEventState }: TextEditorProps) => { - const [content, setContent] = useState(''); +const TextEditor = ({ value = '', onChange, setEventState, onValidationChange }: TextEditorProps) => { + const [content, setContent] = useState(value); const quillRef = useRef(null); const imageHandler = async () => { @@ -81,15 +68,18 @@ const TextEditor = ({ value, onChange, setEventState }: TextEditorProps) => { ); const handleChange = (value: string) => { - setContent(value); - onChange?.(value); - if (setEventState) { - setEventState(prev => ({ ...prev, description: value })); - } + setContent(value); // 내부 상태 업데이트 + onChange?.(value); // 외부로 전달 + setEventState?.(prev => ({ ...prev, description: value })); + + const plainText = value.replace(/<[^>]*>/g, '').trim(); + onValidationChange?.(plainText.length > 0); }; useEffect(() => { - setContent(value ?? ''); + setContent(value); // 외부 value가 바뀌면 내부에 반영 + const plainText = value.replace(/<[^>]*>/g, '').trim(); + onValidationChange?.(plainText.length > 0); }, [value]); return ( @@ -107,4 +97,5 @@ const TextEditor = ({ value, onChange, setEventState }: TextEditorProps) => {
); }; + export default TextEditor; diff --git a/src/pages/event/ui/create-event/EventInfoPage.tsx b/src/pages/event/ui/create-event/EventInfoPage.tsx index 665d2164..15de8d62 100644 --- a/src/pages/event/ui/create-event/EventInfoPage.tsx +++ b/src/pages/event/ui/create-event/EventInfoPage.tsx @@ -2,6 +2,7 @@ import FileUpload from '../../../../features/event/ui/FileUpload'; import TextEditor from '../../../../features/event/ui/TextEditor'; import LinkInput from '../../../../features/event/ui/LinkInput'; import { useFunnelState } from '../../../../features/event/model/FunnelContext'; +import { useEffect, useState } from 'react'; interface EventInfoPageProps { onValidationChange?: (isValid: boolean) => void; @@ -9,10 +10,25 @@ interface EventInfoPageProps { const EventInfoPage = ({ onValidationChange }: EventInfoPageProps) => { const { setEventState } = useFunnelState(); + const [isFileValid, setIsFileValid] = useState(false); + const [isTextValid, setIsTextValid] = useState(false); + + const handleFileValidation = (valid: boolean) => { + setIsFileValid(valid); + }; + + const handleTextValidation = (valid: boolean) => { + setIsTextValid(valid); + }; + useEffect(() => { + const allValid = isFileValid && isTextValid; + onValidationChange?.(allValid); + }, [isFileValid, isTextValid, onValidationChange]); + return (
- - + +
); From 04ccd213df087a610f4731f4a0900452176d775a Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Fri, 6 Jun 2025 16:45:33 +0900 Subject: [PATCH 16/20] =?UTF-8?q?refact:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=84=A4=EB=AA=85=20=EA=B8=80=EC=9E=90?= =?UTF-8?q?=EC=88=98=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/event/ui/TextEditor.tsx | 35 ++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/features/event/ui/TextEditor.tsx b/src/features/event/ui/TextEditor.tsx index 7d917cb7..eadda408 100644 --- a/src/features/event/ui/TextEditor.tsx +++ b/src/features/event/ui/TextEditor.tsx @@ -11,6 +11,8 @@ interface TextEditorProps { onValidationChange?: (isValid: boolean) => void; } +const MAX_LENGTH = 200; + const formats = [ 'font', 'header', 'bold', 'italic', 'underline', 'strike', 'blockquote', 'list', 'bullet', 'indent', 'link', 'image', 'align', 'color', 'background', @@ -20,6 +22,7 @@ const formats = [ const TextEditor = ({ value = '', onChange, setEventState, onValidationChange }: TextEditorProps) => { const [content, setContent] = useState(value); const quillRef = useRef(null); + const [isOverLimit, setIsOverLimit] = useState(false); const imageHandler = async () => { if (!quillRef.current) return; @@ -68,12 +71,21 @@ const TextEditor = ({ value = '', onChange, setEventState, onValidationChange }: ); const handleChange = (value: string) => { - setContent(value); // 내부 상태 업데이트 - onChange?.(value); // 외부로 전달 - setEventState?.(prev => ({ ...prev, description: value })); - const plainText = value.replace(/<[^>]*>/g, '').trim(); - onValidationChange?.(plainText.length > 0); + + if (plainText.length <= MAX_LENGTH) { + setContent(value); + onChange?.(value); + setEventState?.(prev => ({ ...prev, description: value })); + onValidationChange?.(plainText.length > 0); + setIsOverLimit(false); + } else { + const editorInstance = quillRef.current?.getEditor(); + if (editorInstance) { + editorInstance.setContents(editorInstance.clipboard.convert(content)); + } + setIsOverLimit(true); + } }; useEffect(() => { @@ -82,6 +94,9 @@ const TextEditor = ({ value = '', onChange, setEventState, onValidationChange }: onValidationChange?.(plainText.length > 0); }, [value]); + const plainTextLength = content.replace(/<[^>]*>/g, '').trim().length; + + return (

이벤트에 대한 상세 설명

@@ -94,6 +109,16 @@ const TextEditor = ({ value = '', onChange, setEventState, onValidationChange }: onChange={handleChange} className="custom-quill-editor" /> +
+

+ {plainTextLength} / {MAX_LENGTH}자 +

+ {isOverLimit && ( +

+ 200자를 초과할 수 없습니다. +

+ )} +
); }; From f89f5a6a883ccf3cc56f5a535e7914d5b63e46b9 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Fri, 6 Jun 2025 17:10:47 +0900 Subject: [PATCH 17/20] =?UTF-8?q?refact:=20=EA=B8=B0=EB=B3=B8=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=ED=98=B8=EC=8A=A4=ED=8A=B8=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=ED=9B=84=20=EC=9E=AC=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EC=9D=B4?= =?UTF-8?q?=EC=A0=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=82=A8=EC=95=84?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/event/ui/EventFunnel.tsx | 10 +++++++++- src/shared/hooks/useImageUpload.ts | 8 ++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/features/event/ui/EventFunnel.tsx b/src/features/event/ui/EventFunnel.tsx index 5ffdf7d9..bfb62bd2 100644 --- a/src/features/event/ui/EventFunnel.tsx +++ b/src/features/event/ui/EventFunnel.tsx @@ -12,10 +12,11 @@ import { EventFunnelInterface, StepNames } from '../../../shared/types/funnelTyp import { useFunnelState } from '../model/FunnelContext'; import { useEventCreation } from '../hooks/useEventHook'; import { useHostCreation } from '../../host/hook/useHostHook'; +import { HostCreationRequest } from '../../host/model/host'; const EventFunnel = ({ onNext, onPrev, Funnel, Step, currentStep }: EventFunnelInterface) => { const navigate = useNavigate(); - const { eventState, hostState } = useFunnelState(); + const { eventState, hostState, setHostState } = useFunnelState(); const { mutate: createEvent } = useEventCreation(); const { mutate: createHost } = useHostCreation(); @@ -33,10 +34,17 @@ const EventFunnel = ({ onNext, onPrev, Funnel, Step, currentStep }: EventFunnelI onNext(nextStep); } }; + const initialHostState: HostCreationRequest = { + profileImageUrl: '', + hostChannelName: '', + hostEmail: '', + channelDescription: '', + }; const handleHostCreation = () => { createHost(hostState, { onSuccess: () => { + setHostState(initialHostState); handleNext(String(currentStep - 1)); }, }); diff --git a/src/shared/hooks/useImageUpload.ts b/src/shared/hooks/useImageUpload.ts index 7b4a9fad..6e4d0a42 100644 --- a/src/shared/hooks/useImageUpload.ts +++ b/src/shared/hooks/useImageUpload.ts @@ -1,5 +1,6 @@ import { useRef, useState, useCallback, useEffect } from 'react'; import { uploadFile } from '../../features/event/hooks/usePresignedUrlHook'; +import basicProfile from '../../../public/assets/event-manage/creation/BasicProfile.png'; const useImageUpload = ({ value, @@ -10,7 +11,6 @@ const useImageUpload = ({ onSuccess?: (url: string) => void; useDefaultImage?: boolean; }) => { - const DEFAULT_BASIC_PROFILE = 'https://gotogetherbucket.s3.ap-northeast-2.amazonaws.com/default.png'; const [previewUrl, setPreviewUrl] = useState(null); const [isDragging, setIsDragging] = useState(false); const fileInputRef = useRef(null); @@ -18,9 +18,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]); From 679b4dbb18a4f18ebcbdb218c81a39f0e564f3ca Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Fri, 6 Jun 2025 17:29:53 +0900 Subject: [PATCH 18/20] =?UTF-8?q?refact:=20=ED=83=80=EC=9E=85=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/event/ui/TextEditor.tsx | 17 ++++++++++------- src/features/join/api/user.ts | 4 ++-- src/features/join/model/userInformation.ts | 2 +- src/pages/join/AgreementPage.tsx | 14 +++++++------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/features/event/ui/TextEditor.tsx b/src/features/event/ui/TextEditor.tsx index eadda408..a1198209 100644 --- a/src/features/event/ui/TextEditor.tsx +++ b/src/features/event/ui/TextEditor.tsx @@ -69,18 +69,21 @@ const TextEditor = ({ value = '', onChange, setEventState, onValidationChange }: }), [] ); - + const getPlainText = (htmlContent: string): string => { + return htmlContent.replace(/<[^>]*>/g, '').trim(); + }; + const handleChange = (value: string) => { - const plainText = value.replace(/<[^>]*>/g, '').trim(); + const editorInstance = quillRef.current?.getEditor(); + const plainTextLength = editorInstance ? editorInstance.getText().trim().length : getPlainText(value).length; - if (plainText.length <= MAX_LENGTH) { + if (plainTextLength <= MAX_LENGTH) { setContent(value); onChange?.(value); setEventState?.(prev => ({ ...prev, description: value })); - onValidationChange?.(plainText.length > 0); + onValidationChange?.(plainTextLength > 0); setIsOverLimit(false); } else { - const editorInstance = quillRef.current?.getEditor(); if (editorInstance) { editorInstance.setContents(editorInstance.clipboard.convert(content)); } @@ -90,11 +93,11 @@ const TextEditor = ({ value = '', onChange, setEventState, onValidationChange }: useEffect(() => { setContent(value); // 외부 value가 바뀌면 내부에 반영 - const plainText = value.replace(/<[^>]*>/g, '').trim(); + const plainText = getPlainText(value); onValidationChange?.(plainText.length > 0); }, [value]); - const plainTextLength = content.replace(/<[^>]*>/g, '').trim().length; + const plainTextLength = getPlainText(content).length; return ( diff --git a/src/features/join/api/user.ts b/src/features/join/api/user.ts index c1392f40..b4a589a4 100644 --- a/src/features/join/api/user.ts +++ b/src/features/join/api/user.ts @@ -1,5 +1,5 @@ import { axiosClient } from '../../../shared/types/api/http-client'; -import { TermsAgreementRequset, UserInfoRequest, UserInfoResponse } from '../model/userInformation'; +import { TermsAgreementRequest, UserInfoRequest, UserInfoResponse } from '../model/userInformation'; export const readUser = async (): Promise => { const response = await axiosClient.get<{ result: UserInfoResponse }>('/users', { @@ -16,7 +16,7 @@ export const updateUser = async (data: UserInfoRequest): Promise { +export const agreeTerms = async (data: TermsAgreementRequest) => { const response = await axiosClient.post('/terms', data, { headers: { isPublicApi: true }, }); diff --git a/src/features/join/model/userInformation.ts b/src/features/join/model/userInformation.ts index 2f81705f..6fdb0ced 100644 --- a/src/features/join/model/userInformation.ts +++ b/src/features/join/model/userInformation.ts @@ -11,7 +11,7 @@ export interface UserInfoRequest { email: string; } -export interface TermsAgreementRequset { +export interface TermsAgreementRequest { serviceAgreed: boolean; privacyPolicyAgree: boolean; personalInfoUsageAgreed: boolean; diff --git a/src/pages/join/AgreementPage.tsx b/src/pages/join/AgreementPage.tsx index 618473a5..89cf3663 100644 --- a/src/pages/join/AgreementPage.tsx +++ b/src/pages/join/AgreementPage.tsx @@ -5,7 +5,7 @@ import AgreementList from '../../features/join/ui/AgreementList'; import { useAgreementStore } from '../../features/join/model/agreementStore'; import { useNavigate } from 'react-router-dom'; import { useAgreeTerms } from '../../features/join/hooks/useUserHook'; -import { TermsAgreementRequset } from '../../features/join/model/userInformation'; +import { TermsAgreementRequest } from '../../features/join/model/userInformation'; const AgreementPage: React.FC = () => { const { isAllRequiredAgreed, getAgreementStates } = useAgreementStore(); @@ -13,13 +13,13 @@ const AgreementPage: React.FC = () => { const { mutate:agreeTerms } = useAgreeTerms(); const handleAgree = () => { - const agreementData: TermsAgreementRequset = { - serviceAgreed: getAgreementStates().serviceAgreed, - privacyPolicyAgree: getAgreementStates().privacyPolicyAgree, - personalInfoUsageAgreed: getAgreementStates().personalInfoUsageAgreed, - marketingAgreed: getAgreementStates().marketingAgreed, + const states = getAgreementStates(); + const agreementData: TermsAgreementRequest = { + serviceAgreed: states.serviceAgreed, + privacyPolicyAgree: states.privacyPolicyAgree, + personalInfoUsageAgreed: states.personalInfoUsageAgreed, + marketingAgreed: states.marketingAgreed, }; - console.log(agreementData) agreeTerms(agreementData, { onSuccess: () => { From 08528d390de0969e0156fc3554101eece1d7a926 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Fri, 6 Jun 2025 17:44:39 +0900 Subject: [PATCH 19/20] =?UTF-8?q?refact:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=97=86=EC=9D=84=20=EC=8B=9C=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/dashboard/ui/ResponsesList.tsx | 3 +++ .../dashboard/ui/ResponseManagementPage.tsx | 23 ++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/features/dashboard/ui/ResponsesList.tsx b/src/features/dashboard/ui/ResponsesList.tsx index efb651b2..df0e8f9a 100644 --- a/src/features/dashboard/ui/ResponsesList.tsx +++ b/src/features/dashboard/ui/ResponsesList.tsx @@ -63,6 +63,9 @@ const ResponsesList = ({ listType, ticketOptionResponses, ticketId }: ResponsesL if (isLoading) return

로딩 중...

; if (error || !data?.result) return

데이터를 불러오지 못했습니다.

; const allOrders = data.result.flatMap(user => user.orders); + if (allOrders.length === 0) { + return

응답이 없습니다.

; + } return ( ); diff --git a/src/pages/dashboard/ui/ResponseManagementPage.tsx b/src/pages/dashboard/ui/ResponseManagementPage.tsx index b282b709..3e632960 100644 --- a/src/pages/dashboard/ui/ResponseManagementPage.tsx +++ b/src/pages/dashboard/ui/ResponseManagementPage.tsx @@ -9,18 +9,29 @@ import { usePurchaserAnswers } from '../../../features/ticket/hooks/useTicketOpt const ResponseManagementPage = () => { const [listType, setListType] = useState<'summary' | 'individual'>('summary'); const { isModalOpen, closeModal, selectedTicketId } = useResponseStore(); - const { data } = usePurchaserAnswers(selectedTicketId); + const { data, isLoading, isError } = usePurchaserAnswers(selectedTicketId); + const orderCount = data?.result?.orderCount ?? 0; return ( {isModalOpen && ( )}
-

응답 {data?.result.orderCount}개

-
- -
- +

+ {isLoading ? '응답 불러오는 중...' : isError ? '응답 0개' : `응답 ${orderCount}개`} +

+ {isError ? ( +
+ 응답이 존재하지 않습니다 +
+ ) : ( + <> +
+ +
+ + + )}
); From 1eb5f56d7afb49abb44bd8f3dc9231ff17faab87 Mon Sep 17 00:00:00 2001 From: hyeeuncho Date: Fri, 6 Jun 2025 18:00:46 +0900 Subject: [PATCH 20/20] =?UTF-8?q?refact:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=84=A4=EB=AA=85=20=EA=B8=80=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EA=B8=B8=EC=9D=B4=20?= =?UTF-8?q?=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/event/ui/TextEditor.tsx | 30 +++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/features/event/ui/TextEditor.tsx b/src/features/event/ui/TextEditor.tsx index a1198209..6cc0f228 100644 --- a/src/features/event/ui/TextEditor.tsx +++ b/src/features/event/ui/TextEditor.tsx @@ -11,7 +11,8 @@ interface TextEditorProps { onValidationChange?: (isValid: boolean) => void; } -const MAX_LENGTH = 200; +const MAX_LENGTH = 2000; +const IMAGE_WEIGHT = 200; const formats = [ 'font', 'header', 'bold', 'italic', 'underline', 'strike', 'blockquote', @@ -48,6 +49,15 @@ const TextEditor = ({ value = '', onChange, setEventState, onValidationChange }: } }; }; + const getImageCount = (htmlContent: string): number => { + const matches = htmlContent.match(/]*src="[^"]*"[^>]*>/g); + return matches ? matches.length : 0; + }; + const getTotalContentLength = (htmlContent: string): number => { + const textLength = getPlainText(htmlContent).length; + const imageCount = getImageCount(htmlContent); + return textLength + imageCount * IMAGE_WEIGHT; + }; const modules = useMemo( () => ({ @@ -72,18 +82,18 @@ const TextEditor = ({ value = '', onChange, setEventState, onValidationChange }: const getPlainText = (htmlContent: string): string => { return htmlContent.replace(/<[^>]*>/g, '').trim(); }; - + const handleChange = (value: string) => { - const editorInstance = quillRef.current?.getEditor(); - const plainTextLength = editorInstance ? editorInstance.getText().trim().length : getPlainText(value).length; + const totalLength = getTotalContentLength(value); - if (plainTextLength <= MAX_LENGTH) { + if (totalLength <= MAX_LENGTH) { setContent(value); onChange?.(value); setEventState?.(prev => ({ ...prev, description: value })); - onValidationChange?.(plainTextLength > 0); + onValidationChange?.(getPlainText(value).length > 0); setIsOverLimit(false); } else { + const editorInstance = quillRef.current?.getEditor(); if (editorInstance) { editorInstance.setContents(editorInstance.clipboard.convert(content)); } @@ -97,7 +107,8 @@ const TextEditor = ({ value = '', onChange, setEventState, onValidationChange }: onValidationChange?.(plainText.length > 0); }, [value]); - const plainTextLength = getPlainText(content).length; + const totalLength = getTotalContentLength(content); + const imageCount = getImageCount(content); return ( @@ -114,11 +125,12 @@ const TextEditor = ({ value = '', onChange, setEventState, onValidationChange }: />

- {plainTextLength} / {MAX_LENGTH}자 + {totalLength} / {MAX_LENGTH}자 + {imageCount > 0 && ` (이미지 ${imageCount}개 포함)`}

{isOverLimit && (

- 200자를 초과할 수 없습니다. + {MAX_LENGTH}자를 초과할 수 없습니다.

)}