"use client" import { dmSans125ClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { useAuth } from "@lib/auth-context" import { useAccountMemberships, useDeleteUserAccount, } from "@/hooks/use-account-settings" import { useOrgSummaries } from "@/hooks/use-org-summaries" import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar" import { PLAN_DISPLAY_NAMES, PLAN_RANK, useTokenUsage, type PlanType, } from "@/hooks/use-token-usage" import { Dialog, DialogContent, DialogTrigger, DialogClose, } from "@ui/components/dialog" import { authClient } from "@lib/auth" import { Popover, PopoverContent, PopoverTrigger } from "@ui/components/popover" import { useCustomer } from "autumn-js/react" import { Check, X, Trash2, LoaderIcon, Settings, ChevronDown, Building2, } from "lucide-react" import { useMemo, useState } from "react" import { toast } from "sonner" function SectionTitle({ children }: { children: React.ReactNode }) { return (

{children}

) } function SettingsCard({ children }: { children: React.ReactNode }) { return (
{children}
) } function PlanComparisonCard({ name, price, period, description, credits, features, highlight, }: { name: string price: string period: string description: string credits: string features: string[] highlight: boolean }) { return (

{name}

{highlight && ( RECOMMENDED )}
{price} {period && ( {period} )}

{description}

{credits}

of usage included

) } function formatOrgRole(role: string): string { const r = role.toLowerCase() if (r === "owner") return "Owner" if (r === "admin") return "Admin" if (r === "member") return "Member" return role ? role.charAt(0).toUpperCase() + role.slice(1).toLowerCase() : "Member" } /** Matches ACTIVE / RECOMMENDED pills in billing settings. */ const orgPlanBadgeBase = cn( dmSans125ClassName(), "inline-flex h-[18px] min-w-[42px] shrink-0 items-center justify-center rounded-[3px] px-1.5 text-[10px] uppercase", ) const ORG_PLAN_BADGE_STYLES: Record = { free: "bg-[#2E353D] font-mono font-medium tracking-[0.12em] text-[#A3A3A3]", pro: "bg-[#4BA0FA] font-bold tracking-[0.36px] text-[#00171A]", scale: "bg-[#0054AD] font-bold tracking-[0.36px] text-[#FAFAFA]", enterprise: "bg-[#FAFAFA] font-bold tracking-[0.36px] text-[#0D121A]", } function OrgPlanBadge({ plan }: { plan: PlanType }) { return ( {PLAN_DISPLAY_NAMES[plan]} ) } function resolveOrgPlan( orgId: string, isCurrent: boolean, currentPlan: PlanType, planByOrgId: Map, ): PlanType { const fromSummary = planByOrgId.get(orgId) if (fromSummary) return fromSummary if (isCurrent) return currentPlan return "free" } export default function Account() { const { user, org, organizations: allOrgs, setActiveOrg, clearActiveOrg, } = useAuth() const autumn = useCustomer() const [isUpgrading, setIsUpgrading] = useState(false) const [isCancelling, setIsCancelling] = useState(false) const [isCancelDialogOpen, setIsCancelDialogOpen] = useState(false) const [emailConfirm, setEmailConfirm] = useState("") const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) const [isClosingAccount, setIsClosingAccount] = useState(false) const [switchingOrgId, setSwitchingOrgId] = useState(null) const [orgMenuOpen, setOrgMenuOpen] = useState(false) const canSwitchOrg = (allOrgs?.length ?? 0) > 1 const { data: memberships, isPending: membershipsPending } = useAccountMemberships() const { data: orgSummaries } = useOrgSummaries() const sortedMemberships = useMemo(() => { if (!memberships?.length) return [] return [...memberships].sort((a, b) => a.name.localeCompare(b.name)) }, [memberships]) const ownedOrgs = useMemo( () => memberships?.filter((m) => m.role === "owner") ?? [], [memberships], ) const hasOwnedOrgWithTeammates = useMemo( () => ownedOrgs.some((m) => m.memberCount > 1), [ownedOrgs], ) const showMembershipsOverview = !membershipsPending && (sortedMemberships.length > 1 || hasOwnedOrgWithTeammates) const deleteUserAccount = useDeleteUserAccount() const emailMatches = user?.email ? emailConfirm.trim().toLowerCase() === user.email.trim().toLowerCase() : false const handleOrgSwitch = async (orgSlug: string, orgId: string) => { if (orgId === org?.id) return setSwitchingOrgId(orgId) try { await setActiveOrg(orgSlug) window.location.reload() } catch (error) { console.error("Failed to switch organization:", error) setSwitchingOrgId(null) } } const { usdIncluded, usdSpent, planUsagePct, currentPlan, hasPaidPlan, isLoading: isCheckingStatus, daysRemaining, } = useTokenUsage(autumn) const formatUsd = (n: number) => n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2, }) const planDisplayNames = PLAN_DISPLAY_NAMES const planByOrgId = useMemo(() => { const map = new Map() for (const summary of orgSummaries ?? []) { map.set(summary.orgId, summary.plan) } return map }, [orgSummaries]) const sortedOrgsForMenu = useMemo(() => { if (!allOrgs?.length) return [] return [...allOrgs].sort((a, b) => { const planA = resolveOrgPlan( a.id, a.id === org?.id, currentPlan, planByOrgId, ) const planB = resolveOrgPlan( b.id, b.id === org?.id, currentPlan, planByOrgId, ) const rankDiff = PLAN_RANK[planB] - PLAN_RANK[planA] if (rankDiff !== 0) return rankDiff return a.name.localeCompare(b.name) }) }, [allOrgs, org?.id, currentPlan, planByOrgId]) // Handlers const handleUpgrade = async () => { setIsUpgrading(true) try { const result = await autumn.attach({ planId: "api_pro", successUrl: `${window.location.origin}/settings#account`, }) if (result?.paymentUrl) { window.open(result.paymentUrl, "_self") return } autumn.refetch?.() } catch (error) { console.error(error) toast.error("Failed to start checkout. Please try again.") } finally { setIsUpgrading(false) } } // Enterprise is contract-based — direct those users to the portal/sales. const cancellablePlanId = currentPlan === "pro" || currentPlan === "scale" ? (`api_${currentPlan}` as const) : null const handleCancelSubscription = async () => { if (!cancellablePlanId) return setIsCancelling(true) try { await autumn.updateSubscription({ planId: cancellablePlanId, cancelAction: "cancel_end_of_cycle", }) autumn.refetch?.() setIsCancelDialogOpen(false) toast.success( `Subscription cancelled. ${planDisplayNames[currentPlan]} features remain active until the end of your billing period.`, ) } catch (error) { console.error(error) toast.error("Failed to cancel subscription. Please try again.") } finally { setIsCancelling(false) } } const handleDeleteAccount = async () => { if (!user?.email || !emailMatches || membershipsPending) return setIsClosingAccount(true) try { await deleteUserAccount.mutateAsync({ confirmation: user.email, }) clearActiveOrg() try { await authClient.signOut() } catch { window.location.assign("/login/new") return } setIsDeleteDialogOpen(false) setEmailConfirm("") window.location.assign("/login/new") } catch (e) { const msg = e instanceof Error ? e.message : "Something went wrong" toast.error(msg) } finally { setIsClosingAccount(false) } } // Format member since date const memberSince = user?.createdAt ? new Date(user.createdAt).toLocaleDateString("en-US", { month: "short", year: "numeric", }) : "—" return (
Profile Details
{/* Avatar + Name/Email */}
{user?.name?.charAt(0) ?? "U"}

{user?.name ?? "—"}

{user?.email ?? "—"}

Organization

{ if (canSwitchOrg) setOrgMenuOpen(open) }} > {org?.name ?? "Personal"} {canSwitchOrg && ( )} {canSwitchOrg && ( {sortedOrgsForMenu.map((organization) => { const isCurrent = organization.id === org?.id const isSwitching = switchingOrgId === organization.id const plan = resolveOrgPlan( organization.id, isCurrent, currentPlan, planByOrgId, ) return ( ) })} )}

Member since

{memberSince}

Billing & Subscription
{hasPaidPlan ? ( <>

{planDisplayNames[currentPlan]} plan

ACTIVE

Expanded memory with connections and more

{/* Plan usage (unified) */}

Plan usage

{planUsagePct < 1 && planUsagePct > 0 ? "< 1" : Math.round(planUsagePct)} % used
80 ? "#ef4444" : "linear-gradient(to right, #4BA0FA 80%, #002757 100%)", }} title={`$${formatUsd(usdSpent)} of $${formatUsd(usdIncluded)} used`} />

{daysRemaining !== null ? `Resets in ${daysRemaining} day${daysRemaining !== 1 ? "s" : ""}` : ""}

{cancellablePlanId && (

Cancel {planDisplayNames[currentPlan]}{" "} subscription?

You'll keep Pro features until the end of your current billing period {daysRemaining !== null ? ` (${daysRemaining} day${daysRemaining !== 1 ? "s" : ""} remaining)` : ""} . After that, your account will switch to the Free plan.

)}
) : ( <>

Free Plan

You are on basic plan

{/* Plan usage (unified) */}

Plan usage

{planUsagePct < 1 && planUsagePct > 0 ? "< 1" : Math.round(planUsagePct)} % used

80 ? "#ef4444" : "#0054AD", }} title={`$${formatUsd(usdSpent)} of $${formatUsd(usdIncluded)} used`} />

{daysRemaining !== null ? `Resets in ${daysRemaining} day${daysRemaining !== 1 ? "s" : ""}` : ""}

)}
Delete Account

Permanently delete all your data and cancel any active subscriptions

{ setIsDeleteDialogOpen(open) if (!open) { setEmailConfirm("") } }} >
{/* Header */}

Delete account?

This cannot be undone.

{hasOwnedOrgWithTeammates && (

You own at least one organization that still has other members. Those organizations will be deleted for everyone when you confirm.

)}
What happens next?

Your account is locked immediately; data removal runs in the background.

  • Removes memories, conversations, and settings; cancels active subscriptions.
  • Orgs where you're only a member: you're removed; the org continues.
  • Orgs you own: deleted for all members.
{showMembershipsOverview && (

Your organizations

{sortedMemberships.map((m) => (

{m.name}

{m.slug ? (

{m.slug}

) : null}
{formatOrgRole(m.role)} {m.memberCount} member {m.memberCount === 1 ? "" : "s"}
))}
)} {/* Confirmation input */}

Type your account email to confirm:

setEmailConfirm(e.target.value)} placeholder={user?.email ?? "you@example.com"} className={cn( "w-full px-4 py-3 bg-transparent", "text-[#FAFAFA] placeholder:text-[#737373]", "text-[14px] tracking-[-0.14px]", "outline-none", dmSans125ClassName(), )} />
{/* Footer */}
{/* Modal inset highlight */}
) }