"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 { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
import { useTokenUsage } 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
{features.map((text) => (
-
{text}
))}
)
}
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"
}
export default function Account() {
const { user, org, setActiveOrg, clearActiveOrg } = useAuth()
const autumn = useCustomer()
const [isUpgrading, setIsUpgrading] = useState(false)
const [emailConfirm, setEmailConfirm] = useState("")
const [notifyWhenDeleted, setNotifyWhenDeleted] = useState(false)
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
const [isClosingAccount, setIsClosingAccount] = useState(false)
const [switchingOrgId, setSwitchingOrgId] = useState(null)
const { data: allOrgs } = authClient.useListOrganizations()
const { data: memberships, isPending: membershipsPending } =
useAccountMemberships()
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: Record = {
free: "Free",
pro: "Pro",
scale: "Scale",
enterprise: "Enterprise",
}
// 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)
}
}
const handleDeleteAccount = async () => {
if (!user?.email || !emailMatches || membershipsPending) return
setIsClosingAccount(true)
try {
await deleteUserAccount.mutateAsync({
confirmation: user.email,
notifyOnComplete: notifyWhenDeleted,
})
clearActiveOrg()
try {
await authClient.signOut()
} catch {
window.location.assign("/login/new")
return
}
setIsDeleteDialogOpen(false)
setEmailConfirm("")
setNotifyWhenDeleted(false)
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
{org?.name ?? "Personal"}
{allOrgs && allOrgs.length > 1 && (
{allOrgs.map((organization) => {
const isCurrent = organization.id === org?.id
const isSwitching = switchingOrgId === organization.id
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" : ""}`
: ""}
>
) : (
<>
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
)
}