Skip to content
Merged
2 changes: 2 additions & 0 deletions src/app/routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import TicketOptionResponsePage from '../../pages/dashboard/ui/ticket/TicketOpti
import TicketOptionAttachPage from '../../pages/dashboard/ui/ticket/TicketOptionAttachPage';
import AuthCallback from '../../pages/join/AuthCallback';
import LogoutPage from '../../pages/join/LogoutPage';
import BookmarkPage from '../../pages/bookmark/ui/BookmarkPage';

const mainRoutes = [
{ path: MAIN_ROUTES.main, element: <MainPage />, requiresAuth: false },
Expand All @@ -45,6 +46,7 @@ const mainRoutes = [
{ path: MAIN_ROUTES.search, element: <SearchPage />, requiresAuth: false },
{ path: MAIN_ROUTES.menu, element: <MenuPage />, requiresAuth: false },
{ path: MAIN_ROUTES.payment, element: <PaymentPage />, requiresAuth: false },
{ path: MAIN_ROUTES.bookmark, element: <BookmarkPage />, requiresAuth: false },
];

const joinRoutes = [
Expand Down
3 changes: 2 additions & 1 deletion src/app/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ export const MAIN_ROUTES = {
main: '/',
eventCreation: '/event-creation',
allEvents: '/all-events',
eventDatail: '/event-details',
eventDatail: '/event-details/:id',
search: '/search',
menu: '/menu',
dashboard: '/dashboard/:id',
payment: '/payment',
bookmark: '/bookmark'
};

export const AUTH_ROUTES = {
Expand Down
6 changes: 4 additions & 2 deletions src/entities/event/api/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { axiosClient } from '../../../shared/types/api/http-client';
import { EventDetailRequest } from '../model/event';

export const eventDetail = async (dto: EventDetailRequest) => {
const response = await axiosClient.get(`/events/${dto.eventId}`);
return response.data;
const response = await axiosClient.get(`/events/${dto.eventId}`, {
params: { userId: dto.userId },
});
return response.data.result;
};
5 changes: 4 additions & 1 deletion src/entities/event/hook/useEventHook.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { useQuery } from '@tanstack/react-query';
import { eventDetail } from '../api/event';
import { useParams } from 'react-router-dom';
import { useUserInfo } from '../../../features/join/hooks/useUserHook';

const useEventDetail = () => {
const { id } = useParams();
const { data: user } = useUserInfo();

const eventId = Number(id);

const { data } = useQuery({
queryKey: ['eventDetail', eventId],
queryFn: () => eventDetail({ eventId }),
queryFn: () => eventDetail({ eventId, userId: user?.id }),
enabled: !!user?.id,
});

return { data };
Expand Down
1 change: 1 addition & 0 deletions src/entities/event/model/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BaseEvent } from '../../../shared/types/baseEventType';

export interface EventDetailRequest {
eventId: number;
userId?: number
}

export interface EventDetailResponse {
Expand Down
17 changes: 17 additions & 0 deletions src/features/bookmark/api/bookmark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { axiosClient } from "../../../shared/types/api/http-client"
import { BookmarkResponse } from "../model/bookmarkInformation";

export const readBookmark = async (): Promise<BookmarkResponse[]> => {
const response = await axiosClient.get<{result:BookmarkResponse[]}>('/events/{eventId}/bookmark');
return response.data.result;
}

export const createBookmark = async (eventId: number) => {
const response = await axiosClient.post(`/events/${eventId}/bookmark`);
return response.data;
}

export const deleteBookmark = async (eventId: number, bookmarkId: number) => {
const response = await axiosClient.delete(`/events/${eventId}/bookmark/${bookmarkId}`);
return response.data;
}
11 changes: 11 additions & 0 deletions src/features/bookmark/model/bookmarkInformation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface BookmarkResponse {
id: number;
bannerImageUrl: string;
title: string;
hostChannelName: string;
startDate: string;
address: string;
onlineType: 'ONLINE' | 'OFFLINE';
hashtags: string[];
remainDays: string;
}
66 changes: 66 additions & 0 deletions src/features/bookmark/model/useBookmarkHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { BookmarkResponse } from "./bookmarkInformation"
import { createBookmark, deleteBookmark, readBookmark } from "../api/bookmark"

export const useBookmarks = () => {
return useQuery<BookmarkResponse[]>({
queryKey: ['bookmarks'],
queryFn: readBookmark,
})
}

export const useCreateBookmark = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createBookmark,
// Optimistic
onMutate: async (eventId: number) => {
await queryClient.cancelQueries({ queryKey: ['eventDetail', eventId] });
const previous = queryClient.getQueryData(['eventDetail', eventId]);

queryClient.setQueryData(['eventDetail', eventId], (old: any) => ({
...old,
bookmarked: true,
}));

return { previous };
},
onError: (_err, eventId, context) => {
if (context?.previous) {
queryClient.setQueryData(['eventDetail', eventId], context.previous);
alert("좋아요 등록에 실패했습니다. 잠시후 다시 시도해 주세요.");
}
},
onSettled: (_data, _error, eventId) => {
queryClient.invalidateQueries({ queryKey: ['eventDetail', eventId] });
},
})
}
export const useDeleteBookmark = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (params: { eventId: number; bookmarkId: number }) =>
deleteBookmark(params.eventId, params.bookmarkId),
// Optimistic
onMutate: async ({ eventId }) => {
await queryClient.cancelQueries({ queryKey: ['eventDetail', eventId] });
const previous = queryClient.getQueryData(['eventDetail', eventId]);

queryClient.setQueryData(['eventDetail', eventId], (old: any) => ({
...old,
bookmarked: false,
}));

return { previous };
},
onError: (_err, { eventId }, context) => {
if (context?.previous) {
queryClient.setQueryData(['eventDetail', eventId], context.previous);
alert("좋아요 삭제에 실패했습니다. 잠시후 다시 시도해 주세요.");
}
},
onSettled: (_data, _error, { eventId }) => {
queryClient.invalidateQueries({ queryKey: ['eventDetail', eventId] });
},
});
}
10 changes: 10 additions & 0 deletions src/features/dashboard/model/EmailStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ interface EmailState {
content: string;
recipients: string[];
reservationDate: string; // 2025-05-01T14:00:00.000Z
targetType: 'ALL' | 'TICKET';
ticketId: number;
setReservationEmailId: (reservationEmailId: number) => void;
setTitle: (title: string) => void;
setContent: (content: string) => void;
setRecipients: (recipients: string[]) => void;
setReservationDate: (date: string) => void;
setTargetType: (type: 'ALL' | 'TICKET') => void;
setTicketId: (ticketId: number) => void;
reset: () => void;
}

Expand All @@ -20,17 +24,23 @@ export const useEmailStore = create<EmailState>((set) => ({
content: '',
recipients: [],
reservationDate: '',
targetType: 'ALL',
ticketId: 0,
setReservationEmailId: (reservationEmailId) => set({reservationEmailId}),
setTitle: (title) => set({ title }),
setContent: (content) => set({ content }),
setRecipients: (recipients) => set({ recipients }),
setReservationDate: (date) => set({ reservationDate: date }),
setTargetType: (targetType) => set({ targetType }),
setTicketId: (ticketId) => set({ ticketId }),
reset: () =>
set({
reservationEmailId: 0,
title: '',
content: '',
recipients: [],
reservationDate: '',
targetType: 'ALL',
ticketId: 0,
}),
}));
3 changes: 3 additions & 0 deletions src/features/dashboard/model/emailInformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export interface EmailRequest {
content: string;
recipients: string[];
reservationDate: string;
targetType: 'ALL' | 'TICKET';
ticketId?: number;
}
export interface EmailResponse {
isSuccess: boolean;
Expand All @@ -18,4 +20,5 @@ export interface ReadEmailResponse {
recipients: string[];
reservationDate: string;
reservationTime: string;
targetName: string
}
42 changes: 42 additions & 0 deletions src/pages/bookmark/ui/BookmarkPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useNavigate } from "react-router-dom";
import Header from "../../../../design-system/ui/Header";
import searchIcon from '../../../../design-system/icons/Search.svg';
import BottomBar from "../../../widgets/main/ui/BottomBar";
import EventCard from "../../../shared/ui/EventCard";
import { useBookmarks } from "../../../features/bookmark/model/useBookmarkHook";

const BookmarkPage = () => {
const navigate = useNavigate();
const { data } = useBookmarks();

return (
<div className="relative">
<Header
centerContent="관심 있는 이벤트"
rightContent={
<button type="button" className="w-5 z-10" onClick={() => navigate('/search')}>
<img src={searchIcon} alt="Search Icon" />
</button>
}
/>
<div className="grid grid-cols-2 gap-4 mx-5 mt-3 md:grid-cols-2 lg:grid-cols-2 z-50">
{data?.map(event => (
<EventCard
id={event.id}
key={event.id}
img={event.bannerImageUrl}
eventTitle={event.title}
dDay={event.remainDays}
host={event.hostChannelName}
eventDate={event.startDate}
location={event.onlineType}
hashtags={event.hashtags}
onClick={() => navigate(`/event-details/${event.id}`)}
/>
))}
</div>
<BottomBar />
</div>
);
}
export default BookmarkPage;
9 changes: 8 additions & 1 deletion src/pages/dashboard/ui/mail/EmailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useParams } from 'react-router-dom';
import { useParticipants } from '../../../../features/dashboard/hook/useParticipants';
import { useEmailStore } from '../../../../features/dashboard/model/EmailStore';
import { useSendEmail } from '../../../../features/dashboard/hook/useEmailHook';
import { EmailRequest } from '../../../../features/dashboard/model/emailInformation';

const EmailPage = () => {
const [ticketModalOpen, setTicketModalOpen] = useState(false);
Expand All @@ -21,6 +22,8 @@ const EmailPage = () => {
recipients,
reservationDate,
setReservationDate,
ticketId,
targetType
} = useEmailStore();

const handleSend = () => {
Expand All @@ -41,13 +44,17 @@ const EmailPage = () => {
return;
}
const eventId = id ? parseInt(id) : 0;
const emailData = {
const emailData: EmailRequest = {
eventId,
title,
content,
recipients,
reservationDate,
targetType,
};
if (targetType === 'TICKET') {
emailData.ticketId = ticketId;
}
sendEmail(emailData);
};

Expand Down
Loading