"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 { formatUsageNumber, tokensToCredits } from "@/lib/billing-utils"
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 PlanFeatureRow({
icon,
text,
variant = "muted",
}: {
icon: "check" | "x"
text: string
variant?: "muted" | "highlight"
}) {
return (
{icon === "check" ? (
) : (
)}
{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 {
tokensUsed,
tokensLimit,
tokensPercent,
searchesUsed,
searchesLimit,
searchesPercent,
currentPlan,
hasPaidPlan,
isLoading: isCheckingStatus,
daysRemaining,
} = useTokenUsage(autumn)
const planDisplayNames: Record = {
free: "Free",
pro: "Pro",
scale: "Scale",
enterprise: "Enterprise",
}
// Handlers
const handleUpgrade = async () => {
setIsUpgrading(true)
try {
await autumn.attach({
productId: "api_pro",
successUrl: "https://app.supermemory.ai/settings#account",
})
window.location.reload()
} catch (error) {
console.error(error)
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
{/* Credits Usage Progress */}
Credits Used
{tokensToCredits(tokensUsed)} /{" "}
{tokensToCredits(tokensLimit)}
80
? "#ef4444"
: "linear-gradient(to right, #4BA0FA 80%, #002757 100%)",
}}
/>
{/* Search Queries Progress */}
Search Queries
{formatUsageNumber(searchesUsed)} /{" "}
{formatUsageNumber(searchesLimit)}
80
? "#ef4444"
: "linear-gradient(to right, #4BA0FA 80%, #002757 100%)",
}}
/>
{/* Days remaining indicator */}
{daysRemaining !== null && (
Resets in {daysRemaining} day
{daysRemaining !== 1 ? "s" : ""}
)}
{/* Free plan card */}
{/* Current plan card - highlighted */}
{planDisplayNames[currentPlan]} plan
ACTIVE
>
) : (
<>
Free Plan
You are on basic plan
{/* Credits Usage Progress */}
Credits Used
{tokensToCredits(tokensUsed)} /{" "}
{tokensToCredits(tokensLimit)}
80 ? "#ef4444" : "#0054AD",
}}
/>
{/* Search Queries Progress */}
Search Queries
{formatUsageNumber(searchesUsed)} /{" "}
{formatUsageNumber(searchesLimit)}
80 ? "#ef4444" : "#0054AD",
}}
/>
{/* Days remaining indicator */}
{daysRemaining !== null && (
Resets in {daysRemaining} day
{daysRemaining !== 1 ? "s" : ""}
)}
{/* Free plan card */}
{/* Pro plan card */}
{/* Header with badge */}
{/* Inset highlight */}
>
)}
Delete Account
Permanently delete all your data and cancel any active
subscriptions
)
}