fix(nav): TopNav auth 구독 안정화 + 로그아웃 UX 보강

코드 리뷰 후속:
- (I-1) useMemo로 supabase client 안정화 → 매 렌더 re-subscribe 제거
- (I-2) getUser() → getSession() → first paint flash 거의 제거 (localStorage 동기 읽기)
- (M-1) 로그아웃 router.push → router.replace → 보호 페이지 백스택 잔존 방지
- (M-2) 모바일 로그아웃 button transition-colors 추가 (데스크톱과 일관)

Defer (별도 검토):
- M-3 로그인 시 Try now 사라짐 — marketing 결정 필요
- M-5 잔여 flash — Phase 2 server prop hydration 시 완전 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-28 03:50:41 +09:00
parent 601bc38a12
commit 22fe05b4d8

View File

@@ -2,7 +2,7 @@
import Link from 'next/link'; import Link from 'next/link';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import { useState, useEffect } from 'react'; import { useMemo, useState, useEffect } from 'react';
import { createClient } from '@/lib/supabase/client'; import { createClient } from '@/lib/supabase/client';
import type { User } from '@supabase/supabase-js'; import type { User } from '@supabase/supabase-js';
@@ -17,7 +17,7 @@ const LINKS = [
export default function TopNav() { export default function TopNav() {
const pathname = usePathname(); const pathname = usePathname();
const router = useRouter(); const router = useRouter();
const supabase = createClient(); const supabase = useMemo(() => 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); const [user, setUser] = useState<User | null>(null);
@@ -32,8 +32,8 @@ export default function TopNav() {
// Supabase auth state subscription (Sidebar.tsx:93-103 패턴) // Supabase auth state subscription (Sidebar.tsx:93-103 패턴)
useEffect(() => { useEffect(() => {
let mounted = true; let mounted = true;
supabase.auth.getUser().then(({ data }) => { supabase.auth.getSession().then(({ data }) => {
if (mounted) setUser(data.user ?? null); if (mounted) setUser(data.session?.user ?? null);
}); });
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
if (mounted) setUser(session?.user ?? null); if (mounted) setUser(session?.user ?? null);
@@ -47,7 +47,7 @@ export default function TopNav() {
const handleLogout = async () => { const handleLogout = async () => {
await supabase.auth.signOut(); await supabase.auth.signOut();
setOpen(false); setOpen(false);
router.push('/'); router.replace('/');
router.refresh(); router.refresh();
}; };
@@ -214,7 +214,7 @@ export default function TopNav() {
</Link> </Link>
<button <button
onClick={handleLogout} onClick={handleLogout}
className="flex-1 py-3 text-center rounded-full text-sm font-bold" className="flex-1 py-3 text-center rounded-full text-sm font-bold transition-colors"
style={{ border: '1px solid rgba(255,255,255,0.15)', color: 'var(--kx-on-surface)', background: 'transparent' }} style={{ border: '1px solid rgba(255,255,255,0.15)', color: 'var(--kx-on-surface)', background: 'transparent' }}
> >