diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 1b2299c..49cd1b6 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -3,10 +3,13 @@ import { useSession } from "@/lib/auth-client"; import { UserProfile } from "@/components/auth/user-profile"; import { Button } from "@/components/ui/button"; +import { Lock } from "lucide-react"; +import { useDiagnostics } from "@/hooks/use-diagnostics"; import Link from "next/link"; export default function DashboardPage() { const { data: session, isPending } = useSession(); + const { isAiReady, loading: diagnosticsLoading } = useDiagnostics(); if (isPending) { return ( @@ -19,7 +22,14 @@ export default function DashboardPage() { if (!session) { return (
-
+
+
+ +

Protected Page

+

+ You need to sign in to access the dashboard +

+
@@ -38,9 +48,15 @@ export default function DashboardPage() {

Start a conversation with AI using the Vercel AI SDK

- + {(diagnosticsLoading || !isAiReady) ? ( + + ) : ( + + )}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 60afe66..bc4806d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -14,8 +14,8 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Next.js Full-Stack Boilerplate", + description: "Complete Next.js starter template with authentication, database, AI integration, and modern tooling by Leon van Zyl", }; export default function RootLayout({ diff --git a/src/app/page.tsx b/src/app/page.tsx index ff23768..98455ea 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,8 +1,12 @@ +"use client"; + import Link from "next/link"; import { Button } from "@/components/ui/button"; import { SetupChecklist } from "@/components/setup-checklist"; +import { useDiagnostics } from "@/hooks/use-diagnostics"; export default function Home() { + const { isAuthReady, isAiReady, loading } = useDiagnostics(); return (
@@ -76,17 +80,42 @@ export default function Home() {

3. Try the features

- - + {(loading || !isAuthReady) ? ( + + ) : ( + + )} + {(loading || !isAiReady) ? ( + + ) : ( + + )}
@@ -103,7 +132,21 @@ export default function Home() {
- Built with Next.js, Better Auth, Drizzle ORM, and Vercel AI SDK +

+ Boilerplate template by Leon van Zyl +

+

+ Visit{" "} + + @leonvanzyl on YouTube + + {" "}for tutorials on using this template +

diff --git a/src/components/auth/sign-out-button.tsx b/src/components/auth/sign-out-button.tsx index be1631d..484ea6b 100644 --- a/src/components/auth/sign-out-button.tsx +++ b/src/components/auth/sign-out-button.tsx @@ -1,33 +1,31 @@ -"use client" +"use client"; -import { signOut, useSession } from "@/lib/auth-client" -import { Button } from "@/components/ui/button" +import { signOut, useSession } from "@/lib/auth-client"; +import { Button } from "@/components/ui/button"; +import { useRouter } from "next/navigation"; export function SignOutButton() { - const { data: session, isPending } = useSession() + const { data: session, isPending } = useSession(); + const router = useRouter(); if (isPending) { - return + return ; } if (!session) { - return null + return null; } return ( - ) -} \ No newline at end of file + ); +} diff --git a/src/components/auth/user-profile.tsx b/src/components/auth/user-profile.tsx index ec8ff0a..56fecf4 100644 --- a/src/components/auth/user-profile.tsx +++ b/src/components/auth/user-profile.tsx @@ -21,25 +21,24 @@ export function UserProfile() { } return ( -
-
- - - - {( - session.user?.name?.[0] || - session.user?.email?.[0] || - "U" - ).toUpperCase()} - - -

{session.user?.name}

-

{session.user?.email}

-
+
+ + Welcome {session.user?.name} + + + + + {( + session.user?.name?.[0] || + session.user?.email?.[0] || + "U" + ).toUpperCase()} + +
); diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index 21439d3..16b21b8 100644 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -10,7 +10,7 @@ export function SiteHeader() { href="/" className="bg-clip-text text-transparent [background-image:linear-gradient(90deg,color-mix(in_oklab,var(--primary)_85%,white_0%),color-mix(in_oklab,var(--primary)_50%,white_0%))] hover:opacity-90" > - Next.js Starter Kit + Next.js Boilerplate diff --git a/src/hooks/use-diagnostics.ts b/src/hooks/use-diagnostics.ts new file mode 100644 index 0000000..606b34b --- /dev/null +++ b/src/hooks/use-diagnostics.ts @@ -0,0 +1,65 @@ +"use client"; + +import { useEffect, useState } from "react"; + +type DiagnosticsResponse = { + timestamp: string; + env: { + DATABASE_URL: boolean; + BETTER_AUTH_SECRET: boolean; + GOOGLE_CLIENT_ID: boolean; + GOOGLE_CLIENT_SECRET: boolean; + OPENAI_API_KEY: boolean; + NEXT_PUBLIC_APP_URL: boolean; + }; + database: { + connected: boolean; + schemaApplied: boolean; + error?: string; + }; + auth: { + configured: boolean; + routeResponding: boolean | null; + }; + ai: { + configured: boolean; + }; + overallStatus: "ok" | "warn" | "error"; +}; + +export function useDiagnostics() { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + async function fetchDiagnostics() { + setLoading(true); + setError(null); + try { + const res = await fetch("/api/diagnostics", { cache: "no-store" }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const json = (await res.json()) as DiagnosticsResponse; + setData(json); + } catch (e) { + setError(e instanceof Error ? e.message : "Failed to load diagnostics"); + } finally { + setLoading(false); + } + } + + useEffect(() => { + fetchDiagnostics(); + }, []); + + const isAuthReady = data?.auth.configured && data?.database.connected && data?.database.schemaApplied; + const isAiReady = data?.ai.configured; + + return { + data, + loading, + error, + refetch: fetchDiagnostics, + isAuthReady: Boolean(isAuthReady), + isAiReady: Boolean(isAiReady), + }; +} \ No newline at end of file