Dashboard
A protected member area that shows access status, plan details, profile settings, and account management.
File structure
src/app/dashboard/
layout.tsx ← auth guard
page.tsx ← main dashboard view
ProfileForm.tsx ← update name and avatar
DeleteAccount.tsx ← account deletion with confirmationAuth guard
layout.tsx checks the Supabase session server-side on every request. If no session exists, the visitor is immediately redirected to config.auth.loginUrl. Every page nested under /dashboard/* is protected automatically — no additional guards needed on individual pages.
Access status badge
The main page shows a status badge in the user identity card. It has three states, determined by live data fetched directly from Stripe or Lemon Squeezy on each request:
Live subscription or confirmed one-time payment. has_access is true and no cancellation is scheduled.
Subscription canceled but the paid period hasn't ended yet. The user still has full access until the date shown. After that date the badge switches to "No paid plan".
No active access — never purchased, payment failed, or subscription fully lapsed. Use this state to prompt an upgrade.
has_access column in the database (set by the webhook).Plan label
Below the badge, the dashboard shows the active plan name and price — for example "Advanced ($149/mo)" or "Starter (Lifetime)". This is resolved live from Stripe or from the Lemon Squeezy config. When the user has no active access, it shows "No active plan" regardless of what is stored in plan_id.
Billing portal
The Manage Billing button only appears for subscription plans — not for one-time purchases, which have no recurring billing to cancel or update. Clicking it redirects the user to the Stripe or Lemon Squeezy customer portal, where they can update their payment method, view invoices, or cancel. After finishing, the provider returns them to the dashboard.
ProfileForm
Lets the user update their display name and avatar URL. Changes are written directly to the profiles table in Supabase. Add or remove fields in src/app/dashboard/ProfileForm.tsx to match whatever your app needs to store.
DeleteAccount
A two-step confirmation flow that permanently deletes the user from Supabase Auth and removes their row from the profiles table. The deletion is handled by the API route at /api/account/delete.
Adding dashboard pages
Any file created inside src/app/dashboard/ automatically inherits the auth guard from the layout:
// src/app/dashboard/settings/page.tsx
import { createClient } from "@/libs/supabase/server";
import { redirect } from "next/navigation";
export default async function SettingsPage() {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) redirect("/signin");
return <div>Settings</div>;
}has_access to show a paywall for free users. Fetch the profile row, check the flag, and redirect to /#pricing if it is false. This is the primary access control mechanism for your product.