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:
32
frontend/src/components/common/UpgradePrompt.tsx
Normal file
32
frontend/src/components/common/UpgradePrompt.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
24
frontend/src/components/subscription/CheckoutButton.tsx
Normal file
24
frontend/src/components/subscription/CheckoutButton.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user