docs: add comprehensive Stripe billing guide
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 4s

Covers architecture, configuration steps, user flows, API routes,
webhooks, pricing, testing with Stripe CLI, production checklist,
and troubleshooting.
This commit is contained in:
Antigravity
2026-05-16 21:10:26 +00:00
parent aa12d2226f
commit bb75b2e763
36 changed files with 2099 additions and 735 deletions

View File

@@ -298,21 +298,21 @@ function SidebarCarnetItem({
<button
onClick={(e) => { e.stopPropagation(); onAddSubNotebook() }}
className="p-1 hover:bg-ink/10 rounded-md transition-all text-concrete hover:text-ink"
title={t('notebook.createSubNotebook') || 'Add sub-carnet'}
title={t('notebook.createSubNotebook')}
>
<Plus size={10} />
</button>
<button
onClick={(e) => { e.stopPropagation(); onRename() }}
className="p-1 hover:bg-ink/10 rounded-md transition-all text-concrete hover:text-ink"
title={t('notebook.rename') || 'Rename'}
title={t('notebook.rename')}
>
<Pencil size={10} />
</button>
<button
onClick={(e) => { e.stopPropagation(); onDelete() }}
className="p-1 hover:bg-rose-50 rounded-md transition-all text-concrete hover:text-rose-500"
title={t('notebook.delete') || 'Delete'}
title={t('notebook.delete')}
>
<Trash2 size={10} />
</button>
@@ -399,7 +399,7 @@ function SidebarCarnetItem({
))}
{isActive && notes.length === 0 && !hasChildren && (
<p className="ps-8 py-2 text-[10px] italic text-muted-foreground/40 font-light">
{t('common.noResults') || 'No notes found'}
{t('common.noResults')}
</p>
)}
</div>
@@ -583,7 +583,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
try {
await moveNotebookToParent(dragId, targetId)
} catch (err) {
toast.error(err instanceof Error ? err.message : t('sidebar.moveFailed') || 'Failed to move notebook')
toast.error(err instanceof Error ? err.message : t('sidebar.moveFailed'))
setOrderedNotebooks(sortedNotebooks)
}
}
@@ -602,7 +602,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
try {
await moveNotebookToParent(dragId, null)
} catch (err) {
toast.error(err instanceof Error ? err.message : t('sidebar.moveFailed') || 'Failed to move notebook')
toast.error(err instanceof Error ? err.message : t('sidebar.moveFailed'))
setOrderedNotebooks(sortedNotebooks)
}
}
@@ -835,14 +835,14 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<DropdownMenuItem asChild>
<Link href="/settings/profile" className="flex items-center gap-2 cursor-pointer">
<User className="h-4 w-4" />
{t('sidebar.profile') || 'Profil'}
{t('sidebar.profile')}
</Link>
</DropdownMenuItem>
{(user as { role?: string } | undefined)?.role === 'ADMIN' && (
<DropdownMenuItem asChild>
<a href="/admin" className="flex items-center gap-2 cursor-pointer">
<Shield className="h-4 w-4" />
{t('nav.adminDashboard') || 'Administration'}
{t('nav.adminDashboard')}
</a>
</DropdownMenuItem>
)}
@@ -852,7 +852,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
onClick={() => signOut({ callbackUrl: '/login' })}
>
<LogOut className="h-4 w-4 me-2" />
{t('sidebar.signOut') || 'Se déconnecter'}
{t('sidebar.signOut')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
@@ -873,7 +873,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<button
onClick={() => { router.push('/home') }}
className="p-1.5 text-muted-ink hover:text-ink transition-all hover:bg-white/50 dark:hover:bg-white/10 rounded-lg border border-transparent hover:border-border"
title={t('nav.home') || 'Accueil'}
title={t('nav.home')}
>
<Home size={14} />
</button>
@@ -1016,7 +1016,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<div
className="h-10 rounded-lg border-2 border-dashed border-brand-accent/20 flex items-center justify-center text-[11px] text-brand-accent/50"
>
{t('sidebar.dropToRoot') || 'Drop here to move to root'}
{t('sidebar.dropToRoot')}
</div>
)}
</div>
@@ -1034,7 +1034,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
</p>
<div className="px-4 py-8 text-center border border-dashed border-border rounded-2xl bg-paper/30">
<Clock size={24} className="mx-auto text-concrete/40 mb-3" />
<p className="text-[11px] text-concrete italic">{t('sidebar.noReminders') || 'No active reminders.'}</p>
<p className="text-[11px] text-concrete italic">{t('sidebar.noReminders')}</p>
</div>
</motion.div>
) : activeView === 'agents' ? (
@@ -1118,7 +1118,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
)}
>
<Users size={14} className={searchParams.get('shared') === '1' && pathname === '/home' ? 'text-accent' : 'text-muted-ink group-hover:text-ink'} />
<span>{t('sidebar.sharedWithMe') || 'Shared'}</span>
<span>{t('sidebar.sharedWithMe')}</span>
</Link>
<Link
@@ -1175,7 +1175,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
onClick={(e) => e.stopPropagation()}
>
<h3 className="text-sm font-semibold mb-4 font-memento-serif">
{t('notebook.rename') || 'Rename notebook'}
{t('notebook.rename')}
</h3>
<input
autoFocus
@@ -1183,7 +1183,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
onChange={(e) => setRenameValue(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') handleConfirmRename(); if (e.key === 'Escape') setRenamingNotebook(null) }}
className="w-full px-3 py-2 text-sm border border-border rounded-lg bg-transparent focus:outline-none focus:ring-2 focus:ring-foreground/20"
placeholder={t('notebook.namePlaceholder') || 'Notebook name'}
placeholder={t('notebook.namePlaceholder')}
/>
<div className="flex justify-end gap-2 mt-4">
<button
@@ -1191,14 +1191,14 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
disabled={isRenaming}
className="px-4 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors rounded-lg"
>
{t('common.cancel') || 'Cancel'}
{t('common.cancel')}
</button>
<button
onClick={handleConfirmRename}
disabled={isRenaming || !renameValue.trim()}
className="px-4 py-1.5 text-xs font-medium bg-foreground text-background rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50"
>
{isRenaming ? '...' : (t('common.confirm') || 'Rename')}
{isRenaming ? '...' : t('notebook.confirmRename')}
</button>
</div>
</motion.div>
@@ -1228,7 +1228,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<Trash2 size={16} className="text-red-500" />
</div>
<h3 className="text-sm font-semibold mb-1 font-memento-serif">
{t('notebook.trashTitle') || 'Move to trash'}
{t('notebook.trashTitle')}
</h3>
<p className="text-xs text-muted-foreground mb-4">
{t('notebook.trashConfirm', { name: deletingNotebook.name }) || `Move "${deletingNotebook.name}" to trash? You can restore it within 30 days.`}
@@ -1244,14 +1244,14 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
disabled={isDeleting}
className="px-4 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors rounded-lg"
>
{t('common.cancel') || 'Cancel'}
{t('common.cancel')}
</button>
<button
onClick={handleConfirmDelete}
disabled={isDeleting}
className="px-4 py-1.5 text-xs font-medium bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors disabled:opacity-50"
>
{isDeleting ? '...' : (t('notebook.moveToTrash') || 'Move to trash')}
{isDeleting ? '...' : t('notebook.moveToTrash')}
</button>
</div>
</motion.div>