feat(settings): land the account page from the avatar, tidy the token row

Clicking the avatar (and the name beside it) in the sidebar user menu
now routes straight to the account settings. On the per-medication API
token row, drop the separate "Copy URL" button in favour of an inline
read-only endpoint field with a copy icon, and switch the generate-token
button to the standard outline variant so it no longer renders as the
lone primary-coloured control on the row.
This commit is contained in:
Marc-André Bombeck committed 2026-06-06 23:15:05 +02:00
1 parent eda79794d9
commit 1b283a9c01
2 files changed
+72 -47

No files matched your search

+48 -30
View File
@@ -124,22 +124,31 @@ function SidebarUserSection({ collapsed }: { collapsed: boolean }) {
className="w-60"
sideOffset={8}
>
<div className="flex items-center gap-3 px-2 py-2">
<Avatar className="h-9 w-9 shrink-0">
{avatarUrl && <AvatarImage src={avatarUrl} alt={user.username} />}
<AvatarFallback className="bg-primary/15 text-primary text-xs font-medium">
{getInitials(user.username)}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold">{user.username}</p>
{user.email && (
<p className="text-muted-foreground truncate text-xs">
{user.email}
</p>
)}
</div>
</div>
{/* R20 — the user header is the direct route into the account
settings. Clicking the avatar / name jumps straight to
`/settings/account` rather than making the user hunt for a
separate "Settings" entry. */}
<DropdownMenuItem asChild>
<Link
href="/settings/account"
className="flex cursor-pointer items-center gap-3 px-2 py-2"
>
<Avatar className="h-9 w-9 shrink-0">
{avatarUrl && <AvatarImage src={avatarUrl} alt={user.username} />}
<AvatarFallback className="bg-primary/15 text-primary text-xs font-medium">
{getInitials(user.username)}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold">{user.username}</p>
{user.email && (
<p className="text-muted-foreground truncate text-xs">
{user.email}
</p>
)}
</div>
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link href="/notifications" className="cursor-pointer">
@@ -219,20 +228,29 @@ function SidebarUserSection({ collapsed }: { collapsed: boolean }) {
return (
<div className="border-sidebar-border border-t p-3">
<div className="flex items-center gap-3 px-2 py-1">
<Avatar className="h-8 w-8 shrink-0">
{avatarUrl && <AvatarImage src={avatarUrl} alt={user.username} />}
<AvatarFallback className="bg-primary/15 text-primary text-xs font-medium">
{getInitials(user.username)}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{user.username}</p>
{user.email && (
<p className="text-muted-foreground truncate text-xs">
{user.email}
</p>
)}
</div>
{/* R20 — the avatar + name in the expanded footer routes straight
into the account settings; the kebab to the right still opens
the rest of the user menu. */}
<Link
href="/settings/account"
aria-label={t("nav.accountSettings")}
className="hover:bg-accent flex min-w-0 flex-1 items-center gap-3 rounded-md transition-colors"
>
<Avatar className="h-8 w-8 shrink-0">
{avatarUrl && <AvatarImage src={avatarUrl} alt={user.username} />}
<AvatarFallback className="bg-primary/15 text-primary text-xs font-medium">
{getInitials(user.username)}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{user.username}</p>
{user.email && (
<p className="text-muted-foreground truncate text-xs">
{user.email}
</p>
)}
</div>
</Link>
<DropdownMenu>
<DropdownMenuTrigger
aria-label={t("nav.userMenu")}
@@ -203,28 +203,35 @@ export function ApiTokensRow({
<p className="text-muted-foreground text-xs">
{t("medications.detail.api.caption", { name: medicationName })}
</p>
{/* The raw endpoint URL is redundant with the copy button below, so
it is rendered muted + single-line-truncated rather than as a
full-width block — the long absolute URL was pure visual noise. */}
<code
className="text-muted-foreground bg-muted/60 block truncate rounded px-2 py-1 font-mono text-xs"
title={endpoint}
{/* R26 — the endpoint URL is an inline read-only field with a copy
icon on the right rather than a separate "URL kopieren" button.
The `<code>` carries the value; the icon button copies it. */}
<div
className="border-input bg-muted/60 flex items-center gap-2 rounded-md border px-2 py-1"
data-slot="api-endpoint-url"
>
POST {endpoint}
</code>
<code
className="text-muted-foreground min-w-0 flex-1 truncate font-mono text-xs"
title={endpoint}
>
POST {endpoint}
</code>
<Button
variant="ghost"
size="icon"
onClick={() =>
void copy(endpoint, t("medications.detail.api.urlCopied"))
}
className="h-7 w-7 shrink-0"
aria-label={t("medications.detail.api.copyUrl")}
data-slot="api-tokens-copy-url"
>
<Copy aria-hidden="true" className="h-3.5 w-3.5" />
</Button>
</div>
<div className="flex flex-wrap gap-2">
<Button
variant="outline"
size="sm"
onClick={() => void copy(endpoint, t("medications.detail.api.urlCopied"))}
className="min-h-11 sm:min-h-9"
data-slot="api-tokens-copy-url"
>
<Copy aria-hidden="true" className="h-4 w-4" />
{t("medications.detail.api.copyUrl")}
</Button>
<Button
size="sm"
onClick={() => void mintToken()}
disabled={busy}