Limited offer: $250 off for the first 200 customers!Claim discount

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 confirmation

Auth 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:

Active membership

Live subscription or confirmed one-time payment. has_access is true and no cancellation is scheduled.

Active until Jul 15, 2026

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 paid plan

No active access — never purchased, payment failed, or subscription fully lapsed. Use this state to prompt an upgrade.

NoteFor subscriptions, the badge reads live from the payment provider on every page load — it reflects cancellations immediately without waiting for a webhook. For one-time payments, it falls back to the 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>;
}
TipGate pages behind 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.