Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,23 @@ export default function LearningNotificationModal({
}: LearningNotificationModalProps) {
const showToast = useToastStore((s) => s.showToast);
const { data: setting } = useGetNotificationSetting();
const { mutateAsync: updateSetting } = useUpdateNotificationSetting();
const updateSettingMutation = useUpdateNotificationSetting();

const [hour, setHour] = useState(8);
const [minute, setMinute] = useState(0);

useEffect(() => {
if (setting?.notifyHour !== null && setting?.notifyHour !== undefined)
setHour(setting.notifyHour);
if (setting?.notifyMinute !== null && setting?.notifyMinute !== undefined)
if (typeof setting?.notifyHour === 'number') setHour(setting.notifyHour);
if (typeof setting?.notifyMinute === 'number')
setMinute(setting.notifyMinute);
}, [setting]);

const handleSave = async () => {
try {
await updateSetting({ notifyHour: hour, notifyMinute: minute });
await updateSettingMutation.mutateAsync({
notifyHour: hour,
notifyMinute: minute,
});
showToast('알림 시간이 저장되었습니다.', 'success');
onOpenChange(false);
} catch {
Expand Down Expand Up @@ -131,9 +133,10 @@ export default function LearningNotificationModal({
color="primary"
size="medium"
className="flex-1 whitespace-nowrap"
disabled={updateSettingMutation.isPending}
onClick={handleSave}
>
저장하기
{updateSettingMutation.isPending ? '저장 중...' : '저장하기'}
</Button>
</div>
</Modal.Footer>
Expand Down
26 changes: 18 additions & 8 deletions src/app/(service)/(my)/my-class/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ function pad(n: number) {
export default function MyClassPage() {
const { data: courses = [] } = useGetCourseList();
const { data: giftEmail } = useGetMyGiftEmail();
const { data: notificationSetting } = useGetNotificationSetting();
const {
data: notificationSetting,
isError: isNotificationSettingError,
isLoading: isNotificationSettingLoading,
} = useGetNotificationSetting();
const [alarmModalOpen, setAlarmModalOpen] = useState(false);

const isEnabled = notificationSetting?.isEnabled ?? false;
Expand All @@ -46,6 +50,8 @@ export default function MyClassPage() {
isEnabled={isEnabled}
notifyHour={notificationSetting?.notifyHour}
notifyMinute={notificationSetting?.notifyMinute}
isLoading={isNotificationSettingLoading}
isError={isNotificationSettingError}
onOpenModal={() => setAlarmModalOpen(true)}
/>
<GiftEmailCard
Expand Down Expand Up @@ -122,21 +128,25 @@ function AlarmCard({
isEnabled,
notifyHour,
notifyMinute,
isLoading,
isError,
onOpenModal,
}: {
isEnabled: boolean;
notifyHour?: number;
notifyMinute?: number;
isLoading: boolean;
isError: boolean;
onOpenModal: () => void;
}) {
const hasTime =
notifyHour !== null &&
notifyHour !== undefined &&
notifyMinute !== null &&
notifyMinute !== undefined;
const timeText = hasTime
? `${pad(notifyHour!)}:${pad(notifyMinute!)}`
: '현재 지정된 시간이 없습니다.';
typeof notifyHour === 'number' && typeof notifyMinute === 'number';
const timeText = (() => {
if (isLoading) return '알림 시간을 불러오는 중입니다.';
if (isError) return '알림 시간 조회에 실패했습니다.';
if (hasTime) return `${pad(notifyHour)}:${pad(notifyMinute)}`;
return '현재 지정된 시간이 없습니다.';
})();

return (
<div
Expand Down
16 changes: 10 additions & 6 deletions src/hooks/queries/notification/use-notification-setting.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { axiosInstanceV5 } from '@/api/client/axios';

interface NotificationSettingResponse {
export interface NotificationSettingResponse {
notifyHour?: number;
notifyMinute?: number;
isEnabled?: boolean;
saved?: boolean;
}

interface NotificationSettingUpdateRequest {
export interface NotificationSettingUpdateRequest {
notifyHour: number;
notifyMinute: number;
}

const QUERY_KEY = ['notification-setting'] as const;
export const NOTIFICATION_SETTING_QUERY_KEY = ['notification-setting'] as const;

export function useGetNotificationSetting() {
return useQuery({
queryKey: QUERY_KEY,
queryKey: NOTIFICATION_SETTING_QUERY_KEY,
queryFn: async () => {
const { data } = await axiosInstanceV5.get<{
content: NotificationSettingResponse;
Expand All @@ -35,8 +36,11 @@ export function useUpdateNotificationSetting() {
}>('members/me/notification-setting', req);
return data.content;
},
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: QUERY_KEY });
onSuccess: async (data) => {
queryClient.setQueryData(NOTIFICATION_SETTING_QUERY_KEY, data);
await queryClient.invalidateQueries({
queryKey: NOTIFICATION_SETTING_QUERY_KEY,
});
Comment on lines +39 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) notification-setting 관련 타입/사용처 확인"
rg -n --type=ts -C3 "NotificationSettingResponse|isEnabled|saved|members/me/notification-setting|setQueryData\\(|useGetNotificationSetting\\("

echo
echo "2) OpenAPI/Swagger 산출물(있다면)에서 notification-setting 응답 스키마 확인"
fd -i "openapi|swagger" .

Repository: code-zero-to-one/study-platform-client

Length of output: 39137


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) use-notification-setting.ts 관련 코드 ==="
sed -n '1,120p' src/hooks/queries/notification/use-notification-setting.ts

echo
echo "=== 2) my-class page.tsx에서 notificationSetting 소비부 ==="
sed -n '1,220p' src/app/(service)/(my)/my-class/page.tsx

echo
echo "=== 3) learning-notification-modal.tsx에서 소비부 ==="
sed -n '1,200p' "src/app/(service)/(my)/my-class/_components/learning-notification-modal.tsx"

echo
echo "=== 4) notification-setting 관련 API/스키마(가능하면) 추가 탐색 ==="
rg -n "notification-setting" src | head -n 50

echo
echo "=== 5) NotificationSettingResponse/saved 사용처 ==="
rg -n "NotificationSettingResponse|\\.saved\\b|isEnabled\\b" src/app src/features src/hooks/queries/notification | head -n 80

Repository: code-zero-to-one/study-platform-client

Length of output: 1626


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) use-notification-setting.ts 관련 코드 ==="
sed -n '1,120p' src/hooks/queries/notification/use-notification-setting.ts

echo
echo "=== 2) my-class page.tsx에서 notificationSetting 소비부 ==="
sed -n '1,220p' src/app/(service)/(my)/my-class/page.tsx

echo
echo "=== 3) learning-notification-modal.tsx에서 소비부 ==="
sed -n '1,200' "src/app/(service)/(my)/my-class/_components/learning-notification-modal.tsx"

echo
echo "=== 4) notification-setting 관련 API/스키마(가능하면) 추가 탐색 ==="
rg -n "notification-setting" src | head -n 50

echo
echo "=== 5) NotificationSettingResponse/saved 사용처 ==="
rg -n "NotificationSettingResponse|\\.saved\\b|isEnabled\\b" src/app src/features src/hooks/queries/notification | head -n 80

Repository: code-zero-to-one/study-platform-client

Length of output: 1626


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) my-class page.tsx에서 notificationSetting 소비부 핵심 ==="
sed -n '1,90p' "src/app/(service)/(my)/my-class/page.tsx"

echo
echo "=== 2) learning-notification-modal.tsx에서 notificationSetting 소비부 핵심 ==="
sed -n '1,140p' "src/app/(service)/(my)/my-class/_components/learning-notification-modal.tsx"

echo
echo "=== 3) NotificationSettingResponse(타입) 직접 사용/필드 접근 추가 탐색 ==="
rg -n "NotificationSettingResponse|notificationSetting\\?\\.isEnabled|\\bisEnabled\\b|\\bsaved\\b" \
  "src/app/(service)/(my)/my-class" src/features/mentoring src/hooks/queries/notification

echo
echo "=== 4) notification-setting query key 사용처(캐시 참조) ==="
rg -n "NOTIFICATION_SETTING_QUERY_KEY|notification-setting" src/hooks src/app

Repository: code-zero-to-one/study-platform-client

Length of output: 11114


src/hooks/queries/notification/use-notification-setting.ts 캐시 업데이트를 부분 응답에 대비해 병합하세요

onSuccess에서 queryClient.setQueryData(NOTIFICATION_SETTING_QUERY_KEY, data)로 통째 교체하면 NotificationSettingResponse가 optional 필드(isEnabled 등)만 부분 포함하는 경우, my-class/page.tsx에서 notificationSetting?.isEnabled ?? false 때문에 토글 상태가 잠깐 잘못(깜빡임) 표시될 수 있습니다.

🔧 제안 수정안
-    onSuccess: async (data) => {
-      queryClient.setQueryData(NOTIFICATION_SETTING_QUERY_KEY, data);
+    onSuccess: async (data) => {
+      queryClient.setQueryData(
+        NOTIFICATION_SETTING_QUERY_KEY,
+        (prev?: NotificationSettingResponse) => ({
+          ...prev,
+          ...data,
+        }),
+      );
       await queryClient.invalidateQueries({
         queryKey: NOTIFICATION_SETTING_QUERY_KEY,
       });
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onSuccess: async (data) => {
queryClient.setQueryData(NOTIFICATION_SETTING_QUERY_KEY, data);
await queryClient.invalidateQueries({
queryKey: NOTIFICATION_SETTING_QUERY_KEY,
});
onSuccess: async (data) => {
queryClient.setQueryData(
NOTIFICATION_SETTING_QUERY_KEY,
(prev?: NotificationSettingResponse) => ({
...prev,
...data,
}),
);
await queryClient.invalidateQueries({
queryKey: NOTIFICATION_SETTING_QUERY_KEY,
});
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/queries/notification/use-notification-setting.ts` around lines 39 -
43, The onSuccess currently replaces the whole cache via
queryClient.setQueryData(NOTIFICATION_SETTING_QUERY_KEY, data) which can
overwrite optional/partial fields and cause UI flicker; instead, in the
onSuccess handler for useNotificationSetting merge incoming data with existing
cache (read existing via
queryClient.getQueryData(NOTIFICATION_SETTING_QUERY_KEY) or use setQueryData(old
=> ...)) and shallow-merge/assign only provided fields (e.g., merge isEnabled
and other optional properties) before writing back, then call
queryClient.invalidateQueries for NOTIFICATION_SETTING_QUERY_KEY if needed;
update the onSuccess in use-notification-setting.ts to perform this merge logic
so partial responses don’t clobber missing fields.

},
});
}
Loading