@bettercone/ui
Getting Started

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:

app/billing/page.tsx
"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:

app/billing/page.tsx
"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:

app/api/billing/portal/route.ts
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 dev

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

app/pricing/page.tsx
"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>
  );
}

View Pricing Components →

Track API usage, storage, and feature access:

app/usage/page.tsx
"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>
  );
}

View Usage Components →

Manage team members and seat allocation:

app/team/page.tsx
"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>
  );
}

View Team Components →

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-auth

TypeScript errors

Problem: Type errors when using components

Solution: Make sure you have Better Auth types installed:

pnpm add -D @types/better-auth

Authentication 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

📖 Component Documentation

Explore all available components and their APIs

View Components →

🎨 Customization Guide

Learn how to customize components to match your brand

Customize Components →

💳 Stripe Integration

Set up Stripe billing and subscriptions

Configure Stripe →

🌍 Internationalization

Add multi-language support to your components

Setup i18n →


Congratulations! You've built your first billing page with @bettercone/ui. 🚀