From 99e9235f1fa433ed65feada9fdc685bedeb6dd34 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Fri, 20 Feb 2026 16:07:17 +0100 Subject: [PATCH] feat: add resume subscription functionality in OrganizationSettings for improved user control over billing --- components/settings/OrganizationSettings.tsx | 53 ++++++++++++++------ lib/api/billing.ts | 7 +++ 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/components/settings/OrganizationSettings.tsx b/components/settings/OrganizationSettings.tsx index f6e9b65..6ea4cfc 100644 --- a/components/settings/OrganizationSettings.tsx +++ b/components/settings/OrganizationSettings.tsx @@ -16,7 +16,7 @@ import { OrganizationInvitation, Organization } from '@/lib/api/organization' -import { getSubscription, createPortalSession, getInvoices, cancelSubscription, changePlan, createCheckoutSession, SubscriptionDetails, Invoice } from '@/lib/api/billing' +import { getSubscription, createPortalSession, getInvoices, cancelSubscription, resumeSubscription, changePlan, createCheckoutSession, SubscriptionDetails, Invoice } from '@/lib/api/billing' import { TRAFFIC_TIERS, PLAN_ID_SOLO, getTierIndexForLimit, getLimitForTierIndex, getSitesLimitForPlan } from '@/lib/plans' import { getAuditLog, AuditLogEntry, GetAuditLogParams } from '@/lib/api/audit' import { getNotificationSettings, updateNotificationSettings } from '@/lib/api/notification-settings' @@ -83,6 +83,7 @@ export default function OrganizationSettings() { const [isRedirectingToPortal, setIsRedirectingToPortal] = useState(false) const [cancelLoadingAction, setCancelLoadingAction] = useState<'period_end' | 'immediate' | null>(null) const [showCancelPrompt, setShowCancelPrompt] = useState(false) + const [isResuming, setIsResuming] = useState(false) const [showChangePlanModal, setShowChangePlanModal] = useState(false) const [changePlanTierIndex, setChangePlanTierIndex] = useState(2) const [changePlanYearly, setChangePlanYearly] = useState(false) @@ -328,6 +329,19 @@ export default function OrganizationSettings() { } } + const handleResumeSubscription = async () => { + setIsResuming(true) + try { + await resumeSubscription() + toast.success('Subscription will continue. Cancellation has been undone.') + loadSubscription() + } catch (error: any) { + toast.error(getAuthErrorMessage(error) || error.message || 'Failed to resume subscription') + } finally { + setIsResuming(false) + } + } + const openChangePlanModal = () => { if (subscription?.pageview_limit != null && subscription.pageview_limit > 0) { setChangePlanTierIndex(getTierIndexForLimit(subscription.pageview_limit)) @@ -813,19 +827,30 @@ export default function OrganizationSettings() { {/* Cancel-at-period-end notice */} {subscription.cancel_at_period_end && ( -
-

- Your subscription will end on{' '} - - {(() => { - const d = subscription.current_period_end ? new Date(subscription.current_period_end as string) : null - return d && !Number.isNaN(d.getTime()) ? d.toLocaleDateString(undefined, { month: 'long', day: 'numeric', year: 'numeric' }) : '—' - })()} - -

-

- You keep full access until then. Your data is retained for 30 days after. Use "Change plan" to resubscribe. -

+
+
+

+ Your subscription will end on{' '} + + {(() => { + const d = subscription.current_period_end ? new Date(subscription.current_period_end as string) : null + return d && !Number.isNaN(d.getTime()) ? d.toLocaleDateString(undefined, { month: 'long', day: 'numeric', year: 'numeric' }) : '—' + })()} + +

+

+ You keep full access until then. Your data is retained for 30 days after. Use "Change plan" to resubscribe. +

+
+
)} diff --git a/lib/api/billing.ts b/lib/api/billing.ts index 1c6cda4..a337a4b 100644 --- a/lib/api/billing.ts +++ b/lib/api/billing.ts @@ -74,6 +74,13 @@ export async function cancelSubscription(params?: CancelSubscriptionParams): Pro }) } +/** Clears cancel_at_period_end so the subscription continues past the current period. */ +export async function resumeSubscription(): Promise<{ ok: boolean }> { + return await billingFetch<{ ok: boolean }>('/api/billing/resume', { + method: 'POST', + }) +} + export interface ChangePlanParams { plan_id: string interval: string