Quick Start
Build your first billing page with @bettercone/ui in 5 minutes
Quick Start
This guide will walk you through creating your first billing page with @bettercone/ui in just 5 minutes.
Prerequisites: Make sure you've completed the Installation guide first.
What We'll Build
A complete billing dashboard that displays:
- Current subscription information
- Payment method management
- Invoice history
All with just a few lines of code using @bettercone/ui's pre-built components.
Create a Billing Page
Create a new page in your Next.js app:
"use client";
import { BillingDashboard } from "@bettercone/ui";
import { authClient } from "@/lib/auth-client";
export default function BillingPage() {
return (
<div className="container mx-auto py-8">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Billing</h1>
<p className="text-muted-foreground">
Manage your subscription and payment methods
</p>
</div>
<BillingDashboard authClient={authClient} />
</div>
);
}That's it! The BillingDashboard component includes everything you need for billing management.
Add Callback Handlers
Make the dashboard interactive by adding callback handlers:
"use client";
import { BillingDashboard } from "@bettercone/ui";
import { authClient } from "@/lib/auth-client";
export default function BillingPage() {
const handleManageSubscription = async (subscription, organization) => {
// Create Stripe billing portal session
const response = await fetch("/api/billing/portal", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ organizationId: organization?.id })
});
const { url } = await response.json();
window.location.href = url;
};
return (
<div className="container mx-auto py-8">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Billing</h1>
<p className="text-muted-foreground">
Manage your subscription and payment methods
</p>
</div>
<BillingDashboard
authClient={authClient}
subscriptionCardProps={{
onManageSubscription: handleManageSubscription
}}
paymentMethodCardProps={{
onManagePayment: handleManageSubscription
}}
invoiceHistoryCardProps={{
onViewInvoices: handleManageSubscription
}}
/>
</div>
);
}Create Billing Portal API Route
Create an API route to generate Stripe billing portal sessions:
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-10-28.acacia"
});
export async function POST(request: NextRequest) {
try {
const { organizationId } = await request.json();
// Get the Stripe customer ID from your database
// Example with Convex:
// const customer = await convex.query(api.billing.getCustomer, { organizationId });
// Example with Prisma:
// const org = await prisma.organization.findUnique({
// where: { id: organizationId },
// select: { stripeCustomerId: true }
// });
const customerId = "cus_xxx"; // Replace with your actual customer ID
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${process.env.NEXT_PUBLIC_APP_URL}/billing`,
});
return NextResponse.json({ url: session.url });
} catch (error) {
console.error("Error creating portal session:", error);
return NextResponse.json(
{ error: "Failed to create portal session" },
{ status: 500 }
);
}
}Test Your Billing Page
Start your development server and navigate to your billing page:
pnpm devOpen http://localhost:3000/billing in your browser.
You should see your billing dashboard! 🎉
Next Steps
Now that you have a basic billing page, explore more components:
Add a pricing page to let users upgrade or change plans:
"use client";
import { PricingDashboard } from "@bettercone/ui";
import { authClient } from "@/lib/auth-client";
import { useRouter } from "next/navigation";
export default function PricingPage() {
const router = useRouter();
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-8 text-center">
Choose Your Plan
</h1>
<PricingDashboard
authClient={authClient}
currentPlan="pro"
onSelectPlan={async (planId) => {
// Handle plan selection
const response = await fetch("/api/billing/checkout", {
method: "POST",
body: JSON.stringify({ planId })
});
const { url } = await response.json();
window.location.href = url;
}}
/>
</div>
);
}Track API usage, storage, and feature access:
"use client";
import { UsageDashboard } from "@bettercone/ui";
import { authClient } from "@/lib/auth-client";
import { useRouter } from "next/navigation";
export default function UsagePage() {
const router = useRouter();
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-8">Usage & Limits</h1>
<UsageDashboard
authClient={authClient}
apiUsageCardProps={{
onUpgrade: () => router.push("/pricing")
}}
featureAccessCardProps={{
onUpgrade: () => router.push("/pricing")
}}
/>
</div>
);
}Manage team members and seat allocation:
"use client";
import { TeamDashboard } from "@bettercone/ui";
import { authClient } from "@/lib/auth-client";
export default function TeamPage() {
const { data: activeOrg } = authClient.useActiveOrganization();
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-8">Team Management</h1>
<TeamDashboard
authClient={authClient}
organizationId={activeOrg?.id}
/>
</div>
);
}Common Patterns
Using Individual Components
Instead of using the dashboard components, you can use individual cards for more control:
import {
SubscriptionCard,
PaymentMethodCard,
InvoiceHistoryCard
} from "@bettercone/ui";
import { authClient } from "@/lib/auth-client";
export default function CustomBillingPage() {
return (
<div className="grid gap-6 md:grid-cols-2">
<SubscriptionCard
authClient={authClient}
className="md:col-span-2"
/>
<PaymentMethodCard authClient={authClient} />
<InvoiceHistoryCard authClient={authClient} />
</div>
);
}Protected Routes
Protect your pages with Better Auth:
"use client";
import { SignedIn, SignedOut } from "@daveyplate/better-auth-ui";
import { Button } from "@/components/ui/button";
import Link from "next/link";
export default function ProtectedPage() {
return (
<>
<SignedOut>
<div className="text-center py-12">
<h1 className="text-2xl font-bold mb-4">Sign in required</h1>
<Button asChild>
<Link href="/auth/sign-in">Sign In</Link>
</Button>
</div>
</SignedOut>
<SignedIn>
{/* Your protected content */}
</SignedIn>
</>
);
}Custom Styling
Customize components with className and classNames props:
import { BillingDashboard } from "@bettercone/ui";
import { authClient } from "@/lib/auth-client";
<BillingDashboard
authClient={authClient}
className="max-w-6xl mx-auto"
classNames={{
header: "bg-gradient-to-r from-blue-500 to-purple-600 text-white",
card: {
base: "border-2 hover:border-primary transition-colors",
title: "text-xl font-bold"
}
}}
/>Troubleshooting
Component not found
Problem: Cannot find module '@bettercone/ui'
Solution: Install the package:
pnpm add @bettercone/ui better-authTypeScript errors
Problem: Type errors when using components
Solution: Make sure you have Better Auth types installed:
pnpm add -D @types/better-authAuthentication issues
Problem: authClient not working
Solution: Verify your Better Auth setup in lib/auth-client.ts:
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_APP_URL
});Learn More
Congratulations! You've built your first billing page with @bettercone/ui. 🚀