import { useEffect, useMemo, useState } from 'react' import { Link } from 'react-router-dom' import { AlertCircle, AlertTriangle, ArrowRight, Building2, Check, Clock, Copy, Crown, FolderTree, Loader2, Mail, MessageSquareText, Palette, Plug, RefreshCw, Server, Settings, ShieldCheck, UserCog, Users, X, } from 'lucide-react' import { PageMeta } from '@/components/common/PageMeta' import { accountsApi } from '@/api/accounts' import type { Account, AccountInvite, AccountMember } from '@/types' import { TransferOwnershipModal } from '@/components/account/TransferOwnershipModal' import { LeaveAccountModal } from '@/components/account/LeaveAccountModal' import { DeleteAccountModal } from '@/components/account/DeleteAccountModal' import { Button } from '@/components/ui/Button' import { ConfirmButton } from '@/components/common/ConfirmButton' import { Spinner } from '@/components/common/Spinner' import { cn } from '@/lib/utils' import { usePermissions } from '@/hooks/usePermissions' import { useSubscription } from '@/hooks/useSubscription' import { useAuthStore } from '@/store/authStore' import { useUserPreferencesStore } from '@/store/userPreferencesStore' import { CheckoutButton } from '@/components/subscription/CheckoutButton' import { toast } from '@/lib/toast' interface SettingsLinkCardProps { to: string icon: React.ReactNode title: string description: string badge?: string } function SettingsLinkCard({ to, icon, title, description, badge }: SettingsLinkCardProps) { return (
{icon}

{title}

{badge && ( {badge} )}

{description}

) } function UsageStat({ label, current, max, }: { label: string current: number max: number | null }) { const isUnlimited = max === null const percentage = isUnlimited ? 0 : Math.min((current / max) * 100, 100) const isNearLimit = !isUnlimited && percentage >= 80 const isAtLimit = !isUnlimited && current >= max return (

{label}

{current} / {isUnlimited ? 'Unlimited' : max}

{!isUnlimited && (
)}
) } function formatShortDate(value: string | null | undefined) { if (!value) return 'Never' return new Date(value).toLocaleDateString() } export function AccountSettingsPage() { const { isAccountOwner } = usePermissions() const { plan, limits, usage } = useSubscription() const { defaultExportFormat, setDefaultExportFormat } = useUserPreferencesStore() const subscription = useAuthStore((s) => s.subscription) const user = useAuthStore((s) => s.user) const refreshUser = useAuthStore((s) => s.fetchUser) const [account, setAccount] = useState(null) const [members, setMembers] = useState([]) const [invites, setInvites] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) const [copiedCode, setCopiedCode] = useState(false) const [isEditingName, setIsEditingName] = useState(false) const [editedName, setEditedName] = useState('') const [isSavingName, setIsSavingName] = useState(false) const [showTransferModal, setShowTransferModal] = useState(false) const [showLeaveModal, setShowLeaveModal] = useState(false) const [showDeleteModal, setShowDeleteModal] = useState(false) const [inviteEmail, setInviteEmail] = useState('') const [inviteRole, setInviteRole] = useState('engineer') const [isInviting, setIsInviting] = useState(false) const [resendingId, setResendingId] = useState(null) useEffect(() => { loadData() }, []) const loadData = async () => { setIsLoading(true) setError(null) try { const accountData = await accountsApi.getMyAccount() setAccount(accountData) setEditedName(accountData.name) if (isAccountOwner) { const [membersData, invitesData] = await Promise.all([ accountsApi.getMembers(), accountsApi.getInvites(), ]) setMembers(membersData) setInvites(invitesData) } } catch (err) { setError('Failed to load account information') console.error(err) } finally { setIsLoading(false) } } const pendingInvites = useMemo(() => invites.filter((invite) => !invite.used_at), [invites]) const ownerMember = useMemo(() => members.find((member) => member.account_role === 'owner') ?? null, [members]) const handleCopyDisplayCode = async () => { if (!account?.display_code) return await navigator.clipboard.writeText(account.display_code) setCopiedCode(true) toast.success('Display code copied') setTimeout(() => setCopiedCode(false), 2000) } const handleSaveName = async () => { if (!editedName.trim() || editedName === account?.name) { setIsEditingName(false) return } setIsSavingName(true) try { const updated = await accountsApi.updateMyAccount({ name: editedName.trim() }) setAccount(updated) setIsEditingName(false) toast.success('Account name updated') await refreshUser() } catch (err) { toast.error('Failed to update account name') console.error('Failed to update account name:', err) } finally { setIsSavingName(false) } } const handleInvite = async (e: React.FormEvent) => { e.preventDefault() if (!inviteEmail.trim()) return setIsInviting(true) try { await accountsApi.createInvite({ email: inviteEmail.trim(), role: inviteRole }) toast.success(`Invitation sent to ${inviteEmail}`) setInviteEmail('') const invitesData = await accountsApi.getInvites() setInvites(invitesData) } catch (err) { toast.error('Failed to send invitation') console.error(err) } finally { setIsInviting(false) } } const handleResendInvite = async (inviteId: string) => { setResendingId(inviteId) try { await accountsApi.resendInvite(inviteId) toast.success('Invite resent with a new code') const invitesData = await accountsApi.getInvites() setInvites(invitesData) } catch { toast.error('Failed to resend invite') } finally { setResendingId(null) } } const handleRemoveMember = async (userId: string) => { try { await accountsApi.removeMember(userId) setMembers((current) => current.filter((member) => member.id !== userId)) toast.success('Member removed') await refreshUser() } catch (err) { toast.error('Failed to remove member') console.error('Failed to remove member:', err) } } if (isLoading) { return (
) } if (error) { return (
{error}
) } const sub = subscription?.subscription return ( <>

Account Management

Manage your account identity, billing, access, and workspace settings.

Account Identity

{isEditingName ? (
setEditedName(e.target.value)} className={cn( 'flex-1 rounded-md border border-border bg-card px-3 py-2', 'text-foreground placeholder:text-muted-foreground', 'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20' )} autoFocus onKeyDown={(e) => { if (e.key === 'Enter') handleSaveName() if (e.key === 'Escape') { setEditedName(account?.name ?? '') setIsEditingName(false) } }} />
) : (
{account?.name} {isAccountOwner && ( )}
)}
{account?.display_code}

Share this code with teammates so they can join your account during registration.

{ownerMember ? ownerMember.name : user?.account_role === 'owner' ? user.email : 'Owner unavailable'}

{ownerMember?.email ?? 'The owner manages billing, branding, integrations, and membership changes.'}

{formatShortDate(account?.created_at)}

{formatShortDate(account?.updated_at)}

Billing & Usage

Monitor plan status, renewal timing, and current account limits.

{plan.charAt(0).toUpperCase() + plan.slice(1)} Plan {sub && ( {sub.status.charAt(0).toUpperCase() + sub.status.slice(1).replace('_', ' ')} )}

Renewal date

{sub?.current_period_end ? new Date(sub.current_period_end).toLocaleDateString() : 'Not scheduled'}

Branding access

{limits?.custom_branding ? 'Included in your plan' : 'Upgrade required'}

{limits && usage && (
)} {plan === 'free' && (
)} {plan === 'pro' && (
)}

Access & Security

Authentication

{plan === 'team' ? 'Password auth available, SSO can be enabled.' : 'Password-based authentication is active.'}

Use profile settings to update your personal details and sign-in information.

Single Sign-On

{plan === 'team' ? 'Enterprise-ready setup available.' : 'Available on higher-tier account setups.'}

Contact support to configure SAML or OIDC for your organization.

{isAccountOwner && (

Need enterprise security controls?

We can help enable SSO and align account security for larger teams.

Contact Us
)}

Settings Areas

Common account management actions, organized the way most SaaS teams expect to find them.

} title="Profile Settings" description="Update your name, email, and personal details." /> {isAccountOwner && ( } title="Branding" description="Customize logo, accent color, and company name." badge={limits?.custom_branding ? 'Included' : 'Plan gated'} /> )} {isAccountOwner && ( } title="Integrations" description="Connect PSA and other external systems for your team." /> )} {isAccountOwner && ( } title="Chat Retention" description="Control conversation retention and assistant data lifecycle." /> )} {isAccountOwner && ( } title="Team Categories" description="Manage shared flow categories for your workspace." /> )} {isAccountOwner && ( } title="Target Lists" description="Maintain saved server and device lists for the team." /> )} } title="Support & Feedback" description="Report bugs, request features, or share product feedback." />
{showTransferModal && ( setShowTransferModal(false)} onTransferred={() => { setShowTransferModal(false) loadData() }} /> )} {showLeaveModal && account && ( setShowLeaveModal(false)} /> )} {showDeleteModal && setShowDeleteModal(false)} />}
) } export default AccountSettingsPage