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 (
)
}
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