Feat: auth 관련페이지 header 적용
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
29
features/home/components/spline-scene.tsx
Normal file
29
features/home/components/spline-scene.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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
79
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user