From 79b7afc057e022de179bbbefc3b74d360120c761 Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Sat, 16 May 2026 14:44:48 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20=EC=84=A4=EC=A0=95=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=82=B4=EC=97=90=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/logo/social-logo/plain/slack.svg | 9 +++ .../setting/NotificationSection.tsx | 75 +++++++++++++++++++ src/pages/setting/Setting.tsx | 2 + 3 files changed, 86 insertions(+) create mode 100644 src/assets/logo/social-logo/plain/slack.svg create mode 100644 src/components/setting/NotificationSection.tsx diff --git a/src/assets/logo/social-logo/plain/slack.svg b/src/assets/logo/social-logo/plain/slack.svg new file mode 100644 index 00000000..9ed0ab03 --- /dev/null +++ b/src/assets/logo/social-logo/plain/slack.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/setting/NotificationSection.tsx b/src/components/setting/NotificationSection.tsx new file mode 100644 index 00000000..615d0137 --- /dev/null +++ b/src/components/setting/NotificationSection.tsx @@ -0,0 +1,75 @@ +import { useState } from "react"; + +import Badge from "@/components/common/badge/Badge"; +import Button from "@/components/common/button/Button"; +import Card from "@/components/common/card/Card"; +import Toggle from "@/components/common/toggle/Toggle"; + +import MailIcon from "@/assets/icon/common/mail.svg?react"; +import BellIcon from "@/assets/icon/sidebar/notification.svg?react"; +import SlackIcon from "@/assets/logo/social-logo/plain/slack.svg?react"; + +type TNotificationSectionProps = { + email: string; +}; + +export default function NotificationSection({ + email, +}: TNotificationSectionProps) { + const [browserPush, setBrowserPush] = useState(false); + const [emailNotif, setEmailNotif] = useState(false); + + return ( + +
+ +

알림 채널 설정

+
+ +
+
+
+ +

브라우저 푸시 알림

+
+
+ {browserPush && 권한 허용됨} + setBrowserPush((p) => !p)} + ariaLabel="브라우저 푸시 알림 켜기/끄기" + /> +
+
+ +
+
+ +
+

이메일 알림 설정

+

{email}

+
+
+ setEmailNotif((p) => !p)} + ariaLabel="이메일 알림 켜기/끄기" + /> +
+ +
+
+ +
+

슬랙 연동하기

+

#채널명

+
+
+ +
+
+
+ ); +} diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index 7f8b5d2e..2498a186 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -6,6 +6,7 @@ import type { IApiErrorResponse } from "@/types/common/common"; import { useImageUploader } from "@/hooks/common/useImageUploader"; import Button from "@/components/common/button/Button"; +import NotificationSection from "@/components/setting/NotificationSection"; import PasswordSection from "@/components/setting/PasswordSection"; import PasswordSectionSkeleton from "@/components/setting/PasswordSectionSkeleton"; import ProfileSection from "@/components/setting/ProfileSection"; @@ -206,6 +207,7 @@ export default function Setting() { setConfirmNewPassword={setConfirmNewPassword} errors={passwordErrors} /> + )} From d5b09dcd2856238a1a2ec6d7ac3eaa96e0af9016 Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Sat, 16 May 2026 14:45:29 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=EC=82=AC=EC=9D=B4=EB=93=9C?= =?UTF-8?q?=EB=B0=94=20footerNav=EC=97=90=20=EC=95=8C=EB=A6=BC=20=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=ED=84=B0=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/sidebarNav.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/constants/sidebarNav.ts b/src/constants/sidebarNav.ts index 1668ba12..0253e4db 100644 --- a/src/constants/sidebarNav.ts +++ b/src/constants/sidebarNav.ts @@ -80,11 +80,6 @@ export const mainNav: INavItem[] = [ ]; export const footerNav: INavItem[] = [ - { - id: "notifications", - label: "알림", - icon: NotificationIcon, - }, { id: "settings", label: "설정", From 1da97e7aaeb2c1789f60a4470c2c180c1eaa4740 Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Sat, 16 May 2026 14:59:07 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=EC=84=A4=EC=A0=95=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=83=AD=20=ED=98=95=EC=8B=9D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/sidebarNav.ts | 1 - src/pages/setting/Setting.tsx | 130 +++++++++++++++++++++++----------- 2 files changed, 89 insertions(+), 42 deletions(-) diff --git a/src/constants/sidebarNav.ts b/src/constants/sidebarNav.ts index 0253e4db..bcafd4d6 100644 --- a/src/constants/sidebarNav.ts +++ b/src/constants/sidebarNav.ts @@ -2,7 +2,6 @@ import type { INavItem } from "@/types/navigation/navItem"; import AdsIcon from "@/assets/icon/sidebar/ads.svg?react"; import DashboardIcon from "@/assets/icon/sidebar/dashboard.svg?react"; -import NotificationIcon from "@/assets/icon/sidebar/notification.svg?react"; import SettingsIcon from "@/assets/icon/sidebar/setting.svg?react"; import WorkspaceIcon from "@/assets/icon/sidebar/workspace.svg?react"; diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index 2498a186..a5e774cb 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -1,5 +1,6 @@ import { type ChangeEvent, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; +import { twMerge } from "tailwind-merge"; import type { IApiErrorResponse } from "@/types/common/common"; @@ -14,6 +15,14 @@ import ProfileSectionSkeleton from "@/components/setting/ProfileSectionSkeleton" import { getMyInfo, updateMyInfo } from "@/api/auth/auth"; +type TSettingTab = "profile" | "security" | "notifications"; + +const TABS: { id: TSettingTab; label: string }[] = [ + { id: "profile", label: "프로필" }, + { id: "security", label: "보안" }, + { id: "notifications", label: "알림" }, +]; + interface IDraftOrganization { name: string; position: string; @@ -31,6 +40,7 @@ interface ISavedProfile { } export default function Setting() { + const [activeTab, setActiveTab] = useState("profile"); const [savedProfile, setSavedProfile] = useState({ name: "", profileImageUrl: null, @@ -78,6 +88,7 @@ export default function Setting() { newPassword: "", confirmNewPassword: "", }); + const validatePassword = () => { const errors = { currentPassword: "", @@ -173,56 +184,93 @@ export default function Setting() { }; fetchMyInfo(); }, [setPreview]); + return (
+ +
- {isLoading ? ( + {activeTab === "profile" && ( <> - - + {isLoading ? ( + + ) : ( + + setDraftProfile((prev) => ({ ...prev, name: v })) + } + organizations={draftProfile.organizations} + email={draftProfile.email} + phoneNumber={draftProfile.phoneNumber} + fileRef={fileRef} + preview={preview} + onPickFile={handlePickFile} + openFilePicker={openFilePicker} + resetImage={() => { + resetImage(); + setIsImageDeleted(true); + }} + /> + )} - ) : ( + )} + + {activeTab === "security" && ( <> - setDraftProfile((prev) => ({ ...prev, name: v }))} - organizations={draftProfile.organizations} - email={draftProfile.email} - phoneNumber={draftProfile.phoneNumber} - fileRef={fileRef} - preview={preview} - onPickFile={handlePickFile} - openFilePicker={openFilePicker} - resetImage={() => { - resetImage(); - setIsImageDeleted(true); - }} - /> - - + {isLoading ? ( + + ) : ( + + )} )} + + {activeTab === "notifications" && ( + + )}
-
- -
+ + {activeTab !== "notifications" && ( +
+ +
+ )}
); } From 8c548e6cc66c2a866059358cce599f0be15028a8 Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Sat, 16 May 2026 15:02:19 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20hasChange=20=ED=83=AD=EB=B3=84?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=ED=95=B4=EC=84=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=82=AC=ED=95=AD=20=EC=A0=80=EC=9E=A5=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=EC=9D=B4=20=ED=83=AD=EB=B3=84?= =?UTF-8?q?=EB=A1=9C=20=EB=8F=85=EB=A6=BD=EC=A0=81=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/setting/Setting.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index a5e774cb..6825d143 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -74,14 +74,16 @@ export default function Setting() { const hasPasswordChanges = !!currentPassword || !!newPassword || !!confirmNewPassword; - const hasChanges = useMemo(() => { + const hasProfileChanges = useMemo(() => { return ( savedProfile.name !== draftProfile.name || savedProfile.profileImageUrl !== preview || - !!file || - hasPasswordChanges + !!file ); - }, [savedProfile, draftProfile, preview, file, hasPasswordChanges]); + }, [savedProfile, draftProfile, preview, file]); + + const hasChanges = + activeTab === "profile" ? hasProfileChanges : hasPasswordChanges; const [passwordErrors, setPasswordErrors] = useState({ currentPassword: "", From 51e3d613e5e47851e35ccdfc6cf2dae62a03d709 Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Sat, 16 May 2026 15:04:25 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20=ED=83=AD?= =?UTF-8?q?=EC=97=90=EB=8F=84=20=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=ED=95=98=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/setting/Setting.tsx | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index 6825d143..bdd05aeb 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -259,20 +259,18 @@ export default function Setting() { )} - {activeTab !== "notifications" && ( -
- -
- )} +
+ +
); } From 170e22926e1895df32983f083d1b684e1700cd61 Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Sat, 16 May 2026 15:26:57 +0900 Subject: [PATCH 06/12] =?UTF-8?q?fix:=20hasChange=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=ED=83=AD=EA=B5=AC=EB=B6=84=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/setting/Setting.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index bdd05aeb..363cf8ab 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -82,8 +82,19 @@ export default function Setting() { ); }, [savedProfile, draftProfile, preview, file]); - const hasChanges = - activeTab === "profile" ? hasProfileChanges : hasPasswordChanges; + const hasChanges = useMemo(() => { + switch (activeTab) { + case "profile": + return hasProfileChanges; + case "security": + return hasPasswordChanges; + case "notifications": + //TODO: API연동 후 알림 설정 변경 감지 추가 + return false; + default: + return false; + } + }, [activeTab, hasProfileChanges, hasPasswordChanges]); const [passwordErrors, setPasswordErrors] = useState({ currentPassword: "", From 059f92798f8364c46edebcd264b6d694a76800d5 Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Sat, 16 May 2026 15:29:00 +0900 Subject: [PATCH 07/12] =?UTF-8?q?fix:=20=EC=95=8C=EB=A6=BC=20=ED=83=AD=20?= =?UTF-8?q?=EB=A1=9C=EB=94=A9=EC=83=81=ED=83=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/setting/Setting.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index 363cf8ab..5b95fe6a 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -266,7 +266,13 @@ export default function Setting() { )} {activeTab === "notifications" && ( - + <> + {isLoading ? ( +
+ ) : ( + + )} + )}
From 05bb7c3cebdccbfd0c3fe2f651b3e48e774cf4d2 Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Tue, 19 May 2026 11:38:31 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=82=B4=20=EC=A1=B0=EC=A7=81=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/setting/ProfileSection.tsx | 29 ------------------- .../setting/ProfileSectionSkeleton.tsx | 4 --- src/pages/setting/Setting.tsx | 12 -------- 3 files changed, 45 deletions(-) diff --git a/src/components/setting/ProfileSection.tsx b/src/components/setting/ProfileSection.tsx index b470f952..1426d4b7 100644 --- a/src/components/setting/ProfileSection.tsx +++ b/src/components/setting/ProfileSection.tsx @@ -12,7 +12,6 @@ import UserProfileCircleIcon from "@/assets/icon/common/userProfileCircle.svg?re type TProfileSectionProps = { name: string; setName: (v: string) => void; - organizations: { name: string; position: string }[]; email: string; phoneNumber: string; fileRef: React.RefObject; @@ -24,7 +23,6 @@ type TProfileSectionProps = { export default function ProfileSection({ name, setName, - organizations, email, phoneNumber, fileRef, @@ -102,33 +100,6 @@ export default function ProfileSection({ onChange={(e) => setName(e.target.value)} /> -
-
소속 조직
- {organizations.length === 0 ? ( -
- 소속된 조직이 없습니다. -
- ) : ( - <> -
- {organizations.map((org, idx) => ( -
- -
- ))} - -
- 조직 정보는 별도 조직페이지에서 수정할 수 있습니다. -
-
- - )} -
-
- - -
diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index 5b95fe6a..9337930f 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -23,13 +23,8 @@ const TABS: { id: TSettingTab; label: string }[] = [ { id: "notifications", label: "알림" }, ]; -interface IDraftOrganization { - name: string; - position: string; -} interface IDraftProfile { name: string; - organizations: IDraftOrganization[]; email: string; phoneNumber: string; } @@ -47,7 +42,6 @@ export default function Setting() { }); const [draftProfile, setDraftProfile] = useState({ name: "", - organizations: [], email: "", phoneNumber: "", }); @@ -174,11 +168,6 @@ export default function Setting() { const profileData = { name: res.data.name, - organizations: - res.data.organizations?.map((org) => ({ - name: org.orgName, - position: org.myRole, - })) ?? [], email: res.data.email, phoneNumber: res.data.phoneNumber, }; @@ -231,7 +220,6 @@ export default function Setting() { setName={(v) => setDraftProfile((prev) => ({ ...prev, name: v })) } - organizations={draftProfile.organizations} email={draftProfile.email} phoneNumber={draftProfile.phoneNumber} fileRef={fileRef} From 14dea8c0a3ceb46143ded60e70218edcfa986585 Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Tue, 19 May 2026 11:43:51 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=ED=83=AD=ED=98=95=EC=8B=9D=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=ED=9B=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=ED=95=98=EB=82=98=EB=A1=9C=20=EC=84=A4=EC=A0=95=20=EC=84=B9?= =?UTF-8?q?=EC=85=98=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/setting/Setting.tsx | 100 +++++++++++----------------------- 1 file changed, 33 insertions(+), 67 deletions(-) diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index 9337930f..3e6b42a8 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -189,78 +189,44 @@ export default function Setting() { return (
- -
- {activeTab === "profile" && ( - <> - {isLoading ? ( - - ) : ( - - setDraftProfile((prev) => ({ ...prev, name: v })) - } - email={draftProfile.email} - phoneNumber={draftProfile.phoneNumber} - fileRef={fileRef} - preview={preview} - onPickFile={handlePickFile} - openFilePicker={openFilePicker} - resetImage={() => { - resetImage(); - setIsImageDeleted(true); - }} - /> - )} - + {isLoading ? ( + + ) : ( + setDraftProfile((prev) => ({ ...prev, name: v }))} + email={draftProfile.email} + phoneNumber={draftProfile.phoneNumber} + fileRef={fileRef} + preview={preview} + onPickFile={handlePickFile} + openFilePicker={openFilePicker} + resetImage={() => { + resetImage(); + setIsImageDeleted(true); + }} + /> )} - {activeTab === "security" && ( - <> - {isLoading ? ( - - ) : ( - - )} - + {isLoading ? ( + + ) : ( + )} - {activeTab === "notifications" && ( - <> - {isLoading ? ( -
- ) : ( - - )} - + {isLoading ? ( +
+ ) : ( + )}
From 8c95cd7331b3b16f7f7dfad7d46f5dee5b6dced6 Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Tue, 19 May 2026 11:58:10 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20hasChange(=ED=83=AD=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=EC=9E=94=EC=9E=AC)=EC=A0=9C=EA=B1=B0,=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EB=B2=84=ED=8A=BC=20=EC=95=8C=EB=A6=BC=ED=86=A0?= =?UTF-8?q?=EA=B8=80=EA=B3=BC=EB=8F=84=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting/NotificationSection.tsx | 22 ++-- src/pages/setting/Setting.tsx | 112 +++++++++++------- 2 files changed, 82 insertions(+), 52 deletions(-) diff --git a/src/components/setting/NotificationSection.tsx b/src/components/setting/NotificationSection.tsx index 615d0137..abd70e96 100644 --- a/src/components/setting/NotificationSection.tsx +++ b/src/components/setting/NotificationSection.tsx @@ -1,5 +1,3 @@ -import { useState } from "react"; - import Badge from "@/components/common/badge/Badge"; import Button from "@/components/common/button/Button"; import Card from "@/components/common/card/Card"; @@ -9,16 +7,26 @@ import MailIcon from "@/assets/icon/common/mail.svg?react"; import BellIcon from "@/assets/icon/sidebar/notification.svg?react"; import SlackIcon from "@/assets/logo/social-logo/plain/slack.svg?react"; +export interface INotificationSettings { + browserPush: boolean; + emailNotif: boolean; +} + type TNotificationSectionProps = { email: string; + browserPush: boolean; + emailNotif: boolean; + onBrowserPushChange: (value: boolean) => void; + onEmailNotifChange: (value: boolean) => void; }; export default function NotificationSection({ email, + browserPush, + emailNotif, + onBrowserPushChange, + onEmailNotifChange, }: TNotificationSectionProps) { - const [browserPush, setBrowserPush] = useState(false); - const [emailNotif, setEmailNotif] = useState(false); - return (
@@ -36,7 +44,7 @@ export default function NotificationSection({ {browserPush && 권한 허용됨} setBrowserPush((p) => !p)} + onToggle={() => onBrowserPushChange(!browserPush)} ariaLabel="브라우저 푸시 알림 켜기/끄기" />
@@ -52,7 +60,7 @@ export default function NotificationSection({
setEmailNotif((p) => !p)} + onToggle={() => onEmailNotifChange(!emailNotif)} ariaLabel="이메일 알림 켜기/끄기" />
diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index 3e6b42a8..da213725 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -1,13 +1,14 @@ import { type ChangeEvent, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; -import { twMerge } from "tailwind-merge"; import type { IApiErrorResponse } from "@/types/common/common"; import { useImageUploader } from "@/hooks/common/useImageUploader"; import Button from "@/components/common/button/Button"; -import NotificationSection from "@/components/setting/NotificationSection"; +import NotificationSection, { + type INotificationSettings, +} from "@/components/setting/NotificationSection"; import PasswordSection from "@/components/setting/PasswordSection"; import PasswordSectionSkeleton from "@/components/setting/PasswordSectionSkeleton"; import ProfileSection from "@/components/setting/ProfileSection"; @@ -15,13 +16,10 @@ import ProfileSectionSkeleton from "@/components/setting/ProfileSectionSkeleton" import { getMyInfo, updateMyInfo } from "@/api/auth/auth"; -type TSettingTab = "profile" | "security" | "notifications"; - -const TABS: { id: TSettingTab; label: string }[] = [ - { id: "profile", label: "프로필" }, - { id: "security", label: "보안" }, - { id: "notifications", label: "알림" }, -]; +const DEFAULT_NOTIFICATION_SETTINGS: INotificationSettings = { + browserPush: false, + emailNotif: false, +}; interface IDraftProfile { name: string; @@ -35,7 +33,6 @@ interface ISavedProfile { } export default function Setting() { - const [activeTab, setActiveTab] = useState("profile"); const [savedProfile, setSavedProfile] = useState({ name: "", profileImageUrl: null, @@ -48,6 +45,10 @@ export default function Setting() { const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmNewPassword, setConfirmNewPassword] = useState(""); + const [savedNotification, setSavedNotification] = + useState(DEFAULT_NOTIFICATION_SETTINGS); + const [draftNotification, setDraftNotification] = + useState(DEFAULT_NOTIFICATION_SETTINGS); const [isImageDeleted, setIsImageDeleted] = useState(false); const [isLoading, setIsLoading] = useState(false); const { @@ -76,19 +77,16 @@ export default function Setting() { ); }, [savedProfile, draftProfile, preview, file]); - const hasChanges = useMemo(() => { - switch (activeTab) { - case "profile": - return hasProfileChanges; - case "security": - return hasPasswordChanges; - case "notifications": - //TODO: API연동 후 알림 설정 변경 감지 추가 - return false; - default: - return false; - } - }, [activeTab, hasProfileChanges, hasPasswordChanges]); + const hasNotificationChanges = useMemo(() => { + return ( + savedNotification.browserPush !== draftNotification.browserPush || + savedNotification.emailNotif !== draftNotification.emailNotif + ); + }, [savedNotification, draftNotification]); + + const hasAccountChanges = hasProfileChanges || hasPasswordChanges; + + const hasChanges = hasAccountChanges || hasNotificationChanges; const [passwordErrors, setPasswordErrors] = useState({ currentPassword: "", @@ -133,27 +131,41 @@ export default function Setting() { }); } try { - const res = await updateMyInfo({ - name: draftProfile.name, - oldPassword: currentPassword || undefined, - newPassword: newPassword || undefined, - isImageDeleted, - imageFile: file, - }); - setSavedProfile({ - name: res.data.name, - profileImageUrl: res.data.profileImageUrl, - }); - setDraftProfile((prev) => ({ - ...prev, - name: res.data.name, - })); - setPreview(res.data.profileImageUrl); - setCurrentPassword(""); - setNewPassword(""); - setConfirmNewPassword(""); - toast.success("회원정보가 수정되었습니다"); - setIsImageDeleted(false); + if (hasAccountChanges) { + const res = await updateMyInfo({ + name: draftProfile.name, + oldPassword: currentPassword || undefined, + newPassword: newPassword || undefined, + isImageDeleted, + imageFile: file, + }); + setSavedProfile({ + name: res.data.name, + profileImageUrl: res.data.profileImageUrl, + }); + setDraftProfile((prev) => ({ + ...prev, + name: res.data.name, + })); + setPreview(res.data.profileImageUrl); + setCurrentPassword(""); + setNewPassword(""); + setConfirmNewPassword(""); + setIsImageDeleted(false); + } + + if (hasNotificationChanges) { + setSavedNotification(draftNotification); + // TODO: 알림 설정 API 연동 + } + + toast.success( + hasAccountChanges && hasNotificationChanges + ? "설정이 저장되었습니다" + : hasNotificationChanges + ? "알림 설정이 저장되었습니다" + : "회원정보가 수정되었습니다", + ); } catch (e) { const error = e as IApiErrorResponse; toast.error(error.message ?? "회원정보수정에 실패했습니다"); @@ -226,7 +238,17 @@ export default function Setting() { {isLoading ? (
) : ( - + + setDraftNotification((prev) => ({ ...prev, browserPush: value })) + } + onEmailNotifChange={(value) => + setDraftNotification((prev) => ({ ...prev, emailNotif: value })) + } + /> )}
From 6080a684d900063b42a2e4eb03e7d5832453312c Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Tue, 19 May 2026 12:13:20 +0900 Subject: [PATCH 11/12] =?UTF-8?q?chore:=20=ED=86=A0=EA=B8=80=20=EB=B1=83?= =?UTF-8?q?=EC=A7=80=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/setting/NotificationSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/setting/NotificationSection.tsx b/src/components/setting/NotificationSection.tsx index abd70e96..e8af7c80 100644 --- a/src/components/setting/NotificationSection.tsx +++ b/src/components/setting/NotificationSection.tsx @@ -41,7 +41,7 @@ export default function NotificationSection({

브라우저 푸시 알림

- {browserPush && 권한 허용됨} + {browserPush && 켜짐} onBrowserPushChange(!browserPush)} From 155004203ebe904c8cdf2cfaf7ef9546681675e0 Mon Sep 17 00:00:00 2001 From: JAESEON PARK Date: Tue, 19 May 2026 14:45:19 +0900 Subject: [PATCH 12/12] =?UTF-8?q?chore:=20Profilesection=EB=82=B4=20?= =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC/=EC=A0=84=ED=99=94=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20flex=EB=A1=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/setting/ProfileSection.tsx | 10 +++++----- src/components/setting/ProfileSectionSkeleton.tsx | 8 ++++---- src/pages/setting/Setting.tsx | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/setting/ProfileSection.tsx b/src/components/setting/ProfileSection.tsx index 1426d4b7..fbf62036 100644 --- a/src/components/setting/ProfileSection.tsx +++ b/src/components/setting/ProfileSection.tsx @@ -55,7 +55,7 @@ export default function ProfileSection({ type="button" onClick={openFilePicker} aria-label="프로필 이미지 업로드 또는 변경" - className="relative flex h-60 w-60 cursor-pointer items-center justify-center overflow-hidden rounded-full border border-surface-400 bg-surface-200 outline-none transition-colors hover:bg-surface-300/70 focus-visible:ring-2 focus-visible:ring-primary-500/40" + className="relative flex h-45 w-45 cursor-pointer items-center justify-center overflow-hidden rounded-full border border-surface-400 bg-surface-200 outline-none transition-colors hover:bg-surface-300/70 focus-visible:ring-2 focus-visible:ring-primary-500/40" > {preview ? (
-
-
+
+
setName(e.target.value)} />
-
+
-
+
-
-
+
+
-
+
-
+
diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index da213725..5711f076 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -236,7 +236,7 @@ export default function Setting() { )} {isLoading ? ( -
+
) : (