로그인 상태에 따른 네비게이션 및 페이지 보호 로직 추가:
Some checks failed
Build Push and Restart Compose / deploy (push) Failing after 1m12s

- `+layout.svelte`에서 로그인 상태에 따라 공개/보호 네비게이션 항목 분리 및 표시.
- 로그인 여부 확인 로직 `+layout.ts`로 이전 (`fetch` 결과에 따라 `loggedIn` 값 반환).
- 보호 페이지(`autotrade`, `asset`)는 미로그인 시 `/login` 리다이렉트 (`next` 파라미터 포함).
- 인증 예외 경로 `/api/watchlist`에 추가.
This commit is contained in:
hayato5246
2026-04-06 20:11:24 +09:00
parent 47bb040eb8
commit 5a29d50752
5 changed files with 56 additions and 18 deletions

View File

@@ -2,16 +2,21 @@
import { page } from '$app/state' import { page } from '$app/state'
import { goto } from '$app/navigation' import { goto } from '$app/navigation'
let { children } = $props() let { children, data } = $props()
const navItems = [ const publicNav = [
{ href: '/', label: '시세' }, { href: '/', label: '시세' },
{ href: '/theme', label: '테마' }, { href: '/theme', label: '테마' },
{ href: '/kospi200', label: '코스피200' }, { href: '/kospi200', label: '코스피200' },
]
const protectedNav = [
{ href: '/asset', label: '자산' }, { href: '/asset', label: '자산' },
{ href: '/autotrade', label: '자동매매' }, { href: '/autotrade', label: '자동매매' },
] ]
let loggedIn = $derived(data.loggedIn)
async function logout() { async function logout() {
await fetch('/logout', { method: 'POST', credentials: 'include' }) await fetch('/logout', { method: 'POST', credentials: 'include' })
goto('/login') goto('/login')
@@ -33,7 +38,7 @@
<!-- 네비 링크 --> <!-- 네비 링크 -->
<nav class="flex items-center gap-1 flex-1"> <nav class="flex items-center gap-1 flex-1">
{#each navItems as item} {#each publicNav as item}
<a <a
href={item.href} href={item.href}
class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors
@@ -44,15 +49,37 @@
{item.label} {item.label}
</a> </a>
{/each} {/each}
{#if loggedIn}
{#each protectedNav as item}
<a
href={item.href}
class="px-3 py-1.5 rounded-md text-sm font-medium transition-colors
{isActive(item.href)
? 'bg-blue-600 text-white'
: 'text-gray-300 hover:text-white hover:bg-gray-700'}"
>
{item.label}
</a>
{/each}
{/if}
</nav> </nav>
<!-- 로그아웃 --> <!-- 로그인/로그아웃 -->
<button {#if loggedIn}
onclick={logout} <button
class="text-xs text-gray-400 hover:text-white transition-colors shrink-0" onclick={logout}
> class="text-xs text-gray-400 hover:text-white transition-colors shrink-0"
로그아웃 >
</button> 로그아웃
</button>
{:else}
<a
href="/login"
class="text-xs text-gray-400 hover:text-white transition-colors shrink-0"
>
로그인
</a>
{/if}
</div> </div>
</div> </div>
</header> </header>

View File

@@ -1,13 +1,9 @@
import { redirect } from '@sveltejs/kit' // 로그인 상태 확인 (리다이렉트하지 않음 — 공개 페이지도 접근 가능)
// 인증 가드: 세션 유효하지 않으면 /login으로 리다이렉트
export async function load({ fetch }) { export async function load({ fetch }) {
try { try {
const res = await fetch('/api/auth/check', { credentials: 'include' }) const res = await fetch('/api/auth/check', { credentials: 'include' })
if (!res.ok) throw redirect(302, '/login') return { loggedIn: res.ok }
} catch (e) { } catch {
// redirect 재throw return { loggedIn: false }
if (e && typeof e === 'object' && 'status' in e) throw e
throw redirect(302, '/login')
} }
} }

View File

@@ -0,0 +1,7 @@
import { redirect } from '@sveltejs/kit'
// 자산 페이지: 로그인 필요
export async function load({ parent }) {
const { loggedIn } = await parent()
if (!loggedIn) throw redirect(302, '/login?next=/asset')
}

View File

@@ -0,0 +1,7 @@
import { redirect } from '@sveltejs/kit'
// 자동매매 페이지: 로그인 필요
export async function load({ parent }) {
const { loggedIn } = await parent()
if (!loggedIn) throw redirect(302, '/login?next=/autotrade')
}

View File

@@ -42,6 +42,7 @@ var publicPaths = []string{
"/api/news", "/api/news",
"/api/disclosure", "/api/disclosure",
"/api/auth/check", "/api/auth/check",
"/api/watchlist",
"/ws", "/ws",
} }