diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx index 00b9e61..c090e33 100644 --- a/app/(auth)/layout.tsx +++ b/app/(auth)/layout.tsx @@ -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 ( -
+
+ {/* ========== 헤더 (홈 이동용) ========== */} +
+ {/* ========== 배경 그라디언트 레이어 ========== */}
@@ -13,8 +24,10 @@ export default function AuthLayout({
- {/* ========== 메인 콘텐츠 영역 ========== */} - {children} + {/* ========== 메인 콘텐츠 영역 (중앙 정렬) ========== */} +
+ {children} +
); } diff --git a/app/(auth)/reset-password/page.tsx b/app/(auth)/reset-password/page.tsx index 4c39f25..bc04848 100644 --- a/app/(auth)/reset-password/page.tsx +++ b/app/(auth)/reset-password/page.tsx @@ -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; diff --git a/app/(home)/page.tsx b/app/(home)/page.tsx index 929885b..db50939 100644 --- a/app/(home)/page.tsx +++ b/app/(home)/page.tsx @@ -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 (
-
- -
- - AutoTrade - - -
- {user ? ( - <> - - - - ) : ( - <> - - - - )} -
-
+
-
-
-

- 투자의 미래,{" "} - - 자동화하세요 - -

-

- AutoTrade는 최첨단 알고리즘을 통해 당신의 암호화폐 투자를 24시간 - 관리합니다. 감정에 휘둘리지 않는 투자를 경험하세요. -

-
- {user ? ( - - ) : ( - - )} - {!user && ( - - )} +
+
+
+

+ 투자의 미래,{" "} + + 자동화하세요 + +

+

+ AutoTrade는 최첨단 알고리즘을 통해 당신의 암호화폐 투자를 24시간 + 관리합니다. 감정에 휘둘리지 않는 투자를 경험하세요. +

+
+ {user ? ( + + ) : ( + + )} + {!user && ( + + )} +
+
+ +
+ {/* Spline Scene Container */} +
+ +
diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx index bebd960..f098512 100644 --- a/app/(main)/layout.tsx +++ b/app/(main)/layout.tsx @@ -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 (
-
+
{children}
diff --git a/features/home/components/spline-scene.tsx b/features/home/components/spline-scene.tsx new file mode 100644 index 0000000..53f38ca --- /dev/null +++ b/features/home/components/spline-scene.tsx @@ -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 ( +
+ {isLoading && ( +
+
+
+ )} + setIsLoading(false)} + className="h-full w-full" + /> +
+ ); +} diff --git a/features/layout/components/header.tsx b/features/layout/components/header.tsx index f25dbac..2c8cb99 100644 --- a/features/layout/components/header.tsx +++ b/features/layout/components/header.tsx @@ -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 (
- +
AutoTrade
-
{user ? ( - + <> + {showDashboardLink && ( + + )} + + ) : ( - + <> + + + )}
diff --git a/package-lock.json b/package-lock.json index 3c3cc73..ff4a2e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 19f7a04..9c5f1d2 100644 --- a/package.json +++ b/package.json @@ -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",