feat: add resume subscription functionality in OrganizationSettings for improved user control over billing

This commit is contained in:
Usman Baig
2026-02-20 16:07:17 +01:00
parent 53ed7493c6
commit 99e9235f1f
2 changed files with 46 additions and 14 deletions

View File

@@ -16,7 +16,7 @@ import {
OrganizationInvitation, OrganizationInvitation,
Organization Organization
} from '@/lib/api/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 { TRAFFIC_TIERS, PLAN_ID_SOLO, getTierIndexForLimit, getLimitForTierIndex, getSitesLimitForPlan } from '@/lib/plans'
import { getAuditLog, AuditLogEntry, GetAuditLogParams } from '@/lib/api/audit' import { getAuditLog, AuditLogEntry, GetAuditLogParams } from '@/lib/api/audit'
import { getNotificationSettings, updateNotificationSettings } from '@/lib/api/notification-settings' import { getNotificationSettings, updateNotificationSettings } from '@/lib/api/notification-settings'
@@ -83,6 +83,7 @@ export default function OrganizationSettings() {
const [isRedirectingToPortal, setIsRedirectingToPortal] = useState(false) const [isRedirectingToPortal, setIsRedirectingToPortal] = useState(false)
const [cancelLoadingAction, setCancelLoadingAction] = useState<'period_end' | 'immediate' | null>(null) const [cancelLoadingAction, setCancelLoadingAction] = useState<'period_end' | 'immediate' | null>(null)
const [showCancelPrompt, setShowCancelPrompt] = useState(false) const [showCancelPrompt, setShowCancelPrompt] = useState(false)
const [isResuming, setIsResuming] = useState(false)
const [showChangePlanModal, setShowChangePlanModal] = useState(false) const [showChangePlanModal, setShowChangePlanModal] = useState(false)
const [changePlanTierIndex, setChangePlanTierIndex] = useState(2) const [changePlanTierIndex, setChangePlanTierIndex] = useState(2)
const [changePlanYearly, setChangePlanYearly] = useState(false) 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 = () => { const openChangePlanModal = () => {
if (subscription?.pageview_limit != null && subscription.pageview_limit > 0) { if (subscription?.pageview_limit != null && subscription.pageview_limit > 0) {
setChangePlanTierIndex(getTierIndexForLimit(subscription.pageview_limit)) setChangePlanTierIndex(getTierIndexForLimit(subscription.pageview_limit))
@@ -813,7 +827,8 @@ export default function OrganizationSettings() {
{/* Cancel-at-period-end notice */} {/* Cancel-at-period-end notice */}
{subscription.cancel_at_period_end && ( {subscription.cancel_at_period_end && (
<div className="p-4 bg-amber-50 dark:bg-amber-900/10 border border-amber-200 dark:border-amber-800 rounded-2xl"> <div className="p-4 bg-amber-50 dark:bg-amber-900/10 border border-amber-200 dark:border-amber-800 rounded-2xl flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
<div>
<p className="text-sm font-medium text-amber-800 dark:text-amber-200"> <p className="text-sm font-medium text-amber-800 dark:text-amber-200">
Your subscription will end on{' '} Your subscription will end on{' '}
<span className="font-semibold"> <span className="font-semibold">
@@ -827,6 +842,16 @@ export default function OrganizationSettings() {
You keep full access until then. Your data is retained for 30 days after. Use "Change plan" to resubscribe. You keep full access until then. Your data is retained for 30 days after. Use "Change plan" to resubscribe.
</p> </p>
</div> </div>
<Button
variant="secondary"
onClick={handleResumeSubscription}
disabled={isResuming}
isLoading={isResuming}
className="shrink-0"
>
Keep my subscription
</Button>
</div>
)} )}
{/* Plan & Usage card */} {/* Plan & Usage card */}

View File

@@ -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 { export interface ChangePlanParams {
plan_id: string plan_id: string
interval: string interval: string