refactor(account): redesign settings index, drop card stack

The index page had ~12 distinct card surfaces with three places of
nested cards-inside-cards, against PRODUCT.md's "elevation = lighter
surface + border" + "nested cards are always wrong" rules. Branding
appeared twice, Display Code lived in Identity but does invite work,
and Preferences got a full card for one dropdown.

Single column, max-w-3xl, no card chrome. Sections separated by
border-t rules + mono-uppercase section labels (existing house style):

- Header: inline-editable name + plan/status/owner/member-count info
  line. No card.
- Plan & usage: renewal date right-aligned in section header, three
  thin progress rows replace the 4-card usage stat grid, upgrade
  CTAs right-aligned at bottom.
- People (owner-only): invite form, unified members + pending invites
  list, display code as a quiet "share to invite during signup" line.
  Non-owners see a one-line "managed by your admin" instead of a card.
- Settings: dense route list (icon + title + summary + status pill +
  chevron). Profile above a thin divider; team-admin rows below,
  owner-gated. Branding row carries the Included/Plan-gated pill.
  Support & Feedback as a dim link at the bottom.
- Account actions: plain rows. Owner: Transfer + Delete. Non-owner:
  Leave. Destructive labels colored, no red box-of-doom.

Drops: Access & Security card (filler), Preferences card,
Settings Areas link grid, billing-card branding-status duplicate,
SettingsLinkCard helper. Default export format moves to Profile
Settings where it belongs (personal preference, not account).

856 -> 710 lines on the index. tsc, eslint, vite build clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 23:57:29 -04:00
parent 0f90c0e199
commit 86120423da
2 changed files with 469 additions and 592 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom'
import { User as UserIcon, Loader2, AlertCircle, Check } from 'lucide-react'
import { authApi } from '@/api/auth'
import { useAuthStore } from '@/store/authStore'
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
import { cn } from '@/lib/utils'
import { toast } from '@/lib/toast'
import type { UserUpdate } from '@/types'
@@ -16,6 +17,7 @@ const inputClass = cn(
export function ProfileSettingsPage() {
const user = useAuthStore((s) => s.user)
const fetchUser = useAuthStore((s) => s.fetchUser)
const { defaultExportFormat, setDefaultExportFormat } = useUserPreferencesStore()
const [name, setName] = useState(user?.name ?? '')
const [email, setEmail] = useState(user?.email ?? '')
@@ -120,6 +122,27 @@ export function ProfileSettingsPage() {
</select>
</div>
{/* Default export format — saved on change, not via Save Changes */}
<div>
<label htmlFor="profile-export-format" className="block text-sm font-medium text-foreground">
Default export format
</label>
<p className="text-xs text-muted-foreground">Pre-selected when exporting sessions.</p>
<select
id="profile-export-format"
value={defaultExportFormat}
onChange={(e) => {
setDefaultExportFormat(e.target.value as 'markdown' | 'text' | 'html')
toast.success('Preference saved')
}}
className={inputClass}
>
<option value="markdown">Markdown (.md)</option>
<option value="text">Plain text (.txt)</option>
<option value="html">HTML (.html)</option>
</select>
</div>
{error && (
<div className="flex items-center gap-2 text-sm text-rose-500">
<AlertCircle className="h-4 w-4 shrink-0" />