Feat: auth 관련페이지 header 적용

This commit is contained in:
2026-02-06 09:14:49 +09:00
parent f1e340d9f1
commit d31e3f9bc9
8 changed files with 208 additions and 84 deletions

View File

@@ -1,10 +1,21 @@
export default function AuthLayout({
import { Header } from "@/features/layout/components/header";
import { createClient } from "@/utils/supabase/server";
export default async function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
return (
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-linear-to-br from-gray-50 via-white to-gray-100 px-4 py-12 dark:from-black dark:via-gray-950 dark:to-gray-900">
<div className="relative flex min-h-screen flex-col items-center justify-center overflow-hidden bg-linear-to-br from-gray-50 via-white to-gray-100 dark:from-black dark:via-gray-950 dark:to-gray-900">
{/* ========== 헤더 (홈 이동용) ========== */}
<Header user={user} />
{/* ========== 배경 그라디언트 레이어 ========== */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,var(--tw-gradient-stops))] from-gray-200/30 via-gray-100/15 to-transparent dark:from-gray-800/30 dark:via-gray-900/20" />
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_left,var(--tw-gradient-stops))] from-gray-300/30 via-gray-200/15 to-transparent dark:from-gray-700/30 dark:via-gray-800/20" />
@@ -13,8 +24,10 @@ export default function AuthLayout({
<div className="absolute left-1/4 top-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-300/20 blur-3xl dark:bg-gray-700/20" />
<div className="absolute bottom-1/4 right-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-400/20 blur-3xl delay-700 dark:bg-gray-600/20" />
{/* ========== 메인 콘텐츠 영역 ========== */}
{children}
{/* ========== 메인 콘텐츠 영역 (중앙 정렬) ========== */}
<main className="z-10 flex w-full flex-1 flex-col items-center justify-center px-4 py-12">
{children}
</main>
</div>
);
}

View File

@@ -30,10 +30,7 @@ export default async function ResetPasswordPage({
} = await supabase.auth.getUser();
if (!user) {
const message = encodeURIComponent(
"유효하지 않은 재설정 링크이거나 만료되었습니다. 다시 시도해 주세요.",
);
redirect(`/login?message=${message}`);
redirect(`/login`);
}
const { message } = params;

View File

@@ -1,8 +1,9 @@
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { createClient } from "@/utils/supabase/server";
import { UserMenu } from "@/features/layout/components/user-menu";
import { Header } from "@/features/layout/components/header";
import { AUTH_ROUTES } from "@/features/auth/constants";
import { SplineScene } from "@/features/home/components/spline-scene";
export default async function HomePage() {
const supabase = await createClient();
@@ -12,67 +13,53 @@ export default async function HomePage() {
return (
<div className="flex min-h-screen flex-col">
<header className="sticky top-0 z-40 flex h-14 w-full items-center justify-between border-b border-zinc-200 bg-white/75 px-6 backdrop-blur-md dark:border-zinc-800 dark:bg-black/75">
<Link href={AUTH_ROUTES.HOME} className="flex items-center gap-2">
<div className="h-6 w-6 rounded-md bg-zinc-900 dark:bg-zinc-50" />
<span className="text-lg font-bold tracking-tight text-zinc-900 dark:text-zinc-50">
AutoTrade
</span>
</Link>
<div className="flex items-center gap-4">
{user ? (
<>
<Button asChild variant="ghost" size="sm">
<Link href={AUTH_ROUTES.DASHBOARD}></Link>
</Button>
<UserMenu user={user} />
</>
) : (
<>
<Button asChild variant="ghost" size="sm">
<Link href={AUTH_ROUTES.LOGIN}></Link>
</Button>
<Button asChild size="sm">
<Link href={AUTH_ROUTES.SIGNUP}> </Link>
</Button>
</>
)}
</div>
</header>
<Header user={user} showDashboardLink={true} />
<main className="flex-1">
<section className="space-y-6 pb-8 pt-6 md:pb-12 md:pt-10 lg:py-32">
<div className="container flex max-w-5xl flex-col items-center gap-4 text-center">
<h1 className="font-heading text-3xl sm:text-5xl md:text-6xl lg:text-7xl">
,{" "}
<span className="text-gradient bg-linear-to-r from-indigo-500 to-purple-600 bg-clip-text text-transparent">
</span>
</h1>
<p className="max-w-2xl leading-normal text-muted-foreground sm:text-xl sm:leading-8">
AutoTrade는 24
. .
</p>
<div className="space-x-4">
{user ? (
<Button asChild size="lg" className="h-11 px-8">
<Link href={AUTH_ROUTES.DASHBOARD}> </Link>
</Button>
) : (
<Button asChild size="lg" className="h-11 px-8">
<Link href={AUTH_ROUTES.LOGIN}> </Link>
</Button>
)}
{!user && (
<Button
asChild
variant="outline"
size="lg"
className="h-11 px-8"
>
<Link href={AUTH_ROUTES.LOGIN}> </Link>
</Button>
)}
<section className="relative overflow-hidden pb-8 pt-6 md:pb-12 md:pt-10 lg:py-32">
<div className="container relative z-10 flex max-w-7xl flex-col items-center gap-4 lg:grid lg:grid-cols-2 lg:gap-8 lg:text-left">
<div className="flex flex-col items-center lg:items-start lg:gap-8">
<h1 className="font-heading text-3xl sm:text-5xl md:text-6xl lg:text-7xl">
,{" "}
<span className="text-gradient bg-linear-to-r from-indigo-500 to-purple-600 bg-clip-text text-transparent">
</span>
</h1>
<p className="max-w-2xl leading-normal text-muted-foreground sm:text-xl sm:leading-8">
AutoTrade는 24
. .
</p>
<div className="space-x-4">
{user ? (
<Button asChild size="lg" className="h-11 px-8">
<Link href={AUTH_ROUTES.DASHBOARD}> </Link>
</Button>
) : (
<Button asChild size="lg" className="h-11 px-8">
<Link href={AUTH_ROUTES.LOGIN}> </Link>
</Button>
)}
{!user && (
<Button
asChild
variant="outline"
size="lg"
className="h-11 px-8"
>
<Link href={AUTH_ROUTES.LOGIN}> </Link>
</Button>
)}
</div>
</div>
<div className="relative mt-8 h-[400px] w-full lg:mt-0 lg:h-[600px]">
{/* Spline Scene Container */}
<div className="absolute inset-0 rounded-2xl bg-zinc-100/50 dark:bg-zinc-900/50">
<SplineScene
sceneUrl="https://prod.spline.design/6Wq1Q7YGyM-iab9i/scene.splinecode"
className="rounded-2xl"
/>
</div>
</div>
</div>
</section>

View File

@@ -1,14 +1,20 @@
import { Header } from "@/features/layout/components/header";
import { Sidebar } from "@/features/layout/components/sidebar";
import { createClient } from "@/utils/supabase/server";
export default function MainLayout({
export default async function MainLayout({
children,
}: {
children: React.ReactNode;
}) {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
return (
<div className="flex min-h-screen flex-col bg-zinc-50 dark:bg-black">
<Header />
<Header user={user} />
<div className="flex flex-1">
<Sidebar />
<main className="flex-1 w-full p-6 md:p-8 lg:p-10">{children}</main>

View File

@@ -0,0 +1,29 @@
"use client";
import Spline from "@splinetool/react-spline";
import { useState } from "react";
import { cn } from "@/lib/utils";
interface SplineSceneProps {
sceneUrl: string;
className?: string;
}
export function SplineScene({ sceneUrl, className }: SplineSceneProps) {
const [isLoading, setIsLoading] = useState(true);
return (
<div className={cn("relative h-full w-full", className)}>
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-zinc-100 dark:bg-zinc-900">
<div className="h-10 w-10 animate-spin rounded-full border-4 border-zinc-200 border-t-indigo-500 dark:border-zinc-800" />
</div>
)}
<Spline
scene={sceneUrl}
onLoad={() => setIsLoading(false)}
className="h-full w-full"
/>
</div>
);
}

View File

@@ -1,33 +1,44 @@
import { createClient } from "@/utils/supabase/server";
import Link from "next/link";
import { UserMenu } from "./user-menu";
import { Button } from "@/components/ui/button";
import { User } from "@supabase/supabase-js";
import { AUTH_ROUTES } from "@/features/auth/constants";
import { UserMenu } from "@/features/layout/components/user-menu";
import { Button } from "@/components/ui/button";
export async function Header() {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
interface HeaderProps {
user: User | null;
showDashboardLink?: boolean;
}
export function Header({ user, showDashboardLink = false }: HeaderProps) {
return (
<header className="sticky top-0 z-40 flex h-14 w-full items-center justify-between border-b border-zinc-200 bg-white/75 px-6 backdrop-blur-md transition-all dark:border-zinc-800 dark:bg-black/75">
<div className="flex items-center gap-2">
<Link href={AUTH_ROUTES.DASHBOARD} className="flex items-center gap-2">
<Link href={AUTH_ROUTES.HOME} className="flex items-center gap-2">
<div className="h-6 w-6 rounded-md bg-zinc-900 dark:bg-zinc-50" />
<span className="text-lg font-bold tracking-tight text-zinc-900 dark:text-zinc-50">
AutoTrade
</span>
</Link>
</div>
<div className="flex items-center gap-4">
{user ? (
<UserMenu user={user} />
<>
{showDashboardLink && (
<Button asChild variant="ghost" size="sm">
<Link href={AUTH_ROUTES.DASHBOARD}></Link>
</Button>
)}
<UserMenu user={user} />
</>
) : (
<Button asChild variant="default" size="sm">
<Link href={AUTH_ROUTES.LOGIN}></Link>
</Button>
<>
<Button asChild variant="ghost" size="sm">
<Link href={AUTH_ROUTES.LOGIN}></Link>
</Button>
<Button asChild size="sm">
<Link href={AUTH_ROUTES.SIGNUP}> </Link>
</Button>
</>
)}
</div>
</header>

79
package-lock.json generated
View File

@@ -14,6 +14,8 @@
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@splinetool/react-spline": "^4.1.0",
"@splinetool/runtime": "^1.12.50",
"@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.93.3",
"@tanstack/react-query": "^5.90.20",
@@ -4383,6 +4385,37 @@
"dev": true,
"license": "MIT"
},
"node_modules/@splinetool/react-spline": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@splinetool/react-spline/-/react-spline-4.1.0.tgz",
"integrity": "sha512-Y379gm17gw+1nxT/YXTCJnVIWuu7tsUH1tp/YxsYb0pZnc9Gljk7Om4Kpq7WPq0bZ4zidVCxf6xn6jgDcbHifQ==",
"dependencies": {
"blurhash": "2.0.5",
"lodash.debounce": "4.0.8",
"react-merge-refs": "2.1.1",
"thumbhash": "0.1.1"
},
"peerDependencies": {
"@splinetool/runtime": "*",
"next": ">=14.2.0",
"react": "*",
"react-dom": "*"
},
"peerDependenciesMeta": {
"next": {
"optional": true
}
}
},
"node_modules/@splinetool/runtime": {
"version": "1.12.50",
"resolved": "https://registry.npmjs.org/@splinetool/runtime/-/runtime-1.12.50.tgz",
"integrity": "sha512-tzdG3D03WgiFD7xP47OAv03OfJmyLa0WDBf4qmQKap+RjHQXz3Uqq7P6kHMg/XXqII2eIqoD8gDKiOvgMZ8B9A==",
"dependencies": {
"on-change": "4.0.0",
"semver-compare": "1.0.0"
}
},
"node_modules/@standard-schema/utils": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
@@ -5752,6 +5785,12 @@
"baseline-browser-mapping": "dist/cli.js"
}
},
"node_modules/blurhash": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz",
"integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -8240,6 +8279,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -8620,6 +8665,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-change": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/on-change/-/on-change-4.0.0.tgz",
"integrity": "sha512-PTu7C9Jsz4b+sNMDpH0eZFTr7uxdOtoDWRnhaVNK50bgrrnW5nvbWI0jm5DG9qOoTnIhBzE9xoKVFPD9xgtbdg==",
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sindresorhus/on-change?sponsor=1"
}
},
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -9079,6 +9136,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/react-merge-refs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-2.1.1.tgz",
"integrity": "sha512-jLQXJ/URln51zskhgppGJ2ub7b2WFKGq3cl3NYKtlHoTG+dN2q7EzWrn3hN3EgPsTMvpR9tpq5ijdp7YwFZkag==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
}
},
"node_modules/react-remove-scroll": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
@@ -9339,6 +9406,12 @@
"semver": "bin/semver.js"
}
},
"node_modules/semver-compare": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"license": "MIT"
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -9799,6 +9872,12 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/thumbhash": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/thumbhash/-/thumbhash-0.1.1.tgz",
"integrity": "sha512-kH5pKeIIBPQXAOni2AiY/Cu/NKdkFREdpH+TLdM0g6WA7RriCv0kPLgP731ady67MhTAqrVG/4mnEeibVuCJcg==",
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",

View File

@@ -15,6 +15,8 @@
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@splinetool/react-spline": "^4.1.0",
"@splinetool/runtime": "^1.12.50",
"@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.93.3",
"@tanstack/react-query": "^5.90.20",