feat: update frontend for account-based subscriptions

Replace all team_id/team_admin references with account_id/owner across
types, store, hooks, API clients, components, and pages. Add new
AccountSettingsPage, UpgradePrompt, CheckoutButton, useSubscription
hook, and accounts API client. AuthStore now parallel-fetches account
and subscription data alongside user profile.

Also fix folder sidebar not refreshing after tree deletion by
dispatching the folder-changed event in handleDeleteTree.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-07 02:39:15 -05:00
parent e0089a9c5a
commit 7a6f839ef4
26 changed files with 786 additions and 38 deletions

View File

@@ -0,0 +1,32 @@
import { cn } from '@/lib/utils'
import { useSubscription } from '@/hooks/useSubscription'
interface UpgradePromptProps {
feature: string // e.g., "create more trees", "start more sessions"
className?: string
}
export function UpgradePrompt({ feature, className }: UpgradePromptProps) {
const { plan } = useSubscription()
return (
<div className={cn(
'rounded-lg border border-yellow-500/30 bg-yellow-500/10 p-4',
className
)}>
<h3 className="font-semibold text-foreground">Plan Limit Reached</h3>
<p className="mt-1 text-sm text-muted-foreground">
Your {plan} plan doesn't allow you to {feature}. Upgrade your plan to continue.
</p>
<button
className={cn(
'mt-3 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
)}
onClick={() => window.location.href = '/account'}
>
View Plans
</button>
</div>
)
}

View File

@@ -49,6 +49,7 @@ export function AppLayout() {
const navItems = [
{ path: '/trees', label: 'Trees' },
{ path: '/sessions', label: 'Sessions' },
{ path: '/account', label: 'Account' },
{ path: '/settings', label: 'Settings' },
]
@@ -98,12 +99,12 @@ export function AppLayout() {
className={cn(
'hidden rounded-full px-2 py-0.5 text-xs font-medium sm:inline-block',
effectiveRole === 'super_admin' && 'bg-red-500/10 text-red-600 dark:text-red-400',
effectiveRole === 'team_admin' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400',
effectiveRole === 'owner' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400',
effectiveRole === 'viewer' && 'bg-gray-500/10 text-gray-600 dark:text-gray-400'
)}
>
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'team_admin' ? 'Team Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
)}
@@ -158,12 +159,12 @@ export function AppLayout() {
className={cn(
'mt-1 inline-block rounded-full px-2 py-0.5 text-xs font-medium',
effectiveRole === 'super_admin' && 'bg-red-500/10 text-red-600 dark:text-red-400',
effectiveRole === 'team_admin' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400',
effectiveRole === 'owner' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400',
effectiveRole === 'viewer' && 'bg-gray-500/10 text-gray-600 dark:text-gray-400'
)}
>
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'team_admin' ? 'Team Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
)}

View File

@@ -27,7 +27,7 @@ export function ProtectedRoute({ requiredRole, children }: ProtectedRouteProps)
if (requiredRole) {
const ROLE_HIERARCHY: Record<EffectiveRole, number> = {
super_admin: 4,
team_admin: 3,
owner: 3,
engineer: 2,
viewer: 1,
}

View File

@@ -0,0 +1,24 @@
import { cn } from '@/lib/utils'
interface CheckoutButtonProps {
plan: 'pro' | 'team'
className?: string
}
export function CheckoutButton({ plan, className }: CheckoutButtonProps) {
const planLabels = { pro: 'Pro', team: 'Team' }
return (
<button
disabled
title="Billing coming soon"
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'disabled:opacity-50 disabled:cursor-not-allowed',
className
)}
>
Upgrade to {planLabels[plan]} (Coming Soon)
</button>
)
}

View File

@@ -119,7 +119,7 @@ export function TreeMetadataForm() {
{categories.map((cat) => (
<option key={cat.id} value={cat.id}>
{cat.name}
{cat.team_id ? ' (Team)' : ''}
{cat.account_id ? ' (Account)' : ''}
</option>
))}
<option value="__custom__">+ Add custom category</option>