Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions public/assets/Home.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/app/routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const dashboardRoutes = [
{ path: DASHBOARD_ROUTES.dashboard, element: <DashboardPage />, requiresAuth: false },
{ path: DASHBOARD_ROUTES.eventInfo, element: <EventInfoPage />, requiresAuth: false },
{ path: DASHBOARD_ROUTES.eventDetail, element: <EventDetailPage />, requiresAuth: false },
{ path: DASHBOARD_ROUTES.eventTag, element: <EventTagPage />, requiresAuth: false },
{ path: DASHBOARD_ROUTES.eventTag, element: (<FunnelProvider><EventTagPage /></FunnelProvider>),requiresAuth: false,},
{ path: DASHBOARD_ROUTES.ticketCreate, element: <TicketCreatePage />, requiresAuth: false },
{ path: DASHBOARD_ROUTES.ticket, element: <TicketListPage />, requiresAuth: false },
{ path: DASHBOARD_ROUTES.ticketOption, element: <TicketOptionPage />, requiresAuth: false },
Expand Down
1 change: 1 addition & 0 deletions src/entities/event/api/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,4 @@ export const eventDeletion = async (eventId: number) => {
const response = await axiosClient.delete(`/events/${eventId}`);
return response.data;
};

50 changes: 49 additions & 1 deletion src/features/event/ui/ShareEventModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import kakao from '../../../../public/assets/event-manage/details/KaKao.svg';
import { shareToKakao } from '../../../shared/lib/kakaoShare';
import stripHtml from '../lib/stripHtml';
import EventInfo from '../../../entities/user/ui/EventInfo';
import { useRef } from 'react';

interface ShareEventModalProps {
closeModal: () => void;
Expand All @@ -20,6 +21,47 @@ const ShareEventModal = ({
eventUrl = window.location.href,
}: ShareEventModalProps) => {
const description = stripHtml(eventDescription);
const startYRef = useRef<number | null>(null);
const modalRef = useRef<HTMLDivElement | null>(null);

const handleTouchStart = (e: React.TouchEvent) => {
startYRef.current = e.touches[0].clientY;
};

const handleTouchMove = (e: React.TouchEvent) => {
const startY = startYRef.current;
if (startY === null) return;

const deltaY = e.touches[0].clientY - startY;

// 모달이 따라 내려오는 효과 (optional)
if (modalRef.current && deltaY > 0) {
modalRef.current.style.transform = `translateY(${deltaY}px)`;
}
};

const handleTouchEnd = (e: React.TouchEvent) => {
const startY = startYRef.current;
if (startY === null) return;

const endY = e.changedTouches[0].clientY;
const deltaY = endY - startY;

if (deltaY > 100) {
// 일정 거리 이상 내려갔을 때 모달 닫기
closeModal();
} else {
// 다시 원위치
if (modalRef.current) {
modalRef.current.style.transform = 'translateY(0)';
modalRef.current.style.transition = 'transform 0.2s ease-out';
setTimeout(() => {
if (modalRef.current) modalRef.current.style.transition = '';
}, 200);
}
}
startYRef.current = null;
};

const handleKakaoShare = async () => {
try {
Expand Down Expand Up @@ -66,7 +108,13 @@ const ShareEventModal = ({
return (
<div className="fixed inset-0 z-50 flex items-end justify-center">
<div className="absolute inset-0 mx-auto w-full max-w-lg bg-black bg-opacity-30" onClick={closeModal}></div>
<div onClick={e => e.stopPropagation()} className="relative w-full max-w-lg bg-white rounded-t-[20px] px-6 py-4">
<div
ref={modalRef}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
className="relative w-full max-w-lg bg-white rounded-t-[20px] px-6 py-4"
>
<div className="flex justify-center">
<div className="w-20 h-1 bg-black bg-opacity-30 rounded-full mb-3" />
</div>
Expand Down
31 changes: 16 additions & 15 deletions src/features/event/ui/TextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,23 @@ const TextEditor = ({ eventState, setEventState, value = '', onChange, onValidat
};

const handleChange = (val: string) => {
const totalLength = getTotalContentLength(val);

if (totalLength <= MAX_LENGTH) {
setEditorContent(val);
onChange?.(val);
setEventState?.(prev => ({ ...prev, description: val }));
onValidationChange?.(getPlainText(val).length > 0);
setIsOverLimit(false);
} else {
const editorInstance = quillRef.current?.getEditor();
if (editorInstance) {
editorInstance.setContents(editorInstance.clipboard.convert(eventState?.description));
}
setIsOverLimit(true);
const totalLength = getTotalContentLength(val);

if (totalLength <= MAX_LENGTH) {
setEditorContent(val);
onChange?.(val);
setEventState?.(prev => ({ ...prev, description: val }));
onValidationChange?.(getPlainText(val).length > 0);
setIsOverLimit(false);
} else {
const editorInstance = quillRef.current?.getEditor();
if (editorInstance) {
editorInstance.clipboard.dangerouslyPasteHTML(eventState?.description ?? '');
}
};
setIsOverLimit(true);
}
};


useEffect(() => {
onValidationChange?.(getPlainText(editorContent).length > 0);
Expand Down
4 changes: 2 additions & 2 deletions src/features/home/ui/EventTags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const EventTags = () => {
return (
<>
<div className="grid grid-cols-1 gap-10 mb-5">
<EventSliderSection title="최신 이벤트" events={latestEvents} />
<EventSliderSection title="요즘 뜨는 이벤트" events={trendingEvents} />
<EventSliderSection title="최신 이벤트 🎉 " events={latestEvents} />
<EventSliderSection title="요즘 뜨는 이벤트 ⭐️" events={trendingEvents} />
<EventSliderSection title="곧 이벤트가 마감돼요! ⏰" events={closingSoonEvents} />
</div>
</>
Expand Down
5 changes: 2 additions & 3 deletions src/features/join/hooks/useUserHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ export const useUserUpdate = () => {
export const useAgreeTerms = () => {
return useMutation({
mutationFn: agreeTerms,
onError: (error) => {
alert('동의 처리 실패');
console.error('이용약관 동의 실패', error);
onError: () => {
alert('약관 동의 처리에 실패했습니다.');
}
});
};
3 changes: 3 additions & 0 deletions src/features/ticket/api/ticket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export const createTicket = async (data: CreateTicketRequest) => {
export const readTicket = async (eventId: number): Promise<{ isSuccess: boolean; result: ReadTicketResponse[] }> => {
const response = await axiosClient.get('/tickets', {
params: { eventId },
headers: {
isPublicApi: true,
},
});
return response.data;
};
Expand Down
14 changes: 10 additions & 4 deletions src/pages/dashboard/ui/EventDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useUpdateEventHook } from '../../../features/dashboard/hook/useEventHoo
import { UpdateEventRequest } from '../../../features/dashboard/model/event';
import { OnlineType } from '../../../shared/types/baseEventType';
import { useEventDetail } from '../../../entities/event/hook/useEventHook';
import { useQueryClient } from '@tanstack/react-query';

const EventDetailPage = () => {
const navigate = useNavigate();
Expand All @@ -20,10 +21,13 @@ const EventDetailPage = () => {
const [description, setDescription] = useState('');
const [referenceLinks, setReferenceLinks] = useState<Link[]>([]);

const queryClient = useQueryClient();

useEffect(() => {
console.log(data?.result.bannerImageUrl)
if (data?.result) {
setHostChannelId(data.result.hostChannelId || 0);
setBannerImageUrl(data.result.bannerImageUrl || '');
setBannerImageUrl(prev => prev || data.result.bannerImageUrl || '');
setDescription(data.result.description || '');
setReferenceLinks(data.result.referenceLinks || []);
}
Expand All @@ -37,7 +41,7 @@ const EventDetailPage = () => {
title: data.result.title,
startDate: data.result.startDate,
endDate: data.result.endDate,
bannerImageUrl: bannerImageUrl || data.result.bannerImageUrl || '',
bannerImageUrl: bannerImageUrl.trim() !== '' ? bannerImageUrl : data.result.bannerImageUrl || '',
description: description || data.result.description || '',
referenceLinks: referenceLinks.map(({ title, url }) => ({ title, url })) || data.result.referenceLinks || [],
onlineType: data.result.onlineType as OnlineType,
Expand All @@ -54,6 +58,8 @@ const EventDetailPage = () => {
mutate(requestData, {
onSuccess: () => {
alert('이벤트 정보가 저장되었습니다.');
queryClient.invalidateQueries({ queryKey: ['eventDetail', data?.result.id] });

navigate(`/dashboard/${data?.result.id}`);
},
onError: () => {
Expand All @@ -63,11 +69,11 @@ const EventDetailPage = () => {
};

return (
<DashboardLayout centerContent="대시보드">
<DashboardLayout centerContent="DASHBOARD">
<div className="flex flex-col gap-5 mt-8 px-7">
<h1 className="text-center text-xl font-bold mb-5">이벤트 상세 정보</h1>
<FileUpload value={bannerImageUrl} onChange={setBannerImageUrl} useDefaultImage={false} />
<TextEditor value={description} />
<TextEditor value={description} onChange={setDescription}/>
<LinkInput value={referenceLinks} onChange={setReferenceLinks} />
</div>
<div className="w-full p-7">
Expand Down
3 changes: 2 additions & 1 deletion src/pages/dashboard/ui/EventInfoPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ const EventInfoPage = () => {
}, [data]);

return (
<DashboardLayout centerContent={title}>
// <DashboardLayout centerContent={title}>
<DashboardLayout centerContent={"DASHBOARD"}>
<div className="flex flex-col gap-5 mt-8 px-7">
<h1 className="text-center text-xl font-bold mb-5">이벤트 기본 정보</h1>
<DefaultTextField
Expand Down
44 changes: 41 additions & 3 deletions src/pages/dashboard/ui/EventTagPage.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,61 @@
import { useNavigate } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import DashboardLayout from '../../../shared/ui/backgrounds/DashboardLayout';
import Button from '../../../../design-system/ui/Button';
import EventCategory from '../../../features/event/ui/EventCategory';
import EventTag from '../../../features/event/ui/EventTag';
import { useFunnelState } from '../../../features/event/model/FunnelContext';
import { useUpdateEventHook } from '../../../features/dashboard/hook/useEventHook';
import { useEventDetail } from '../../../entities/event/hook/useEventHook';
import { useEffect } from 'react';

const EventTagPage = () => {
const navigate = useNavigate();
const { eventState, setEventState } = useFunnelState();
const { id } = useParams();
const { mutate } = useUpdateEventHook();
const { data } = useEventDetail();
useEffect(() => {
if (data?.result.hashtags && setEventState) {
setEventState(prev => ({
...prev,
hashtags: data.result.hashtags,
}));
}
}, [data, setEventState]);
const handleEventTag = () => {
if (!id || !data?.result) return;

const cleanedTags = eventState.hashtags.filter(tag => tag.trim() !== '');

const requestData = {
...data.result,
hostChannelId: data.result.hostChannelId,
hashtags: cleanedTags,
};

mutate(requestData, {
onSuccess: () => {
alert('이벤트 정보가 저장되었습니다.');
navigate(`/dashboard/${id}`);
},
onError: () => {
alert('저장에 실패했습니다.');
},
});
}


return (
<DashboardLayout centerContent="DASHBOARD">
<div className="flex flex-col gap-5 mt-8 px-7">
<h1 className="text-center text-xl font-bold mb-5">이벤트 태그 정보</h1>
<EventCategory />
<EventTag />
<EventTag eventState={eventState} setEventState={setEventState} />
</div>
<div className="w-full p-7">
<Button
label="저장하기"
onClick={() => navigate('/dashbord/eventDetail')}
onClick={() => handleEventTag()}
className="w-full h-12 rounded-full"
/>
</div>
Expand Down
21 changes: 18 additions & 3 deletions src/pages/dashboard/ui/mail/MailBoxPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useMemo, useState } from 'react';
import TextButton from '../../../../../design-system/ui/buttons/TextButton';
import DashboardLayout from '../../../../shared/ui/backgrounds/DashboardLayout';
import SearchBar from '../../../../shared/ui/SearchBar';
Expand All @@ -13,11 +13,21 @@ const MailBoxPage = () => {
const [listType, setListType] = useState<'completed' | 'pending'>('completed');
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedEmailId, setSelectedEmailId] = useState<number | null>(null);
const [searchKeyword, setSearchKeyword] = useState('');

const status = listType === 'pending' ? 'PENDING' : 'SENT';
const { data: emails = [], isLoading } = useReadEmail(eventId, status);
const { mutate: deleteEmail } = useDeleteEmail();

const filteredEmails = useMemo(() => {
if (!searchKeyword.trim()) return emails;
const keyword = searchKeyword.toLowerCase();
return emails.filter(email =>
email.title.toLowerCase().includes(keyword) ||
email.targetName.toLowerCase().includes(keyword)
);
}, [emails, searchKeyword]);

const handleDelete = (reservationEmailId: number) => {
deleteEmail(reservationEmailId);
setIsModalOpen(false);
Expand All @@ -28,7 +38,12 @@ const MailBoxPage = () => {
<div className={`flex flex-col gap-2 mt-8 px-7 ${isModalOpen ? 'blur-sm' : ''}`}>
<h1 className="w-full text-center font-bold text-xl">보낸 메일함</h1>
<div className="flex justify-end">
<SearchBar placeholder="제목 검색" className="w-[35%] my-2" />
<SearchBar
placeholder="제목 검색"
className="w-[35%] my-2"
value={searchKeyword}
onChange={setSearchKeyword}
/>
</div>
<div className="flex gap-3 font-semibold text-15">
<TextButton
Expand All @@ -45,7 +60,7 @@ const MailBoxPage = () => {
{isLoading ? (
<div>로딩 중...</div>
) : (
emails.map(mail => (
filteredEmails.map(mail => (
<SentMailCard
key={mail.id}
mail={mail}
Expand Down
7 changes: 3 additions & 4 deletions src/pages/dashboard/ui/ticket/TIcketConfirmPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useState } from 'react';
import Header from '../../../../../design-system/ui/Header';
import Search from '../../../../../design-system/icons/Search.svg';
import { useLocation, useNavigate } from 'react-router-dom';
import EmailDeleteModal from '../../../../widgets/dashboard/ui/email/EmailDeleteModal';
import PurchaseBanner from '../../../../widgets/dashboard/ui/ticket/PurchaseBanner';
import OrganizerInfo from '../../../../widgets/event/ui/OrganizerInfo';
import KakaoMap from '../../../../shared/ui/KakaoMap';
import { useCancelTicket, useTicketOrderDetail } from '../../../../features/ticket/hooks/useOrderHook';
import { TicketConfirm } from '../../../../features/ticket/model/orderInformation';
import HomeButton from '../../../../../public/assets/bottomBar/HomeIcon.svg';

const TicketConfirmPage = () => {
const navigate = useNavigate();
Expand All @@ -20,7 +20,7 @@ const TicketConfirmPage = () => {
const { mutate: cancelTicket } = useCancelTicket();

const handlePreviousButton = () => {
navigate(-1);
navigate("/");
};
const cancleOrderTicket = async (orderIds: number[]) => {
cancelTicket(orderIds, {
Expand All @@ -34,9 +34,8 @@ const TicketConfirmPage = () => {
<Header
leftButtonClassName="text-xl hover:no-underline z-30"
leftButtonClick={handlePreviousButton}
leftButtonLabel="<"
leftButtonLabel={<img src={HomeButton} />}
centerContent="티켓 구매 확인"
rightContent={<img src={Search} alt="검색" className="w-4" />}
/>
{isLoading ? (
<p className="text-center text-gray-500">티켓 정보를 불러오는 중...</p>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/dashboard/ui/ticket/TicketOptionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const TicketOptionPage = () => {
: [];

return (
<DashboardLayout centerContent="DASHBOARDs">
<DashboardLayout centerContent="DASHBOARD">
<div className="mt-8 px-7">
<div className="text-center text-xl font-bold mb-5">티켓에 추가 옵션 부착 하기</div>
<p className="text-placeholderText text-xs mb-5">
Expand Down
Loading
Loading