Add accounts UI with list, create, edit, detail tabs for all sub-entities

Accounts list with paginated table, search, sort. Account detail page with
tabs for members, payment methods, tax exemptions, and processor links.
All sub-entities have create/edit dialogs and delete actions. Forms use
shared Zod schemas via react-hook-form.
This commit is contained in:
Ryan Moon
2026-03-28 07:45:52 -05:00
parent e734ef4606
commit 9abdf6c050
23 changed files with 1688 additions and 6 deletions

View File

@@ -0,0 +1,71 @@
import { createFileRoute, Outlet, Link, useParams } from '@tanstack/react-router'
import { useQuery } from '@tanstack/react-query'
import { accountDetailOptions } from '@/api/accounts'
import { Skeleton } from '@/components/ui/skeleton'
import { Badge } from '@/components/ui/badge'
import { cn } from '@/lib/utils'
export const Route = createFileRoute('/_authenticated/accounts/$accountId')({
component: AccountDetailLayout,
})
const tabs = [
{ label: 'Overview', to: '/accounts/$accountId' },
{ label: 'Members', to: '/accounts/$accountId/members' },
{ label: 'Payment Methods', to: '/accounts/$accountId/payment-methods' },
{ label: 'Tax Exemptions', to: '/accounts/$accountId/tax-exemptions' },
{ label: 'Processor Links', to: '/accounts/$accountId/processor-links' },
] as const
function AccountDetailLayout() {
const { accountId } = useParams({ from: '/_authenticated/accounts/$accountId' })
const { data: account, isLoading } = useQuery(accountDetailOptions(accountId))
if (isLoading) {
return (
<div className="space-y-4">
<Skeleton className="h-8 w-64" />
<Skeleton className="h-4 w-96" />
<Skeleton className="h-10 w-full" />
</div>
)
}
if (!account) {
return <p className="text-muted-foreground">Account not found</p>
}
return (
<div className="space-y-6">
<div>
<div className="flex items-center gap-3">
<h1 className="text-2xl font-bold">{account.name}</h1>
<Badge variant="secondary">{account.billingMode}</Badge>
</div>
{account.email && <p className="text-muted-foreground">{account.email}</p>}
</div>
<nav className="flex gap-1 border-b">
{tabs.map((tab) => (
<Link
key={tab.to}
to={tab.to}
params={{ accountId }}
activeOptions={{ exact: tab.to === '/accounts/$accountId' }}
className={cn(
'px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors',
'text-muted-foreground border-transparent hover:text-foreground hover:border-border',
)}
activeProps={{
className: 'text-foreground border-primary',
}}
>
{tab.label}
</Link>
))}
</nav>
<Outlet />
</div>
)
}