-
알림 6개
-
+
+
+
+ 알림 {notifications?.totalCount ?? 0}개
+
+
+
+
+
+
+
+ {items.length ? (
+ <>
+ {items.map((n) => {
+ const notificationType = getNotificationType(n.content);
+ const { relative } = dateToCalendarDate(new Date(n.createdAt));
+ const formatted = parseContent(n.content);
+
+ return (
+
+
+
+
+
+ 예약{' '}
+ {notificationType === 'RESERVATION_APPROVED'
+ ? '승인'
+ : '거절'}
+
+
+ {relative}
+
+
+
+
+
+
+
+ );
+ })}
+ {hasMore && (
+
+ 불러오는 중...
+
+ )}
+ >
+ ) : (
+
+ 아직 알람이 없습니다
+
+ )}
-
므아지경
);
}
diff --git a/src/components/primitives/toast/ToastContainer.tsx b/src/components/primitives/toast/ToastContainer.tsx
index 73b3b56..335d1d8 100644
--- a/src/components/primitives/toast/ToastContainer.tsx
+++ b/src/components/primitives/toast/ToastContainer.tsx
@@ -1,3 +1,5 @@
+'use client';
+
import Toast from '@/src/components/primitives/toast/Toast';
import { useToastStore } from '@/src/store/useToastStore';
import { createPortal } from 'react-dom';
@@ -5,6 +7,8 @@ import { createPortal } from 'react-dom';
export default function ToastContainer() {
const toasts = useToastStore((state) => state.toasts);
+ if (toasts.length === 0) return;
+
return createPortal(
{toasts.map((toast, idx) => (
diff --git a/src/services/pages/detail/postReservation.ts b/src/services/pages/detail/postReservation.ts
index 9702628..1fc4736 100644
--- a/src/services/pages/detail/postReservation.ts
+++ b/src/services/pages/detail/postReservation.ts
@@ -1,19 +1,20 @@
import { IReservation } from '@/src/types/activityType';
import { apiClient } from '../../primitives/apiClient';
+import { CancelMyReservationResponse } from '@/src/types/myReservationType';
export async function createReservation(
activityId: number,
body: IReservation
) {
try {
- const res = await apiClient.post(
+ const res = await apiClient.post(
`/activities/${activityId}/reservations`,
body
);
- if (res.status !== 201) throw new Error(res.data.message);
return res.data;
} catch (err) {
if (err instanceof Error)
console.error('예약에 실패했습니다!', err.message);
+ throw err;
}
}
diff --git a/src/services/pages/myExperiences/api.ts b/src/services/pages/myExperiences/api.ts
index cd7c8d9..3981e6a 100644
--- a/src/services/pages/myExperiences/api.ts
+++ b/src/services/pages/myExperiences/api.ts
@@ -44,3 +44,15 @@ export async function getMyReservationStatus(
throw err;
}
}
+
+// 내 체험 지우기
+export async function deleteMyExperiences(experienceId: number) {
+ try {
+ await apiClient.delete(`/my-activities/${experienceId}`);
+
+ return { ok: true };
+ } catch (err) {
+ if (err instanceof Error)
+ console.error('체험을 지우는데 실패했습니다!', err.message);
+ }
+}
diff --git a/src/services/pages/notifications/api.ts b/src/services/pages/notifications/api.ts
new file mode 100644
index 0000000..f2898a1
--- /dev/null
+++ b/src/services/pages/notifications/api.ts
@@ -0,0 +1,21 @@
+import { INotifications } from '@/src/types/notificationType';
+import { apiClient } from '../../primitives/apiClient';
+
+interface Params {
+ cursorId?: number;
+ size?: number;
+}
+
+export async function getNotifications(params?: Params) {
+ const { data } = await apiClient.get('/my-notifications', {
+ params,
+ });
+ return data;
+}
+
+export async function deleteNotificationById(notificationId: number) {
+ const { data } = await apiClient.delete(
+ `/my-notifications/${notificationId}`
+ );
+ return data;
+}
diff --git a/src/services/primitives/queries.ts b/src/services/primitives/queries.ts
index 5f89ad2..50d9e3f 100644
--- a/src/services/primitives/queries.ts
+++ b/src/services/primitives/queries.ts
@@ -2,8 +2,13 @@ import { ActivitiesParams, getActivities } from '@/src/services/pages/main/api';
import { getMyReservationList } from '@/src/services/pages/myReservation/api';
import getUserInfo from '@/src/services/primitives/getUserInfo';
import { ReservationStatus } from '@/src/types/myReservationType';
-import { infiniteQueryOptions, queryOptions } from '@tanstack/react-query';
import {
+ infiniteQueryOptions,
+ mutationOptions,
+ queryOptions,
+} from '@tanstack/react-query';
+import {
+ deleteMyExperiences,
getMyExperiences,
getMyReservationStatus,
} from '../pages/myExperiences/api';
@@ -12,6 +17,9 @@ import {
getReservedSchedule,
getTimeSchedule,
} from '../pages/myReservationStatus/myActivities';
+import { getQueryClient } from '@/src/utils/getQueryClient';
+
+const queryClient = getQueryClient();
export const queries = {
user: () => ['user'],
@@ -39,6 +47,16 @@ export const queries = {
queryKey: [...queries.myExperiences()],
queryFn: () => getMyExperiences(),
}),
+ myExperiencesMutationOptions: () =>
+ mutationOptions({
+ mutationKey: [...queries.myExperiences()],
+ mutationFn: (experienceId: number) =>
+ deleteMyExperiences(experienceId ?? 0),
+ onSuccess: async () =>
+ await queryClient.invalidateQueries({
+ queryKey: [...queries.myExperiences()],
+ }),
+ }),
allActivities: (params: ActivitiesParams) => ['allActivities', params],
allActivitiesOptions: (params: ActivitiesParams) =>
queryOptions({
diff --git a/src/store/TimeSlotStore.ts b/src/store/TimeSlotStore.ts
index d264016..a1a8f02 100644
--- a/src/store/TimeSlotStore.ts
+++ b/src/store/TimeSlotStore.ts
@@ -1,6 +1,7 @@
import { create } from 'zustand';
interface TimeSlot {
+ id?: number;
startTime?: string;
endTime?: string;
}
diff --git a/src/store/useTokenStore.ts b/src/store/useTokenStore.ts
index 6f56494..019957d 100644
--- a/src/store/useTokenStore.ts
+++ b/src/store/useTokenStore.ts
@@ -2,7 +2,7 @@ import { create } from 'zustand';
import { createJSONStorage, persist } from 'zustand/middleware';
interface TokenState {
- accessToken: null | string | undefined;
+ accessToken: null | string;
setAccessToken: (token: string | null) => void;
deleteAccessToken: () => void;
}
@@ -11,7 +11,7 @@ export const useTokenStore = create()(
persist(
(set) => {
return {
- accessToken: undefined,
+ accessToken: null,
setAccessToken: (token) => set({ accessToken: token }),
deleteAccessToken: () => set({ accessToken: null }),
};
@@ -19,11 +19,6 @@ export const useTokenStore = create()(
{
name: 'accessToken',
storage: createJSONStorage(() => localStorage),
- onRehydrateStorage: () => (state) => {
- if (state?.accessToken === undefined) {
- state?.setAccessToken(null);
- }
- },
}
)
);
diff --git a/src/styles/globals.css b/src/styles/globals.css
index 04b8f18..6f92163 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -98,4 +98,25 @@
opacity: 0;
}
}
+
+ @layer base {
+ .scrollbar-hide::-webkit-scrollbar {
+ display: none;
+ }
+ .scrollbar-hide {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ }
+
+ .thin-scrollbar::-webkit-scrollbar {
+ width: 6px;
+ }
+ .thin-scrollbar::-webkit-scrollbar-thumb {
+ background-color: rgba(0, 0, 0, 0.2);
+ border-radius: 9999px;
+ }
+ .thin-scrollbar::-webkit-scrollbar-track {
+ background: transparent;
+ }
+ }
}
diff --git a/src/types/notificationType.ts b/src/types/notificationType.ts
index 8ef0de8..1b10b1a 100644
--- a/src/types/notificationType.ts
+++ b/src/types/notificationType.ts
@@ -1,12 +1,15 @@
export interface INotification {
- cursorId: number;
- notification: {
- id: number;
- teamId: string;
- userId: number;
- createdAt: string;
- updatedAt: string;
- deletedAt: string;
- }[];
+ id: number;
+ teamId: string;
+ userId: number;
+ content: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+}
+
+export interface INotifications {
+ cursorId: number | null;
+ notifications: INotification[];
totalCount: number;
}
diff --git a/src/utils/dateParser.ts b/src/utils/dateParser.ts
index adce60e..6979c8c 100644
--- a/src/utils/dateParser.ts
+++ b/src/utils/dateParser.ts
@@ -1,4 +1,5 @@
-import { format, getDay } from 'date-fns';
+import { format, formatDistanceToNow, getDay } from 'date-fns';
+import { ko } from 'date-fns/locale';
export function dateToCalendarDate(date: Date) {
const calendarDate = {
@@ -7,6 +8,7 @@ export function dateToCalendarDate(date: Date) {
month: format(date, 'MM'),
day: format(date, 'dd'),
yoil: getDay(date),
+ relative: formatDistanceToNow(date, { addSuffix: true, locale: ko }),
};
return calendarDate;
diff --git a/src/utils/notifications.ts b/src/utils/notifications.ts
new file mode 100644
index 0000000..b2939f1
--- /dev/null
+++ b/src/utils/notifications.ts
@@ -0,0 +1,32 @@
+export const isRecent = (createdAt: string, updatedAt?: string) => {
+ const baseTime = updatedAt || createdAt;
+ return (
+ new Date().getTime() - new Date(baseTime).getTime() < 24 * 60 * 60 * 1000
+ );
+};
+
+export type NotificationType =
+ | 'RESERVATION_APPROVED'
+ | 'RESERVATION_REJECTED'
+ | 'OTHER';
+
+export const getNotificationType = (content: string): NotificationType => {
+ if (content.includes('승인')) return 'RESERVATION_APPROVED';
+ if (content.includes('거절')) return 'RESERVATION_REJECTED';
+ return 'OTHER';
+};
+
+export const parseContent = (content: string) => {
+ let formatted = content.replace(/(\))\s*(예약)/, '$1
$2');
+ if (content.includes('승인'))
+ formatted = formatted.replace(
+ '승인',
+ `승인`
+ );
+ if (content.includes('거절'))
+ formatted = formatted.replace(
+ '거절',
+ `거절`
+ );
+ return formatted;
+};