mirror of https://github.com/kortix-ai/suna.git
billing modal
This commit is contained in:
parent
ad48d9ebe8
commit
008c8944bc
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
import { Brain, Clock, Crown, Sparkles, Zap } from 'lucide-react';
|
||||
import { UpgradeDialog as UnifiedUpgradeDialog } from '@/components/ui/upgrade-dialog';
|
||||
import { BillingModal } from '@/components/billing/billing-modal';
|
||||
|
||||
interface UpgradeDialogProps {
|
||||
open: boolean;
|
||||
|
@ -10,72 +10,91 @@ interface UpgradeDialogProps {
|
|||
}
|
||||
|
||||
export function UpgradeDialog({ open, onOpenChange, onDismiss }: UpgradeDialogProps) {
|
||||
const router = useRouter();
|
||||
const [showBillingModal, setShowBillingModal] = useState(false);
|
||||
|
||||
const handleUpgradeClick = () => {
|
||||
router.push('/settings/billing');
|
||||
// Close the upgrade dialog and open the billing modal
|
||||
onOpenChange(false);
|
||||
setShowBillingModal(true);
|
||||
localStorage.setItem('suna_upgrade_dialog_displayed', 'true');
|
||||
};
|
||||
|
||||
const handleBillingModalClose = (isOpen: boolean) => {
|
||||
setShowBillingModal(isOpen);
|
||||
if (!isOpen) {
|
||||
// If billing modal is closed, we can consider the upgrade flow complete
|
||||
onDismiss();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<UnifiedUpgradeDialog
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
icon={Crown}
|
||||
title="Unlock the Full Suna Experience"
|
||||
description="You're currently using Suna's free tier with limited capabilities. Upgrade now to access our most powerful AI model."
|
||||
theme="primary"
|
||||
size="sm"
|
||||
preventOutsideClick={true}
|
||||
actions={[
|
||||
{
|
||||
label: "Maybe Later",
|
||||
onClick: onDismiss,
|
||||
variant: "outline"
|
||||
},
|
||||
{
|
||||
label: "Upgrade Now",
|
||||
onClick: handleUpgradeClick,
|
||||
icon: Sparkles
|
||||
}
|
||||
]}
|
||||
>
|
||||
<div className="py-4">
|
||||
<h3 className="text-sm font-medium text-slate-700 dark:text-slate-300 mb-3">Pro Benefits</h3>
|
||||
<>
|
||||
<UnifiedUpgradeDialog
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
icon={Crown}
|
||||
title="Unlock the Full Suna Experience"
|
||||
description="You're currently using Suna's free tier with limited capabilities. Upgrade now to access our most powerful AI model."
|
||||
theme="primary"
|
||||
size="sm"
|
||||
preventOutsideClick={true}
|
||||
actions={[
|
||||
{
|
||||
label: "Maybe Later",
|
||||
onClick: onDismiss,
|
||||
variant: "outline"
|
||||
},
|
||||
{
|
||||
label: "Upgrade Now",
|
||||
onClick: handleUpgradeClick,
|
||||
icon: Sparkles
|
||||
}
|
||||
]}
|
||||
>
|
||||
<div className="py-4">
|
||||
<h3 className="text-sm font-medium text-slate-700 dark:text-slate-300 mb-3">Pro Benefits</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start">
|
||||
<div className="rounded-full bg-secondary/10 p-2 flex-shrink-0 mt-0.5">
|
||||
<Brain className="h-4 w-4 text-secondary" />
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start">
|
||||
<div className="rounded-full bg-secondary/10 p-2 flex-shrink-0 mt-0.5">
|
||||
<Brain className="h-4 w-4 text-secondary" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h4 className="text-sm font-medium text-slate-900 dark:text-slate-100">Advanced AI Models</h4>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">Get access to advanced models suited for complex tasks</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h4 className="text-sm font-medium text-slate-900 dark:text-slate-100">Advanced AI Models</h4>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">Get access to advanced models suited for complex tasks</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start">
|
||||
<div className="rounded-full bg-secondary/10 p-2 flex-shrink-0 mt-0.5">
|
||||
<Zap className="h-4 w-4 text-secondary" />
|
||||
<div className="flex items-start">
|
||||
<div className="rounded-full bg-secondary/10 p-2 flex-shrink-0 mt-0.5">
|
||||
<Zap className="h-4 w-4 text-secondary" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h4 className="text-sm font-medium text-slate-900 dark:text-slate-100">Faster Responses</h4>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">Get access to faster models that breeze through your tasks</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h4 className="text-sm font-medium text-slate-900 dark:text-slate-100">Faster Responses</h4>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">Get access to faster models that breeze through your tasks</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start">
|
||||
<div className="rounded-full bg-secondary/10 p-2 flex-shrink-0 mt-0.5">
|
||||
<Clock className="h-4 w-4 text-secondary" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h4 className="text-sm font-medium text-slate-900 dark:text-slate-100">Higher Usage Limits</h4>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">Enjoy more conversations and longer run durations</p>
|
||||
<div className="flex items-start">
|
||||
<div className="rounded-full bg-secondary/10 p-2 flex-shrink-0 mt-0.5">
|
||||
<Clock className="h-4 w-4 text-secondary" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h4 className="text-sm font-medium text-slate-900 dark:text-slate-100">Higher Usage Limits</h4>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">Enjoy more conversations and longer run durations</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UnifiedUpgradeDialog>
|
||||
</UnifiedUpgradeDialog>
|
||||
|
||||
{/* Billing Modal */}
|
||||
<BillingModal
|
||||
open={showBillingModal}
|
||||
onOpenChange={handleBillingModalClose}
|
||||
returnUrl={typeof window !== 'undefined' ? window?.location?.href || '/' : '/'}
|
||||
showUsageLimitAlert={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -11,6 +11,7 @@ import { createClient } from '@/lib/supabase/client';
|
|||
import { User, Session } from '@supabase/supabase-js';
|
||||
import { SupabaseClient } from '@supabase/supabase-js';
|
||||
import { checkAndInstallSunaAgent } from '@/lib/utils/install-suna-agent';
|
||||
import { clearUserLocalStorage } from '@/lib/utils/clear-local-storage';
|
||||
|
||||
type AuthContextType = {
|
||||
supabase: SupabaseClient;
|
||||
|
@ -57,6 +58,8 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||
}
|
||||
break;
|
||||
case 'SIGNED_OUT':
|
||||
// Clear local storage when user is signed out (handles all logout scenarios)
|
||||
clearUserLocalStorage();
|
||||
break;
|
||||
case 'TOKEN_REFRESHED':
|
||||
break;
|
||||
|
@ -75,6 +78,8 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||
const signOut = async () => {
|
||||
try {
|
||||
await supabase.auth.signOut();
|
||||
// Clear local storage after successful sign out
|
||||
clearUserLocalStorage();
|
||||
} catch (error) {
|
||||
console.error('❌ Error signing out:', error);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
useUnenrollFactor,
|
||||
} from '@/hooks/react-query/phone-verification';
|
||||
import { signOut } from '@/app/auth/actions';
|
||||
import { clearUserLocalStorage } from '@/lib/utils/clear-local-storage';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { LogOut, Loader2 } from 'lucide-react';
|
||||
|
@ -194,6 +195,8 @@ export function PhoneVerificationPage({
|
|||
|
||||
const signOutMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
// Clear local storage before sign out
|
||||
clearUserLocalStorage();
|
||||
await signOut().catch(() => void 0);
|
||||
window.location.href = '/';
|
||||
},
|
||||
|
|
|
@ -14,6 +14,7 @@ import Link from 'next/link';
|
|||
import { UserIcon } from 'lucide-react';
|
||||
import { signOut } from '@/app/auth/actions';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { clearUserLocalStorage } from '@/lib/utils/clear-local-storage';
|
||||
|
||||
interface ClientUserAccountButtonProps {
|
||||
userName?: string;
|
||||
|
@ -27,6 +28,8 @@ export default function ClientUserAccountButton({
|
|||
const router = useRouter();
|
||||
|
||||
const handleSignOut = async () => {
|
||||
// Clear local storage before sign out
|
||||
clearUserLocalStorage();
|
||||
await signOut();
|
||||
router.refresh();
|
||||
};
|
||||
|
|
|
@ -237,223 +237,206 @@ export function BillingModal({ open, onOpenChange, returnUrl = typeof window !==
|
|||
<DialogTitle>Upgrade Your Plan</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{isLoading || authLoading ? (
|
||||
<div className="space-y-4">
|
||||
<Skeleton className="h-20 w-full" />
|
||||
<Skeleton className="h-40 w-full" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="p-4 bg-destructive/10 border border-destructive/20 rounded-lg text-center">
|
||||
<p className="text-sm text-destructive">Error loading billing status: {error}</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Usage Limit Alert */}
|
||||
{showUsageLimitAlert && (
|
||||
<div className="mb-6">
|
||||
<div className="flex items-start p-3 sm:p-4 bg-destructive/5 border border-destructive/50 rounded-lg">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 mt-0.5">
|
||||
<Zap className="w-4 h-4 sm:w-5 sm:h-5 text-destructive" />
|
||||
</div>
|
||||
<div className="text-xs sm:text-sm min-w-0">
|
||||
<p className="font-medium text-destructive">Usage Limit Reached</p>
|
||||
<p className="text-destructive break-words">
|
||||
Your current plan has been exhausted for this billing period.
|
||||
</p>
|
||||
</div>
|
||||
<>
|
||||
{/* Usage Limit Alert */}
|
||||
{showUsageLimitAlert && (
|
||||
<div className="mb-6">
|
||||
<div className="flex items-start p-3 sm:p-4 bg-destructive/5 border border-destructive/50 rounded-lg">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 mt-0.5">
|
||||
<Zap className="w-4 h-4 sm:w-5 sm:h-5 text-destructive" />
|
||||
</div>
|
||||
<div className="text-xs sm:text-sm min-w-0">
|
||||
<p className="font-medium text-destructive">Usage Limit Reached</p>
|
||||
<p className="text-destructive break-words">
|
||||
Your current plan has been exhausted for this billing period.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{subscriptionData && (
|
||||
<div className="mb-6">
|
||||
<div className="rounded-lg border bg-background p-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-foreground/90">
|
||||
Agent Usage This Month
|
||||
</span>
|
||||
<span className="text-sm font-medium">
|
||||
${subscriptionData.current_usage?.toFixed(2) || '0'} /{' '}
|
||||
${subscriptionData.cost_limit || '0'}
|
||||
</span>
|
||||
</div>
|
||||
{/* Usage section - show loading state or actual data */}
|
||||
{isLoading || authLoading ? (
|
||||
<div className="mb-6">
|
||||
<div className="rounded-lg border bg-background p-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<Skeleton className="h-4 w-40" />
|
||||
<Skeleton className="h-4 w-24" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Credit Balance Display - Only show for users who can purchase credits
|
||||
{subscriptionData?.can_purchase_credits && (
|
||||
<div className="mb-6">
|
||||
<CreditBalanceDisplay
|
||||
balance={subscriptionData.credit_balance || 0}
|
||||
canPurchase={subscriptionData.can_purchase_credits}
|
||||
onPurchaseClick={() => setShowCreditPurchaseModal(true)}
|
||||
/>
|
||||
</div>
|
||||
) : subscriptionData && (
|
||||
<div className="mb-6">
|
||||
<div className="rounded-lg border bg-background p-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-foreground/90">
|
||||
Agent Usage This Month
|
||||
</span>
|
||||
<span className="text-sm font-medium">
|
||||
${subscriptionData.current_usage?.toFixed(2) || '0'} /{' '}
|
||||
${subscriptionData.cost_limit || '0'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<PricingSection returnUrl={returnUrl} showTitleAndTabs={false} />
|
||||
{/* Show pricing section immediately - no loading state */}
|
||||
<PricingSection returnUrl={returnUrl} showTitleAndTabs={false} />
|
||||
|
||||
{/* Minimalistic Subscription Management Section */}
|
||||
{subscriptionData?.subscription && (
|
||||
<div className="mt-6 pt-6 border-t border-border">
|
||||
<div className="space-y-3">
|
||||
{/* Subscription Status Row */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{subscriptionData.subscription.cancel_at_period_end || subscriptionData.subscription.cancel_at
|
||||
? 'Plan Status'
|
||||
: 'Current Plan'}
|
||||
</span>
|
||||
{commitmentInfo?.has_commitment && (
|
||||
<Badge variant="outline" className="text-xs px-2 py-0">
|
||||
{commitmentInfo.months_remaining || 0}mo left
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<Badge variant={
|
||||
subscriptionData.subscription.cancel_at_period_end || subscriptionData.subscription.cancel_at
|
||||
? 'destructive'
|
||||
: 'secondary'
|
||||
} className="text-xs">
|
||||
{/* Subscription Management Section - only show if there's actual subscription data */}
|
||||
{error ? (
|
||||
<div className="mt-6 pt-4 border-t border-border">
|
||||
<div className="p-4 bg-destructive/10 border border-destructive/20 rounded-lg text-center">
|
||||
<p className="text-sm text-destructive">Error loading billing status: {error}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : subscriptionData?.subscription && (
|
||||
<div className="mt-6 pt-4 border-t border-border">
|
||||
{/* Subscription Status Info Box */}
|
||||
<div className="bg-muted/30 border border-border rounded-lg p-3 mb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-medium">
|
||||
{subscriptionData.subscription.cancel_at_period_end || subscriptionData.subscription.cancel_at
|
||||
? 'Ending ' + getEffectiveCancellationDate()
|
||||
: 'Active'}
|
||||
</Badge>
|
||||
? 'Plan Status'
|
||||
: 'Current Plan'}
|
||||
</span>
|
||||
{commitmentInfo?.has_commitment && (
|
||||
<Badge variant="outline" className="text-xs px-1.5 py-0">
|
||||
{commitmentInfo.months_remaining || 0}mo left
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<Badge variant={
|
||||
subscriptionData.subscription.cancel_at_period_end || subscriptionData.subscription.cancel_at
|
||||
? 'destructive'
|
||||
: 'secondary'
|
||||
} className="text-xs px-2 py-0.5">
|
||||
{subscriptionData.subscription.cancel_at_period_end || subscriptionData.subscription.cancel_at
|
||||
? 'Ending ' + getEffectiveCancellationDate()
|
||||
: 'Active'}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Cancellation Alert */}
|
||||
{(subscriptionData.subscription.cancel_at_period_end || subscriptionData.subscription.cancel_at) && (
|
||||
<Alert className="py-2 border-destructive/30 bg-destructive/5">
|
||||
<AlertTriangle className="h-4 w-4 text-destructive" />
|
||||
<AlertDescription className="text-xs text-destructive">
|
||||
{subscriptionData.subscription.cancel_at ?
|
||||
'Your plan is scheduled to end at commitment completion. You can reactivate anytime.' :
|
||||
'Your plan is scheduled to end at period completion. You can reactivate anytime.'
|
||||
}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{/* Cancellation Alert */}
|
||||
{(subscriptionData.subscription.cancel_at_period_end || subscriptionData.subscription.cancel_at) && (
|
||||
<div className="mt-2 flex items-start gap-2 p-2 bg-destructive/5 border border-destructive/20 rounded">
|
||||
<AlertTriangle className="h-3 w-3 text-destructive mt-0.5 flex-shrink-0" />
|
||||
<p className="text-xs text-destructive">
|
||||
{subscriptionData.subscription.cancel_at ?
|
||||
'Your plan is scheduled to end at commitment completion. You can reactivate anytime.' :
|
||||
'Your plan is scheduled to end at period completion. You can reactivate anytime.'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-2">
|
||||
{/* Cancel/Reactivate Button */}
|
||||
{!(subscriptionData.subscription.cancel_at_period_end || subscriptionData.subscription.cancel_at) ? (
|
||||
<Dialog open={showCancelDialog} onOpenChange={setShowCancelDialog}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-xs"
|
||||
disabled={isCancelling}
|
||||
>
|
||||
{isCancelling ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="animate-spin h-3 w-3 border border-current border-t-transparent rounded-full" />
|
||||
Processing...
|
||||
</div>
|
||||
) : (
|
||||
commitmentInfo?.has_commitment && !commitmentInfo?.can_cancel
|
||||
? 'Schedule End'
|
||||
: 'Cancel Plan'
|
||||
)}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg">
|
||||
{commitmentInfo?.has_commitment && !commitmentInfo?.can_cancel
|
||||
? 'Schedule Cancellation'
|
||||
: 'Cancel Subscription'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm">
|
||||
{commitmentInfo?.has_commitment && !commitmentInfo?.can_cancel ? (
|
||||
<>
|
||||
Your subscription will be scheduled to end on{' '}
|
||||
{commitmentInfo?.commitment_end_date
|
||||
? formatEndDate(commitmentInfo.commitment_end_date)
|
||||
: 'your commitment end date'}
|
||||
. You'll keep full access until then.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Your subscription will end on{' '}
|
||||
{formatDate(subscriptionData.subscription.current_period_end)}.
|
||||
You'll keep access until then.
|
||||
</>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowCancelDialog(false)}
|
||||
disabled={isCancelling}
|
||||
size="sm"
|
||||
>
|
||||
Keep Plan
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleCancel}
|
||||
disabled={isCancelling}
|
||||
size="sm"
|
||||
>
|
||||
{isCancelling ? 'Processing...' : 'Confirm'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
) : (
|
||||
{/* Action Buttons underneath */}
|
||||
<div className="flex gap-2 justify-center">
|
||||
{/* Cancel/Reactivate Button */}
|
||||
{!(subscriptionData.subscription.cancel_at_period_end || subscriptionData.subscription.cancel_at) ? (
|
||||
<Dialog open={showCancelDialog} onOpenChange={setShowCancelDialog}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleReactivate}
|
||||
className="text-xs"
|
||||
disabled={isCancelling}
|
||||
className="text-xs bg-green-600 hover:bg-green-700 text-white"
|
||||
>
|
||||
{isCancelling ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="animate-spin h-3 w-3 border border-white border-t-transparent rounded-full" />
|
||||
<div className="animate-spin h-3 w-3 border border-current border-t-transparent rounded-full" />
|
||||
Processing...
|
||||
</div>
|
||||
) : (
|
||||
'Reactivate Plan'
|
||||
commitmentInfo?.has_commitment && !commitmentInfo?.can_cancel
|
||||
? 'Schedule End'
|
||||
: 'Cancel Plan'
|
||||
)}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg">
|
||||
{commitmentInfo?.has_commitment && !commitmentInfo?.can_cancel
|
||||
? 'Schedule Cancellation'
|
||||
: 'Cancel Subscription'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm">
|
||||
{commitmentInfo?.has_commitment && !commitmentInfo?.can_cancel ? (
|
||||
<>
|
||||
Your subscription will be scheduled to end on{' '}
|
||||
{commitmentInfo?.commitment_end_date
|
||||
? formatEndDate(commitmentInfo.commitment_end_date)
|
||||
: 'your commitment end date'}
|
||||
. You'll keep full access until then.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Your subscription will end on{' '}
|
||||
{formatDate(subscriptionData.subscription.current_period_end)}.
|
||||
You'll keep access until then.
|
||||
</>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowCancelDialog(false)}
|
||||
disabled={isCancelling}
|
||||
size="sm"
|
||||
>
|
||||
Keep Plan
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleCancel}
|
||||
disabled={isCancelling}
|
||||
size="sm"
|
||||
>
|
||||
{isCancelling ? 'Processing...' : 'Confirm'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
) : (
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={handleReactivate}
|
||||
disabled={isCancelling}
|
||||
className="text-xs bg-green-600 hover:bg-green-700 text-white"
|
||||
>
|
||||
{isCancelling ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="animate-spin h-3 w-3 border border-white border-t-transparent rounded-full" />
|
||||
Processing...
|
||||
</div>
|
||||
) : (
|
||||
'Reactivate Plan'
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Manage Subscription Button */}
|
||||
<Button
|
||||
onClick={handleManageSubscription}
|
||||
disabled={isManaging}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-xs"
|
||||
>
|
||||
{isManaging ? 'Loading...' : 'Dashboard'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Manage Subscription Button */}
|
||||
<Button
|
||||
onClick={handleManageSubscription}
|
||||
disabled={isManaging}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-xs"
|
||||
>
|
||||
{isManaging ? 'Loading...' : 'Dashboard'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Legacy Manage Button for non-subscription users */}
|
||||
{subscriptionData && !subscriptionData.subscription && (
|
||||
<Button
|
||||
onClick={handleManageSubscription}
|
||||
disabled={isManaging}
|
||||
className="max-w-xs mx-auto w-full bg-primary hover:bg-primary/90 shadow-md hover:shadow-lg transition-all mt-4"
|
||||
>
|
||||
{isManaging ? 'Loading...' : 'Manage Subscription'}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</DialogContent>
|
||||
|
||||
{/* Credit Purchase Modal */}
|
||||
|
|
|
@ -53,6 +53,7 @@ import { createClient } from '@/lib/supabase/client';
|
|||
import { useTheme } from 'next-themes';
|
||||
import { isLocalMode } from '@/lib/config';
|
||||
import { useFeatureFlag } from '@/lib/feature-flags';
|
||||
import { clearUserLocalStorage } from '@/lib/utils/clear-local-storage';
|
||||
|
||||
export function NavUserWithTeams({
|
||||
user,
|
||||
|
@ -148,6 +149,8 @@ export function NavUserWithTeams({
|
|||
const handleLogout = async () => {
|
||||
const supabase = createClient();
|
||||
await supabase.auth.signOut();
|
||||
// Clear local storage after sign out
|
||||
clearUserLocalStorage();
|
||||
router.push('/auth');
|
||||
};
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ export const UsagePreview: React.FC<UsagePreviewProps> = ({
|
|||
<div className="flex-1 min-w-0">
|
||||
<motion.div className="flex items-center gap-2 mb-1">
|
||||
<h4 className="text-sm font-medium text-foreground truncate">
|
||||
Upgrade for more usage & better AI Models
|
||||
Upgrade for the best AI Models & more usage
|
||||
</h4>
|
||||
</motion.div>
|
||||
|
||||
|
|
|
@ -5,11 +5,9 @@ export const clearUserLocalStorage = () => {
|
|||
// Note: Preserve model preference on logout - user choice should persist
|
||||
// localStorage.removeItem('suna-preferred-model-v3');
|
||||
localStorage.removeItem('customModels');
|
||||
|
||||
localStorage.removeItem('suna-model-selection-v2');
|
||||
localStorage.removeItem('agent-selection-storage');
|
||||
|
||||
localStorage.removeItem('auth-tracking-storage');
|
||||
|
||||
localStorage.removeItem('pendingAgentPrompt');
|
||||
localStorage.removeItem('suna_upgrade_dialog_displayed');
|
||||
Object.keys(localStorage).forEach(key => {
|
||||
|
|
Loading…
Reference in New Issue