11// components/SettingsPage.tsx
22'use client'
33
4- import React , { useState , useEffect , FormEvent } from 'react'
4+ import React , { useState , useEffect } from 'react'
5+ import { motion , AnimatePresence } from 'framer-motion'
56import { useAuthStore , selectUserId , selectLogout } from '@/store/AuthStore'
67import {
78 getUserProfile ,
@@ -12,6 +13,10 @@ import {
1213import { PersonaManager } from '@/components/Persona/PersonaManager'
1314import ImageUploader from '@/components/Common/ImageUploader'
1415import { Button } from '@/components/Common/Button'
16+ import { ProfilePreviewCard } from '@/components/Settings/ProfilePreviewCard'
17+ import { SettingsTabs , SettingsTab } from '@/components/Settings/SettingsTabs'
18+ import { DeleteAccountModal } from '@/components/Settings/DeleteAccountModal'
19+ import SettingsSkeleton from '@/components/Skeletons/SettingsSkeleton'
1520import Image from 'next/image'
1621import { useRouter } from 'next/navigation'
1722import { useToast } from '@/contexts/ToastContext'
@@ -22,6 +27,9 @@ export default function SettingsPage() {
2227 const router = useRouter ( )
2328 const toast = useToast ( )
2429
30+ // ํญ ์ํ
31+ const [ activeTab , setActiveTab ] = useState < SettingsTab > ( 'profile' )
32+
2533 // ํ๋กํ ์ํ
2634 const [ profile , setProfile ] = useState < UserProfile | null > ( null )
2735 const [ nickname , setNickname ] = useState ( '' )
@@ -77,108 +85,164 @@ export default function SettingsPage() {
7785 }
7886 }
7987
80- return (
81- < div className = "max-w-3xl mx-auto p-6 space-y-8" >
82- { /* ํ๋กํ ์ค์ ์น์
*/ }
83- < section className = "bg-white rounded-xl shadow-md p-6" >
84- < h2 className = "text-2xl font-bold mb-4 border-b pb-2" >
85- ํ๋กํ ์ค์
86- </ h2 >
87-
88- { loadingProfile ? (
89- < p className = "text-center text-gray-500" > ๋ก๋ฉ ์คโฆ</ p >
90- ) : errorProfile ? (
91- < p className = "text-red-600" > { errorProfile } </ p >
92- ) : profile ? (
93- < div className = "space-y-6" >
94- < div className = "grid grid-cols-1 md:grid-cols-2 gap-6" >
95- { /* ๋๋ค์ ์
๋ ฅ */ }
96- < div >
97- < label className = "block mb-2 text-sm font-medium text-gray-700" >
98- ๋๋ค์
99- </ label >
100- < input
101- type = "text"
102- value = { nickname }
103- onChange = { e => setNickname ( e . target . value ) }
104- className = "
105- w-full rounded-md border border-gray-200
106- px-4 py-2 text-gray-800
107- focus:outline-none focus:ring-2 focus:ring-blue-300
108- "
109- />
88+ // ๋ก๋ฉ ์ํ
89+ if ( loadingProfile ) {
90+ return < SettingsSkeleton />
91+ }
92+
93+ // ํญ ์ฝํ
์ธ ๋ ๋๋ง
94+ const renderTabContent = ( ) => {
95+ switch ( activeTab ) {
96+ case 'profile' :
97+ return (
98+ < motion . div
99+ key = "profile"
100+ initial = { { opacity : 0 , x : 20 } }
101+ animate = { { opacity : 1 , x : 0 } }
102+ exit = { { opacity : 0 , x : - 20 } }
103+ transition = { { duration : 0.3 } }
104+ className = "space-y-6"
105+ >
106+ { errorProfile ? (
107+ < div className = "bg-red-50 border border-red-200 rounded-lg p-4" >
108+ < p className = "text-red-600" > { errorProfile } </ p >
110109 </ div >
110+ ) : profile ? (
111+ < >
112+ { /* ๋๋ค์ ์
๋ ฅ */ }
113+ < div >
114+ < label className = "block mb-2 text-sm font-semibold text-gray-700" >
115+ ๋๋ค์
116+ </ label >
117+ < input
118+ type = "text"
119+ value = { nickname }
120+ onChange = { e => setNickname ( e . target . value ) }
121+ maxLength = { 20 }
122+ className = "
123+ w-full rounded-lg border border-gray-300
124+ px-4 py-3 text-gray-800
125+ focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
126+ transition-all
127+ "
128+ placeholder = "๋๋ค์์ ์
๋ ฅํ์ธ์"
129+ />
130+ < p className = "mt-1 text-xs text-gray-500" >
131+ { nickname . length } /20์
132+ </ p >
133+ </ div >
111134
112- { /* ํ๋กํ ์ด๋ฏธ์ง ์
๋ก๋ */ }
113- < div className = "flex flex-col items-start" >
114- < label className = "block mb-2 text-sm font-medium text-gray-700" >
115- ํ๋กํ ์ด๋ฏธ์ง
116- </ label >
117- { profileImageUrl && (
118- < Image
119- src = { profileImageUrl }
120- alt = "ํ๋กํ ๋ฏธ๋ฆฌ๋ณด๊ธฐ"
121- width = { 100 }
122- height = { 100 }
123- className = "rounded-full object-cover border mb-2"
135+ { /* ํ๋กํ ์ด๋ฏธ์ง ์
๋ก๋ */ }
136+ < div >
137+ < label className = "block mb-3 text-sm font-semibold text-gray-700" >
138+ ํ๋กํ ์ด๋ฏธ์ง
139+ </ label >
140+ < ImageUploader
141+ folder = "profile-images"
142+ onUploaded = { url => setProfileImageUrl ( url ) }
124143 />
125- ) }
126- < ImageUploader
127- folder = "profile-images"
128- onUploaded = { url => setProfileImageUrl ( url ) }
144+ </ div >
145+
146+ { /* ์ ์ฅ / ํํด ๋ฒํผ */ }
147+ < div className = "flex flex-col sm:flex-row justify-between gap-4 pt-6 border-t" >
148+ < Button
149+ variant = "outline"
150+ className = "text-red-600 hover:bg-red-50 border-red-200"
151+ onClick = { ( ) => setConfirmOpen ( true ) }
152+ >
153+ ๊ณ์ ํํด
154+ </ Button >
155+ < Button
156+ onClick = { saveProfile }
157+ className = "bg-blue-600 hover:bg-blue-700"
158+ >
159+ ๋ณ๊ฒฝ์ฌํญ ์ ์ฅ
160+ </ Button >
161+ </ div >
162+ </ >
163+ ) : null }
164+ </ motion . div >
165+ )
166+
167+ case 'persona' :
168+ return (
169+ < motion . div
170+ key = "persona"
171+ initial = { { opacity : 0 , x : 20 } }
172+ animate = { { opacity : 1 , x : 0 } }
173+ exit = { { opacity : 0 , x : - 20 } }
174+ transition = { { duration : 0.3 } }
175+ >
176+ { userId && < PersonaManager userId = { userId } /> }
177+ </ motion . div >
178+ )
179+
180+ case 'security' :
181+ return (
182+ < motion . div
183+ key = "security"
184+ initial = { { opacity : 0 , x : 20 } }
185+ animate = { { opacity : 1 , x : 0 } }
186+ exit = { { opacity : 0 , x : - 20 } }
187+ transition = { { duration : 0.3 } }
188+ className = "text-center py-16"
189+ >
190+ < p className = "text-gray-500" > ๋ณด์ ์ค์ ์ ์ค๋น ์ค์
๋๋ค.</ p >
191+ </ motion . div >
192+ )
193+
194+ default :
195+ return null
196+ }
197+ }
198+
199+ return (
200+ < motion . div
201+ initial = { { opacity : 0 , y : 20 } }
202+ animate = { { opacity : 1 , y : 0 } }
203+ transition = { { duration : 0.4 } }
204+ className = "min-h-screen py-8"
205+ >
206+ < div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
207+ < div className = "grid grid-cols-1 lg:grid-cols-12 gap-8" >
208+ { /* ์ฌ์ด๋๋ฐ - ํ๋กํ ๋ฏธ๋ฆฌ๋ณด๊ธฐ */ }
209+ < aside className = "lg:col-span-4" >
210+ < div className = "lg:sticky lg:top-8" >
211+ { profile && (
212+ < ProfilePreviewCard
213+ profile = { profile }
214+ profileImageUrl = { profileImageUrl }
129215 />
130- </ div >
216+ ) }
131217 </ div >
218+ </ aside >
132219
133- { /* ์ ์ฅ / ํํด ๋ฒํผ */ }
134- < div className = "flex justify-end gap-4" >
135- < Button
136- variant = "outline"
137- className = "text-red-600 hover:bg-red-50"
138- onClick = { ( ) => setConfirmOpen ( true ) }
139- >
140- ํํด
141- </ Button >
142- < Button onClick = { saveProfile } > ์ ์ฅ</ Button >
143- </ div >
144- </ div >
145- ) : null }
146- </ section >
147-
148- { /* ํ๋ฅด์๋ ์ค์ ์น์
*/ }
149- < section className = "bg-white rounded-xl shadow-md p-6" >
150- < h2 className = "text-2xl font-bold mb-4 border-b pb-2" >
151- ํ๋ฅด์๋ ์ค์
152- </ h2 >
153- { userId && < PersonaManager userId = { userId } /> }
154- </ section >
220+ { /* ๋ฉ์ธ ์ฝํ
์ธ */ }
221+ < main className = "lg:col-span-8" >
222+ < div className = "bg-white rounded-2xl shadow-lg overflow-hidden" >
223+ { /* ํญ ๋ค๋น๊ฒ์ด์
*/ }
224+ < SettingsTabs
225+ activeTab = { activeTab }
226+ onTabChange = { setActiveTab }
227+ />
155228
156- { /* ํํด ํ์ธ ๋ชจ๋ฌ */ }
157- { confirmOpen && (
158- < div className = "fixed inset-0 z-50 flex items-center justify-center bg-black/50" >
159- < div className = "bg-white rounded-lg p-6 w-80 space-y-4" >
160- < h3 className = "text-lg font-semibold" > ์ ๋ง ๊ณ์ ์ ์ญ์ ํ์๊ฒ ์ต๋๊น?</ h3 >
161- < p className = "text-sm text-gray-600" >
162- ์ญ์ ๋ ๊ณ์ ์ ๋ณต๊ตฌํ ์ ์์ต๋๋ค.
163- </ p >
164- < div className = "flex justify-end gap-2" >
165- < Button
166- variant = "outline"
167- className = "text-gray-700 hover:bg-gray-100"
168- onClick = { ( ) => setConfirmOpen ( false ) }
169- >
170- ์ทจ์
171- </ Button >
172- < Button
173- className = "bg-red-600 hover:bg-red-700"
174- onClick = { handleWithdraw }
175- >
176- ์ญ์
177- </ Button >
229+ { /* ํญ ์ฝํ
์ธ */ }
230+ < div className = "p-6 sm:p-8" >
231+ < AnimatePresence mode = "wait" >
232+ { renderTabContent ( ) }
233+ </ AnimatePresence >
234+ </ div >
178235 </ div >
179- </ div >
236+ </ main >
180237 </ div >
181- ) }
182- </ div >
238+ </ div >
239+
240+ { /* ํํด ํ์ธ ๋ชจ๋ฌ */ }
241+ < DeleteAccountModal
242+ isOpen = { confirmOpen }
243+ onClose = { ( ) => setConfirmOpen ( false ) }
244+ onConfirm = { handleWithdraw }
245+ />
246+ </ motion . div >
183247 )
184248}
0 commit comments