Account Management

User account pages with profile, subscription, and billing management.

Profile

  • • Profile information
  • • Avatar from OAuth
  • • Email verification status
  • • Account creation date

Billing

  • • Subscription details
  • • Usage tracking
  • • Stripe billing portal
  • • Plan upgrade/downgrade

Account Page

src/app/account/page.tsx
1// src/app/account/page.tsx
2import { auth } from "~/auth";
3import { db } from "~/server/db";
4import { redirect } from "next/navigation";
5
6export default async function AccountPage() {
7 const session = await auth();
8 if (!session?.user) redirect("/auth/signin");
9
10 const [subscription, user] = await Promise.all([
11 db.subscription.findUnique({ where: { userId: session.user.id } }),
12 db.user.findUnique({
13 where: { id: session.user.id },
14 select: { name: true, email: true, image: true, createdAt: true },
15 }),
16 ]);
17
18 return (
19 <div className="container max-w-3xl py-8">
20 {/* Profile Section */}
21 {/* Subscription Section */}
22 </div>
23 );
24}

Account Messages

Display messages after Stripe redirects:

src/components/AccountMessages.tsx
1"use client";
2
3import { useSearchParams } from "next/navigation";
4
5export function AccountMessages() {
6 const searchParams = useSearchParams();
7 const sessionId = searchParams.get("session_id");
8 const message = searchParams.get("message");
9
10 if (sessionId) {
11 return (
12 <div className="rounded-xl bg-green-500/10 p-4 text-green-700">
13 Welcome to Pro! Your subscription is now active.
14 </div>
15 );
16 }
17
18 if (message === "subscription_canceled") {
19 return (
20 <div className="rounded-xl bg-amber-500/10 p-4 text-amber-700">
21 Your subscription has been canceled.
22 </div>
23 );
24 }
25
26 return null;
27}

Subscription Management

1"use client";
2
3import { useState } from "react";
4import { createBillingPortalSession } from "~/server/actions/stripe";
5
6export function SubscriptionManagement({ subscription }) {
7 const [loading, setLoading] = useState(false);
8
9 const handleBillingPortal = async () => {
10 setLoading(true);
11 const result = await createBillingPortalSession();
12 if (result.url) window.open(result.url, "_blank");
13 setLoading(false);
14 };
15
16 return (
17 <button onClick={handleBillingPortal} disabled={loading}>
18 {loading ? "Loading..." : "Manage Billing"}
19 </button>
20 );
21}

Billing Portal

src/server/actions/stripe.ts
1"use server";
2
3import { auth } from "~/auth";
4import { stripe } from "~/server/lib/stripe";
5import { db } from "~/server/db";
6
7export async function createBillingPortalSession() {
8 const session = await auth();
9 if (!session?.user?.id) return { error: "Not authenticated" };
10
11 const subscription = await db.subscription.findUnique({
12 where: { userId: session.user.id },
13 select: { stripeCustomerId: true },
14 });
15
16 if (!subscription?.stripeCustomerId) {
17 return { error: "No billing information found" };
18 }
19
20 const portalSession = await stripe.billingPortal.sessions.create({
21 customer: subscription.stripeCustomerId,
22 return_url: `${process.env.NEXTAUTH_URL}/account`,
23 });
24
25 return { url: portalSession.url };
26}

Users can update payment methods, view invoices, and cancel subscriptions through Stripe's hosted portal.

Best Practices

UX

  • • Clear status indicators
  • • Easy billing portal access
  • • Transparent usage tracking
  • • Confirmation for destructive actions

Technical

  • • Validate server-side
  • • Handle loading states
  • • Secure session management
  • • Proper error handling

Next: Admin Dashboard

Analytics and user management components

Continue →