diff --git a/apps/rowboat/app/billing/app.tsx b/apps/rowboat/app/billing/app.tsx index b79c3eda..660ca18e 100644 --- a/apps/rowboat/app/billing/app.tsx +++ b/apps/rowboat/app/billing/app.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Progress, Badge } from "@heroui/react"; +import { Progress, Badge, Chip } from "@heroui/react"; import { Button } from "@/components/ui/button"; import { Label } from "@/app/lib/components/label"; import { Customer, UsageResponse, UsageType } from "@/app/lib/types/billing_types"; @@ -32,9 +32,19 @@ interface BillingPageProps { usage: z.infer; } +function getDisplayStatus(status: string | undefined) { + if (status === "active") { + return "Active"; + } else if (status === "past_due") { + return "Past Due!"; + } else { + return "Inactive"; + } +} + export function BillingPage({ customer, usage }: BillingPageProps) { const plan = customer.subscriptionPlan || "free"; - const isActive = customer.subscriptionActive || false; + const displayStatus = getDisplayStatus(customer.subscriptionStatus); const planInfo = planDetails[plan]; async function handleManageSubscription() { @@ -77,13 +87,13 @@ export function BillingPage({ customer, usage }: BillingPageProps) { )}> {planInfo.name} - - {isActive ? "Active" : "Inactive"} - + {displayStatus} +
diff --git a/apps/rowboat/app/lib/billing.ts b/apps/rowboat/app/lib/billing.ts index c23a0706..f99e3400 100644 --- a/apps/rowboat/app/lib/billing.ts +++ b/apps/rowboat/app/lib/billing.ts @@ -17,8 +17,9 @@ const GUEST_BILLING_CUSTOMER = { name: "Guest", email: "guest@rowboatlabs.com", stripeCustomerId: "guest", + stripeSubscriptionId: "test", subscriptionPlan: "free" as const, - subscriptionActive: true, + subscriptionStatus: "active" as const, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; @@ -190,7 +191,7 @@ export async function getPrices(): Promise> { } export async function updateSubscriptionPlan(customerId: string, request: z.infer): Promise { - const response = await fetch(`${BILLING_API_URL}/api/customers/${customerId}/update-subscription-plan`, { + const response = await fetch(`${BILLING_API_URL}/api/customers/${customerId}/update-sub-session`, { method: 'POST', headers: { 'Authorization': `Bearer ${BILLING_API_KEY}`, @@ -296,8 +297,8 @@ export async function requireBillingCustomer(): Promise>> { const billingCustomer = await requireBillingCustomer(); - if (USE_BILLING && !billingCustomer?.subscriptionActive) { - redirect('/billing/checkout'); + if (USE_BILLING && billingCustomer.subscriptionStatus !== "active" && billingCustomer.subscriptionStatus !== "past_due") { + redirect('/billing'); } return billingCustomer; } diff --git a/apps/rowboat/app/lib/types/billing_types.ts b/apps/rowboat/app/lib/types/billing_types.ts index 21e1a37e..f258b65e 100644 --- a/apps/rowboat/app/lib/types/billing_types.ts +++ b/apps/rowboat/app/lib/types/billing_types.ts @@ -13,8 +13,9 @@ export const Customer = z.object({ userId: z.string(), email: z.string(), stripeCustomerId: z.string(), + stripeSubscriptionId: z.string().optional(), subscriptionPlan: SubscriptionPlan.optional(), - subscriptionActive: z.boolean().optional(), + subscriptionStatus: z.enum([ 'active', 'past_due' ]).optional(), createdAt: z.string().datetime(), updatedAt: z.string().datetime(), subscriptionPlanUpdatedAt: z.string().datetime().optional(), diff --git a/apps/rowboat/app/projects/layout/components/app-layout.tsx b/apps/rowboat/app/projects/layout/components/app-layout.tsx index 4088c507..38fc77f2 100644 --- a/apps/rowboat/app/projects/layout/components/app-layout.tsx +++ b/apps/rowboat/app/projects/layout/components/app-layout.tsx @@ -1,7 +1,10 @@ 'use client'; -import { ReactNode, useState } from 'react'; +import { ReactNode, useEffect, useState } from 'react'; import Sidebar from './sidebar'; import { usePathname } from 'next/navigation'; +import { getCustomer } from '../../../actions/billing_actions'; +import { Button } from '@heroui/react'; +import { useRouter } from 'next/navigation'; interface AppLayoutProps { children: ReactNode; @@ -11,14 +14,31 @@ interface AppLayoutProps { } export default function AppLayout({ children, useRag = false, useAuth = false, useBilling = false }: AppLayoutProps) { + const router = useRouter(); const [sidebarCollapsed, setSidebarCollapsed] = useState(true); + const [billingPastDue, setBillingPastDue] = useState(false); const pathname = usePathname(); - let projectId: string|null = null; + let projectId: string | null = null; if (pathname.startsWith('/projects')) { projectId = pathname.split('/')[2]; } + useEffect(() => { + async function checkBillingPastDue() { + const billingCustomer = await getCustomer(); + if (billingCustomer.subscriptionStatus === "past_due") { + setBillingPastDue(true); + } + } + + if (!useBilling) { + return; + } + + checkBillingPastDue(); + }, [useBilling]); + // Layout with sidebar for all routes return (
@@ -35,8 +55,24 @@ export default function AppLayout({ children, useRag = false, useAuth = false, u
{/* Main content area */} -
- {children} +
+ {billingPastDue &&
+
+ Your subscription is past due. Please update your payment information to avoid losing access to your projects. + +
+
} +
+ {children} +
);