+
- {!notification.isRead && (
+ {!notification.read && (
{
className="shrink-0"
/>
)}
- {notification.isRead && }
+ {notification.read && }
- {notification.content}
+ {getNotificationText(notification)}
- {notification.time}
+ {formatTimeAgo(notification.createdAt)}
);
diff --git a/src/hooks/mutations/useNotificationMutations.ts b/src/hooks/mutations/useNotificationMutations.ts
index 7fc4678..13b5b32 100644
--- a/src/hooks/mutations/useNotificationMutations.ts
+++ b/src/hooks/mutations/useNotificationMutations.ts
@@ -20,3 +20,14 @@ export const useToggleNotificationMutation = () => {
},
});
};
+
+export const useReadNotificationMutation = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (notificationId: number) => notificationService.readNotification(notificationId),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: notificationKeys.infiniteList() });
+ },
+ });
+};
diff --git a/src/hooks/queries/useClubQueries.ts b/src/hooks/queries/useClubQueries.ts
new file mode 100644
index 0000000..f2330ca
--- /dev/null
+++ b/src/hooks/queries/useClubQueries.ts
@@ -0,0 +1,14 @@
+import { useQuery } from "@tanstack/react-query";
+import { clubService } from "@/services/clubService";
+
+export const clubKeys = {
+ all: ["clubs"] as const,
+ myList: () => [...clubKeys.all, "myList"] as const,
+};
+
+export const useMyClubsQuery = () => {
+ return useQuery({
+ queryKey: clubKeys.myList(),
+ queryFn: () => clubService.getMyClubs(),
+ });
+};
diff --git a/src/hooks/queries/useMemberQueries.ts b/src/hooks/queries/useMemberQueries.ts
index 15c73a4..660db0c 100644
--- a/src/hooks/queries/useMemberQueries.ts
+++ b/src/hooks/queries/useMemberQueries.ts
@@ -4,6 +4,7 @@ import { memberService } from "@/services/memberService";
export const memberKeys = {
all: ["members"] as const,
recommended: () => [...memberKeys.all, "recommended"] as const,
+ profile: () => [...memberKeys.all, "profile"] as const,
};
export const useRecommendedMembersQuery = (enabled: boolean = true) => {
@@ -13,3 +14,11 @@ export const useRecommendedMembersQuery = (enabled: boolean = true) => {
enabled,
});
};
+
+export const useProfileQuery = (enabled: boolean = true) => {
+ return useQuery({
+ queryKey: memberKeys.profile(),
+ queryFn: () => memberService.getProfile(),
+ enabled,
+ });
+};
diff --git a/src/hooks/queries/useNotificationQueries.ts b/src/hooks/queries/useNotificationQueries.ts
index 27bcd16..b4fa840 100644
--- a/src/hooks/queries/useNotificationQueries.ts
+++ b/src/hooks/queries/useNotificationQueries.ts
@@ -1,9 +1,10 @@
-import { useQuery } from "@tanstack/react-query";
+import { useQuery, useInfiniteQuery } from "@tanstack/react-query";
import { notificationService } from "@/services/notificationService";
export const notificationKeys = {
all: ["notifications"] as const,
settings: () => [...notificationKeys.all, "settings"] as const,
+ infiniteList: () => [...notificationKeys.all, "infiniteList"] as const,
};
export const useNotificationSettingsQuery = () => {
@@ -12,3 +13,15 @@ export const useNotificationSettingsQuery = () => {
queryFn: () => notificationService.getSettings(),
});
};
+
+export const useInfiniteNotificationsQuery = () => {
+ return useInfiniteQuery({
+ queryKey: notificationKeys.infiniteList(),
+ queryFn: ({ pageParam }) => notificationService.getNotifications(pageParam ?? undefined),
+ initialPageParam: null as number | null,
+ getNextPageParam: (lastPage) => {
+ if (!lastPage || !lastPage.hasNext) return undefined;
+ return lastPage.nextCursor;
+ },
+ });
+};
diff --git a/src/hooks/queries/useStoryQueries.ts b/src/hooks/queries/useStoryQueries.ts
index 94c6871..e68cbeb 100644
--- a/src/hooks/queries/useStoryQueries.ts
+++ b/src/hooks/queries/useStoryQueries.ts
@@ -6,6 +6,7 @@ export const storyKeys = {
all: ["stories"] as const,
list: () => [...storyKeys.all, "list"] as const,
infiniteList: () => [...storyKeys.all, "infiniteList"] as const,
+ myList: () => [...storyKeys.all, "myList"] as const,
detail: (id: number) => [...storyKeys.all, "detail", id] as const,
};
@@ -35,3 +36,15 @@ export const useInfiniteStoriesQuery = () => {
},
});
};
+
+export const useMyInfiniteStoriesQuery = () => {
+ return useInfiniteQuery({
+ queryKey: storyKeys.myList(),
+ queryFn: ({ pageParam }) => storyService.getMyStories(pageParam ?? undefined),
+ initialPageParam: null as number | null,
+ getNextPageParam: (lastPage) => {
+ if (!lastPage || !lastPage.hasNext) return undefined;
+ return lastPage.nextCursor;
+ },
+ });
+};
diff --git a/src/lib/api/endpoints/bookstory.ts b/src/lib/api/endpoints/bookstory.ts
index 7f13100..a88bb46 100644
--- a/src/lib/api/endpoints/bookstory.ts
+++ b/src/lib/api/endpoints/bookstory.ts
@@ -2,4 +2,5 @@ import { API_BASE_URL } from "./base";
export const STORY_ENDPOINTS = {
LIST: `${API_BASE_URL}/book-stories`,
+ ME: `${API_BASE_URL}/book-stories/me`,
};
diff --git a/src/lib/api/endpoints/club.ts b/src/lib/api/endpoints/club.ts
new file mode 100644
index 0000000..ae15a93
--- /dev/null
+++ b/src/lib/api/endpoints/club.ts
@@ -0,0 +1,5 @@
+import { API_BASE_URL } from "./base";
+
+export const CLUB_ENDPOINTS = {
+ MY_CLUBS: `${API_BASE_URL}/me/clubs`,
+};
diff --git a/src/lib/api/endpoints/member.ts b/src/lib/api/endpoints/member.ts
index bb85e2d..bdc973c 100644
--- a/src/lib/api/endpoints/member.ts
+++ b/src/lib/api/endpoints/member.ts
@@ -1,6 +1,7 @@
import { API_BASE_URL } from "./base";
export const MEMBER_ENDPOINTS = {
+ GET_PROFILE: `${API_BASE_URL}/members/me`,
RECOMMEND: `${API_BASE_URL}/members/me/recommend`,
UPDATE_PROFILE: `${API_BASE_URL}/members/me`,
UPDATE_PASSWORD: `${API_BASE_URL}/members/me/update-password`,
diff --git a/src/lib/api/endpoints/notification.ts b/src/lib/api/endpoints/notification.ts
index 456986a..872b0d5 100644
--- a/src/lib/api/endpoints/notification.ts
+++ b/src/lib/api/endpoints/notification.ts
@@ -3,4 +3,6 @@ import { API_BASE_URL } from "./base";
export const NOTIFICATION_ENDPOINTS = {
GET_SETTINGS: `${API_BASE_URL}/notifications/settings`,
UPDATE_SETTING: (settingType: string) => `${API_BASE_URL}/notifications/settings/${settingType}`,
+ GET_NOTIFICATIONS: `${API_BASE_URL}/notifications`,
+ READ_NOTIFICATION: (id: number) => `${API_BASE_URL}/notifications/${id}/read`,
};
diff --git a/src/services/clubService.ts b/src/services/clubService.ts
new file mode 100644
index 0000000..592fbdf
--- /dev/null
+++ b/src/services/clubService.ts
@@ -0,0 +1,13 @@
+import { apiClient } from "@/lib/api/client";
+import { CLUB_ENDPOINTS } from "@/lib/api/endpoints/club";
+import { MyClubListResponse } from "@/types/club";
+import { ApiResponse } from "@/types/auth";
+
+export const clubService = {
+ getMyClubs: async (): Promise
=> {
+ const response = await apiClient.get>(
+ CLUB_ENDPOINTS.MY_CLUBS
+ );
+ return response.result!;
+ },
+};
diff --git a/src/services/memberService.ts b/src/services/memberService.ts
index 78b1b36..4bd95be 100644
--- a/src/services/memberService.ts
+++ b/src/services/memberService.ts
@@ -1,6 +1,6 @@
import { apiClient } from "@/lib/api/client";
import { MEMBER_ENDPOINTS } from "@/lib/api/endpoints/member";
-import { RecommendResponse, UpdateProfileRequest, UpdatePasswordRequest } from "@/types/member";
+import { RecommendResponse, UpdateProfileRequest, UpdatePasswordRequest, ProfileResponse } from "@/types/member";
import { ApiResponse } from "@/types/auth";
export const memberService = {
@@ -28,4 +28,10 @@ export const memberService = {
throw new Error(response.message || "Failed to update password");
}
},
+ getProfile: async (): Promise => {
+ const response = await apiClient.get>(
+ MEMBER_ENDPOINTS.GET_PROFILE
+ );
+ return response.result!;
+ },
};
diff --git a/src/services/notificationService.ts b/src/services/notificationService.ts
index ad0c5bc..f819e21 100644
--- a/src/services/notificationService.ts
+++ b/src/services/notificationService.ts
@@ -1,6 +1,6 @@
import { apiClient } from "@/lib/api/client";
import { NOTIFICATION_ENDPOINTS } from "@/lib/api/endpoints/notification";
-import { NotificationSettings, NotificationSettingType } from "@/types/notification";
+import { NotificationSettings, NotificationSettingType, NotificationListResponse } from "@/types/notification";
import { ApiResponse } from "@/types/auth";
export const notificationService = {
@@ -18,4 +18,23 @@ export const notificationService = {
throw new Error(response.message || "Failed to update notification setting");
}
},
+ getNotifications: async (cursorId?: number): Promise => {
+ const url = new URL(NOTIFICATION_ENDPOINTS.GET_NOTIFICATIONS);
+ if (cursorId) {
+ url.searchParams.append("cursorId", cursorId.toString());
+ }
+
+ const response = await apiClient.get>(
+ url.toString()
+ );
+ return response.result!;
+ },
+ readNotification: async (notificationId: number): Promise => {
+ const response = await apiClient.patch>(
+ NOTIFICATION_ENDPOINTS.READ_NOTIFICATION(notificationId)
+ );
+ if (!response.isSuccess) {
+ throw new Error(response.message || "Failed to mark notification as read");
+ }
+ },
};
diff --git a/src/services/storyService.ts b/src/services/storyService.ts
index 9ff936e..b0e7d35 100644
--- a/src/services/storyService.ts
+++ b/src/services/storyService.ts
@@ -13,6 +13,15 @@ export const storyService = {
);
return response.result!;
},
+ getMyStories: async (cursorId?: number): Promise => {
+ const response = await apiClient.get>(
+ STORY_ENDPOINTS.ME,
+ {
+ params: { cursorId },
+ }
+ );
+ return response.result!;
+ },
getStoryById: async (id: number): Promise => {
const response = await apiClient.get>(
`${STORY_ENDPOINTS.LIST}/${id}`
diff --git a/src/types/club.ts b/src/types/club.ts
new file mode 100644
index 0000000..175527d
--- /dev/null
+++ b/src/types/club.ts
@@ -0,0 +1,8 @@
+export interface MyClubInfo {
+ clubId: number;
+ clubName: string;
+}
+
+export interface MyClubListResponse {
+ clubList: MyClubInfo[];
+}
diff --git a/src/types/member.ts b/src/types/member.ts
index b7a1c18..f2f0043 100644
--- a/src/types/member.ts
+++ b/src/types/member.ts
@@ -18,3 +18,10 @@ export interface UpdatePasswordRequest {
newPassword?: string;
confirmPassword?: string;
}
+
+export interface ProfileResponse {
+ nickname: string;
+ description: string;
+ profileImageUrl: string;
+ categories: string[];
+}
diff --git a/src/types/notification.ts b/src/types/notification.ts
index 164e805..35d6edf 100644
--- a/src/types/notification.ts
+++ b/src/types/notification.ts
@@ -14,3 +14,20 @@ export interface NotificationSettings {
newFollower: boolean;
joinClub: boolean;
}
+
+export interface NotificationBasicInfo {
+ notificationId: number;
+ notificationType: "LIKE" | "COMMENT" | "FOLLOW" | "JOIN_CLUB" | "CLUB_MEETING_CREATED" | "CLUB_NOTICE_CREATED";
+ domainId: number;
+ sourceId: number;
+ displayName: string;
+ read: boolean;
+ createdAt: string;
+}
+
+export interface NotificationListResponse {
+ notifications: NotificationBasicInfo[];
+ hasNext: boolean;
+ nextCursor: number | null;
+ pageSize: number;
+}