From e41c7cc367cb31a18561aec503f5e5eb3789580a Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:54:36 +0900 Subject: [PATCH 01/98] =?UTF-8?q?style:=20=EC=B6=94=EA=B0=80=EB=90=9C=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=AC=20=ED=86=A0=ED=81=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/globals.css | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/app/globals.css b/src/app/globals.css index 1c020a1e2..1bca9efd9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -55,6 +55,10 @@ --pink-700: #8e448a; --pink-900: #280526; + --opacity-60: #ffffff99; + --opacity-100: #00c8aa1f; + --opacity-200: #00c8aa4d; + --background: var(--neutral-200); --background-2: var(--neutral-300); --foreground: var(--text-normal); @@ -65,8 +69,9 @@ --text-alternative: var(--neutral-600); --text-disabled: var(--neutral-500); --text-inverse: var(--neutral-0); - --container-code: var(--neutral-400); + --text-brand: var(--primary-500); + --container-code: var(--neutral-400); --code-keyword: #d73a49; --code-string: #032f62; --code-comment: #6a737d; @@ -84,11 +89,15 @@ --container-secondary: var(--secondary-500); --container-secondary-interaction: var(--secondary-700); --container-secondary-alternative: var(--secondary-100); + --container-opacity: var(--opacity-60); --button-neutral: var(--neutral-400); --button-neutral-interaction: var(--neutral-500); --button-primary: var(--primary-500); --button-primary-interaction: var(--primary-600); + --button-primary-subtle: var(--opacity-100); + --button-primary-subtle-interaction: var(--opacity-200); + --button-disabled: var(--neutral-400); --icon-normal: var(--neutral-800); --icon-strong: var(--neutral-900); @@ -278,6 +287,15 @@ --pink-700: #f3a7f0; --pink-900: #faddf9; + --opacity-60: #1e202199; + --opacity-100: #355d5a80; + --opacity-200: #00c8aa66; + + --button-primary-subtle: var(--opacity-100); + --button-primary-subtle-interaction: var(--opacity-200); + + --container-opacity: var(--opacity-60); + --card: oklch(0.205 0 0); --card-foreground: oklch(0.985 0 0); --popover: oklch(0.205 0 0); @@ -337,6 +355,11 @@ --color-button-neutral-interaction: var(--button-neutral-interaction); --color-button-primary: var(--button-primary); --color-button-primary-interaction: var(--button-primary-interaction); + --color-button-primary-subtle: var(--button-primary-subtle); + --color-button-primary-subtle-interaction: var(--button-primary-subtle-interaction); + --color-button-disabled: var(--button-disabled); + + --color-container-opacity: var(--container-opacity); --color-icon-normal: var(--icon-normal); --color-icon-strong: var(--icon-strong); From 7f38d0903e669d6348a43878906844f6fea2be41 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:54:56 +0900 Subject: [PATCH 02/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=9A=A9=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EC=97=90=EC=85=8B=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/icons/add_round.svg | 3 +++ src/assets/icons/calendar.svg | 3 +++ src/assets/icons/index.ts | 5 +++++ src/assets/icons/mail.svg | 3 +++ src/assets/icons/phone.svg | 3 +++ src/assets/icons/setting.svg | 3 +++ 6 files changed, 20 insertions(+) create mode 100644 src/assets/icons/add_round.svg create mode 100644 src/assets/icons/calendar.svg create mode 100644 src/assets/icons/mail.svg create mode 100644 src/assets/icons/phone.svg create mode 100644 src/assets/icons/setting.svg diff --git a/src/assets/icons/add_round.svg b/src/assets/icons/add_round.svg new file mode 100644 index 000000000..8fbebb658 --- /dev/null +++ b/src/assets/icons/add_round.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/calendar.svg b/src/assets/icons/calendar.svg new file mode 100644 index 000000000..d06bdd63b --- /dev/null +++ b/src/assets/icons/calendar.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts index 0e0a865f2..ce23ff3e7 100644 --- a/src/assets/icons/index.ts +++ b/src/assets/icons/index.ts @@ -57,3 +57,8 @@ export { default as ProfileCompleteIcon } from './profile_complete.svg'; export { default as TaskFinishedIcon } from './task_finished.svg'; export { default as RushIcon } from './rush.svg'; export { default as QuestionCircleIcon } from './question_circle.svg'; +export { default as PhoneIcon } from './phone.svg'; +export { default as MailIcon } from './mail.svg'; +export { default as SettingIcon } from './setting.svg'; +export { default as AddRoundIcon } from './add_round.svg'; +export { default as CalendarIcon } from './calendar.svg'; diff --git a/src/assets/icons/mail.svg b/src/assets/icons/mail.svg new file mode 100644 index 000000000..3b316f994 --- /dev/null +++ b/src/assets/icons/mail.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/phone.svg b/src/assets/icons/phone.svg new file mode 100644 index 000000000..9f2e372a1 --- /dev/null +++ b/src/assets/icons/phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/setting.svg b/src/assets/icons/setting.svg new file mode 100644 index 000000000..2031b0e13 --- /dev/null +++ b/src/assets/icons/setting.svg @@ -0,0 +1,3 @@ + + + From 4e95755ff8ae7f76b6c0bafd40ec4aad5e39da59 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:55:25 +0900 Subject: [PATCH 03/98] =?UTF-8?q?fix:=20=EB=B2=84=ED=8A=BC=20primarySoft?= =?UTF-8?q?=20variants=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20disabled=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EB=B3=B4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/Button.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index cf69a87bc..7a4cca9fa 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -7,11 +7,13 @@ const buttonVariants = cva( variants: { variant: { primary: - 'bg-button-primary text-text-inverse hover:bg-button-primary-interaction active:bg-button-primary-interaction disabled:bg-button-neutral disabled:text-text-disabled', + 'bg-button-primary text-text-inverse hover:bg-button-primary-interaction active:bg-button-primary-interaction disabled:bg-button-disabled disabled:text-text-disabled', secondary: - 'bg-button-neutral text-text-normal hover:bg-button-neutral-interaction active:bg-button-neutral-interaction disabled:bg-button-neutral disabled:text-text-disabled', + 'bg-button-neutral text-text-normal hover:bg-button-neutral-interaction active:bg-button-neutral-interaction disabled:bg-button-disabled disabled:text-text-disabled', tertiary: 'bg-transparent text-text-normal hover:bg-container-neutral-interaction active:bg-container-neutral-interaction disabled:text-text-disabled', + primarySoft: + 'bg-button-primary-subtle text-brand-primary hover:bg-button-primary-subtle-interaction active:bg-button-primary-subtle-interaction disabled:bg-button-disabled disabled:text-text-disabled', danger: 'bg-state-error text-text-inverse hover:opacity-90 active:opacity-80 disabled:bg-button-neutral disabled:text-text-disabled', kakao: From 93f3eee6af749d70b6b23c4f84772b49c0231304 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:55:41 +0900 Subject: [PATCH 04/98] =?UTF-8?q?fix:=20=EC=95=84=EB=B0=94=ED=83=80=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EB=B0=8F=20=ED=91=9C=EC=8B=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B3=B4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/avatar.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx index 71761e12d..475e97b59 100644 --- a/src/components/ui/avatar.tsx +++ b/src/components/ui/avatar.tsx @@ -13,8 +13,10 @@ const avatarVariants = cva('group/avatar relative flex shrink-0 overflow-hidden }, size: { 128: 'size-32', + 100: 'size-25', 64: 'size-16', 40: 'size-10', + 36: 'size-9', 24: 'size-6', }, colorScheme: { @@ -25,8 +27,10 @@ const avatarVariants = cva('group/avatar relative flex shrink-0 overflow-hidden }, compoundVariants: [ { type: 'square', size: 128, className: 'rounded-[32px]' }, + { type: 'square', size: 100, className: 'rounded-[25px]' }, { type: 'square', size: 64, className: 'rounded-lg' }, { type: 'square', size: 40, className: 'rounded-md' }, + { type: 'square', size: 36, className: 'rounded-md' }, { type: 'square', size: 24, className: 'rounded-[6px]' }, ], defaultVariants: { From bf28551ed17fd911b36e5d4f2199a66a8525131d Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:56:10 +0900 Subject: [PATCH 05/98] =?UTF-8?q?refactor:=20=EA=B2=8C=EC=8B=9C=ED=8C=90?= =?UTF-8?q?=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=EB=AF=B8=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/board/CategorySelector.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/board/CategorySelector.tsx b/src/components/board/CategorySelector.tsx index 28eb1bd37..163cae415 100644 --- a/src/components/board/CategorySelector.tsx +++ b/src/components/board/CategorySelector.tsx @@ -13,7 +13,6 @@ import { Icon, } from '@/components/ui'; import { ChannelList } from '@/components/board/ChannelList'; -import { useClubName } from '@/stores'; import type { BoardNavItem } from '@/types/board'; interface CategorySelectorProps { @@ -38,9 +37,6 @@ function CategorySelector({ }: CategorySelectorProps) { const [open, setOpen] = useState(false); const activeItem = items.find((item) => item.id === activeId); - const clubName = useClubName(); - - const boardName = clubName ?? '게시판'; const channelName = activeItem?.label ?? ''; // 아이템 선택 후 드롭다운 닫기 From 1e8bfbf3dde86bdec6db0d033ae0838cb31fc923 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:56:32 +0900 Subject: [PATCH 06/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B3=B5=ED=86=B5=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(private)/[clubId]/(main)/mypage/layout.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/layout.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/layout.tsx b/src/app/(private)/[clubId]/(main)/mypage/layout.tsx new file mode 100644 index 000000000..367d5c335 --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/layout.tsx @@ -0,0 +1,16 @@ +import { MyPageNav } from '@/components/mypage/MyPageNav'; + +interface MyPageLayoutProps { + children: React.ReactNode; +} + +export default function MyPageLayout({ children }: MyPageLayoutProps) { + return ( +
+ +
{children}
+
+ ); +} From d6ff651150b6c897cb3d3c18a114dc79fb1131dc Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:56:46 +0900 Subject: [PATCH 07/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B3=B5=ED=86=B5=20=EB=84=A4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/MyPageNav.tsx | 55 +++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/components/mypage/MyPageNav.tsx diff --git a/src/components/mypage/MyPageNav.tsx b/src/components/mypage/MyPageNav.tsx new file mode 100644 index 000000000..dfff351a0 --- /dev/null +++ b/src/components/mypage/MyPageNav.tsx @@ -0,0 +1,55 @@ +'use client'; + +import Link from 'next/link'; +import { useParams, usePathname } from 'next/navigation'; +import { cn } from '@/lib/cn'; + +const NAV_ITEMS = [ + { id: 'profile', label: '프로필', href: '/mypage' }, + { id: 'activity', label: '활동정보', href: '/mypage/activity' }, + { id: 'settings', label: '서비스 설정', href: '/mypage/settings' }, +] as const; + +function MyPageNav({ className, ...props }: React.ComponentProps<'nav'>) { + const pathname = usePathname(); + const { clubId } = useParams<{ clubId: string }>(); + + return ( + + ); +} + +export { MyPageNav }; From 412fed05f78428bb7feb786a59612fd9bd9ad9aa Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:57:11 +0900 Subject: [PATCH 08/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=99=9C=EB=8F=99=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[clubId]/(main)/mypage/activity/page.tsx | 7 ++++ .../mypage/MyPageActivityContent.tsx | 38 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/activity/page.tsx create mode 100644 src/components/mypage/MyPageActivityContent.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/activity/page.tsx b/src/app/(private)/[clubId]/(main)/mypage/activity/page.tsx new file mode 100644 index 000000000..ae3f7836f --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/activity/page.tsx @@ -0,0 +1,7 @@ +import { MyPageActivityContent } from '@/components/mypage/MyPageActivityContent'; + +export const dynamic = 'force-dynamic'; + +export default function MyPageActivityPage() { + return ; +} diff --git a/src/components/mypage/MyPageActivityContent.tsx b/src/components/mypage/MyPageActivityContent.tsx new file mode 100644 index 000000000..ebce55219 --- /dev/null +++ b/src/components/mypage/MyPageActivityContent.tsx @@ -0,0 +1,38 @@ +'use client'; + +import { useParams } from 'next/navigation'; +import { cn } from '@/lib/cn'; +import { useMyPageQueries } from '@/hooks/queries/mypage/useMyPageQueries'; +import { ClubInfoCard } from './ClubInfoCard'; + +type MyPageActivityContentProps = React.HTMLAttributes; + +function MyPageActivityContent({ className, ...props }: MyPageActivityContentProps) { + const { clubId } = useParams<{ clubId: string }>(); + const [, { data: clubs = [] }] = useMyPageQueries(clubId); + // TODO: UI 확인용 mock 삭제 필요 + const mockUnsetCardinalClub = clubs[0] + ? { + ...clubs[0], + id: 'mock-unset-cardinal', + name: `${clubs[0].name}`, + cardinals: [], + } + : null; + + return ( +
+

활동정보

+
+ {clubs.map((club) => ( + + ))} + {mockUnsetCardinalClub && ( + + )} +
+
+ ); +} + +export { MyPageActivityContent, type MyPageActivityContentProps }; From 730a47152ae0bb05501b98f3919d764ba634e031 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:57:33 +0900 Subject: [PATCH 09/98] =?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=EA=B0=80=20=EC=93=B4=20=EA=B8=80?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[clubId]/(main)/mypage/posts/page.tsx | 7 ++++ src/components/mypage/MyPagePostsContent.tsx | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/posts/page.tsx create mode 100644 src/components/mypage/MyPagePostsContent.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/posts/page.tsx b/src/app/(private)/[clubId]/(main)/mypage/posts/page.tsx new file mode 100644 index 000000000..1b7c27817 --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/posts/page.tsx @@ -0,0 +1,7 @@ +import { MyPagePostsContent } from '@/components/mypage/MyPagePostsContent'; + +export const dynamic = 'force-dynamic'; + +export default function MyPagePostsPage() { + return ; +} diff --git a/src/components/mypage/MyPagePostsContent.tsx b/src/components/mypage/MyPagePostsContent.tsx new file mode 100644 index 000000000..a15270443 --- /dev/null +++ b/src/components/mypage/MyPagePostsContent.tsx @@ -0,0 +1,34 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { BackIcon } from '@/assets/icons'; +import { Icon } from '@/components/ui'; +import { cn } from '@/lib/cn'; +import { BoardContent } from '@/components/board/BoardContent'; + +type MyPagePostsContentProps = React.HTMLAttributes; + +function MyPagePostsContent({ className, ...props }: MyPagePostsContentProps) { + const router = useRouter(); + + return ( +
+
+ +
+

내가 쓴 글

+
+
+ + +
+ ); +} + +export { MyPagePostsContent, type MyPagePostsContentProps }; From 9e423a49a42052439cf5b346a323419d4d38bdf5 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:57:59 +0900 Subject: [PATCH 10/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=B6=9C=EC=84=9D=20=EC=84=B8=EC=85=98=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[clubId]/(main)/mypage/sessions/page.tsx | 25 ++++++ .../mypage/MyPageSessionsContent.tsx | 84 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/sessions/page.tsx create mode 100644 src/components/mypage/MyPageSessionsContent.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/sessions/page.tsx b/src/app/(private)/[clubId]/(main)/mypage/sessions/page.tsx new file mode 100644 index 000000000..129752c23 --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/sessions/page.tsx @@ -0,0 +1,25 @@ +import { MyPageSessionsContent } from '@/components/mypage/MyPageSessionsContent'; +import { attendanceServerApi } from '@/lib/apis/attendance.server'; +import type { AttendanceSummary } from '@/types/attendance'; + +interface MyPageSessionsPageProps { + params: Promise<{ clubId: string }>; +} + +export const dynamic = 'force-dynamic'; + +export default async function MyPageSessionsPage({ params }: MyPageSessionsPageProps) { + const { clubId } = await params; + + let summary: AttendanceSummary | undefined; + let errorMessage: string | undefined; + + try { + const response = await attendanceServerApi.getDetail(clubId); + summary = response.data; + } catch { + errorMessage = '출석 기록을 불러오지 못했습니다.'; + } + + return ; +} diff --git a/src/components/mypage/MyPageSessionsContent.tsx b/src/components/mypage/MyPageSessionsContent.tsx new file mode 100644 index 000000000..556c6bb3c --- /dev/null +++ b/src/components/mypage/MyPageSessionsContent.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { BackIcon, LocationIcon, CalendarIcon } from '@/assets/icons'; +import { Icon, Tag } from '@/components/ui'; +import { cn } from '@/lib/cn'; +import { toastError } from '@/stores/useToastStore'; +import type { AttendanceSummary } from '@/types/attendance'; +import { useEffect } from 'react'; +import { formatSessionDateParts } from '@/utils/shared/date'; + +type MyPageSessionsContentProps = React.HTMLAttributes & { + summary?: AttendanceSummary; + errorMessage?: string; +}; + +function MyPageSessionsContent({ + summary, + errorMessage, + className, + ...props +}: MyPageSessionsContentProps) { + const router = useRouter(); + const records = summary?.attendances ?? []; + + useEffect(() => { + if (errorMessage) toastError(errorMessage); + }, [errorMessage]); + + return ( +
+
+ +
+

출석한 세션

+
+
+ +
+ {records.length === 0 ? ( +

+ 출석한 세션이 없습니다. +

+ ) : ( + records.map((record) => { + const { day, weekday, timeLabel } = formatSessionDateParts(record.start); + + return ( +
+
+ {day} + {weekday} +
+ +
+

{record.title}

+
+ 세션 + + + {timeLabel} + + + + {record.location} + +
+
+
+ ); + }) + )} +
+
+ ); +} + +export { MyPageSessionsContent, type MyPageSessionsContentProps }; From ff3e8342b98085895395fe043be5df56d324b827 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:58:18 +0900 Subject: [PATCH 11/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[clubId]/(main)/mypage/settings/page.tsx | 7 ++ .../mypage/MyPageSettingsContent.tsx | 119 ++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/settings/page.tsx create mode 100644 src/components/mypage/MyPageSettingsContent.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/settings/page.tsx b/src/app/(private)/[clubId]/(main)/mypage/settings/page.tsx new file mode 100644 index 000000000..f3851245c --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/settings/page.tsx @@ -0,0 +1,7 @@ +import { MyPageSettingsContent } from '@/components/mypage/MyPageSettingsContent'; + +export const dynamic = 'force-dynamic'; + +export default function MyPageSettingsPage() { + return ; +} diff --git a/src/components/mypage/MyPageSettingsContent.tsx b/src/components/mypage/MyPageSettingsContent.tsx new file mode 100644 index 000000000..243c75808 --- /dev/null +++ b/src/components/mypage/MyPageSettingsContent.tsx @@ -0,0 +1,119 @@ +'use client'; + +import { useState } from 'react'; +import { useParams } from 'next/navigation'; +import { cn } from '@/lib/cn'; +import { useThemeStore } from '@/stores/theme-store'; +import { InfoSection } from './InfoSection'; +import { LogoutConfirmDialog } from './LogoutConfirmDialog'; +import { SupportListItem } from './SupportListItem'; +import { ThemeModeModal } from './ThemeModeModal'; +import { WithdrawConfirmDialog } from './WithdrawConfirmDialog'; + +type MyPageSettingsContentProps = React.HTMLAttributes; +type ThemeMode = 'auto' | 'light' | 'dark'; + +const THEME_MODE_LABELS: Record = { + auto: '자동', + light: '라이트', + dark: '다크', +}; + +function MyPageSettingsContent({ className, ...props }: MyPageSettingsContentProps) { + const { clubId } = useParams<{ clubId: string }>(); + const mode = useThemeStore((state) => state.mode); + const hasHydrated = useThemeStore((state) => state.hasHydrated); + const setMode = useThemeStore((state) => state.setMode); + const [isThemeModalOpen, setIsThemeModalOpen] = useState(false); + const [selectedThemeMode, setSelectedThemeMode] = useState('auto'); + const [logoutOpen, setLogoutOpen] = useState(false); + const [withdrawOpen, setWithdrawOpen] = useState(false); + + const handleOpenThemeModal = () => { + setSelectedThemeMode(mode); + setIsThemeModalOpen(true); + }; + + const handleConfirmThemeMode = () => { + setMode(selectedThemeMode); + setIsThemeModalOpen(false); + }; + + return ( +
+

서비스 설정

+ + + + + + +
+
+ + +
+ +
+
+
+ +
+ setLogoutOpen(true)} + /> + setWithdrawOpen(true)} + /> +
+ +
+
+
+ + +
+ + + + + + +
+ ); +} + +export { MyPageSettingsContent, type MyPageSettingsContentProps }; From 0ab980f542ac22fc4c08185bd820fba658966926 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:58:39 +0900 Subject: [PATCH 12/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=94=84=EB=A1=9C=ED=95=84=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[clubId]/(main)/mypage/profiles/page.tsx | 7 + .../mypage/ProfileManagementContent.tsx | 124 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/profiles/page.tsx create mode 100644 src/components/mypage/ProfileManagementContent.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/profiles/page.tsx b/src/app/(private)/[clubId]/(main)/mypage/profiles/page.tsx new file mode 100644 index 000000000..3f9da1dbc --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/profiles/page.tsx @@ -0,0 +1,7 @@ +import { ProfileManagementContent } from '@/components/mypage'; + +export const dynamic = 'force-dynamic'; + +export default function ProfilesPage() { + return ; +} diff --git a/src/components/mypage/ProfileManagementContent.tsx b/src/components/mypage/ProfileManagementContent.tsx new file mode 100644 index 000000000..9e0cd10e5 --- /dev/null +++ b/src/components/mypage/ProfileManagementContent.tsx @@ -0,0 +1,124 @@ +'use client'; + +import { useState } from 'react'; +import { useParams } from 'next/navigation'; +import { Button, Icon } from '@/components/ui'; +import { cn } from '@/lib/cn'; +import { useMyPageQueries } from '@/hooks/queries/mypage/useMyPageQueries'; +import { AddRoundIcon, BackIcon } from '@/assets/icons'; +import { useRouter } from 'next/navigation'; +import { AddProfileModal } from './AddProfileModal'; +import { ProfileCard } from './ProfileCard'; + +type ProfileManagementContentProps = React.HTMLAttributes; + +function ProfileManagementContent({ className, ...props }: ProfileManagementContentProps) { + const router = useRouter(); + const { clubId } = useParams<{ clubId: string }>(); + const [, { data: clubs = [] }] = useMyPageQueries(clubId); + const [isAddModalOpen, setIsAddModalOpen] = useState(false); + + return ( +
+
+ +
+

프로필 관리

+

+ 같은 프로필을 사용하는 동아리에는 프로필 사진, 이름, 소개글이 함께 반영돼요. +

+
+
+ +
+

사용 중인 프로필 {clubs.length}

+
+ {clubs.map((club) => ( + + ))} + {/* TODO: mock — 빈 프로필 empty state 확인용 */} + + {/* TODO: mock — 클럽 2개 확인용 */} + +
+ +
+ + +
+ ); +} +export { ProfileManagementContent, type ProfileManagementContentProps }; From 9707f25667917bbc6b5083c52f8563afeed7d01b Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:59:21 +0900 Subject: [PATCH 13/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=B9=B4=EB=93=9C=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ProfileCard.tsx | 94 +++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/components/mypage/ProfileCard.tsx diff --git a/src/components/mypage/ProfileCard.tsx b/src/components/mypage/ProfileCard.tsx new file mode 100644 index 000000000..e44a12ace --- /dev/null +++ b/src/components/mypage/ProfileCard.tsx @@ -0,0 +1,94 @@ +'use client'; + +import { useState } from 'react'; +import { EditIcon, InfoCircleIcon } from '@/assets/icons'; +import { Avatar, AvatarFallback, AvatarImage, Button, Icon } from '@/components/ui'; +import type { ClubDto } from '@/types/mypage'; +import { EditProfileModal } from './EditProfileModal'; +import { ProfileSelectModal } from './ProfileSelectModal'; + +interface ProfileCardProps { + profile: ClubDto; + clubs: ClubDto[]; + clubId: string; + availableProfiles: ClubDto[]; +} + +function ProfileCard({ profile, clubs, availableProfiles }: ProfileCardProps) { + const [selectedClub, setSelectedClub] = useState(null); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + + return ( + <> +
+
+ + + + +
+

{profile.name}

+ {profile.description && ( +

{profile.description}

+ )} +
+ +
+ + {clubs.length === 0 ? ( +
+ + 사용하는 동아리가 없습니다. +
+ ) : ( +
+ {clubs.map((club) => ( +
+
+ + + + + {club.name} +
+ +
+ ))} +
+ )} +
+ + {selectedClub && ( + { + if (!open) setSelectedClub(null); + }} + /> + )} + + + + ); +} + +export { ProfileCard, type ProfileCardProps }; From 67f4a3aaee214c57f766b06cde1b964bc76a7ea4 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:59:37 +0900 Subject: [PATCH 14/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EB=AA=A8=EB=8B=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ProfileSelectModal.tsx | 127 +++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 src/components/mypage/ProfileSelectModal.tsx diff --git a/src/components/mypage/ProfileSelectModal.tsx b/src/components/mypage/ProfileSelectModal.tsx new file mode 100644 index 000000000..bde79d3a7 --- /dev/null +++ b/src/components/mypage/ProfileSelectModal.tsx @@ -0,0 +1,127 @@ +'use client'; + +import { useState } from 'react'; +import { CheckIcon, DeleteIcon } from '@/assets/icons'; +import { + Avatar, + AvatarFallback, + AvatarImage, + Button, + Dialog, + DialogContent, + DialogTitle, + Icon, +} from '@/components/ui'; +import { cn } from '@/lib/cn'; +import type { ClubDto } from '@/types/mypage'; + +interface ProfileSelectModalProps { + open: boolean; + club: ClubDto; + currentProfileId: string; + profiles: ClubDto[]; + onOpenChange: (open: boolean) => void; + onConfirm?: (profileId: string) => void; +} + +function ProfileSelectModal({ + open, + club, + currentProfileId, + profiles, + onOpenChange, + onConfirm, +}: ProfileSelectModalProps) { + const [selectedProfileId, setSelectedProfileId] = useState(currentProfileId); + + const handleClose = () => { + onOpenChange(false); + }; + + const handleConfirm = () => { + onConfirm?.(selectedProfileId); + onOpenChange(false); + }; + + return ( + + +
+
+ + + + + + {club.name}에서
+ 사용할 프로필을 선택하세요. +
+
+ +
+ +
+ {profiles.map((profile) => { + const isSelected = profile.id === selectedProfileId; + + return ( + + ); + })} +
+ +
+ + +
+
+
+ ); +} + +export { ProfileSelectModal, type ProfileSelectModalProps }; From 7e5e22b16c1138b1c95fc36b3b4c76fdc8b1a39c Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 14:59:57 +0900 Subject: [PATCH 15/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=AA=A8=EB=8B=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/AddProfileModal.tsx | 92 +++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/components/mypage/AddProfileModal.tsx diff --git a/src/components/mypage/AddProfileModal.tsx b/src/components/mypage/AddProfileModal.tsx new file mode 100644 index 000000000..7ae0713c7 --- /dev/null +++ b/src/components/mypage/AddProfileModal.tsx @@ -0,0 +1,92 @@ +'use client'; + +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Dialog, DialogContent } from '@/components/ui'; +import { editProfileSchema, type EditProfileFormData } from '@/lib/schemas/editProfile'; +import { AddProfileModalHeader } from './AddProfileModal/Header'; +import { StepOneContent } from './AddProfileModal/StepOneContent'; +import { StepTwoContent } from './AddProfileModal/StepTwoContent'; + +interface AddProfileModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +function AddProfileModal({ open, onOpenChange }: AddProfileModalProps) { + const [step, setStep] = useState(1); + const [selectedClubIds, setSelectedClubIds] = useState(['1', '2']); + const stepOneSchema = editProfileSchema.pick({ name: true, bio: true }); + + const { + control, + reset, + trigger, + formState: { errors }, + } = useForm>({ + resolver: zodResolver(stepOneSchema), + mode: 'onChange', + reValidateMode: 'onChange', + defaultValues: { + name: '', + bio: '', + }, + }); + + const handleClose = () => { + setStep(1); + reset({ name: '', bio: '' }); + setSelectedClubIds(['1', '2']); + onOpenChange(false); + }; + + const handleToggleClub = (clubId: string) => { + setSelectedClubIds((prev) => + prev.includes(clubId) ? prev.filter((id) => id !== clubId) : [...prev, clubId], + ); + }; + + const handleConfirm = () => { + handleClose(); + }; + + const handleNext = async () => { + const isValid = await trigger(); + if (!isValid) return; + setStep(2); + }; + + return ( + + + + + {step === 1 ? ( + + ) : ( + setStep(1)} + onConfirm={handleConfirm} + /> + )} + + + ); +} + +export { AddProfileModal }; From 9eeab5a379c587920c4e5f19d7285fcd87529864 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:00:08 +0900 Subject: [PATCH 16/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=ED=97=A4=EB=8D=94=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/AddProfileModal/Header.tsx | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/components/mypage/AddProfileModal/Header.tsx diff --git a/src/components/mypage/AddProfileModal/Header.tsx b/src/components/mypage/AddProfileModal/Header.tsx new file mode 100644 index 000000000..18e17030f --- /dev/null +++ b/src/components/mypage/AddProfileModal/Header.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { DeleteIcon } from '@/assets/icons'; +import { DialogTitle, Icon } from '@/components/ui'; +import { cn } from '@/lib/cn'; + +const TOTAL_STEPS = 2; + +interface AddProfileModalHeaderProps { + step: number; + title: string; + onClose: () => void; +} + +function AddProfileModalHeader({ step, title, onClose }: AddProfileModalHeaderProps) { + return ( +
+
+
+ {Array.from({ length: TOTAL_STEPS }).map((_, index) => ( +
+ ))} +
+ {title} +
+ +
+ ); +} + +export { AddProfileModalHeader, type AddProfileModalHeaderProps }; From 5adb936d81366a49345d134488874ebaff129de0 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:00:21 +0900 Subject: [PATCH 17/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=AA=A8=EB=8B=AC=20step=201=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/AddProfileModal/StepOneContent.tsx | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/components/mypage/AddProfileModal/StepOneContent.tsx diff --git a/src/components/mypage/AddProfileModal/StepOneContent.tsx b/src/components/mypage/AddProfileModal/StepOneContent.tsx new file mode 100644 index 000000000..42f1587b2 --- /dev/null +++ b/src/components/mypage/AddProfileModal/StepOneContent.tsx @@ -0,0 +1,120 @@ +'use client'; + +import type { Control, FieldErrors } from 'react-hook-form'; +import { Controller, useWatch } from 'react-hook-form'; +import { Button, DialogClose, Input } from '@/components/ui'; +import { FormFieldWrapper } from '@/components/auth/hub'; +import type { EditProfileFormData } from '@/lib/schemas/editProfile'; +import { ProfileBackgroundImageEditor } from '../edit/ProfileBackgroundImageEditor'; +import { ProfileImageEditor } from '../edit/ProfileImageEditor'; + +const MAX_LENGTH = 30; + +interface StepOneContentProps { + control: Control>; + errors: FieldErrors>; + onCancel: () => void; + onNext: () => void; +} + +function CharacterCountRow({ error, value }: { error?: string; value: string }) { + return ( +
+
+ {error ? ( + {error} + ) : null} +
+ + {value.length}/{MAX_LENGTH} + +
+ ); +} + +function StepOneContent({ control, errors, onCancel, onNext }: StepOneContentProps) { + const name = useWatch({ control, name: 'name' }) ?? ''; + + return ( + <> + <> + + +
+ +
+ +
+ ( + +
+ + +
+
+ )} + /> + + ( + +
+ + +
+
+ )} + /> +
+
+ + + + +
+ + ); +} + +export { StepOneContent, type StepOneContentProps }; From ea34f0f2cf3190d4361751214789772cbdfaa619 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:00:28 +0900 Subject: [PATCH 18/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=AA=A8=EB=8B=AC=20step=202=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/AddProfileModal/StepTwoContent.tsx | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/components/mypage/AddProfileModal/StepTwoContent.tsx diff --git a/src/components/mypage/AddProfileModal/StepTwoContent.tsx b/src/components/mypage/AddProfileModal/StepTwoContent.tsx new file mode 100644 index 000000000..cce1b3d23 --- /dev/null +++ b/src/components/mypage/AddProfileModal/StepTwoContent.tsx @@ -0,0 +1,77 @@ +'use client'; + +import { CheckIcon, PeopleIcon } from '@/assets/icons'; +import { Avatar, AvatarFallback, Button, Icon } from '@/components/ui'; +import { cn } from '@/lib/cn'; + +const MOCK_CLUBS = [ + { id: '1', name: '가천대 검도부', memberCount: 368 }, + { id: '2', name: '가천대 테니스부', memberCount: 368 }, + { id: '3', name: '가천대 산악부', memberCount: 368 }, + { id: '4', name: '가천대 종이접기부', memberCount: 368 }, +]; + +interface StepTwoContentProps { + selectedClubIds: string[]; + onToggleClub: (clubId: string) => void; + onPrev: () => void; + onConfirm: () => void; +} + +function StepTwoContent({ selectedClubIds, onToggleClub, onPrev, onConfirm }: StepTwoContentProps) { + return ( +
+
+ {MOCK_CLUBS.map((club) => { + const isSelected = selectedClubIds.includes(club.id); + + return ( + + ); + })} +
+ +
+ + +
+
+ ); +} + +export { StepTwoContent, type StepTwoContentProps }; From 83f94c835dbdb639b3e8d882fea3831562ea5e42 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:06:29 +0900 Subject: [PATCH 19/98] =?UTF-8?q?fix:=20CharacterCountRow=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/AddProfileModal/StepOneContent.tsx | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/components/mypage/AddProfileModal/StepOneContent.tsx b/src/components/mypage/AddProfileModal/StepOneContent.tsx index 42f1587b2..c8740477f 100644 --- a/src/components/mypage/AddProfileModal/StepOneContent.tsx +++ b/src/components/mypage/AddProfileModal/StepOneContent.tsx @@ -5,6 +5,7 @@ import { Controller, useWatch } from 'react-hook-form'; import { Button, DialogClose, Input } from '@/components/ui'; import { FormFieldWrapper } from '@/components/auth/hub'; import type { EditProfileFormData } from '@/lib/schemas/editProfile'; +import { CharacterCountRow } from '../CharacterCountRow'; import { ProfileBackgroundImageEditor } from '../edit/ProfileBackgroundImageEditor'; import { ProfileImageEditor } from '../edit/ProfileImageEditor'; @@ -17,21 +18,6 @@ interface StepOneContentProps { onNext: () => void; } -function CharacterCountRow({ error, value }: { error?: string; value: string }) { - return ( -
-
- {error ? ( - {error} - ) : null} -
- - {value.length}/{MAX_LENGTH} - -
- ); -} - function StepOneContent({ control, errors, onCancel, onNext }: StepOneContentProps) { const name = useWatch({ control, name: 'name' }) ?? ''; @@ -68,7 +54,11 @@ function StepOneContent({ control, errors, onCancel, onNext }: StepOneContentPro className="bg-background border-line typo-body1 text-text-strong placeholder:text-text-alternative rounded-lg p-300" aria-label="이름" /> - +
)} @@ -91,7 +81,11 @@ function StepOneContent({ control, errors, onCancel, onNext }: StepOneContentPro className="bg-background border-line typo-body1 text-text-strong placeholder:text-text-alternative rounded-lg p-300" aria-label="소개글" /> - +
)} From 0ab3d1eaf3a3e0b74bef3eea4abecb332e2995db Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:06:45 +0900 Subject: [PATCH 20/98] =?UTF-8?q?feat:=20CharacterCountRow=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/CharacterCountRow.tsx | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/components/mypage/CharacterCountRow.tsx diff --git a/src/components/mypage/CharacterCountRow.tsx b/src/components/mypage/CharacterCountRow.tsx new file mode 100644 index 000000000..9e7f5530f --- /dev/null +++ b/src/components/mypage/CharacterCountRow.tsx @@ -0,0 +1,22 @@ +interface CharacterCountRowProps { + error?: string; + value: string; + maxLength: number; +} + +function CharacterCountRow({ error, value, maxLength }: CharacterCountRowProps) { + return ( +
+
+ {error ? ( + {error} + ) : null} +
+ + {value.length}/{maxLength} + +
+ ); +} + +export { CharacterCountRow, type CharacterCountRowProps }; From 064b6c9aac3fad0e19945038dfb4c3f810265907 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:08:11 +0900 Subject: [PATCH 21/98] =?UTF-8?q?fix:=20=EB=82=B4=EA=B0=80=20=EC=93=B4=20?= =?UTF-8?q?=EA=B8=80=20=EB=AA=A9=EB=A1=9D=EC=97=90=EC=84=9C=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=9E=AC=EC=82=AC=EC=9A=A9=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/board/BoardContent.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/components/board/BoardContent.tsx b/src/components/board/BoardContent.tsx index 99b1830f6..9bee2b417 100644 --- a/src/components/board/BoardContent.tsx +++ b/src/components/board/BoardContent.tsx @@ -24,9 +24,15 @@ function toDisplayImages(files: FileItem[]) { interface BoardContentProps { boardId: number | null; + onlyCurrentUser?: boolean; + emptyMessage?: string; } -function BoardContent({ boardId }: BoardContentProps) { +function BoardContent({ + boardId, + onlyCurrentUser = false, + emptyMessage = '아직 게시글이 없습니다.', +}: BoardContentProps) { const router = useRouter(); const { clubId } = useParams<{ clubId: string }>(); const currentUserId = useUserId(); @@ -43,6 +49,10 @@ function BoardContent({ boardId }: BoardContentProps) { const { ref: sentinelRef, isIntersecting } = useIntersectionObserver({ rootMargin: '200px', }); + const filteredPosts = + onlyCurrentUser && currentUserId != null + ? posts?.filter((post) => post.author.id === currentUserId) + : posts; useEffect(() => { if (!isError || !error) return; @@ -72,16 +82,16 @@ function BoardContent({ boardId }: BoardContentProps) { ); - if (!posts || posts.length === 0) + if (!filteredPosts || filteredPosts.length === 0) return (
-

아직 게시글이 없습니다.

+

{emptyMessage}

); return (
- {posts.map((post) => ( + {filteredPosts.map((post) => ( Date: Wed, 1 Jul 2026 15:08:42 +0900 Subject: [PATCH 22/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=8B=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/EditProfileModal.tsx | 204 +++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 src/components/mypage/EditProfileModal.tsx diff --git a/src/components/mypage/EditProfileModal.tsx b/src/components/mypage/EditProfileModal.tsx new file mode 100644 index 000000000..4339f00bd --- /dev/null +++ b/src/components/mypage/EditProfileModal.tsx @@ -0,0 +1,204 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { Controller, useForm, useWatch } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { DeleteIcon } from '@/assets/icons'; +import { FormFieldWrapper } from '@/components/auth/hub'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + Button, + Dialog, + DialogContent, + DialogTitle, + Icon, + Input, +} from '@/components/ui'; +import { editProfileSchema, type EditProfileFormData } from '@/lib/schemas/editProfile'; +import type { ClubDto } from '@/types/mypage'; +import { CharacterCountRow } from './CharacterCountRow'; +import { ProfileBackgroundImageEditor } from './edit/ProfileBackgroundImageEditor'; +import { ProfileImageEditor } from './edit/ProfileImageEditor'; + +const MAX_LENGTH = 30; + +interface EditProfileModalProps { + open: boolean; + profile: ClubDto; + onOpenChange: (open: boolean) => void; +} + +function EditProfileModal({ open, profile, onOpenChange }: EditProfileModalProps) { + const editProfileModalSchema = editProfileSchema.pick({ name: true, bio: true }); + const { + control, + reset, + formState: { errors }, + } = useForm>({ + resolver: zodResolver(editProfileModalSchema), + mode: 'onChange', + reValidateMode: 'onChange', + defaultValues: { + name: profile.name, + bio: profile.description, + }, + }); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const name = useWatch({ control, name: 'name' }) ?? ''; + + useEffect(() => { + if (!open) return; + reset({ + name: profile.name, + bio: profile.description, + }); + }, [open, profile.description, profile.name, reset]); + + const handleClose = () => { + reset({ + name: profile.name, + bio: profile.description, + }); + setIsDeleteDialogOpen(false); + onOpenChange(false); + }; + + const handleOpenDeleteDialog = () => { + onOpenChange(false); + setIsDeleteDialogOpen(true); + }; + + const handleDelete = () => { + setIsDeleteDialogOpen(false); + handleClose(); + }; + + return ( + + +
+ 프로필 수정 + +
+ +
+ + +
+ +
+
+ +
+ ( + +
+ + +
+
+ )} + /> + + ( + +
+ + +
+
+ )} + /> +
+ + + +
+ + +
+
+ + + 삭제 + 취소 + +
+ ); +} + +export { EditProfileModal, type EditProfileModalProps }; From 430de4318bd0266f62d3920f733bdf21f9791dbe Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:09:06 +0900 Subject: [PATCH 23/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=B0=B0=EA=B2=BD=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../edit/ProfileBackgroundImageEditor.tsx | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/components/mypage/edit/ProfileBackgroundImageEditor.tsx diff --git a/src/components/mypage/edit/ProfileBackgroundImageEditor.tsx b/src/components/mypage/edit/ProfileBackgroundImageEditor.tsx new file mode 100644 index 000000000..7ab1a7cea --- /dev/null +++ b/src/components/mypage/edit/ProfileBackgroundImageEditor.tsx @@ -0,0 +1,121 @@ +'use client'; + +import { useRef, useState } from 'react'; +import Image from 'next/image'; +import { CameraIcon } from '@/assets/icons'; +import { + Divider, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + Icon, +} from '@/components/ui'; +import { cn } from '@/lib/cn'; + +interface ProfileBackgroundImageEditorProps { + backgroundImageUrl?: string; + onFileChange?: (file: File) => void; + onResetImage?: () => void; + className?: string; + imageClassName?: string; + triggerClassName?: string; + triggerIconClassName?: string; + triggerIconSize?: number; +} + +function ProfileBackgroundImageEditor({ + backgroundImageUrl, + onFileChange, + onResetImage, + className, + imageClassName, + triggerClassName, + triggerIconClassName, + triggerIconSize = 16, +}: ProfileBackgroundImageEditorProps) { + const fileInputRef = useRef(null); + const previewUrlRef = useRef(null); + const [previewUrl, setPreviewUrl] = useState(null); + const [isReset, setIsReset] = useState(false); + + const handleChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + if (previewUrlRef.current) URL.revokeObjectURL(previewUrlRef.current); + const url = URL.createObjectURL(file); + previewUrlRef.current = url; + setPreviewUrl(url); + setIsReset(false); + onFileChange?.(file); + }; + + const handleReset = () => { + if (previewUrlRef.current) { + URL.revokeObjectURL(previewUrlRef.current); + previewUrlRef.current = null; + } + setPreviewUrl(null); + setIsReset(true); + onResetImage?.(); + }; + + const displayUrl = isReset ? null : (previewUrl ?? backgroundImageUrl ?? null); + + return ( +
+ {displayUrl && ( + + )} +
+ + + + + + + fileInputRef.current?.click()} + > + 이미지 업로드 + + + + 기본 이미지 + + + +
+
+ ); +} + +export { ProfileBackgroundImageEditor, type ProfileBackgroundImageEditorProps }; From 5b233e064684ad05ed7dac012d26d070f34d67ab Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:09:30 +0900 Subject: [PATCH 24/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=8E=B8=EC=A7=91=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/edit/ProfileImageEditor.tsx | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/components/mypage/edit/ProfileImageEditor.tsx b/src/components/mypage/edit/ProfileImageEditor.tsx index 237f27b87..4f8a22b93 100644 --- a/src/components/mypage/edit/ProfileImageEditor.tsx +++ b/src/components/mypage/edit/ProfileImageEditor.tsx @@ -2,23 +2,32 @@ import { useRef, useState } from 'react'; import { - Avatar, - AvatarFallback, - AvatarImage, Icon, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, Divider, + Avatar, + AvatarFallback, + AvatarImage, } from '@/components/ui'; -import { EditIcon } from '@/assets/icons'; +import { CameraIcon } from '@/assets/icons'; +import { cn } from '@/lib/cn'; +import type { AvatarProps } from '@/components/ui'; interface ProfileImageEditorProps { name: string; profileImageUrl?: string; onFileChange?: (file: File) => void; onResetImage?: () => void; + className?: string; + avatarSize?: AvatarProps['size']; + avatarClassName?: string; + fallbackClassName?: string; + triggerClassName?: string; + triggerIconClassName?: string; + triggerIconSize?: number; } function ProfileImageEditor({ @@ -26,6 +35,13 @@ function ProfileImageEditor({ profileImageUrl, onFileChange, onResetImage, + className, + avatarSize = 128, + avatarClassName, + fallbackClassName, + triggerClassName, + triggerIconClassName, + triggerIconSize = 16, }: ProfileImageEditorProps) { const fileInputRef = useRef(null); const previewUrlRef = useRef(null); @@ -56,15 +72,15 @@ function ProfileImageEditor({ const displayUrl = isReset ? null : (previewUrl ?? profileImageUrl ?? null); return ( -
- +
+ - + - + From ea9b49ea97d95d37151c3feec238f7e156024c1a Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:11:55 +0900 Subject: [PATCH 25/98] =?UTF-8?q?fix:=20=EA=B0=9C=EC=9D=B8=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=88=98=EC=A0=95=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=95=88=EC=93=B0=EB=8A=94=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/edit/EditProfileContent.tsx | 69 +++++-------------- 1 file changed, 18 insertions(+), 51 deletions(-) diff --git a/src/components/mypage/edit/EditProfileContent.tsx b/src/components/mypage/edit/EditProfileContent.tsx index 0bdb1effb..6f129f3bb 100644 --- a/src/components/mypage/edit/EditProfileContent.tsx +++ b/src/components/mypage/edit/EditProfileContent.tsx @@ -1,23 +1,11 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { isAxiosError } from 'axios'; -import Link from 'next/link'; import { useParams, useRouter } from 'next/navigation'; -import { useForm, useWatch } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, - Button, -} from '@/components/ui'; +import { AlertDialog, AlertDialogAction, AlertDialogCancel, Button, Icon } from '@/components/ui'; import { useNavigationGuard } from '@/hooks/useNavigationGuard'; import { cn } from '@/lib/cn'; import { createEditProfileSchema, type EditProfileFormData } from '@/lib/schemas/editProfile'; @@ -26,9 +14,9 @@ import { useUpdateProfileMutation } from '@/hooks/mutations/useUpdateProfileMuta import { toastSuccess, toastError } from '@/stores/useToastStore'; import { formatPhone } from '@/utils/shared'; import { EditProfileSkeleton } from '@/components/mypage/skeleton'; -import { ProfileImageEditor } from './ProfileImageEditor'; import { PersonalInfoFields } from './PersonalInfoFields'; import { SchoolInfoFields } from './SchoolInfoFields'; +import { BackIcon } from '@/assets/icons'; const toFormString = (value: string | null | undefined) => value ?? ''; @@ -42,8 +30,8 @@ function EditProfileContent({ className, schools, majors, ...props }: EditProfil const { clubId } = useParams<{ clubId: string }>(); const { data: me, isPending: isMePending } = useMyMemberQuery(clubId); const { mutate: updateProfile, isPending } = useUpdateProfileMutation(); - const [selectedFile, setSelectedFile] = useState(null); - const [resetToDefault, setResetToDefault] = useState(false); + const selectedFile: File | null = null; + const resetToDefault = false; const editProfileSchema = createEditProfileSchema(); const { @@ -84,7 +72,6 @@ function EditProfileContent({ className, schools, majors, ...props }: EditProfil void trigger(['phone', 'school', 'department', 'studentId']); }, [me, reset, trigger]); - const name = useWatch({ control, name: 'name' }); const hasChanges = isDirty || !!selectedFile || resetToDefault; const { open, onConfirm, onCancel, allowNavigation } = useNavigationGuard({ @@ -134,48 +121,28 @@ function EditProfileContent({ className, schools, majors, ...props }: EditProfil <>
-
- - - - - - My - - - - - - 개인정보 수정 - - - - -

개인정보 수정

+
+ +
+

개인정보 수정

+
- { - setSelectedFile(file); - setResetToDefault(false); - }} - onResetImage={() => { - setSelectedFile(null); - setResetToDefault(true); - }} - /> -
From 26d5a8c4b5a847b3185a1ab01bebaa1c1d6d25f6 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:12:38 +0900 Subject: [PATCH 26/98] =?UTF-8?q?remove:=20=EA=B0=9C=EC=9D=B8=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=88=98=EC=A0=95=20=ED=95=84=EB=93=9C=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=9D=B4=EB=A6=84,=20=EC=86=8C=EA=B0=9C=EA=B8=80?= =?UTF-8?q?=20=EB=B0=8F=20CharacterCountRow=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/edit/PersonalInfoFields.tsx | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/src/components/mypage/edit/PersonalInfoFields.tsx b/src/components/mypage/edit/PersonalInfoFields.tsx index d90008de6..757d3c245 100644 --- a/src/components/mypage/edit/PersonalInfoFields.tsx +++ b/src/components/mypage/edit/PersonalInfoFields.tsx @@ -12,65 +12,10 @@ interface PersonalInfoFieldsProps { control: Control; } -function CharacterCountRow({ error, value }: { error?: string; value: string }) { - return ( -
-
- {error ? ( - {error} - ) : null} -
- - {value.length}/30 - -
- ); -} - function PersonalInfoFields({ control }: PersonalInfoFieldsProps) { const { errors } = useFormState({ control }); return (
- - ( - <> - - - - )} - /> - - - - ( - <> - - - - )} - /> - - Date: Wed, 1 Jul 2026 15:12:54 +0900 Subject: [PATCH 27/98] =?UTF-8?q?feat:=20=ED=99=94=EB=A9=B4=20=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=EC=84=A0=ED=83=9D=20=EC=9D=B4=EB=AF=B8=EC=A7=80=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/assets/image/theme/theme_auto.png | Bin 0 -> 11674 bytes src/assets/image/theme/theme_dark.png | Bin 0 -> 5680 bytes src/assets/image/theme/theme_light.png | Bin 0 -> 5450 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/assets/image/theme/theme_auto.png create mode 100644 src/assets/image/theme/theme_dark.png create mode 100644 src/assets/image/theme/theme_light.png diff --git a/src/assets/image/theme/theme_auto.png b/src/assets/image/theme/theme_auto.png new file mode 100644 index 0000000000000000000000000000000000000000..7cbbed6f8d066e7e2e03ef9bd877d587aa9242b7 GIT binary patch literal 11674 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 literal 0 HcmV?d00001 diff --git a/src/assets/image/theme/theme_light.png b/src/assets/image/theme/theme_light.png new file mode 100644 index 0000000000000000000000000000000000000000..b537f549d739da51b3df026a9da9416ab7d930f0 GIT binary patch literal 5450 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 Date: Wed, 1 Jul 2026 15:13:25 +0900 Subject: [PATCH 28/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=99=9C=EB=8F=99=20=EB=8F=99=EC=95=84?= =?UTF-8?q?=EB=A6=AC=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ActiveClubList.tsx | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/components/mypage/ActiveClubList.tsx diff --git a/src/components/mypage/ActiveClubList.tsx b/src/components/mypage/ActiveClubList.tsx new file mode 100644 index 000000000..e19d9d6b2 --- /dev/null +++ b/src/components/mypage/ActiveClubList.tsx @@ -0,0 +1,67 @@ +import Link from 'next/link'; +import { Avatar, AvatarImage, AvatarFallback, Tag, Icon } from '@/components/ui'; +import { cn } from '@/lib/cn'; +import type { ClubDto } from '@/types/mypage'; +import { SettingIcon } from '@/assets/icons'; + +interface ActiveClubListProps extends React.HTMLAttributes { + clubs?: ClubDto[]; + clubId: string; +} + +function ActiveClubList({ clubs = [], clubId, className, ...props }: ActiveClubListProps) { + return ( +
+

+ 사용 중인 프로필 {clubs.length} +

+
+ {clubs.map((club) => ( + + ))} + +
+
+ ); +} + +interface ActiveClubCardProps { + club: ClubDto; +} + +function ActiveClubCard({ club }: ActiveClubCardProps) { + return ( +
+ + + + +
+ {club.name} + {club.description && ( + {club.description} + )} + + {club.name} + +
+
+ ); +} + +function ProfileManageCard({ clubId }: { clubId: string }) { + return ( + + + 프로필 관리 + + ); +} + +export { ActiveClubList, type ActiveClubListProps }; From dd1baa5daea25aff68ddece9b03c8d50b182daaa Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:14:21 +0900 Subject: [PATCH 29/98] =?UTF-8?q?feat:=20=ED=99=94=EB=A9=B4=20=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=EC=84=A0=ED=83=9D=20=EB=AA=A8=EB=8B=AC=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/components/mypage/ThemeModeModal.tsx | 150 +++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/components/mypage/ThemeModeModal.tsx diff --git a/src/components/mypage/ThemeModeModal.tsx b/src/components/mypage/ThemeModeModal.tsx new file mode 100644 index 000000000..2d0240e59 --- /dev/null +++ b/src/components/mypage/ThemeModeModal.tsx @@ -0,0 +1,150 @@ +'use client'; + +import Image, { type StaticImageData } from 'next/image'; +import { CheckIcon, DeleteIcon } from '@/assets/icons'; +import ThemeAutoImage from '@/assets/image/theme/theme_auto.png'; +import ThemeDarkImage from '@/assets/image/theme/theme_dark.png'; +import ThemeLightImage from '@/assets/image/theme/theme_light.png'; +import { Button, Dialog, DialogContent, DialogTitle, Icon } from '@/components/ui'; +import { cn } from '@/lib/cn'; + +type ThemeMode = 'auto' | 'light' | 'dark'; + +interface ThemeModeModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + selectedMode: ThemeMode; + onSelectMode: (mode: ThemeMode) => void; + onConfirm: () => void; + disabled?: boolean; +} + +const THEME_OPTIONS: { + value: ThemeMode; + label: string; + previewImage: StaticImageData; +}[] = [ + { + value: 'auto', + label: '자동', + previewImage: ThemeAutoImage, + }, + { + value: 'light', + label: '라이트', + previewImage: ThemeLightImage, + }, + { + value: 'dark', + label: '다크', + previewImage: ThemeDarkImage, + }, +]; + +function ThemePreviewCard({ + selected, + option, +}: { + selected: boolean; + option: (typeof THEME_OPTIONS)[number]; +}) { + return ( +
+
+ {`${option.label} +
+ + + {option.label} + + + +
+ ); +} + +function ThemeModeModal({ + open, + onOpenChange, + selectedMode, + onSelectMode, + onConfirm, + disabled = false, +}: ThemeModeModalProps) { + return ( + + +
+ 화면 모드 + +
+ +
+ {THEME_OPTIONS.map((option) => { + const selected = selectedMode === option.value; + + return ( + + ); + })} +
+ +
+ + +
+
+
+ ); +} + +export { ThemeModeModal, type ThemeModeModalProps }; From 05026b11fcec3fc3b14e7e939bf3f8bf3482c0bd Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:14:34 +0900 Subject: [PATCH 30/98] =?UTF-8?q?feat:=20=ED=83=88=ED=87=B4=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EB=AA=A8=EB=8B=AC=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/WithdrawConfirmDialog.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/components/mypage/WithdrawConfirmDialog.tsx diff --git a/src/components/mypage/WithdrawConfirmDialog.tsx b/src/components/mypage/WithdrawConfirmDialog.tsx new file mode 100644 index 000000000..bf3b46090 --- /dev/null +++ b/src/components/mypage/WithdrawConfirmDialog.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { AlertDialog, AlertDialogAction, AlertDialogCancel } from '@/components/ui'; + +interface WithdrawConfirmDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onConfirm?: () => void; +} + +function WithdrawConfirmDialog({ open, onOpenChange, onConfirm }: WithdrawConfirmDialogProps) { + const handleConfirm = () => { + onConfirm?.(); + onOpenChange(false); + }; + + return ( + + 탈퇴하기 + 취소 + + ); +} + +export { WithdrawConfirmDialog, type WithdrawConfirmDialogProps }; From 053f33042850aea15dce4abfefef4d4cbc70375b Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:14:44 +0900 Subject: [PATCH 31/98] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=ED=99=95=EC=9D=B8=20=EB=AA=A8=EB=8B=AC=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/LogoutConfirmDialog.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/components/mypage/LogoutConfirmDialog.tsx diff --git a/src/components/mypage/LogoutConfirmDialog.tsx b/src/components/mypage/LogoutConfirmDialog.tsx new file mode 100644 index 000000000..73acdb6dc --- /dev/null +++ b/src/components/mypage/LogoutConfirmDialog.tsx @@ -0,0 +1,27 @@ +'use client'; + +import { useLogout } from '@/hooks'; +import { AlertDialog, AlertDialogAction, AlertDialogCancel } from '@/components/ui'; + +interface LogoutConfirmDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +function LogoutConfirmDialog({ open, onOpenChange }: LogoutConfirmDialogProps) { + const handleLogout = useLogout(); + + return ( + + 로그아웃 + 취소 + + ); +} + +export { LogoutConfirmDialog, type LogoutConfirmDialogProps }; From 21316a7125100c124351544146a9e071e69805a0 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:15:13 +0900 Subject: [PATCH 32/98] =?UTF-8?q?feat:=20=ED=99=9C=EB=8F=99=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=8A=A4=EC=BC=88=EB=A0=88=ED=86=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/mypage/activity/loading.tsx | 5 +++++ .../skeleton/MyPageActivitySkeleton.tsx | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/activity/loading.tsx create mode 100644 src/components/mypage/skeleton/MyPageActivitySkeleton.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/activity/loading.tsx b/src/app/(private)/[clubId]/(main)/mypage/activity/loading.tsx new file mode 100644 index 000000000..2e92c3513 --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/activity/loading.tsx @@ -0,0 +1,5 @@ +import { MyPageActivitySkeleton } from '@/components/mypage/skeleton'; + +export default function Loading() { + return ; +} diff --git a/src/components/mypage/skeleton/MyPageActivitySkeleton.tsx b/src/components/mypage/skeleton/MyPageActivitySkeleton.tsx new file mode 100644 index 000000000..2081702c4 --- /dev/null +++ b/src/components/mypage/skeleton/MyPageActivitySkeleton.tsx @@ -0,0 +1,20 @@ +import { Skeleton } from '@/components/ui'; +import { ClubInfoCardSkeleton } from './MyPageContentSkeletons'; + +function MyPageActivitySkeleton() { + return ( +
+
+ +
+ +
+ {Array.from({ length: 3 }).map((_, index) => ( + + ))} +
+
+ ); +} + +export { MyPageActivitySkeleton }; From 6bda710984c5ff28b1f94d7d73eeaf50594c4fb8 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:17:41 +0900 Subject: [PATCH 33/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=8A=A4=EC=BC=88=EB=A0=88=ED=86=A4=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=20=ED=99=94=EB=A9=B4=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../skeleton/MyPageContentSkeletons.tsx | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/components/mypage/skeleton/MyPageContentSkeletons.tsx b/src/components/mypage/skeleton/MyPageContentSkeletons.tsx index 168a5ce77..1718c8d03 100644 --- a/src/components/mypage/skeleton/MyPageContentSkeletons.tsx +++ b/src/components/mypage/skeleton/MyPageContentSkeletons.tsx @@ -2,11 +2,38 @@ import { Skeleton } from '@/components/ui'; function ProfileSectionSkeleton() { return ( -
- -
- - +
+ +
+
+ +
+ + +
+
+
+
+ + +
+ + +
+
+ +
+
+
+ + +
+
+
+ + +
+
); From b889a5533b60eb536a0da94a792f0ec292dbc00b Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:18:27 +0900 Subject: [PATCH 34/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B8=B0=EC=A1=B4=20=EC=BD=98=ED=85=90?= =?UTF-8?q?=EC=B8=A0=20=EA=B5=AC=EC=A1=B0=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/MyPageContent.tsx | 130 ++++-------------------- 1 file changed, 18 insertions(+), 112 deletions(-) diff --git a/src/components/mypage/MyPageContent.tsx b/src/components/mypage/MyPageContent.tsx index 2b59da9af..cc11e4d61 100644 --- a/src/components/mypage/MyPageContent.tsx +++ b/src/components/mypage/MyPageContent.tsx @@ -1,130 +1,36 @@ 'use client'; import { useParams } from 'next/navigation'; -import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbPage } from '@/components/ui'; import { cn } from '@/lib/cn'; import { useMyPageQueries } from '@/hooks/queries/mypage/useMyPageQueries'; -import { InfoCard } from './InfoCard'; -import { InfoSection } from './InfoSection'; import { ProfileSection } from './ProfileSection'; -import { SupportListItem } from './SupportListItem'; -import { ThemeToggle } from './ThemeToggle'; -import { MyPageDropdownMenu } from './MyPageDropdownMenu'; -import { ClubInfoCard } from './ClubInfoCard'; -import { ProfileSectionSkeleton, InfoCardSkeleton, ClubInfoCardSkeleton } from './skeleton'; +import { ProfileSectionSkeleton } from './skeleton'; +import { ActiveClubList } from './ActiveClubList'; type MyPageContentProps = React.HTMLAttributes; function MyPageContent({ className, ...props }: MyPageContentProps) { const { clubId } = useParams<{ clubId: string }>(); - const [{ data: me, isPending: isMePending }, { data: clubs, isPending: isClubsPending }] = - useMyPageQueries(clubId); + const [{ data: me }, { data: clubs }] = useMyPageQueries(clubId); const displayName = me?.name ?? ''; return ( -
+

프로필

+ {me ? ( + + ) : ( + )} - {...props} - > - {/* PageNavigation */} -
- - - - My - - - - -
-

My

- -
-
- - {/* Main Content */} -
- {/* 프로필 */} - {me ? ( - - ) : ( - - )} - - {/* 개인정보 */} - -
- {me && !isMePending ? ( - <> - - - - ) : ( - <> - - - - )} -
-
- - {/* 활동정보 */} - -
- {clubs && !isClubsPending - ? clubs.map((club) => ) - : Array.from({ length: 1 }).map((_, index) => )} -
-
- - {/* 서비스 설정 */} - -
-
- 모드 설정 - 다크/라이트 모드 -
- -
-
- - {/* 고객지원 */} - -
- - - -
-
-
+
); } From cdb4709c7bbccbf22cdbb33e8e525f6ca1d9a1c2 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:18:48 +0900 Subject: [PATCH 35/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EC=8A=A4=EC=BC=88=EB=A0=88=ED=86=A4=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 --- .../(main)/mypage/profiles/loading.tsx | 5 ++ .../skeleton/ProfileManagementSkeleton.tsx | 57 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/profiles/loading.tsx create mode 100644 src/components/mypage/skeleton/ProfileManagementSkeleton.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/profiles/loading.tsx b/src/app/(private)/[clubId]/(main)/mypage/profiles/loading.tsx new file mode 100644 index 000000000..6c7de46f7 --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/profiles/loading.tsx @@ -0,0 +1,5 @@ +import { ProfileManagementSkeleton } from '@/components/mypage/skeleton/ProfileManagementSkeleton'; + +export default function Loading() { + return ; +} diff --git a/src/components/mypage/skeleton/ProfileManagementSkeleton.tsx b/src/components/mypage/skeleton/ProfileManagementSkeleton.tsx new file mode 100644 index 000000000..0b2e0b908 --- /dev/null +++ b/src/components/mypage/skeleton/ProfileManagementSkeleton.tsx @@ -0,0 +1,57 @@ +import { Skeleton } from '@/components/ui'; + +function ProfileManagementCardSkeleton() { + return ( +
+
+ +
+ + +
+ +
+ +
+ {Array.from({ length: 2 }).map((_, index) => ( +
+
+ + +
+ +
+ ))} +
+
+ ); +} + +function ProfileManagementSkeleton() { + return ( +
+
+ +
+ + +
+
+ +
+ +
+ {Array.from({ length: 3 }).map((_, index) => ( + + ))} +
+ +
+
+ ); +} + +export { ProfileManagementSkeleton }; From 5912780862846d8b8fa2f36fc1566c09ca9d1462 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:19:01 +0900 Subject: [PATCH 36/98] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=20=EC=8A=A4=EC=BC=88=EB=A0=88=ED=86=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/mypage/sessions/loading.tsx | 5 +++ .../skeleton/MyPageSessionsSkeleton.tsx | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/sessions/loading.tsx create mode 100644 src/components/mypage/skeleton/MyPageSessionsSkeleton.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/sessions/loading.tsx b/src/app/(private)/[clubId]/(main)/mypage/sessions/loading.tsx new file mode 100644 index 000000000..0f9a11a9c --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/sessions/loading.tsx @@ -0,0 +1,5 @@ +import { MyPageSessionsSkeleton } from '@/components/mypage/skeleton/MyPageSessionsSkeleton'; + +export default function Loading() { + return ; +} diff --git a/src/components/mypage/skeleton/MyPageSessionsSkeleton.tsx b/src/components/mypage/skeleton/MyPageSessionsSkeleton.tsx new file mode 100644 index 000000000..1a923ccaf --- /dev/null +++ b/src/components/mypage/skeleton/MyPageSessionsSkeleton.tsx @@ -0,0 +1,36 @@ +import { Skeleton } from '@/components/ui'; + +function MyPageSessionsSkeleton() { + return ( +
+
+ +
+ +
+
+ +
+ {Array.from({ length: 4 }).map((_, index) => ( +
+
+ + +
+ +
+ +
+ + + +
+
+
+ ))} +
+
+ ); +} + +export { MyPageSessionsSkeleton }; From 7bc9197113ba34957b4c2349dde6ca927055080d Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:19:16 +0900 Subject: [PATCH 37/98] =?UTF-8?q?feat:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=8A=A4=EC=BC=88=EB=A0=88=ED=86=A4=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 --- .../(main)/mypage/settings/loading.tsx | 5 ++ .../skeleton/MyPageSettingsSkeleton.tsx | 58 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/settings/loading.tsx create mode 100644 src/components/mypage/skeleton/MyPageSettingsSkeleton.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/settings/loading.tsx b/src/app/(private)/[clubId]/(main)/mypage/settings/loading.tsx new file mode 100644 index 000000000..546d5a5cd --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/settings/loading.tsx @@ -0,0 +1,5 @@ +import { MyPageSettingsSkeleton } from '@/components/mypage/skeleton'; + +export default function Loading() { + return ; +} diff --git a/src/components/mypage/skeleton/MyPageSettingsSkeleton.tsx b/src/components/mypage/skeleton/MyPageSettingsSkeleton.tsx new file mode 100644 index 000000000..d62b33f31 --- /dev/null +++ b/src/components/mypage/skeleton/MyPageSettingsSkeleton.tsx @@ -0,0 +1,58 @@ +import { Skeleton } from '@/components/ui'; + +function SettingsRowSkeleton({ trailing = 'arrow' }: { trailing?: 'arrow' | 'button' }) { + return ( +
+ + {trailing === 'button' ? ( + + ) : ( + + )} +
+ ); +} + +function MyPageSettingsSkeleton() { + return ( +
+ + +
+ +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + +
+
+
+ ); +} + +export { MyPageSettingsSkeleton }; From 99cc6b687d4e685a5da7ba2569c4ec87a000087b Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:19:47 +0900 Subject: [PATCH 38/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=8A=A4=EC=BC=88=EB=A0=88=ED=86=A4=20exp?= =?UTF-8?q?ort=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/index.ts | 13 +++++++++++++ src/components/mypage/skeleton/index.ts | 2 ++ 2 files changed, 15 insertions(+) diff --git a/src/components/mypage/index.ts b/src/components/mypage/index.ts index 59c39b863..b895a557d 100644 --- a/src/components/mypage/index.ts +++ b/src/components/mypage/index.ts @@ -1,10 +1,21 @@ export { MyPageContent, type MyPageContentProps } from './MyPageContent'; +export { MyPageActivityContent, type MyPageActivityContentProps } from './MyPageActivityContent'; +export { MyPagePostsContent, type MyPagePostsContentProps } from './MyPagePostsContent'; +export { MyPageSessionsContent, type MyPageSessionsContentProps } from './MyPageSessionsContent'; +export { MyPageSettingsContent, type MyPageSettingsContentProps } from './MyPageSettingsContent'; +export { + ProfileManagementContent, + type ProfileManagementContentProps, +} from './ProfileManagementContent'; +export { ActiveClubList, type ActiveClubListProps } from './ActiveClubList'; export { ProfileSection, type ProfileSectionProps } from './ProfileSection'; export { InfoSection, type InfoSectionProps } from './InfoSection'; export { InfoCard, type InfoCardProps, type InfoCardItem } from './InfoCard'; export { ThemeToggle, type ThemeToggleProps } from './ThemeToggle'; +export { LogoutConfirmDialog, type LogoutConfirmDialogProps } from './LogoutConfirmDialog'; export { SupportListItem, type SupportListItemProps } from './SupportListItem'; export { MyPageDropdownMenu } from './MyPageDropdownMenu'; +export { WithdrawConfirmDialog, type WithdrawConfirmDialogProps } from './WithdrawConfirmDialog'; export { EditProfileContent, type EditProfileContentProps } from './edit'; export { FormField, type FormFieldProps } from './FormField'; export { SearchSelect, type SearchSelectProps } from './SearchSelect'; @@ -12,6 +23,8 @@ export { ClubInfoCard, type ClubInfoCardProps } from './ClubInfoCard'; export { SetCardinalModal, type SetCardinalModalProps } from './SetCardinalModal'; export { MyPageSkeleton, + MyPageActivitySkeleton, + MyPageSettingsSkeleton, ProfileSectionSkeleton, InfoCardSkeleton, ClubInfoCardSkeleton, diff --git a/src/components/mypage/skeleton/index.ts b/src/components/mypage/skeleton/index.ts index bc98380f2..57647a9e7 100644 --- a/src/components/mypage/skeleton/index.ts +++ b/src/components/mypage/skeleton/index.ts @@ -1,4 +1,6 @@ export { MyPageSkeleton } from './MyPageSkeleton'; +export { MyPageActivitySkeleton } from './MyPageActivitySkeleton'; +export { MyPageSettingsSkeleton } from './MyPageSettingsSkeleton'; export { ProfileSectionSkeleton, InfoCardSkeleton, From f964045d0ba57697916d9b40752c0d174f65c9d2 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:20:22 +0900 Subject: [PATCH 39/98] =?UTF-8?q?refactor:=20=EC=B6=9C=EC=84=9D=20?= =?UTF-8?q?=EC=84=B8=EC=85=98=20=EB=82=A0=EC=A7=9C=20=ED=8F=AC=EB=A7=B7=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/shared/date.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/utils/shared/date.ts b/src/utils/shared/date.ts index fe719ecc6..79035a62c 100644 --- a/src/utils/shared/date.ts +++ b/src/utils/shared/date.ts @@ -100,3 +100,23 @@ export function formatTimeDisplay(timeStr: string): string { const displayHour = h % 12 || 12; return `${period} ${displayHour}:${String(m).padStart(2, '0')}`; } + +export function formatSessionDateParts(start: string): { + day: string; + weekday: string; + timeLabel: string; +} { + const date = new Date(start); + const month = date.getMonth() + 1; + const day = date.getDate(); + const hours = date.getHours(); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const period = hours < 12 ? '오전' : '오후'; + const displayHour = hours % 12 || 12; + + return { + day: String(day), + weekday: DAY_META[date.getDay()].en.toUpperCase(), + timeLabel: `${month}월 ${day}일 ${period} ${displayHour}:${minutes}`, + }; +} From a7a79918c896f90b943064ce226ccea25fc60a6c Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:21:00 +0900 Subject: [PATCH 40/98] =?UTF-8?q?feat:=20=ED=99=9C=EB=8F=99=20=EB=8F=99?= =?UTF-8?q?=EC=95=84=EB=A6=AC=20=EC=B9=B4=EB=93=9C=20UI=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/components/mypage/ClubInfoCard.tsx | 75 ++++++++++++++++---------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/src/components/mypage/ClubInfoCard.tsx b/src/components/mypage/ClubInfoCard.tsx index 0623c3e0e..1ed30c0f1 100644 --- a/src/components/mypage/ClubInfoCard.tsx +++ b/src/components/mypage/ClubInfoCard.tsx @@ -1,13 +1,13 @@ 'use client'; import { useState } from 'react'; -import Link from 'next/link'; import dynamic from 'next/dynamic'; import { cn } from '@/lib/cn'; -import { Avatar, AvatarFallback, AvatarImage, Button, Divider, Icon, Tag } from '@/components/ui'; -import { ExitIcon, PeopleIcon } from '@/assets/icons'; +import { Avatar, AvatarFallback, AvatarImage, Divider, Icon, Tag } from '@/components/ui'; +import { PeopleIcon } from '@/assets/icons'; import type { ClubDto } from '@/types/mypage'; import { cardClass } from './InfoCard'; +import { MyPageDropdownMenu } from './LeaveClubDropdownMenu'; const SetCardinalModal = dynamic(() => import('./SetCardinalModal').then((m) => ({ default: m.SetCardinalModal })), @@ -22,9 +22,9 @@ function ClubInfoCard({ club, className }: ClubInfoCardProps) { return ( <> -
+
-
+
- - - +
-
- {club.name} +
+ {club.name} {club.description && ( -

{club.description}

+

+ {club.description} +

)} +
+ + {club.memberCount}명 +
+
+ + {club.profileImageUrl && ( + + )} + + +
+ {club.name} + {club.description && ( +

+ {club.description} +

+ )} +
+
-
- - {club.memberCount}명 -
-
+
활동 기수
{club.cardinals.length > 0 ? ( @@ -71,14 +88,18 @@ function ClubInfoCard({ club, className }: ClubInfoCardProps) { ))}
) : ( - +
+ + 미설정 + + +
)}
From b2f715da230115cbcd9c58450ed7f98aa44433f1 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:21:24 +0900 Subject: [PATCH 41/98] =?UTF-8?q?feat:=20=EC=83=88=EB=A1=9C=EC=9A=B4=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EA=B4=80=EB=A0=A8=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=ED=83=80=EC=9E=85=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/mypage.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/types/mypage.ts b/src/types/mypage.ts index 1d53f5e85..df1fc744e 100644 --- a/src/types/mypage.ts +++ b/src/types/mypage.ts @@ -17,6 +17,16 @@ export interface MyMember { bio: string | null; } +export interface ProfileData { + name: string; + bio?: string; + profileImageUrl?: string; + tel?: string; + email?: string; + school?: string; + department?: string; +} + export interface ClubDto { id: string; name: string; From 77b48112647e94cef68a81119fae6b02026cead3 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:21:48 +0900 Subject: [PATCH 42/98] =?UTF-8?q?feat:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=9A=A9=20=EA=B3=A0=EA=B0=9D=EC=A7=80?= =?UTF-8?q?=EC=9B=90=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=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/components/mypage/InfoSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/mypage/InfoSection.tsx b/src/components/mypage/InfoSection.tsx index 3f29c86ae..3e5c43d04 100644 --- a/src/components/mypage/InfoSection.tsx +++ b/src/components/mypage/InfoSection.tsx @@ -9,7 +9,7 @@ interface InfoSectionProps extends React.HTMLAttributes { function InfoSection({ title, children, className, ...props }: InfoSectionProps) { return ( -
+

{title}

{children}
From 6f83c34d805b68b7075209fc4570cbc09c6f00d4 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:22:03 +0900 Subject: [PATCH 43/98] =?UTF-8?q?feat:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=9A=A9=20=EA=B3=A0=EA=B0=9D=EC=A7=80?= =?UTF-8?q?=EC=9B=90=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20UI=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/components/mypage/SupportListItem.tsx | 68 ++++++++++++++++++----- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/src/components/mypage/SupportListItem.tsx b/src/components/mypage/SupportListItem.tsx index 4295a94ab..9f71f2661 100644 --- a/src/components/mypage/SupportListItem.tsx +++ b/src/components/mypage/SupportListItem.tsx @@ -1,6 +1,6 @@ import Link from 'next/link'; import { cn } from '@/lib/cn'; -import { Icon } from '@/components/ui'; +import { Button, Icon } from '@/components/ui'; import { ArrowRightIcon, CopyIcon } from '@/assets/icons'; import { toastSuccess } from '@/stores/useToastStore'; @@ -10,6 +10,7 @@ interface SupportListItemProps extends React.HTMLAttributes { variant?: 'link' | 'copy'; href?: string; copyText?: string; + layout?: 'card' | 'row'; } function SupportListItem({ @@ -18,30 +19,68 @@ function SupportListItem({ variant = 'link', href, copyText, + layout = 'card', className, + onClick, ...props }: SupportListItemProps) { + const hasSubContent = !!description || variant === 'copy'; + const isRow = layout === 'row'; + const content = ( <> -
- {title} -
- {description &&

{description}

} - {variant === 'copy' && ( - +
+
+ {title} + {!isRow && hasSubContent && ( +
+ {description &&

{description}

} + {variant === 'copy' && ( + + )} +
)}
-
- - {variant === 'link' && ( - + + {isRow ? ( + variant === 'copy' ? ( + + ) : ( + + ) + ) : ( + + {variant === 'link' && ( + + )} + )} - +
); const baseClass = cn( - 'bg-container-neutral relative flex w-full cursor-pointer flex-col items-start rounded-lg p-400 text-left', + isRow + ? 'relative flex w-full cursor-pointer flex-col items-start px-400 py-300 text-left' + : 'bg-container-neutral relative flex w-full cursor-pointer flex-col items-start rounded-lg px-400 py-300 text-left', className, ); @@ -53,11 +92,12 @@ function SupportListItem({ ); } - const handleClick = () => { + const handleClick = (e: React.MouseEvent) => { if (copyText) { navigator.clipboard.writeText(copyText); toastSuccess('복사되었습니다!'); } + onClick?.(e); }; return ( From 6111ef29731a9918d93f8372a267c482e19f6a0f Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:22:43 +0900 Subject: [PATCH 44/98] =?UTF-8?q?feat:=20=EC=84=A4=EC=A0=95=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=20=EA=B3=B5=EC=9A=A9=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/LeaveClubDropdownMenu.tsx | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/components/mypage/LeaveClubDropdownMenu.tsx diff --git a/src/components/mypage/LeaveClubDropdownMenu.tsx b/src/components/mypage/LeaveClubDropdownMenu.tsx new file mode 100644 index 000000000..c84ee0f5d --- /dev/null +++ b/src/components/mypage/LeaveClubDropdownMenu.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { useState } from 'react'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + Icon, +} from '@/components/ui'; +import { AdminMeatballIcon } from '@/assets/icons/admin'; +import { WithdrawConfirmDialog } from './WithdrawConfirmDialog'; + +function MyPageDropdownMenu() { + const [withdrawOpen, setWithdrawOpen] = useState(false); + + return ( + <> + + + + + + setWithdrawOpen(true)}> + 탈퇴하기 + + + + + + + ); +} + +export { MyPageDropdownMenu }; From 8c11d7e8191c94fdc55e7a6937a2deb78400f8b6 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:23:02 +0900 Subject: [PATCH 45/98] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=8A=A4=EC=BC=88=EB=A0=88=ED=86=A4?= =?UTF-8?q?=EC=9D=84=20=EC=B5=9C=EC=8B=A0=20=ED=8E=B8=EC=A7=91=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/skeleton/EditProfileSkeleton.tsx | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/src/components/mypage/skeleton/EditProfileSkeleton.tsx b/src/components/mypage/skeleton/EditProfileSkeleton.tsx index 6093d36a7..d6f3fa4e8 100644 --- a/src/components/mypage/skeleton/EditProfileSkeleton.tsx +++ b/src/components/mypage/skeleton/EditProfileSkeleton.tsx @@ -7,39 +7,51 @@ function EditProfileSkeleton({ className, ...props }: EditProfileSkeletonProps) return (
-
-
- - - +
+ +
+
-
-
- - -
-
-
- - - - -
-
- - - - +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+
From 6f63160177ca98a5bdb4909e52bfbe471d2109ed Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:24:02 +0900 Subject: [PATCH 46/98] =?UTF-8?q?fix:=20=EA=B8=B0=EC=88=98=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=AA=A8=EB=8B=AC=20=EC=A0=9C=EB=AA=A9=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/components/mypage/SetCardinalModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/mypage/SetCardinalModal/index.tsx b/src/components/mypage/SetCardinalModal/index.tsx index 089d647c4..e7bf32ab4 100644 --- a/src/components/mypage/SetCardinalModal/index.tsx +++ b/src/components/mypage/SetCardinalModal/index.tsx @@ -88,7 +88,7 @@ function SetCardinalModal({ open, onOpenChange, club, onSave }: SetCardinalModal ]; const { title, body, footer } = STEPS[step - 1]; - const overline = `'${club.name}' 활동 기수 설정`; + const overline = `${club.name}`; return ( From 3d43eb611efa5bdd84841feb8b8522cacf99f918 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 15:24:32 +0900 Subject: [PATCH 47/98] =?UTF-8?q?fix:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B0=9C=EC=9D=B8=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=8B=AC=20=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/MyPageDropdownMenu.tsx | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/components/mypage/MyPageDropdownMenu.tsx b/src/components/mypage/MyPageDropdownMenu.tsx index 5017a49f1..09064ad79 100644 --- a/src/components/mypage/MyPageDropdownMenu.tsx +++ b/src/components/mypage/MyPageDropdownMenu.tsx @@ -19,7 +19,6 @@ import { useLogout } from '@/hooks'; function MyPageDropdownMenu() { const { clubId } = useParams<{ clubId: string }>(); - // const [withdrawOpen, setWithdrawOpen] = useState(false); const [logoutOpen, setLogoutOpen] = useState(false); const handleLogout = useLogout(); @@ -40,26 +39,9 @@ function MyPageDropdownMenu() { 개인정보 수정 - setLogoutOpen(true)}>로그아웃 - {/* - setWithdrawOpen(true)}> - 서비스 탈퇴 - */} - {/* TODO: "탈퇴하기"와 "로그아웃" 버튼에 onClick 핸들러 */} - {/* - 탈퇴하기 - 취소 - */} - Date: Wed, 1 Jul 2026 15:25:06 +0900 Subject: [PATCH 48/98] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=ED=99=94=EB=A9=B4=EC=97=90=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=ED=8E=B8=EC=A7=91=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ProfileSection.tsx | 146 +++++++++++++++++++---- 1 file changed, 126 insertions(+), 20 deletions(-) diff --git a/src/components/mypage/ProfileSection.tsx b/src/components/mypage/ProfileSection.tsx index 20487da14..3ee4f4f99 100644 --- a/src/components/mypage/ProfileSection.tsx +++ b/src/components/mypage/ProfileSection.tsx @@ -1,30 +1,136 @@ -import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui'; +'use client'; + +import { useParams, useRouter } from 'next/navigation'; +import { Icon } from '@/components/ui'; +import { ArrowRightIcon, PhoneIcon, MailIcon } from '@/assets/icons'; import { cn } from '@/lib/cn'; +import type { ProfileData } from '@/types/mypage'; +import { ProfileBackgroundImageEditor } from './edit/ProfileBackgroundImageEditor'; +import { ProfileImageEditor } from './edit/ProfileImageEditor'; +import { MyPageDropdownMenu } from './MyPageDropdownMenu'; -interface ProfileSectionProps extends React.HTMLAttributes { - name: string; - bio?: string; - profileImageUrl?: string; +interface ProfileSectionProps extends React.HTMLAttributes, ProfileData { + postCount?: number; + sessionCount?: number; } -function ProfileSection({ name, bio, profileImageUrl, className, ...props }: ProfileSectionProps) { +const ProfileSection = ({ + name, + bio, + profileImageUrl, + tel, + email, + school, + department, + postCount = 0, + sessionCount = 0, + className, + ...props +}: ProfileSectionProps) => { + const router = useRouter(); + const { clubId } = useParams<{ clubId: string }>(); + const schoolLabel = [school, department].filter(Boolean).join(' · '); + return ( -
- - - - -
-

{name}

- {bio &&

{bio}

} +
+ + +
+
+ + +
+ +
+
+ +
+
+

{name}

+ {schoolLabel && ( + + {schoolLabel} + + )} +
+ {bio &&

{bio}

} + {(tel || email) && ( +
+ {tel && ( + + + {tel} + + )} + {email && ( + + + {email} + + )} +
+ )} +
+ +
+ + +
+ + +
); -} +}; export { ProfileSection, type ProfileSectionProps }; From b5c791bdd354d6235278ffafa05261830aa76ae0 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:16:34 +0900 Subject: [PATCH 49/98] =?UTF-8?q?feat:=20=ED=99=94=EB=A9=B4=20=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=EC=84=A0=ED=83=9D=20=EA=B3=B5=EC=9A=A9=20=EC=85=80?= =?UTF-8?q?=EB=A0=89=ED=84=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ThemeModeSelector.tsx | 115 ++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/components/mypage/ThemeModeSelector.tsx diff --git a/src/components/mypage/ThemeModeSelector.tsx b/src/components/mypage/ThemeModeSelector.tsx new file mode 100644 index 000000000..54addcf46 --- /dev/null +++ b/src/components/mypage/ThemeModeSelector.tsx @@ -0,0 +1,115 @@ +'use client'; + +import Image, { type StaticImageData } from 'next/image'; +import { CheckIcon } from '@/assets/icons'; +import ThemeAutoImage from '@/assets/image/theme/theme_auto.png'; +import ThemeDarkImage from '@/assets/image/theme/theme_dark.png'; +import ThemeLightImage from '@/assets/image/theme/theme_light.png'; +import { Icon } from '@/components/ui'; +import { cn } from '@/lib/cn'; + +type ThemeMode = 'auto' | 'light' | 'dark'; + +const THEME_MODE_OPTIONS: { + value: ThemeMode; + label: string; + previewImage: StaticImageData; +}[] = [ + { value: 'auto', label: '자동', previewImage: ThemeAutoImage }, + { value: 'light', label: '라이트', previewImage: ThemeLightImage }, + { value: 'dark', label: '다크', previewImage: ThemeDarkImage }, +]; + +interface ThemeModeSelectorProps { + selectedMode: ThemeMode; + onSelectMode: (mode: ThemeMode) => void; + disabled?: boolean; + className?: string; + layout?: 'default' | 'mobile-page'; +} + +function ThemePreviewCard({ + selected, + option, +}: { + selected: boolean; + option: (typeof THEME_MODE_OPTIONS)[number]; +}) { + return ( +
+
+ {`${option.label} +
+ + + {option.label} + + + +
+ ); +} + +function ThemeModeSelector({ + selectedMode, + onSelectMode, + disabled = false, + className, + layout = 'default', +}: ThemeModeSelectorProps) { + const renderOption = (option: (typeof THEME_MODE_OPTIONS)[number]) => { + const selected = selectedMode === option.value; + + return ( + + ); + }; + + if (layout === 'mobile-page') { + const [autoOption, ...restOptions] = THEME_MODE_OPTIONS; + + return ( +
+
{renderOption(autoOption)}
+
+ {restOptions.map((option) => renderOption(option))} +
+
+ ); + } + + return ( +
+ {THEME_MODE_OPTIONS.map((option) => renderOption(option))} +
+ ); +} + +export { ThemeModeSelector, type ThemeModeSelectorProps, type ThemeMode }; From 7e371e93bcc3ade1d3582b9d766792e39d7fe4f9 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:16:49 +0900 Subject: [PATCH 50/98] =?UTF-8?q?feat:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=84=EC=9A=A9=20=ED=99=94=EB=A9=B4=20=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/mypage/settings/theme/page.tsx | 7 ++ .../mypage/ThemeModePageContent.tsx | 68 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/settings/theme/page.tsx create mode 100644 src/components/mypage/ThemeModePageContent.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/settings/theme/page.tsx b/src/app/(private)/[clubId]/(main)/mypage/settings/theme/page.tsx new file mode 100644 index 000000000..91725e0c2 --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/settings/theme/page.tsx @@ -0,0 +1,7 @@ +import { ThemeModePageContent } from '@/components/mypage/ThemeModePageContent'; + +export const dynamic = 'force-dynamic'; + +export default function ThemeModePage() { + return ; +} diff --git a/src/components/mypage/ThemeModePageContent.tsx b/src/components/mypage/ThemeModePageContent.tsx new file mode 100644 index 000000000..5359b2224 --- /dev/null +++ b/src/components/mypage/ThemeModePageContent.tsx @@ -0,0 +1,68 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { BackIcon } from '@/assets/icons'; +import { useThemeStore } from '@/stores/theme-store'; +import { Button, Icon } from '@/components/ui'; +import { cn } from '@/lib/cn'; +import { ThemeModeSelector, type ThemeMode } from './ThemeModeSelector'; + +type ThemeModePageContentProps = React.HTMLAttributes; + +function ThemeModePageContent({ className, ...props }: ThemeModePageContentProps) { + const router = useRouter(); + const mode = useThemeStore((state) => state.mode); + const hasHydrated = useThemeStore((state) => state.hasHydrated); + const setMode = useThemeStore((state) => state.setMode); + const [selectedMode, setSelectedMode] = useState(mode); + + useEffect(() => { + setSelectedMode(mode); + }, [mode]); + + const handleConfirm = () => { + setMode(selectedMode); + router.back(); + }; + + return ( +
+
+ +

화면 모드

+
+ + + +
+ +
+
+ ); +} + +export { ThemeModePageContent, type ThemeModePageContentProps }; From 41ff31e8fa57e61fd1c77b3d2c9576205761a0a4 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:17:04 +0900 Subject: [PATCH 51/98] =?UTF-8?q?feat:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=97=90=EC=84=9C=20=EB=AA=A8=EB=B0=94?= =?UTF-8?q?=EC=9D=BC=20=ED=99=94=EB=A9=B4=20=EB=AA=A8=EB=93=9C=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=B6=84=EA=B8=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/MyPageSettingsContent.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/mypage/MyPageSettingsContent.tsx b/src/components/mypage/MyPageSettingsContent.tsx index 243c75808..4d43ac9b3 100644 --- a/src/components/mypage/MyPageSettingsContent.tsx +++ b/src/components/mypage/MyPageSettingsContent.tsx @@ -1,7 +1,8 @@ 'use client'; import { useState } from 'react'; -import { useParams } from 'next/navigation'; +import { useParams, useRouter } from 'next/navigation'; +import { useMediaQuery } from '@/hooks'; import { cn } from '@/lib/cn'; import { useThemeStore } from '@/stores/theme-store'; import { InfoSection } from './InfoSection'; @@ -20,7 +21,9 @@ const THEME_MODE_LABELS: Record = { }; function MyPageSettingsContent({ className, ...props }: MyPageSettingsContentProps) { + const router = useRouter(); const { clubId } = useParams<{ clubId: string }>(); + const isBelowTablet = useMediaQuery('(max-width: 695.98px)'); const mode = useThemeStore((state) => state.mode); const hasHydrated = useThemeStore((state) => state.hasHydrated); const setMode = useThemeStore((state) => state.setMode); @@ -30,6 +33,10 @@ function MyPageSettingsContent({ className, ...props }: MyPageSettingsContentPro const [withdrawOpen, setWithdrawOpen] = useState(false); const handleOpenThemeModal = () => { + if (isBelowTablet) { + router.push(`/${clubId}/mypage/settings/theme`); + return; + } setSelectedThemeMode(mode); setIsThemeModalOpen(true); }; @@ -69,7 +76,7 @@ function MyPageSettingsContent({ className, ...props }: MyPageSettingsContentPro />
-
+
@@ -88,7 +95,7 @@ function MyPageSettingsContent({ className, ...props }: MyPageSettingsContentPro />
-
+
From 5160c70a7da1229c24a4bdc7fee01ae07963579c Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:17:15 +0900 Subject: [PATCH 52/98] =?UTF-8?q?refactor:=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=AA=A8=EB=93=9C=20=EB=AA=A8=EB=8B=AC=EC=97=90=20=EA=B3=B5?= =?UTF-8?q?=EC=9A=A9=20=EC=85=80=EB=A0=89=ED=84=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ThemeModeModal.tsx | 96 ++---------------------- 1 file changed, 7 insertions(+), 89 deletions(-) diff --git a/src/components/mypage/ThemeModeModal.tsx b/src/components/mypage/ThemeModeModal.tsx index 2d0240e59..1c523fe64 100644 --- a/src/components/mypage/ThemeModeModal.tsx +++ b/src/components/mypage/ThemeModeModal.tsx @@ -1,14 +1,8 @@ 'use client'; -import Image, { type StaticImageData } from 'next/image'; -import { CheckIcon, DeleteIcon } from '@/assets/icons'; -import ThemeAutoImage from '@/assets/image/theme/theme_auto.png'; -import ThemeDarkImage from '@/assets/image/theme/theme_dark.png'; -import ThemeLightImage from '@/assets/image/theme/theme_light.png'; +import { DeleteIcon } from '@/assets/icons'; import { Button, Dialog, DialogContent, DialogTitle, Icon } from '@/components/ui'; -import { cn } from '@/lib/cn'; - -type ThemeMode = 'auto' | 'light' | 'dark'; +import { ThemeModeSelector, type ThemeMode } from './ThemeModeSelector'; interface ThemeModeModalProps { open: boolean; @@ -19,69 +13,6 @@ interface ThemeModeModalProps { disabled?: boolean; } -const THEME_OPTIONS: { - value: ThemeMode; - label: string; - previewImage: StaticImageData; -}[] = [ - { - value: 'auto', - label: '자동', - previewImage: ThemeAutoImage, - }, - { - value: 'light', - label: '라이트', - previewImage: ThemeLightImage, - }, - { - value: 'dark', - label: '다크', - previewImage: ThemeDarkImage, - }, -]; - -function ThemePreviewCard({ - selected, - option, -}: { - selected: boolean; - option: (typeof THEME_OPTIONS)[number]; -}) { - return ( -
-
- {`${option.label} -
- - - {option.label} - - - -
- ); -} - function ThemeModeModal({ open, onOpenChange, @@ -104,24 +35,11 @@ function ThemeModeModal({
- -
- {THEME_OPTIONS.map((option) => { - const selected = selectedMode === option.value; - - return ( - - ); - })} -
+
-

개인정보 수정

+

개인정보 수정

- +
@@ -152,13 +150,24 @@ function EditProfileContent({ className, schools, majors, ...props }: EditProfil type="submit" size="lg" disabled={isPending || !isValid || !hasChanges} - className="w-full" + className="tablet:flex hidden w-full" > {isPending ? '수정 중...' : '수정 완료'}
+
+ +
Date: Wed, 1 Jul 2026 17:18:59 +0900 Subject: [PATCH 56/98] =?UTF-8?q?fix:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B3=B5=EC=82=AC=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/SupportListItem.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/mypage/SupportListItem.tsx b/src/components/mypage/SupportListItem.tsx index 9f71f2661..bd02f2de9 100644 --- a/src/components/mypage/SupportListItem.tsx +++ b/src/components/mypage/SupportListItem.tsx @@ -1,6 +1,6 @@ import Link from 'next/link'; import { cn } from '@/lib/cn'; -import { Button, Icon } from '@/components/ui'; +import { Icon } from '@/components/ui'; import { ArrowRightIcon, CopyIcon } from '@/assets/icons'; import { toastSuccess } from '@/stores/useToastStore'; @@ -50,9 +50,9 @@ function SupportListItem({ {isRow ? ( variant === 'copy' ? ( - + ) : ( Date: Wed, 1 Jul 2026 17:19:14 +0900 Subject: [PATCH 57/98] =?UTF-8?q?fix:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=A0=84=EC=B2=B4=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EB=B0=98=EC=9D=91=ED=98=95=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(private)/[clubId]/(main)/mypage/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(private)/[clubId]/(main)/mypage/layout.tsx b/src/app/(private)/[clubId]/(main)/mypage/layout.tsx index 367d5c335..2abe250e7 100644 --- a/src/app/(private)/[clubId]/(main)/mypage/layout.tsx +++ b/src/app/(private)/[clubId]/(main)/mypage/layout.tsx @@ -6,7 +6,7 @@ interface MyPageLayoutProps { export default function MyPageLayout({ children }: MyPageLayoutProps) { return ( -
+
From 867795d0b3fb8819ffbc742eeba1d0e065d5a039 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:19:42 +0900 Subject: [PATCH 58/98] =?UTF-8?q?fix:=20=ED=99=9C=EB=8F=99=20=EB=8F=99?= =?UTF-8?q?=EC=95=84=EB=A6=AC=20=EC=B9=B4=EB=93=9C=20=EB=AA=A8=EB=B0=94?= =?UTF-8?q?=EC=9D=BC=20=EA=B0=80=EB=A1=9C=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ActiveClubList.tsx | 79 +++++++++++++++++------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/src/components/mypage/ActiveClubList.tsx b/src/components/mypage/ActiveClubList.tsx index e19d9d6b2..170d74e0c 100644 --- a/src/components/mypage/ActiveClubList.tsx +++ b/src/components/mypage/ActiveClubList.tsx @@ -15,10 +15,13 @@ function ActiveClubList({ clubs = [], clubId, className, ...props }: ActiveClubL className={cn('bg-container-neutral w-full overflow-hidden rounded-lg p-450', className)} {...props} > -

+

사용 중인 프로필 {clubs.length}

-
+
{clubs.map((club) => ( ))} @@ -34,33 +37,63 @@ interface ActiveClubCardProps { function ActiveClubCard({ club }: ActiveClubCardProps) { return ( -
- - - - -
- {club.name} - {club.description && ( - {club.description} - )} - - {club.name} - + <> +
+ + + + +
+ {club.name} + {club.description && ( + + {club.description} + + )} + + {club.name} + +
-
+
+ + + + +
+ {club.name} + {club.description && ( + + {club.description} + + )} + + {club.name} + +
+
+ ); } function ProfileManageCard({ clubId }: { clubId: string }) { return ( - - - 프로필 관리 - + <> + + + 프로필 관리 + + + + 프로필 관리 + + ); } From 08a39cf4454a69a51031feb41aabe657f3fff8a1 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:24:43 +0900 Subject: [PATCH 59/98] =?UTF-8?q?refactor:=20=ED=99=9C=EB=8F=99=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EB=B0=98=EC=9D=91=ED=98=95=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=97=B4=20=EA=B3=84=EC=82=B0=20=EB=A1=9C=EC=A7=81=20=ED=9B=85?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/index.ts | 1 + src/hooks/useResponsiveGridColumns.ts | 48 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/hooks/useResponsiveGridColumns.ts diff --git a/src/hooks/index.ts b/src/hooks/index.ts index b3fac0526..a8a50f8a0 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -24,4 +24,5 @@ export { useProgressAnimation } from './useProgressAnimation'; export { useCodeHighlight } from './useCodeHighlight'; export { useMonthNavigator } from './useMonthNavigator'; export { useMediaQuery } from './useMediaQuery'; +export { useResponsiveGridColumns } from './useResponsiveGridColumns'; export { useResetKeyOnOpen } from './useResetKeyOnOpen'; diff --git a/src/hooks/useResponsiveGridColumns.ts b/src/hooks/useResponsiveGridColumns.ts new file mode 100644 index 000000000..4477110c0 --- /dev/null +++ b/src/hooks/useResponsiveGridColumns.ts @@ -0,0 +1,48 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; + +interface UseResponsiveGridColumnsOptions { + itemCount: number; + minColumnWidth: number; + gap: number; +} + +function useResponsiveGridColumns({ + itemCount, + minColumnWidth, + gap, +}: UseResponsiveGridColumnsOptions) { + const containerRef = useRef(null); + const [columnCount, setColumnCount] = useState(1); + + useEffect(() => { + const element = containerRef.current; + if (!element) return; + + const measureColumnCount = () => { + const containerWidth = element.clientWidth; + const nextColumnCount = Math.max( + 1, + Math.min(itemCount, Math.floor((containerWidth + gap) / (minColumnWidth + gap))), + ); + + setColumnCount(nextColumnCount); + }; + + measureColumnCount(); + + const resizeObserver = new ResizeObserver(measureColumnCount); + resizeObserver.observe(element); + + return () => resizeObserver.disconnect(); + }, [gap, itemCount, minColumnWidth]); + + return { + containerRef, + columnCount, + isSingleColumn: columnCount <= 1, + }; +} + +export { useResponsiveGridColumns, type UseResponsiveGridColumnsOptions }; From 01b87796dbdadd528e9258bb58dac19975f669a7 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:25:03 +0900 Subject: [PATCH 60/98] =?UTF-8?q?fix:=20=ED=99=9C=EB=8F=99=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20UI=20=ED=99=95=EC=9D=B8=EC=9A=A9=20=EB=AA=A9?= =?UTF-8?q?=EC=97=85=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/MyPageActivityContent.tsx | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/components/mypage/MyPageActivityContent.tsx b/src/components/mypage/MyPageActivityContent.tsx index ebce55219..ca6a14b69 100644 --- a/src/components/mypage/MyPageActivityContent.tsx +++ b/src/components/mypage/MyPageActivityContent.tsx @@ -1,35 +1,47 @@ 'use client'; import { useParams } from 'next/navigation'; +import { useResponsiveGridColumns } from '@/hooks'; import { cn } from '@/lib/cn'; import { useMyPageQueries } from '@/hooks/queries/mypage/useMyPageQueries'; import { ClubInfoCard } from './ClubInfoCard'; type MyPageActivityContentProps = React.HTMLAttributes; +const TABLET_CARD_WIDTH = 314; +const TABLET_CARD_GAP = 12; function MyPageActivityContent({ className, ...props }: MyPageActivityContentProps) { const { clubId } = useParams<{ clubId: string }>(); const [, { data: clubs = [] }] = useMyPageQueries(clubId); - // TODO: UI 확인용 mock 삭제 필요 - const mockUnsetCardinalClub = clubs[0] - ? { - ...clubs[0], - id: 'mock-unset-cardinal', - name: `${clubs[0].name}`, - cardinals: [], - } - : null; + const { containerRef, columnCount, isSingleColumn } = useResponsiveGridColumns({ + itemCount: clubs.length, + minColumnWidth: TABLET_CARD_WIDTH, + gap: TABLET_CARD_GAP, + }); + const mobileCardClassName = + clubs.length === 1 + ? 'w-full shrink-0 tablet:w-full' + : cn('w-[250px] shrink-0', isSingleColumn ? 'tablet:w-full' : 'tablet:w-[314px]'); return (

활동정보

-
- {clubs.map((club) => ( - - ))} - {mockUnsetCardinalClub && ( - - )} +
+
+ {clubs.map((club) => ( + + ))} +
); From 5e1a00f404a636d99ba1eaae2d706c97769695b8 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:25:48 +0900 Subject: [PATCH 61/98] =?UTF-8?q?feat:=20=ED=81=B4=EB=9F=BD=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=B9=B4=EB=93=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EC=97=90=EC=84=9C=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=9E=85=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/components/mypage/ClubInfoCard.tsx | 35 ++++++++++++++++++-------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/components/mypage/ClubInfoCard.tsx b/src/components/mypage/ClubInfoCard.tsx index 1ed30c0f1..ec3785624 100644 --- a/src/components/mypage/ClubInfoCard.tsx +++ b/src/components/mypage/ClubInfoCard.tsx @@ -3,11 +3,12 @@ import { useState } from 'react'; import dynamic from 'next/dynamic'; import { cn } from '@/lib/cn'; -import { Avatar, AvatarFallback, AvatarImage, Divider, Icon, Tag } from '@/components/ui'; -import { PeopleIcon } from '@/assets/icons'; +import { Avatar, AvatarFallback, AvatarImage, Button, Divider, Icon, Tag } from '@/components/ui'; +import { ArrowRightIcon, PeopleIcon } from '@/assets/icons'; import type { ClubDto } from '@/types/mypage'; import { cardClass } from './InfoCard'; import { MyPageDropdownMenu } from './LeaveClubDropdownMenu'; +import { useRouter } from 'next/navigation'; const SetCardinalModal = dynamic(() => import('./SetCardinalModal').then((m) => ({ default: m.SetCardinalModal })), @@ -18,11 +19,12 @@ interface ClubInfoCardProps extends React.HTMLAttributes { } function ClubInfoCard({ club, className }: ClubInfoCardProps) { + const router = useRouter(); const [modalOpen, setModalOpen] = useState(false); return ( <> -
+
@@ -42,9 +44,11 @@ function ClubInfoCard({ club, className }: ClubInfoCardProps) {
- {club.name} + + {club.name} + {club.description && ( -

+

{club.description}

)} @@ -54,9 +58,9 @@ function ClubInfoCard({ club, className }: ClubInfoCardProps) {
{club.profileImageUrl && ( @@ -64,9 +68,11 @@ function ClubInfoCard({ club, className }: ClubInfoCardProps) {
- {club.name} + + {club.name} + {club.description && ( -

+

{club.description}

)} @@ -77,7 +83,7 @@ function ClubInfoCard({ club, className }: ClubInfoCardProps) {
- 활동 기수 + 활동 기수
{club.cardinals.length > 0 ? (
@@ -103,6 +109,15 @@ function ClubInfoCard({ club, className }: ClubInfoCardProps) { )}
+
From 96d130dc369479bea96baaa97c8756c3a8517e11 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:26:17 +0900 Subject: [PATCH 62/98] =?UTF-8?q?fix:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=9D=B8=ED=8F=AC=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=83=80=EC=9D=B4=ED=8F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/InfoSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/mypage/InfoSection.tsx b/src/components/mypage/InfoSection.tsx index 3e5c43d04..c15ea0992 100644 --- a/src/components/mypage/InfoSection.tsx +++ b/src/components/mypage/InfoSection.tsx @@ -10,7 +10,7 @@ interface InfoSectionProps extends React.HTMLAttributes { function InfoSection({ title, children, className, ...props }: InfoSectionProps) { return (
-

{title}

+

{title}

{children}
); From 597711a101e66bf00c22464a3c2fd2a26c16b8bf Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:26:28 +0900 Subject: [PATCH 63/98] =?UTF-8?q?fix:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20=EB=93=9C=EB=A1=AD=EB=8B=A4=EC=9A=B4=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=83=89=EC=83=81=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/components/mypage/LeaveClubDropdownMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/mypage/LeaveClubDropdownMenu.tsx b/src/components/mypage/LeaveClubDropdownMenu.tsx index c84ee0f5d..f948348e6 100644 --- a/src/components/mypage/LeaveClubDropdownMenu.tsx +++ b/src/components/mypage/LeaveClubDropdownMenu.tsx @@ -23,7 +23,7 @@ function MyPageDropdownMenu() { className="flex size-[24px] cursor-pointer items-center justify-center rounded-sm" aria-label="더보기" > - + From 224a4dcbd02e6676466eee5e0c050a59a5d28cef Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:26:39 +0900 Subject: [PATCH 64/98] =?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=AA=A8=EB=B0=94=EC=9D=BC=20=EB=B0=98?= =?UTF-8?q?=EC=9D=91=ED=98=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/MyPageContent.tsx | 80 +++++++++++++++++++------ 1 file changed, 63 insertions(+), 17 deletions(-) diff --git a/src/components/mypage/MyPageContent.tsx b/src/components/mypage/MyPageContent.tsx index cc11e4d61..afdcf278a 100644 --- a/src/components/mypage/MyPageContent.tsx +++ b/src/components/mypage/MyPageContent.tsx @@ -6,6 +6,9 @@ import { useMyPageQueries } from '@/hooks/queries/mypage/useMyPageQueries'; import { ProfileSection } from './ProfileSection'; import { ProfileSectionSkeleton } from './skeleton'; import { ActiveClubList } from './ActiveClubList'; +import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from '../ui'; +import { MyPageActivityContent } from './MyPageActivityContent'; +import { MyPageSettingsContent } from './MyPageSettingsContent'; type MyPageContentProps = React.HTMLAttributes; @@ -15,23 +18,66 @@ function MyPageContent({ className, ...props }: MyPageContentProps) { const displayName = me?.name ?? ''; return ( -
-

프로필

- {me ? ( - - ) : ( - - )} - -
+ <> + +
+
+ + + + + 프로필 + + + + +

프로필

+
+
+
+ {me ? ( + + ) : ( + + )} + +
+
+ +
+
+ +
+
+
+ ); } From 6c08078e97d3b1f348b04ce70240d18eab5c96a4 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:26:55 +0900 Subject: [PATCH 65/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ProfileSection.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/mypage/ProfileSection.tsx b/src/components/mypage/ProfileSection.tsx index 3ee4f4f99..2855e27cb 100644 --- a/src/components/mypage/ProfileSection.tsx +++ b/src/components/mypage/ProfileSection.tsx @@ -63,7 +63,7 @@ const ProfileSection = ({

{name}

{schoolLabel && ( - + {schoolLabel} )} @@ -95,6 +95,11 @@ const ProfileSection = ({ )}
)} + {schoolLabel && ( + + {schoolLabel} + + )}
@@ -103,13 +108,13 @@ const ProfileSection = ({ className="flex flex-1 flex-col items-center justify-center gap-2 py-200" onClick={() => router.push(`/${clubId}/mypage/posts`)} > - + 내가 쓴 글
- {postCount}개 + {postCount}개
@@ -119,13 +124,13 @@ const ProfileSection = ({ className="flex flex-1 flex-col items-center justify-center gap-2 py-200" onClick={() => router.push(`/${clubId}/mypage/sessions`)} > - + 출석한 세션
- {sessionCount}개 + {sessionCount}개
From 255c609105bad58ac165e1096355cab054cb33f0 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:27:05 +0900 Subject: [PATCH 66/98] =?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=EA=B0=80=20=EC=93=B4=20=EA=B8=80?= =?UTF-8?q?=20=EB=B0=98=EC=9D=91=ED=98=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/MyPagePostsContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/mypage/MyPagePostsContent.tsx b/src/components/mypage/MyPagePostsContent.tsx index a15270443..0c6d88599 100644 --- a/src/components/mypage/MyPagePostsContent.tsx +++ b/src/components/mypage/MyPagePostsContent.tsx @@ -13,7 +13,7 @@ function MyPagePostsContent({ className, ...props }: MyPagePostsContentProps) { return (
-
+
-

내가 쓴 글

+

내가 쓴 글

From bf95af4ba4bd241459dc8a7904533342b4ea909b Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:27:13 +0900 Subject: [PATCH 67/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=B6=9C=EC=84=9D=20=EC=84=B8=EC=85=98=20?= =?UTF-8?q?=EB=B0=98=EC=9D=91=ED=98=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/MyPageSessionsContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/mypage/MyPageSessionsContent.tsx b/src/components/mypage/MyPageSessionsContent.tsx index 556c6bb3c..1430abbd6 100644 --- a/src/components/mypage/MyPageSessionsContent.tsx +++ b/src/components/mypage/MyPageSessionsContent.tsx @@ -29,7 +29,7 @@ function MyPageSessionsContent({ return (
-
+
-

출석한 세션

+

출석한 세션

From f8a47919c4de65e7b71c89c389f3136d23506041 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Wed, 1 Jul 2026 17:27:27 +0900 Subject: [PATCH 68/98] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EB=B0=98=EC=9D=91=ED=98=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ProfileCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/mypage/ProfileCard.tsx b/src/components/mypage/ProfileCard.tsx index e44a12ace..34c7178c5 100644 --- a/src/components/mypage/ProfileCard.tsx +++ b/src/components/mypage/ProfileCard.tsx @@ -59,7 +59,7 @@ function ProfileCard({ profile, clubs, availableProfiles }: ProfileCardProps) { - {club.name} + {club.name}
-
-

프로필 관리

-

+

+

프로필 관리

+

같은 프로필을 사용하는 동아리에는 프로필 사진, 이름, 소개글이 함께 반영돼요.

+
+ 같은 프로필을 사용하는 동아리에는 프로필 사진, 이름, 소개글이 함께 반영돼요. +

사용 중인 프로필 {clubs.length}

-
+
{clubs.map((club) => ( Date: Thu, 2 Jul 2026 12:45:42 +0900 Subject: [PATCH 70/98] =?UTF-8?q?fix:=20=EB=AA=A8=EB=B0=94=EC=9D=BC?= =?UTF-8?q?=EC=97=90=EC=84=9C=EB=A7=8C=20=EB=B3=B4=EC=9D=B4=EB=8A=94=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20=ED=97=A4=EB=8D=94?= =?UTF-8?q?=EC=97=90=20=ED=8C=A8=EB=94=A9=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/MyPagePostsContent.tsx | 2 +- src/components/mypage/MyPageSessionsContent.tsx | 2 +- src/components/mypage/ThemeModePageContent.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/mypage/MyPagePostsContent.tsx b/src/components/mypage/MyPagePostsContent.tsx index 0c6d88599..ec410680c 100644 --- a/src/components/mypage/MyPagePostsContent.tsx +++ b/src/components/mypage/MyPagePostsContent.tsx @@ -13,7 +13,7 @@ function MyPagePostsContent({ className, ...props }: MyPagePostsContentProps) { return (
-
+
+ )} - )} - - - )} -
- )} - + + )} +
+ )} + + )}
diff --git a/src/constants/mypage/routes.ts b/src/constants/mypage/routes.ts new file mode 100644 index 000000000..c5b4c84c0 --- /dev/null +++ b/src/constants/mypage/routes.ts @@ -0,0 +1,41 @@ +const MYPAGE_PROFILE_SUB_PATHS = [ + '/mypage/profiles', + '/mypage/posts', + '/mypage/sessions', + '/mypage/edit', +] as const; + +const MYPAGE_SETTINGS_SUB_PATHS = ['/mypage/settings'] as const; + +const MOBILE_HEADER_HIDDEN_MYPAGE_PATHS = [ + '/mypage/profiles', + '/mypage/posts', + '/mypage/sessions', + '/mypage/edit', + '/mypage/settings/theme', + '/mypage/profiles/add', +] as const; + +function isMyPageProfileSubPath(pathname: string, clubId: string) { + return MYPAGE_PROFILE_SUB_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}`)); +} + +function isMyPageSettingsSubPath(pathname: string, clubId: string) { + return MYPAGE_SETTINGS_SUB_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}/`)); +} + +function shouldHideMobileHeaderOnMyPage(pathname: string, clubId: string) { + return ( + MOBILE_HEADER_HIDDEN_MYPAGE_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}`)) || + new RegExp(`^/${clubId}/mypage/profiles/[^/]+/edit$`).test(pathname) + ); +} + +export { + MYPAGE_PROFILE_SUB_PATHS, + MYPAGE_SETTINGS_SUB_PATHS, + MOBILE_HEADER_HIDDEN_MYPAGE_PATHS, + isMyPageProfileSubPath, + isMyPageSettingsSubPath, + shouldHideMobileHeaderOnMyPage, +}; From cb6ea3eca35eec2e8acb10a9cd81e35b0a4d2fbb Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Thu, 2 Jul 2026 13:01:27 +0900 Subject: [PATCH 73/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/profiles/[profileId]/edit/page.tsx | 7 ++ .../mypage/EditProfilePageContent.tsx | 100 ++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/profiles/[profileId]/edit/page.tsx create mode 100644 src/components/mypage/EditProfilePageContent.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/profiles/[profileId]/edit/page.tsx b/src/app/(private)/[clubId]/(main)/mypage/profiles/[profileId]/edit/page.tsx new file mode 100644 index 000000000..e7fd05abd --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/profiles/[profileId]/edit/page.tsx @@ -0,0 +1,7 @@ +import { EditProfilePageContent } from '@/components/mypage/EditProfilePageContent'; + +export const dynamic = 'force-dynamic'; + +export default function EditProfilePage() { + return ; +} diff --git a/src/components/mypage/EditProfilePageContent.tsx b/src/components/mypage/EditProfilePageContent.tsx new file mode 100644 index 000000000..017e05ba3 --- /dev/null +++ b/src/components/mypage/EditProfilePageContent.tsx @@ -0,0 +1,100 @@ +'use client'; + +import { useMemo, useState } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { BackIcon } from '@/assets/icons'; +import { Button, Icon } from '@/components/ui'; +import { useEditProfileForm } from '@/hooks'; +import { useMyPageQueries } from '@/hooks/queries/mypage/useMyPageQueries'; +import { DeleteProfileDialog } from './DeleteProfileDialog'; +import { EditProfileFormContent } from './EditProfileFormContent'; +import { ProfileManagementSkeleton } from './skeleton/ProfileManagementSkeleton'; + +function EditProfilePageContent() { + const router = useRouter(); + const { clubId, profileId } = useParams<{ clubId: string; profileId: string }>(); + const [, { data: clubs = [], isPending }] = useMyPageQueries(clubId); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + + const profile = useMemo(() => clubs.find((club) => club.id === profileId), [clubs, profileId]); + + const { + control, + resetToProfile, + name, + formState: { errors }, + } = useEditProfileForm(profile ?? null); + + const handleClose = () => { + resetToProfile(); + setIsDeleteDialogOpen(false); + router.back(); + }; + + const handleDelete = () => { + setIsDeleteDialogOpen(false); + handleClose(); + }; + + if (isPending || !profile) { + return ; + } + + return ( + <> +
+
+ +

프로필 수정

+
+ + +
+ +
+ + +
+ + +
+
+ + + + ); +} + +export { EditProfilePageContent }; From fdcc68c50c8f34a0a3728862bcc1ace4660e371b Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Thu, 2 Jul 2026 13:01:47 +0900 Subject: [PATCH 74/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=ED=8F=BC=20=EC=BB=A8=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/EditProfileFormContent.tsx | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/components/mypage/EditProfileFormContent.tsx diff --git a/src/components/mypage/EditProfileFormContent.tsx b/src/components/mypage/EditProfileFormContent.tsx new file mode 100644 index 000000000..5685493c3 --- /dev/null +++ b/src/components/mypage/EditProfileFormContent.tsx @@ -0,0 +1,102 @@ +'use client'; + +import type { Control, FieldErrors } from 'react-hook-form'; +import { Controller, useWatch } from 'react-hook-form'; +import { FormFieldWrapper } from '@/components/auth/hub'; +import { MYPAGE_PROFILE_TEXT_MAX_LENGTH } from '@/constants/mypage/profile'; +import { Input } from '@/components/ui'; +import type { EditProfileFormData } from '@/lib/schemas/editProfile'; +import { CharacterCountRow } from './CharacterCountRow'; +import { ProfileBackgroundImageEditor } from './edit/ProfileBackgroundImageEditor'; +import { ProfileImageEditor } from './edit/ProfileImageEditor'; + +interface EditProfileFormContentProps { + control: Control>; + errors: FieldErrors>; + fallbackName: string; + profileImageUrl?: string; + className?: string; +} + +function EditProfileFormContent({ + control, + errors, + fallbackName, + profileImageUrl, + className, +}: EditProfileFormContentProps) { + const name = useWatch({ control, name: 'name' }) ?? ''; + + return ( +
+ + +
+ +
+ +
+ ( + +
+ + +
+
+ )} + /> + + ( + +
+ + +
+
+ )} + /> +
+
+ ); +} + +export { EditProfileFormContent, type EditProfileFormContentProps }; From 25ee0b0c2104c6dd8ef5f28b94ea39fc989e3419 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Thu, 2 Jul 2026 13:02:07 +0900 Subject: [PATCH 75/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=8B=AC=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=ED=95=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EB=B0=8F=20=ED=9B=85=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/EditProfileModal.tsx | 131 +++------------------ 1 file changed, 17 insertions(+), 114 deletions(-) diff --git a/src/components/mypage/EditProfileModal.tsx b/src/components/mypage/EditProfileModal.tsx index 4339f00bd..8dc40b1a2 100644 --- a/src/components/mypage/EditProfileModal.tsx +++ b/src/components/mypage/EditProfileModal.tsx @@ -1,28 +1,18 @@ 'use client'; -import { useEffect, useState } from 'react'; -import { Controller, useForm, useWatch } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; +import { useState } from 'react'; import { DeleteIcon } from '@/assets/icons'; -import { FormFieldWrapper } from '@/components/auth/hub'; import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, Button, Dialog, DialogContent, DialogTitle, Icon, - Input, } from '@/components/ui'; -import { editProfileSchema, type EditProfileFormData } from '@/lib/schemas/editProfile'; +import { useEditProfileForm } from '@/hooks'; import type { ClubDto } from '@/types/mypage'; -import { CharacterCountRow } from './CharacterCountRow'; -import { ProfileBackgroundImageEditor } from './edit/ProfileBackgroundImageEditor'; -import { ProfileImageEditor } from './edit/ProfileImageEditor'; - -const MAX_LENGTH = 30; +import { DeleteProfileDialog } from './DeleteProfileDialog'; +import { EditProfileFormContent } from './EditProfileFormContent'; interface EditProfileModalProps { open: boolean; @@ -31,36 +21,16 @@ interface EditProfileModalProps { } function EditProfileModal({ open, profile, onOpenChange }: EditProfileModalProps) { - const editProfileModalSchema = editProfileSchema.pick({ name: true, bio: true }); const { control, - reset, + resetToProfile, + name, formState: { errors }, - } = useForm>({ - resolver: zodResolver(editProfileModalSchema), - mode: 'onChange', - reValidateMode: 'onChange', - defaultValues: { - name: profile.name, - bio: profile.description, - }, - }); + } = useEditProfileForm(profile, open); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); - const name = useWatch({ control, name: 'name' }) ?? ''; - - useEffect(() => { - if (!open) return; - reset({ - name: profile.name, - bio: profile.description, - }); - }, [open, profile.description, profile.name, reset]); const handleClose = () => { - reset({ - name: profile.name, - bio: profile.description, - }); + resetToProfile(); setIsDeleteDialogOpen(false); onOpenChange(false); }; @@ -93,74 +63,12 @@ function EditProfileModal({ open, profile, onOpenChange }: EditProfileModalProps
-
- - -
- -
-
- -
- ( - -
- - -
-
- )} - /> - - ( - -
- - -
-
- )} - /> -
+
); } From c416d271b9784771159e6846080a916010de06b1 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Thu, 2 Jul 2026 13:02:21 +0900 Subject: [PATCH 76/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=98=EA=B8=B0=20=EB=AA=A8=EB=B0=94?= =?UTF-8?q?=EC=9D=BC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/mypage/profiles/add/page.tsx | 7 ++ .../mypage/AddProfilePageContent.tsx | 85 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/app/(private)/[clubId]/(main)/mypage/profiles/add/page.tsx create mode 100644 src/components/mypage/AddProfilePageContent.tsx diff --git a/src/app/(private)/[clubId]/(main)/mypage/profiles/add/page.tsx b/src/app/(private)/[clubId]/(main)/mypage/profiles/add/page.tsx new file mode 100644 index 000000000..43a87508b --- /dev/null +++ b/src/app/(private)/[clubId]/(main)/mypage/profiles/add/page.tsx @@ -0,0 +1,7 @@ +import { AddProfilePageContent } from '@/components/mypage/AddProfilePageContent'; + +export const dynamic = 'force-dynamic'; + +export default function AddProfilePage() { + return ; +} diff --git a/src/components/mypage/AddProfilePageContent.tsx b/src/components/mypage/AddProfilePageContent.tsx new file mode 100644 index 000000000..067b84192 --- /dev/null +++ b/src/components/mypage/AddProfilePageContent.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { BackIcon } from '@/assets/icons'; +import { useAddProfileFlow } from '@/hooks/mypage'; +import { Icon } from '@/components/ui'; +import { cn } from '@/lib/cn'; +import { StepOneContent } from './AddProfileModal/StepOneContent'; +import { StepTwoContent } from './AddProfileModal/StepTwoContent'; + +type AddProfilePageContentProps = React.HTMLAttributes; + +function AddProfilePageContent({ className, ...props }: AddProfilePageContentProps) { + const router = useRouter(); + const { + step, + setStep, + selectedClubIds, + control, + errors, + resetFlow, + handleToggleClub, + handleNext, + } = useAddProfileFlow(); + + const handleClose = () => { + resetFlow(); + router.back(); + }; + + const handleConfirm = () => { + handleClose(); + }; + + return ( +
+
+ +

프로필 추가하기

+
+ +
+ {Array.from({ length: 2 }).map((_, index) => ( +
+ ))} +
+ + {step === 1 ? ( + + ) : ( + setStep(1)} + onConfirm={handleConfirm} + mobileFixedFooter + /> + )} +
+ ); +} + +export { AddProfilePageContent, type AddProfilePageContentProps }; From fc9af0bfaec149cc3eea43c8a631554d3bba0bfc Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Thu, 2 Jul 2026 13:02:33 +0900 Subject: [PATCH 77/98] =?UTF-8?q?fix:=20=ED=8F=BC=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=EC=97=90=EC=84=9C=20label=20=EC=84=A0=ED=83=9D=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=9C=BC=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/components/auth/hub/FormFieldWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/auth/hub/FormFieldWrapper.tsx b/src/components/auth/hub/FormFieldWrapper.tsx index 38d601434..c0969ddc5 100644 --- a/src/components/auth/hub/FormFieldWrapper.tsx +++ b/src/components/auth/hub/FormFieldWrapper.tsx @@ -8,7 +8,7 @@ function FieldError({ message }: { message?: string }) { } interface FormFieldWrapperProps { - label: string; + label?: string; error?: string; children: React.ReactNode; } From 72aaa81c248bc7fad40059f3b9d6e0eb107534b1 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Thu, 2 Jul 2026 13:03:07 +0900 Subject: [PATCH 78/98] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=AA=A8=EB=8B=AC=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=9B=85=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/AddProfileModal.tsx | 45 ++++------------- src/hooks/mypage/useAddProfileFlow.ts | 59 +++++++++++++++++++++++ 2 files changed, 69 insertions(+), 35 deletions(-) create mode 100644 src/hooks/mypage/useAddProfileFlow.ts diff --git a/src/components/mypage/AddProfileModal.tsx b/src/components/mypage/AddProfileModal.tsx index 7ae0713c7..a8c276ea9 100644 --- a/src/components/mypage/AddProfileModal.tsx +++ b/src/components/mypage/AddProfileModal.tsx @@ -1,10 +1,7 @@ 'use client'; -import { useState } from 'react'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; +import { useAddProfileFlow } from '@/hooks/mypage'; import { Dialog, DialogContent } from '@/components/ui'; -import { editProfileSchema, type EditProfileFormData } from '@/lib/schemas/editProfile'; import { AddProfileModalHeader } from './AddProfileModal/Header'; import { StepOneContent } from './AddProfileModal/StepOneContent'; import { StepTwoContent } from './AddProfileModal/StepTwoContent'; @@ -15,48 +12,26 @@ interface AddProfileModalProps { } function AddProfileModal({ open, onOpenChange }: AddProfileModalProps) { - const [step, setStep] = useState(1); - const [selectedClubIds, setSelectedClubIds] = useState(['1', '2']); - const stepOneSchema = editProfileSchema.pick({ name: true, bio: true }); - const { + step, + setStep, + selectedClubIds, control, - reset, - trigger, - formState: { errors }, - } = useForm>({ - resolver: zodResolver(stepOneSchema), - mode: 'onChange', - reValidateMode: 'onChange', - defaultValues: { - name: '', - bio: '', - }, - }); + errors, + resetFlow, + handleToggleClub, + handleNext, + } = useAddProfileFlow(); const handleClose = () => { - setStep(1); - reset({ name: '', bio: '' }); - setSelectedClubIds(['1', '2']); + resetFlow(); onOpenChange(false); }; - const handleToggleClub = (clubId: string) => { - setSelectedClubIds((prev) => - prev.includes(clubId) ? prev.filter((id) => id !== clubId) : [...prev, clubId], - ); - }; - const handleConfirm = () => { handleClose(); }; - const handleNext = async () => { - const isValid = await trigger(); - if (!isValid) return; - setStep(2); - }; - return ( (['1', '2']); + const stepOneSchema = editProfileSchema.pick({ name: true, bio: true }); + + const { + control, + reset, + trigger, + formState: { errors }, + } = useForm>({ + resolver: zodResolver(stepOneSchema), + mode: 'onChange', + reValidateMode: 'onChange', + defaultValues: { + name: '', + bio: '', + }, + }); + + const resetFlow = () => { + setStep(1); + reset({ name: '', bio: '' }); + setSelectedClubIds(['1', '2']); + }; + + const handleToggleClub = (clubId: string) => { + setSelectedClubIds((prev) => + prev.includes(clubId) ? prev.filter((id) => id !== clubId) : [...prev, clubId], + ); + }; + + const handleNext = async () => { + const isValid = await trigger(); + if (!isValid) return false; + setStep(2); + return true; + }; + + return { + step, + setStep, + selectedClubIds, + control, + errors, + resetFlow, + handleToggleClub, + handleNext, + }; +} + +export { useAddProfileFlow }; From 08748f7f85251c3cf979952f2dbc92ad0f0a06d4 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Thu, 2 Jul 2026 13:03:36 +0900 Subject: [PATCH 79/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20dialog=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/DeleteProfileDialog.tsx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/components/mypage/DeleteProfileDialog.tsx diff --git a/src/components/mypage/DeleteProfileDialog.tsx b/src/components/mypage/DeleteProfileDialog.tsx new file mode 100644 index 000000000..f6a859be3 --- /dev/null +++ b/src/components/mypage/DeleteProfileDialog.tsx @@ -0,0 +1,26 @@ +'use client'; + +import { AlertDialog, AlertDialogAction, AlertDialogCancel } from '@/components/ui'; + +interface DeleteProfileDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onDelete: () => void; +} + +function DeleteProfileDialog({ open, onOpenChange, onDelete }: DeleteProfileDialogProps) { + return ( + + 삭제 + 취소 + + ); +} + +export { DeleteProfileDialog, type DeleteProfileDialogProps }; From 405fd16a8e330ae4066d3a2efd571590150544d3 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Thu, 2 Jul 2026 13:03:58 +0900 Subject: [PATCH 80/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=ED=8F=BC=20=ED=9B=85=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/mypage/useEditProfileForm.ts | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/hooks/mypage/useEditProfileForm.ts diff --git a/src/hooks/mypage/useEditProfileForm.ts b/src/hooks/mypage/useEditProfileForm.ts new file mode 100644 index 000000000..7a609f994 --- /dev/null +++ b/src/hooks/mypage/useEditProfileForm.ts @@ -0,0 +1,54 @@ +'use client'; + +import { useEffect } from 'react'; +import { useForm, useWatch } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { editProfileSchema, type EditProfileFormData } from '@/lib/schemas/editProfile'; + +interface EditProfileFormValuesSource { + name: string; + description: string; +} + +function useEditProfileForm(profile: EditProfileFormValuesSource | null, enabled = true) { + const editProfileFormSchema = editProfileSchema.pick({ name: true, bio: true }); + + const form = useForm>({ + resolver: zodResolver(editProfileFormSchema), + mode: 'onChange', + reValidateMode: 'onChange', + defaultValues: { + name: profile?.name ?? '', + bio: profile?.description ?? '', + }, + }); + + const { control, reset } = form; + const name = useWatch({ control, name: 'name' }) ?? ''; + + useEffect(() => { + if (!enabled || !profile) return; + + reset({ + name: profile.name, + bio: profile.description, + }); + }, [enabled, profile, reset]); + + const resetToProfile = () => { + if (!profile) return; + + reset({ + name: profile.name, + bio: profile.description, + }); + }; + + return { + ...form, + name, + resetToProfile, + }; +} + +export { useEditProfileForm, type EditProfileFormValuesSource }; From 804238505d22a8857a7b080e76ea2ee46ee92a18 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Thu, 2 Jul 2026 13:04:15 +0900 Subject: [PATCH 81/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=AA=A8=EB=B0=94=EC=9D=BC=EA=B3=BC=20=ED=83=9C?= =?UTF-8?q?=EB=B8=94=EB=A6=BF=20=EC=9D=B4=EC=83=81=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/ProfileManagementContent.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/mypage/ProfileManagementContent.tsx b/src/components/mypage/ProfileManagementContent.tsx index 3c51c1943..5f1f6f1e5 100644 --- a/src/components/mypage/ProfileManagementContent.tsx +++ b/src/components/mypage/ProfileManagementContent.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { useParams } from 'next/navigation'; +import { useMediaQuery } from '@/hooks'; import { Button, Icon } from '@/components/ui'; import { cn } from '@/lib/cn'; import { useMyPageQueries } from '@/hooks/queries/mypage/useMyPageQueries'; @@ -15,12 +16,22 @@ type ProfileManagementContentProps = React.HTMLAttributes; function ProfileManagementContent({ className, ...props }: ProfileManagementContentProps) { const router = useRouter(); const { clubId } = useParams<{ clubId: string }>(); + const isBelowTablet = useMediaQuery('(max-width: 695.98px)'); const [, { data: clubs = [] }] = useMyPageQueries(clubId); const [isAddModalOpen, setIsAddModalOpen] = useState(false); + const handleOpenAddProfile = () => { + if (isBelowTablet) { + router.push(`/${clubId}/mypage/profiles/add`); + return; + } + + setIsAddModalOpen(true); + }; + return (
-
+
From 9462cfde3d2af98618889f2d3b492062bcb75fe4 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Thu, 2 Jul 2026 13:07:34 +0900 Subject: [PATCH 86/98] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=201=EB=8B=A8=EA=B3=84=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=ED=95=98=EB=8B=A8=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EB=B0=8F=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B5=9C=EB=8C=80=20=EB=86=92=EC=9D=B4=20?= =?UTF-8?q?=EC=83=81=EC=88=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/AddProfileModal/StepOneContent.tsx | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/components/mypage/AddProfileModal/StepOneContent.tsx b/src/components/mypage/AddProfileModal/StepOneContent.tsx index c8740477f..e14b9511a 100644 --- a/src/components/mypage/AddProfileModal/StepOneContent.tsx +++ b/src/components/mypage/AddProfileModal/StepOneContent.tsx @@ -4,44 +4,51 @@ import type { Control, FieldErrors } from 'react-hook-form'; import { Controller, useWatch } from 'react-hook-form'; import { Button, DialogClose, Input } from '@/components/ui'; import { FormFieldWrapper } from '@/components/auth/hub'; +import { MYPAGE_PROFILE_TEXT_MAX_LENGTH } from '@/constants/mypage/profile'; +import { cn } from '@/lib/cn'; import type { EditProfileFormData } from '@/lib/schemas/editProfile'; import { CharacterCountRow } from '../CharacterCountRow'; import { ProfileBackgroundImageEditor } from '../edit/ProfileBackgroundImageEditor'; import { ProfileImageEditor } from '../edit/ProfileImageEditor'; -const MAX_LENGTH = 30; - interface StepOneContentProps { control: Control>; errors: FieldErrors>; onCancel: () => void; onNext: () => void; + cancelAsDialogClose?: boolean; + mobileFixedFooter?: boolean; } -function StepOneContent({ control, errors, onCancel, onNext }: StepOneContentProps) { +function StepOneContent({ + control, + errors, + onCancel, + onNext, + cancelAsDialogClose = true, + mobileFixedFooter = false, +}: StepOneContentProps) { const name = useWatch({ control, name: 'name' }) ?? ''; return ( <> - <> - + -
- -
- -
+
+ +
+
( - +
@@ -68,7 +75,7 @@ function StepOneContent({ control, errors, onCancel, onNext }: StepOneContentPro name="bio" control={control} render={({ field }) => ( - +
)} />
-
- - + + ) : ( + - + )} - -
- - + {activityItems.map((item, index) => ( + + {index > 0 &&
} + + + ))}
From 7d22fb6973ef323404cd2c69001eb5a9f676e882 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Mon, 6 Jul 2026 18:55:06 +0900 Subject: [PATCH 92/98] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=AF=B8=EB=A6=AC=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EB=A1=9C=EC=A7=81=20useImagePreview=20=ED=9B=85?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../edit/ProfileBackgroundImageEditor.tsx | 39 +++--------- .../mypage/edit/ProfileImageEditor.tsx | 34 ++-------- src/hooks/mypage/index.ts | 1 + src/hooks/mypage/useImagePreview.ts | 63 +++++++++++++++++++ 4 files changed, 80 insertions(+), 57 deletions(-) create mode 100644 src/hooks/mypage/useImagePreview.ts diff --git a/src/components/mypage/edit/ProfileBackgroundImageEditor.tsx b/src/components/mypage/edit/ProfileBackgroundImageEditor.tsx index 7ab1a7cea..d2fd26215 100644 --- a/src/components/mypage/edit/ProfileBackgroundImageEditor.tsx +++ b/src/components/mypage/edit/ProfileBackgroundImageEditor.tsx @@ -1,6 +1,5 @@ 'use client'; -import { useRef, useState } from 'react'; import Image from 'next/image'; import { CameraIcon } from '@/assets/icons'; import { @@ -11,12 +10,14 @@ import { DropdownMenuTrigger, Icon, } from '@/components/ui'; +import { useImagePreview } from '@/hooks/mypage'; import { cn } from '@/lib/cn'; interface ProfileBackgroundImageEditorProps { backgroundImageUrl?: string; onFileChange?: (file: File) => void; onResetImage?: () => void; + priority?: boolean; className?: string; imageClassName?: string; triggerClassName?: string; @@ -28,39 +29,18 @@ function ProfileBackgroundImageEditor({ backgroundImageUrl, onFileChange, onResetImage, + priority = false, className, imageClassName, triggerClassName, triggerIconClassName, triggerIconSize = 16, }: ProfileBackgroundImageEditorProps) { - const fileInputRef = useRef(null); - const previewUrlRef = useRef(null); - const [previewUrl, setPreviewUrl] = useState(null); - const [isReset, setIsReset] = useState(false); - - const handleChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - if (previewUrlRef.current) URL.revokeObjectURL(previewUrlRef.current); - const url = URL.createObjectURL(file); - previewUrlRef.current = url; - setPreviewUrl(url); - setIsReset(false); - onFileChange?.(file); - }; - - const handleReset = () => { - if (previewUrlRef.current) { - URL.revokeObjectURL(previewUrlRef.current); - previewUrlRef.current = null; - } - setPreviewUrl(null); - setIsReset(true); - onResetImage?.(); - }; - - const displayUrl = isReset ? null : (previewUrl ?? backgroundImageUrl ?? null); + const { fileInputRef, displayUrl, isPreview, handleChange, handleReset } = useImagePreview({ + initialImageUrl: backgroundImageUrl, + onFileChange, + onResetImage, + }); return (
@@ -69,7 +49,8 @@ function ProfileBackgroundImageEditor({ src={displayUrl} alt="" fill - unoptimized + priority={priority} + unoptimized={isPreview} className={cn('absolute inset-0 object-cover', imageClassName)} /> )} diff --git a/src/components/mypage/edit/ProfileImageEditor.tsx b/src/components/mypage/edit/ProfileImageEditor.tsx index 4f8a22b93..247d16b15 100644 --- a/src/components/mypage/edit/ProfileImageEditor.tsx +++ b/src/components/mypage/edit/ProfileImageEditor.tsx @@ -1,6 +1,5 @@ 'use client'; -import { useRef, useState } from 'react'; import { Icon, DropdownMenu, @@ -13,6 +12,7 @@ import { AvatarImage, } from '@/components/ui'; import { CameraIcon } from '@/assets/icons'; +import { useImagePreview } from '@/hooks/mypage'; import { cn } from '@/lib/cn'; import type { AvatarProps } from '@/components/ui'; @@ -43,33 +43,11 @@ function ProfileImageEditor({ triggerIconClassName, triggerIconSize = 16, }: ProfileImageEditorProps) { - const fileInputRef = useRef(null); - const previewUrlRef = useRef(null); - const [previewUrl, setPreviewUrl] = useState(null); - const [isReset, setIsReset] = useState(false); - - const handleChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - if (previewUrlRef.current) URL.revokeObjectURL(previewUrlRef.current); - const url = URL.createObjectURL(file); - previewUrlRef.current = url; - setPreviewUrl(url); - setIsReset(false); - onFileChange?.(file); - }; - - const handleReset = () => { - if (previewUrlRef.current) { - URL.revokeObjectURL(previewUrlRef.current); - previewUrlRef.current = null; - } - setPreviewUrl(null); - setIsReset(true); - onResetImage?.(); - }; - - const displayUrl = isReset ? null : (previewUrl ?? profileImageUrl ?? null); + const { fileInputRef, displayUrl, handleChange, handleReset } = useImagePreview({ + initialImageUrl: profileImageUrl, + onFileChange, + onResetImage, + }); return (
diff --git a/src/hooks/mypage/index.ts b/src/hooks/mypage/index.ts index c5370b734..60fdaba87 100644 --- a/src/hooks/mypage/index.ts +++ b/src/hooks/mypage/index.ts @@ -1,2 +1,3 @@ export { useAddProfileFlow } from './useAddProfileFlow'; export { useEditProfileForm, type EditProfileFormValuesSource } from './useEditProfileForm'; +export { useImagePreview, type UseImagePreviewOptions } from './useImagePreview'; diff --git a/src/hooks/mypage/useImagePreview.ts b/src/hooks/mypage/useImagePreview.ts new file mode 100644 index 000000000..74759af3d --- /dev/null +++ b/src/hooks/mypage/useImagePreview.ts @@ -0,0 +1,63 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; + +interface UseImagePreviewOptions { + initialImageUrl?: string; + onFileChange?: (file: File) => void; + onResetImage?: () => void; +} + +function useImagePreview({ initialImageUrl, onFileChange, onResetImage }: UseImagePreviewOptions) { + const fileInputRef = useRef(null); + const previewUrlRef = useRef(null); + const [previewUrl, setPreviewUrl] = useState(null); + const [isReset, setIsReset] = useState(false); + + useEffect(() => { + return () => { + if (previewUrlRef.current) { + URL.revokeObjectURL(previewUrlRef.current); + } + }; + }, []); + + const handleChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + if (previewUrlRef.current) { + URL.revokeObjectURL(previewUrlRef.current); + } + + const url = URL.createObjectURL(file); + previewUrlRef.current = url; + setPreviewUrl(url); + setIsReset(false); + onFileChange?.(file); + }; + + const handleReset = () => { + if (previewUrlRef.current) { + URL.revokeObjectURL(previewUrlRef.current); + previewUrlRef.current = null; + } + + setPreviewUrl(null); + setIsReset(true); + onResetImage?.(); + }; + + const displayUrl = isReset ? null : (previewUrl ?? initialImageUrl ?? null); + const isPreview = !!previewUrl && !isReset; + + return { + fileInputRef, + displayUrl, + isPreview, + handleChange, + handleReset, + }; +} + +export { useImagePreview, type UseImagePreviewOptions }; From f9ee955189842eb7a4d5ac7abecf3b6d9ba21af2 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Mon, 6 Jul 2026 18:55:24 +0900 Subject: [PATCH 93/98] =?UTF-8?q?refactor:=20=ED=85=8C=EB=A7=88=20?= =?UTF-8?q?=EB=AA=A8=EB=93=9C=20=ED=83=80=EC=9E=85=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ThemeModeModal.tsx | 3 ++- src/components/mypage/ThemeModePageContent.tsx | 3 ++- src/components/mypage/ThemeModeSelector.tsx | 5 ++--- src/components/mypage/ThemeToggle.tsx | 3 +-- src/stores/theme-store.ts | 3 +-- src/types/theme.ts | 1 + 6 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 src/types/theme.ts diff --git a/src/components/mypage/ThemeModeModal.tsx b/src/components/mypage/ThemeModeModal.tsx index 1c523fe64..26a68eccc 100644 --- a/src/components/mypage/ThemeModeModal.tsx +++ b/src/components/mypage/ThemeModeModal.tsx @@ -2,7 +2,8 @@ import { DeleteIcon } from '@/assets/icons'; import { Button, Dialog, DialogContent, DialogTitle, Icon } from '@/components/ui'; -import { ThemeModeSelector, type ThemeMode } from './ThemeModeSelector'; +import type { ThemeMode } from '@/types/theme'; +import { ThemeModeSelector } from './ThemeModeSelector'; interface ThemeModeModalProps { open: boolean; diff --git a/src/components/mypage/ThemeModePageContent.tsx b/src/components/mypage/ThemeModePageContent.tsx index 0fe4325b1..68b2d19b1 100644 --- a/src/components/mypage/ThemeModePageContent.tsx +++ b/src/components/mypage/ThemeModePageContent.tsx @@ -6,7 +6,8 @@ import { BackIcon } from '@/assets/icons'; import { useThemeStore } from '@/stores/theme-store'; import { Button, Icon } from '@/components/ui'; import { cn } from '@/lib/cn'; -import { ThemeModeSelector, type ThemeMode } from './ThemeModeSelector'; +import type { ThemeMode } from '@/types/theme'; +import { ThemeModeSelector } from './ThemeModeSelector'; type ThemeModePageContentProps = React.HTMLAttributes; diff --git a/src/components/mypage/ThemeModeSelector.tsx b/src/components/mypage/ThemeModeSelector.tsx index 54addcf46..8f322f05b 100644 --- a/src/components/mypage/ThemeModeSelector.tsx +++ b/src/components/mypage/ThemeModeSelector.tsx @@ -7,8 +7,7 @@ import ThemeDarkImage from '@/assets/image/theme/theme_dark.png'; import ThemeLightImage from '@/assets/image/theme/theme_light.png'; import { Icon } from '@/components/ui'; import { cn } from '@/lib/cn'; - -type ThemeMode = 'auto' | 'light' | 'dark'; +import type { ThemeMode } from '@/types/theme'; const THEME_MODE_OPTIONS: { value: ThemeMode; @@ -39,7 +38,7 @@ function ThemePreviewCard({
diff --git a/src/components/mypage/ThemeToggle.tsx b/src/components/mypage/ThemeToggle.tsx index 9392083f3..1bdcd1b25 100644 --- a/src/components/mypage/ThemeToggle.tsx +++ b/src/components/mypage/ThemeToggle.tsx @@ -2,8 +2,7 @@ import { cn } from '@/lib/cn'; import { useThemeStore } from '@/stores/theme-store'; - -type ThemeMode = 'auto' | 'light' | 'dark'; +import type { ThemeMode } from '@/types/theme'; const OPTIONS: { value: ThemeMode; label: string }[] = [ { value: 'auto', label: '자동' }, diff --git a/src/stores/theme-store.ts b/src/stores/theme-store.ts index ce44e8bdb..2790205e5 100644 --- a/src/stores/theme-store.ts +++ b/src/stores/theme-store.ts @@ -1,7 +1,6 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; - -type ThemeMode = 'auto' | 'light' | 'dark'; +import type { ThemeMode } from '@/types/theme'; interface ThemeStore { mode: ThemeMode; diff --git a/src/types/theme.ts b/src/types/theme.ts new file mode 100644 index 000000000..a6ef4e91d --- /dev/null +++ b/src/types/theme.ts @@ -0,0 +1 @@ +export type ThemeMode = 'auto' | 'light' | 'dark'; From f2850faa787811b95d5288eb4409a62726b31280 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Mon, 6 Jul 2026 18:55:59 +0900 Subject: [PATCH 94/98] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EB=B2=84=ED=8A=BC=20=ED=98=B8=EB=B2=84=20?= =?UTF-8?q?=EC=83=89=EC=83=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ActiveClubList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/mypage/ActiveClubList.tsx b/src/components/mypage/ActiveClubList.tsx index 719ddc4f6..e71fc89de 100644 --- a/src/components/mypage/ActiveClubList.tsx +++ b/src/components/mypage/ActiveClubList.tsx @@ -81,14 +81,14 @@ function ProfileManageCard({ clubId }: { clubId: string }) { <> 프로필 관리 프로필 관리 From 76a7abd9a99aeea7487958b52ccf8480b9bb8381 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Mon, 6 Jul 2026 18:56:28 +0900 Subject: [PATCH 95/98] =?UTF-8?q?fix:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20=EB=93=9C=EB=A1=AD=EB=8B=A4=EC=9A=B4=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/ClubInfoCard.tsx | 6 +++--- src/components/mypage/LeaveClubDropdownMenu.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/mypage/ClubInfoCard.tsx b/src/components/mypage/ClubInfoCard.tsx index ec3785624..1a3c69e5c 100644 --- a/src/components/mypage/ClubInfoCard.tsx +++ b/src/components/mypage/ClubInfoCard.tsx @@ -7,7 +7,7 @@ import { Avatar, AvatarFallback, AvatarImage, Button, Divider, Icon, Tag } from import { ArrowRightIcon, PeopleIcon } from '@/assets/icons'; import type { ClubDto } from '@/types/mypage'; import { cardClass } from './InfoCard'; -import { MyPageDropdownMenu } from './LeaveClubDropdownMenu'; +import { LeaveClubDropdownMenu } from './LeaveClubDropdownMenu'; import { useRouter } from 'next/navigation'; const SetCardinalModal = dynamic(() => @@ -40,7 +40,7 @@ function ClubInfoCard({ club, className }: ClubInfoCardProps) {
- +
@@ -53,7 +53,7 @@ function ClubInfoCard({ club, className }: ClubInfoCardProps) {

)}
- + {club.memberCount}명
diff --git a/src/components/mypage/LeaveClubDropdownMenu.tsx b/src/components/mypage/LeaveClubDropdownMenu.tsx index f948348e6..feb9f8149 100644 --- a/src/components/mypage/LeaveClubDropdownMenu.tsx +++ b/src/components/mypage/LeaveClubDropdownMenu.tsx @@ -11,7 +11,7 @@ import { import { AdminMeatballIcon } from '@/assets/icons/admin'; import { WithdrawConfirmDialog } from './WithdrawConfirmDialog'; -function MyPageDropdownMenu() { +function LeaveClubDropdownMenu() { const [withdrawOpen, setWithdrawOpen] = useState(false); return ( @@ -38,4 +38,4 @@ function MyPageDropdownMenu() { ); } -export { MyPageDropdownMenu }; +export { LeaveClubDropdownMenu }; From df93ac7a71c57fa310ca8eb6901c838e4b04be63 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Mon, 6 Jul 2026 18:56:45 +0900 Subject: [PATCH 96/98] =?UTF-8?q?fix:=20=EB=B6=84=EB=A6=AC=ED=95=9C=20?= =?UTF-8?q?=ED=85=8C=EB=A7=88=20=ED=83=80=EC=9E=85=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mypage/MyPageSettingsContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/mypage/MyPageSettingsContent.tsx b/src/components/mypage/MyPageSettingsContent.tsx index 4d43ac9b3..efda11c54 100644 --- a/src/components/mypage/MyPageSettingsContent.tsx +++ b/src/components/mypage/MyPageSettingsContent.tsx @@ -5,6 +5,7 @@ import { useParams, useRouter } from 'next/navigation'; import { useMediaQuery } from '@/hooks'; import { cn } from '@/lib/cn'; import { useThemeStore } from '@/stores/theme-store'; +import type { ThemeMode } from '@/types/theme'; import { InfoSection } from './InfoSection'; import { LogoutConfirmDialog } from './LogoutConfirmDialog'; import { SupportListItem } from './SupportListItem'; @@ -12,7 +13,6 @@ import { ThemeModeModal } from './ThemeModeModal'; import { WithdrawConfirmDialog } from './WithdrawConfirmDialog'; type MyPageSettingsContentProps = React.HTMLAttributes; -type ThemeMode = 'auto' | 'light' | 'dark'; const THEME_MODE_LABELS: Record = { auto: '자동', From 5e38b115e0554da4218b48517838579ea76abbf7 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Mon, 6 Jul 2026 18:56:58 +0900 Subject: [PATCH 97/98] =?UTF-8?q?fix:=20button=20=ED=83=9C=EA=B7=B8?= =?UTF-8?q?=EC=97=90=20=EC=BB=A4=EC=84=9C=20=ED=8F=AC=EC=9D=B8=ED=84=B0=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/components/mypage/MyPageContent.tsx | 42 ++++++++------------ src/components/mypage/MyPagePostsContent.tsx | 2 +- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/components/mypage/MyPageContent.tsx b/src/components/mypage/MyPageContent.tsx index afdcf278a..aee4ec7c3 100644 --- a/src/components/mypage/MyPageContent.tsx +++ b/src/components/mypage/MyPageContent.tsx @@ -17,23 +17,25 @@ function MyPageContent({ className, ...props }: MyPageContentProps) { const [{ data: me }, { data: clubs }] = useMyPageQueries(clubId); const displayName = me?.name ?? ''; + const profileSection = me ? ( + + ) : ( + + ); + return ( <>
- {me ? ( - - ) : ( - - )} + {profileSection}
diff --git a/src/components/mypage/MyPagePostsContent.tsx b/src/components/mypage/MyPagePostsContent.tsx index ec410680c..d3a57a093 100644 --- a/src/components/mypage/MyPagePostsContent.tsx +++ b/src/components/mypage/MyPagePostsContent.tsx @@ -17,7 +17,7 @@ function MyPagePostsContent({ className, ...props }: MyPagePostsContentProps) { From 133c9312ca6472dc0ab3024d819a184371075028 Mon Sep 17 00:00:00 2001 From: Kimjiwon Date: Mon, 6 Jul 2026 19:15:11 +0900 Subject: [PATCH 98/98] =?UTF-8?q?fix:=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/mypage/useImagePreview.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/mypage/useImagePreview.ts b/src/hooks/mypage/useImagePreview.ts index 74759af3d..eba1dd76c 100644 --- a/src/hooks/mypage/useImagePreview.ts +++ b/src/hooks/mypage/useImagePreview.ts @@ -35,6 +35,7 @@ function useImagePreview({ initialImageUrl, onFileChange, onResetImage }: UseIma setPreviewUrl(url); setIsReset(false); onFileChange?.(file); + e.target.value = ''; }; const handleReset = () => {