feat: billing/invoice details (full name, address, phone) under Settings > Billing (v0.76.0)
Adds a new user_billing_details table (1:1 with users, separate from the display name) with a form and GET/PUT API route. Full name is required by the form/API; address and phone stay optional. Not wired into Stripe invoicing yet — just captured for future use. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import type { Metadata } from "next";
|
||||
import { headers } from "next/headers";
|
||||
import { auth } from "@/lib/auth/server";
|
||||
import { db, users, tierDefinitions, tierEnum, userUsage, eq, and } from "@epicure/db";
|
||||
import { db, users, tierDefinitions, tierEnum, userUsage, userBillingDetails, eq, and } from "@epicure/db";
|
||||
import { getRecipeCount, getStorageUsedMb, UNLIMITED } from "@/lib/tiers";
|
||||
import { BillingPlanCards } from "@/components/settings/billing-plan-cards";
|
||||
import { ManageBillingButton } from "@/components/settings/manage-billing-button";
|
||||
import { UsageQuotaSection } from "@/components/settings/usage-quota-section";
|
||||
import { BillingDetailsForm } from "@/components/settings/billing-details-form";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export const metadata: Metadata = {};
|
||||
@@ -23,7 +24,7 @@ export default async function BillingPage({ searchParams }: { searchParams: Prom
|
||||
|
||||
const currentMonth = new Date().toISOString().slice(0, 7);
|
||||
|
||||
const [dbUser, allTierDefs, usage, recipeCount, storageUsedMb] = await Promise.all([
|
||||
const [dbUser, allTierDefs, usage, recipeCount, storageUsedMb, billingDetails] = await Promise.all([
|
||||
db.query.users.findFirst({
|
||||
where: eq(users.id, session.user.id),
|
||||
columns: { tier: true, subscriptionStatus: true, currentPeriodEnd: true, stripeCustomerId: true },
|
||||
@@ -32,6 +33,7 @@ export default async function BillingPage({ searchParams }: { searchParams: Prom
|
||||
db.query.userUsage.findFirst({ where: and(eq(userUsage.userId, session.user.id), eq(userUsage.month, currentMonth)) }),
|
||||
getRecipeCount(session.user.id),
|
||||
getStorageUsedMb(session.user.id),
|
||||
db.query.userBillingDetails.findFirst({ where: eq(userBillingDetails.userId, session.user.id) }),
|
||||
]);
|
||||
|
||||
const currentTier = dbUser?.tier ?? "free";
|
||||
@@ -100,6 +102,14 @@ export default async function BillingPage({ searchParams }: { searchParams: Prom
|
||||
<UsageQuotaSection metrics={usageMetrics} />
|
||||
</section>
|
||||
|
||||
<section className="rounded-xl border p-6 space-y-4">
|
||||
<div>
|
||||
<h2 className="font-semibold text-lg">Billing details</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1">Used on invoices. Full name is required; address and phone are optional.</p>
|
||||
</div>
|
||||
<BillingDetailsForm initialDetails={billingDetails ?? null} />
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="font-semibold text-lg">Plans</h2>
|
||||
<BillingPlanCards currentTier={currentTier} plans={plans} />
|
||||
|
||||
Reference in New Issue
Block a user