feat(nav): TopNav supabase auth 구독 + 로그인 상태 토글
- 로그아웃 시: "로그인" link + "Try now" 버튼 (기존) - 로그인 시: "마이페이지" link + "로그아웃" 버튼 (신규) - 데스크톱 + 모바일 오버레이 둘 다 동일 패턴 - Sidebar.tsx:93-103 의 auth 구독 패턴 차용 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { createClient } from '@/lib/supabase/client';
|
||||||
|
import type { User } from '@supabase/supabase-js';
|
||||||
|
|
||||||
const LINKS = [
|
const LINKS = [
|
||||||
{ href: '/', label: '홈' },
|
{ href: '/', label: '홈' },
|
||||||
@@ -14,8 +16,11 @@ const LINKS = [
|
|||||||
|
|
||||||
export default function TopNav() {
|
export default function TopNav() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
const router = useRouter();
|
||||||
|
const supabase = createClient();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [scrolled, setScrolled] = useState(false);
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onScroll = () => setScrolled(window.scrollY > 8);
|
const onScroll = () => setScrolled(window.scrollY > 8);
|
||||||
@@ -24,6 +29,28 @@ export default function TopNav() {
|
|||||||
return () => window.removeEventListener('scroll', onScroll);
|
return () => window.removeEventListener('scroll', onScroll);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Supabase auth state subscription (Sidebar.tsx:93-103 패턴)
|
||||||
|
useEffect(() => {
|
||||||
|
let mounted = true;
|
||||||
|
supabase.auth.getUser().then(({ data }) => {
|
||||||
|
if (mounted) setUser(data.user ?? null);
|
||||||
|
});
|
||||||
|
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
||||||
|
if (mounted) setUser(session?.user ?? null);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
subscription.unsubscribe();
|
||||||
|
};
|
||||||
|
}, [supabase]);
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
await supabase.auth.signOut();
|
||||||
|
setOpen(false);
|
||||||
|
router.push('/');
|
||||||
|
router.refresh();
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => { setOpen(false); }, [pathname]);
|
useEffect(() => { setOpen(false); }, [pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -89,20 +116,45 @@ export default function TopNav() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Link
|
{user ? (
|
||||||
href="/login"
|
<>
|
||||||
className="hidden sm:inline-block text-sm font-medium px-4 py-2 transition-colors"
|
<Link
|
||||||
style={{ color: 'var(--kx-on-variant)', textDecoration: 'none' }}
|
href="/mypage"
|
||||||
>
|
className="hidden sm:inline-block text-sm font-medium px-4 py-2 transition-colors"
|
||||||
로그인
|
style={{ color: 'var(--kx-on-variant)', textDecoration: 'none' }}
|
||||||
</Link>
|
>
|
||||||
<Link
|
마이페이지
|
||||||
href="/services/music"
|
</Link>
|
||||||
className="kx-btn-primary hidden sm:inline-flex items-center px-5 py-2 rounded-full text-sm"
|
<button
|
||||||
style={{ textDecoration: 'none' }}
|
onClick={handleLogout}
|
||||||
>
|
className="hidden sm:inline-flex items-center px-5 py-2 rounded-full text-sm font-medium transition-colors"
|
||||||
Try now
|
style={{
|
||||||
</Link>
|
color: 'var(--kx-on-surface)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.15)',
|
||||||
|
background: 'transparent',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
로그아웃
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="hidden sm:inline-block text-sm font-medium px-4 py-2 transition-colors"
|
||||||
|
style={{ color: 'var(--kx-on-variant)', textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
로그인
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/services/music"
|
||||||
|
className="kx-btn-primary hidden sm:inline-flex items-center px-5 py-2 rounded-full text-sm"
|
||||||
|
style={{ textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
Try now
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
aria-label="메뉴 열기"
|
aria-label="메뉴 열기"
|
||||||
@@ -151,20 +203,41 @@ export default function TopNav() {
|
|||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
<div className="mt-6 flex gap-3">
|
<div className="mt-6 flex gap-3">
|
||||||
<Link
|
{user ? (
|
||||||
href="/login"
|
<>
|
||||||
className="flex-1 py-3 text-center rounded-full text-sm font-bold"
|
<Link
|
||||||
style={{ border: '1px solid rgba(255,255,255,0.15)', color: 'var(--kx-on-surface)', textDecoration: 'none' }}
|
href="/mypage"
|
||||||
>
|
className="flex-1 py-3 text-center rounded-full text-sm font-bold"
|
||||||
로그인
|
style={{ border: '1px solid rgba(255,255,255,0.15)', color: 'var(--kx-on-surface)', textDecoration: 'none' }}
|
||||||
</Link>
|
>
|
||||||
<Link
|
마이페이지
|
||||||
href="/services/music"
|
</Link>
|
||||||
className="kx-btn-primary flex-1 py-3 text-center rounded-full text-sm"
|
<button
|
||||||
style={{ textDecoration: 'none' }}
|
onClick={handleLogout}
|
||||||
>
|
className="flex-1 py-3 text-center rounded-full text-sm font-bold"
|
||||||
Try now
|
style={{ border: '1px solid rgba(255,255,255,0.15)', color: 'var(--kx-on-surface)', background: 'transparent' }}
|
||||||
</Link>
|
>
|
||||||
|
로그아웃
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="flex-1 py-3 text-center rounded-full text-sm font-bold"
|
||||||
|
style={{ border: '1px solid rgba(255,255,255,0.15)', color: 'var(--kx-on-surface)', textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
로그인
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/services/music"
|
||||||
|
className="kx-btn-primary flex-1 py-3 text-center rounded-full text-sm"
|
||||||
|
style={{ textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
Try now
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user