fix: replace flyout profile card with logout slider, i18n auth errors

- Replace static account card in mobile flyout with swipe-to-logout widget
- Remove redundant logout button from flyout bottom
- Make LogoutButton full-width via class prop and dynamic maxSlide
- Extract clean GraphQL error messages instead of raw JSON in all auth forms
- Add i18n keys for known backend errors (invalid credentials, email taken, invalid token)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 17:26:14 +01:00
parent 0ec27117ae
commit 19d29cbfc6
8 changed files with 29 additions and 54 deletions

View File

@@ -8,8 +8,6 @@
import { getAssetUrl } from "$lib/api";
import LogoutButton from "../logout-button/logout-button.svelte";
import Separator from "../ui/separator/separator.svelte";
import { Avatar, AvatarFallback, AvatarImage } from "$lib/components/ui/avatar";
import { getUserInitials } from "$lib/utils";
import BurgerMenuButton from "../burger-menu-button/burger-menu-button.svelte";
import Logo from "../logo/logo.svelte";
@@ -180,34 +178,17 @@
</div>
<div class="flex-1 py-6 px-5 space-y-6">
<!-- User Profile Card -->
<!-- User logout slider -->
{#if authStatus.authenticated}
<div
class="relative overflow-hidden rounded-2xl border border-border/50 bg-gradient-to-br from-card to-card/50 p-4"
>
<div class="absolute inset-0 bg-gradient-to-br from-primary/5 to-accent/5"></div>
<div class="relative flex items-center gap-3">
<Avatar class="h-9 w-9 ring-2 ring-primary/30">
<AvatarImage
src={getAssetUrl(authStatus.user!.avatar, "mini")}
alt={authStatus.user!.artist_name}
/>
<AvatarFallback
class="bg-gradient-to-br from-primary to-accent text-primary-foreground font-semibold"
>
{getUserInitials(authStatus.user!.artist_name)}
</AvatarFallback>
</Avatar>
<div class="flex flex-1 flex-col min-w-0">
<p class="text-sm font-semibold text-foreground truncate">
{authStatus.user!.artist_name || authStatus.user!.email.split("@")[0]}
</p>
<p class="text-xs text-muted-foreground truncate">
{authStatus.user!.email}
</p>
</div>
</div>
</div>
<LogoutButton
user={{
name: authStatus.user!.artist_name || authStatus.user!.email.split("@")[0] || "User",
avatar: getAssetUrl(authStatus.user!.avatar, "mini")!,
email: authStatus.user!.email,
}}
onLogout={handleLogout}
class="w-full"
/>
{/if}
<!-- Navigation -->
@@ -340,21 +321,5 @@
</div>
</div>
{#if authStatus.authenticated}
<button
class="cursor-pointer flex w-full items-center gap-3 rounded-xl border border-destructive/20 bg-destructive/5 px-4 py-3 transition-all duration-200 hover:bg-destructive/10 hover:border-destructive/30 group"
onclick={handleLogout}
>
<div
class="flex h-8 w-8 items-center justify-center rounded-lg bg-destructive/10 group-hover:bg-destructive/20 transition-colors"
>
<span class="icon-[ri--logout-circle-r-line] h-4 w-4 text-destructive"></span>
</div>
<div class="flex flex-1 flex-col gap-0.5 text-left">
<span class="text-sm font-medium text-foreground">{$_("header.logout")}</span>
<span class="text-xs text-muted-foreground">{$_("header.logout_hint")}</span>
</div>
</button>
{/if}
</div>
</div>

View File

@@ -11,15 +11,17 @@
interface Props {
user: User;
onLogout: () => void;
class?: string;
}
let { user, onLogout }: Props = $props();
let { user, onLogout, class: className = "" }: Props = $props();
let container: HTMLDivElement;
let isDragging = $state(false);
let slidePosition = $state(0);
let startX = 0;
let currentX = 0;
let maxSlide = 117; // Maximum slide distance
let maxSlide = $derived(container ? container.offsetWidth - 40 : 117);
let threshold = 0.75; // 70% threshold to trigger logout
// Calculate slide progress (0 to 1)
@@ -102,9 +104,10 @@
</script>
<div
bind:this={container}
class="relative h-10 w-40 rounded-full bg-muted/30 overflow-hidden select-none transition-all duration-300 bg-muted/40 shadow-lg shadow-accent/10 {isDragging
? 'cursor-grabbing'
: ''}"
: ''} {className}"
style="background: linear-gradient(90deg,
oklch(var(--primary) / 0.3) 0%,
oklch(var(--primary) / 0.3) {(1 - slideProgress) * 100}%,