style: refine admin & me UI — card forms, back arrows, avatar in admin sidebar, Empty component

- Replace ← text with icon-[ri--arrow-left-line] in admin and me layouts
- Add avatar + admin shield badge to admin sidebar header
- Wrap all admin edit forms in Card (bg-card/50 border-primary/20) with styled inputs
- Fix sm:pl-6 → lg:pl-6 so extra left padding only applies when sidebar is visible
- Update security form submit button to gradient style matching profile
- Remove "View Public Profile" button from me/profile
- Use shadcn-svelte Empty component for recordings empty state
- Install empty component via shadcn-svelte

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 18:16:39 +01:00
parent ba648c796a
commit d9a60f0572
26 changed files with 873 additions and 643 deletions

View File

@@ -38,7 +38,7 @@
isMobileMenuOpen = false; isMobileMenuOpen = false;
} }
function isActiveLink(link: { name: string; href: string }) { function isActiveLink(link: { name?: string; href: string }) {
return ( return (
(page.url.pathname === "/" && link === navLinks[0]) || (page.url.pathname === "/" && link === navLinks[0]) ||
(page.url.pathname.startsWith(link.href) && link !== navLinks[0]) (page.url.pathname.startsWith(link.href) && link !== navLinks[0])
@@ -80,20 +80,6 @@
{#if authStatus.authenticated} {#if authStatus.authenticated}
<div class="w-full flex items-center justify-end"> <div class="w-full flex items-center justify-end">
<div class="flex items-center gap-2 rounded-full bg-muted/30 p-1"> <div class="flex items-center gap-2 rounded-full bg-muted/30 p-1">
<Button
variant="link"
size="icon"
class={`flex h-9 w-9 rounded-full p-0 relative text-foreground/80 group ${isActiveLink({ href: "/me" }) ? "text-foreground" : "hover:text-foreground"}`}
href="/me"
title={$_("header.dashboard")}
>
<span class="icon-[ri--dashboard-2-line] h-4 w-4"></span>
<span
class={`absolute -bottom-1 left-0 w-0 h-0.5 bg-gradient-to-r from-primary to-accent transition-all duration-300 ${isActiveLink({ href: "/me" }) ? "w-full" : "group-hover:w-full"}`}
></span>
<span class="sr-only">{$_("header.dashboard")}</span>
</Button>
<Button <Button
variant="link" variant="link"
size="icon" size="icon"

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="empty-content"
class={cn(
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
className
)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="empty-description"
class={cn(
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4",
className
)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="empty-header"
class={cn("flex max-w-sm flex-col items-center gap-2 text-center", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,41 @@
<script lang="ts" module>
import { tv, type VariantProps } from "tailwind-variants";
export const emptyMediaVariants = tv({
base: "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: {
variant: {
default: "bg-transparent",
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
},
},
defaultVariants: {
variant: "default",
},
});
export type EmptyMediaVariant = VariantProps<typeof emptyMediaVariants>["variant"];
</script>
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
variant = "default",
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & { variant?: EmptyMediaVariant } = $props();
</script>
<div
bind:this={ref}
data-slot="empty-icon"
data-variant={variant}
class={cn(emptyMediaVariants({ variant }), className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="empty-title"
class={cn("text-lg font-medium tracking-tight", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="empty"
class={cn(
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
className
)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,22 @@
import Root from "./empty.svelte";
import Header from "./empty-header.svelte";
import Media from "./empty-media.svelte";
import Title from "./empty-title.svelte";
import Description from "./empty-description.svelte";
import Content from "./empty-content.svelte";
export {
Root,
Header,
Media,
Title,
Description,
Content,
//
Root as Empty,
Header as EmptyHeader,
Media as EmptyMedia,
Title as EmptyTitle,
Description as EmptyDescription,
Content as EmptyContent,
};

View File

@@ -96,8 +96,8 @@ export default {
security: "Security", security: "Security",
recordings: "Recordings", recordings: "Recordings",
analytics: "Analytics", analytics: "Analytics",
back_to_site: "Back to site", back_to_site: "Back to site",
back_mobile: "Back", back_mobile: "Back",
}, },
analytics: { analytics: {
title: "Analytics", title: "Analytics",
@@ -922,8 +922,8 @@ export default {
}, },
admin: { admin: {
nav: { nav: {
back_to_site: "Back to site", back_to_site: "Back to site",
back_mobile: "Back", back_mobile: "Back",
title: "Admin", title: "Admin",
users: "Users", users: "Users",
videos: "Videos", videos: "Videos",

View File

@@ -1,8 +1,17 @@
<script lang="ts"> <script lang="ts">
import { page } from "$app/state"; import { page } from "$app/state";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { Avatar, AvatarImage, AvatarFallback } from "$lib/components/ui/avatar";
import { getUserInitials } from "$lib/utils";
import { getAssetUrl } from "$lib/api";
const { children } = $props(); const { children, data } = $props();
const user = $derived(data.authStatus.user!);
const avatarUrl = $derived(
user.avatar ? (getAssetUrl(user.avatar, "thumbnail") ?? undefined) : undefined,
);
const displayName = $derived(user.artist_name ?? user.email);
const navLinks = $derived([ const navLinks = $derived([
{ name: $_("admin.nav.users"), href: "/admin/users", icon: "icon-[ri--team-line]" }, { name: $_("admin.nav.users"), href: "/admin/users", icon: "icon-[ri--team-line]" },
@@ -33,9 +42,10 @@
<div class="flex items-center gap-1 overflow-x-auto py-2 scrollbar-none"> <div class="flex items-center gap-1 overflow-x-auto py-2 scrollbar-none">
<a <a
href="/" href="/"
class="shrink-0 text-xs text-muted-foreground hover:text-foreground transition-colors px-2" class="shrink-0 flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors px-2"
> >
{$_("admin.nav.back_mobile")} <span class="icon-[ri--arrow-left-line] h-4 w-4"></span>
<span class="hidden sm:inline">{$_("admin.nav.back_mobile")}</span>
</a> </a>
{#each navLinks as link (link.href)} {#each navLinks as link (link.href)}
<a <a
@@ -58,10 +68,27 @@
<!-- Sidebar (desktop only) --> <!-- Sidebar (desktop only) -->
<aside class="hidden lg:flex w-56 shrink-0 flex-col border-r border-border/40"> <aside class="hidden lg:flex w-56 shrink-0 flex-col border-r border-border/40">
<div class="px-4 py-5 border-b border-border/40"> <div class="px-4 py-5 border-b border-border/40">
<a href="/" class="text-xs text-muted-foreground hover:text-foreground transition-colors"> <a href="/" class="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors">
<span class="icon-[ri--arrow-left-line] h-3.5 w-3.5"></span>
{$_("admin.nav.back_to_site")} {$_("admin.nav.back_to_site")}
</a> </a>
<h1 class="mt-2 text-base font-bold text-foreground">{$_("admin.nav.title")}</h1> <div class="mt-3 flex items-center gap-3">
<div class="relative shrink-0">
<Avatar class="h-9 w-9">
<AvatarImage src={avatarUrl} alt={displayName} />
<AvatarFallback class="text-xs">
{getUserInitials(displayName)}
</AvatarFallback>
</Avatar>
<span class="absolute -bottom-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-primary ring-2 ring-background">
<span class="icon-[ri--shield-keyhole-fill] h-2.5 w-2.5 text-primary-foreground"></span>
</span>
</div>
<div class="min-w-0">
<p class="text-sm font-semibold text-foreground truncate">{displayName}</p>
<p class="text-xs text-primary font-medium">{$_("admin.nav.title")}</p>
</div>
</div>
</div> </div>
<nav class="flex-1 p-3 space-y-1"> <nav class="flex-1 p-3 space-y-1">

View File

@@ -67,7 +67,7 @@
<Meta title={$_("admin.articles.title")} description={null} /> <Meta title={$_("admin.articles.title")} description={null} />
<div class="py-3 sm:py-6 sm:pl-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center justify-between mb-6 px-3 sm:px-0"> <div class="flex items-center justify-between mb-6 px-3 sm:px-0">
<h1 class="text-2xl font-bold">{$_("admin.articles.title")}</h1> <h1 class="text-2xl font-bold">{$_("admin.articles.title")}</h1>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">

View File

@@ -11,6 +11,7 @@
import { Textarea } from "$lib/components/ui/textarea"; import { Textarea } from "$lib/components/ui/textarea";
import { TagsInput } from "$lib/components/ui/tags-input"; import { TagsInput } from "$lib/components/ui/tags-input";
import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone"; import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone";
import { Card, CardContent } from "$lib/components/ui/card";
import { getAssetUrl } from "$lib/api"; import { getAssetUrl } from "$lib/api";
import { Select, SelectContent, SelectItem, SelectTrigger } from "$lib/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger } from "$lib/components/ui/select";
import { DatePicker } from "$lib/components/ui/date-picker"; import { DatePicker } from "$lib/components/ui/date-picker";
@@ -96,29 +97,43 @@
<Meta title={$_("admin.article_form.edit_title")} description={null} /> <Meta title={$_("admin.article_form.edit_title")} description={null} />
<div class="p-3 sm:p-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center gap-4 mb-6"> <div class="flex items-center gap-4 mb-6">
<Button variant="ghost" href="/admin/articles" size="sm"> <Button variant="ghost" href="/admin/articles" size="sm" class="shrink-0">
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>{$_("common.back")} <span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>{$_("common.back")}
</Button> </Button>
<h1 class="text-2xl font-bold">{$_("admin.article_form.edit_title")}</h1> <h1 class="text-2xl font-bold">{$_("admin.article_form.edit_title")}</h1>
</div> </div>
<div class="space-y-5 max-w-4xl"> <Card class="bg-card/50 border-primary/20 max-w-4xl">
<CardContent class="space-y-5 pt-6">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="title">{$_("admin.common.title_field")}</Label> <Label for="title">{$_("admin.common.title_field")}</Label>
<Input id="title" bind:value={title} /> <Input
id="title"
bind:value={title}
class="bg-background/50 border-primary/20 focus:border-primary"
/>
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="slug">{$_("admin.common.slug_field")}</Label> <Label for="slug">{$_("admin.common.slug_field")}</Label>
<Input id="slug" bind:value={slug} /> <Input
id="slug"
bind:value={slug}
class="bg-background/50 border-primary/20 focus:border-primary"
/>
</div> </div>
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="excerpt">{$_("admin.article_form.excerpt")}</Label> <Label for="excerpt">{$_("admin.article_form.excerpt")}</Label>
<Textarea id="excerpt" bind:value={excerpt} rows={2} /> <Textarea
id="excerpt"
bind:value={excerpt}
rows={2}
class="bg-background/50 border-primary/20 focus:border-primary"
/>
</div> </div>
<!-- Markdown editor with live preview --> <!-- Markdown editor with live preview -->
@@ -143,7 +158,7 @@
<div class="sm:grid sm:grid-cols-2 sm:gap-4 min-h-96"> <div class="sm:grid sm:grid-cols-2 sm:gap-4 min-h-96">
<Textarea <Textarea
bind:value={content} bind:value={content}
class={`h-full min-h-96 font-mono text-sm resize-none ${editorTab === "preview" ? "hidden sm:flex" : ""}`} class={`h-full min-h-96 font-mono text-sm resize-none bg-background/50 border-primary/20 focus:border-primary ${editorTab === "preview" ? "hidden sm:flex" : ""}`}
/> />
<div <div
class={`rounded-lg border border-border/40 bg-muted/20 p-4 overflow-auto prose prose-sm max-w-none prose-headings:text-foreground prose-p:text-muted-foreground min-h-96 ${editorTab === "write" ? "hidden sm:block" : ""}`} class={`rounded-lg border border-border/40 bg-muted/20 p-4 overflow-auto prose prose-sm max-w-none prose-headings:text-foreground prose-p:text-muted-foreground min-h-96 ${editorTab === "write" ? "hidden sm:block" : ""}`}
@@ -171,11 +186,10 @@
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} /> <FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} />
</div> </div>
<!-- Author -->
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.article_form.author")}</Label> <Label>{$_("admin.article_form.author")}</Label>
<Select type="single" bind:value={authorId}> <Select type="single" bind:value={authorId}>
<SelectTrigger class="w-full"> <SelectTrigger class="w-full bg-background/50 border-primary/20">
{#if selectedAuthor} {#if selectedAuthor}
{#if selectedAuthor.avatar} {#if selectedAuthor.avatar}
<img <img
@@ -210,7 +224,11 @@
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="category">{$_("admin.article_form.category")}</Label> <Label for="category">{$_("admin.article_form.category")}</Label>
<Input id="category" bind:value={category} /> <Input
id="category"
bind:value={category}
class="bg-background/50 border-primary/20 focus:border-primary"
/>
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.common.publish_date")}</Label> <Label>{$_("admin.common.publish_date")}</Label>
@@ -220,7 +238,7 @@
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.common.tags")}</Label> <Label>{$_("admin.common.tags")}</Label>
<TagsInput bind:value={tags} /> <TagsInput bind:value={tags} class="bg-background/50 border-primary/20 focus:border-primary" />
</div> </div>
<label class="flex items-center gap-2 cursor-pointer"> <label class="flex items-center gap-2 cursor-pointer">
@@ -232,11 +250,12 @@
<Button <Button
onclick={handleSubmit} onclick={handleSubmit}
disabled={saving} disabled={saving}
class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90" class="cursor-pointer bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
> >
{saving ? $_("admin.common.saving") : $_("admin.common.save_changes")} {saving ? $_("admin.common.saving") : $_("admin.common.save_changes")}
</Button> </Button>
<Button variant="outline" href="/admin/articles">{$_("common.cancel")}</Button> <Button variant="outline" href="/admin/articles">{$_("common.cancel")}</Button>
</div> </div>
</div> </CardContent>
</Card>
</div> </div>

View File

@@ -11,6 +11,7 @@
import { TagsInput } from "$lib/components/ui/tags-input"; import { TagsInput } from "$lib/components/ui/tags-input";
import { DatePicker } from "$lib/components/ui/date-picker"; import { DatePicker } from "$lib/components/ui/date-picker";
import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone"; import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone";
import { Card, CardContent } from "$lib/components/ui/card";
import Meta from "$lib/components/meta/meta.svelte"; import Meta from "$lib/components/meta/meta.svelte";
let title = $state(""); let title = $state("");
@@ -78,15 +79,16 @@
<Meta title={$_("admin.article_form.new_title")} description={null} /> <Meta title={$_("admin.article_form.new_title")} description={null} />
<div class="p-3 sm:p-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center gap-4 mb-6"> <div class="flex items-center gap-4 mb-6">
<Button variant="ghost" href="/admin/articles" size="sm"> <Button variant="ghost" href="/admin/articles" size="sm" class="shrink-0">
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>{$_("common.back")} <span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>{$_("common.back")}
</Button> </Button>
<h1 class="text-2xl font-bold">{$_("admin.article_form.new_title")}</h1> <h1 class="text-2xl font-bold">{$_("admin.article_form.new_title")}</h1>
</div> </div>
<div class="space-y-5 max-w-4xl"> <Card class="bg-card/50 border-primary/20 max-w-4xl">
<CardContent class="space-y-5 pt-6">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="title">{$_("admin.common.title_field")}</Label> <Label for="title">{$_("admin.common.title_field")}</Label>
@@ -97,6 +99,7 @@
if (!slug) slug = generateSlug(title); if (!slug) slug = generateSlug(title);
}} }}
placeholder={$_("admin.article_form.title_placeholder")} placeholder={$_("admin.article_form.title_placeholder")}
class="bg-background/50 border-primary/20 focus:border-primary"
/> />
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
@@ -105,6 +108,7 @@
id="slug" id="slug"
bind:value={slug} bind:value={slug}
placeholder={$_("admin.article_form.slug_placeholder")} placeholder={$_("admin.article_form.slug_placeholder")}
class="bg-background/50 border-primary/20 focus:border-primary"
/> />
</div> </div>
</div> </div>
@@ -116,6 +120,7 @@
bind:value={excerpt} bind:value={excerpt}
placeholder={$_("admin.article_form.excerpt_placeholder")} placeholder={$_("admin.article_form.excerpt_placeholder")}
rows={2} rows={2}
class="bg-background/50 border-primary/20 focus:border-primary"
/> />
</div> </div>
@@ -143,7 +148,7 @@
<Textarea <Textarea
bind:value={content} bind:value={content}
placeholder={$_("admin.article_form.content_placeholder")} placeholder={$_("admin.article_form.content_placeholder")}
class={`h-full min-h-96 font-mono text-sm resize-none ${editorTab === "preview" ? "hidden sm:flex" : ""}`} class={`h-full min-h-96 font-mono text-sm resize-none bg-background/50 border-primary/20 focus:border-primary ${editorTab === "preview" ? "hidden sm:flex" : ""}`}
/> />
<div <div
class={`rounded-lg border border-border/40 bg-muted/20 p-4 overflow-auto prose prose-sm max-w-none prose-headings:text-foreground prose-p:text-muted-foreground min-h-96 ${editorTab === "write" ? "hidden sm:block" : ""}`} class={`rounded-lg border border-border/40 bg-muted/20 p-4 overflow-auto prose prose-sm max-w-none prose-headings:text-foreground prose-p:text-muted-foreground min-h-96 ${editorTab === "write" ? "hidden sm:block" : ""}`}
@@ -162,9 +167,9 @@
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.common.cover_image")}</Label> <Label>{$_("admin.common.cover_image")}</Label>
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} /> <FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} />
{#if imageId}<p class="text-xs text-green-600 mt-1"> {#if imageId}
{$_("admin.common.image_uploaded")} <p class="text-xs text-green-600 mt-1">{$_("admin.common.image_uploaded")}</p>
</p>{/if} {/if}
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
@@ -174,6 +179,7 @@
id="category" id="category"
bind:value={category} bind:value={category}
placeholder={$_("admin.article_form.category_placeholder")} placeholder={$_("admin.article_form.category_placeholder")}
class="bg-background/50 border-primary/20 focus:border-primary"
/> />
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
@@ -184,7 +190,7 @@
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.common.tags")}</Label> <Label>{$_("admin.common.tags")}</Label>
<TagsInput bind:value={tags} /> <TagsInput bind:value={tags} class="bg-background/50 border-primary/20 focus:border-primary" />
</div> </div>
<label class="flex items-center gap-2 cursor-pointer"> <label class="flex items-center gap-2 cursor-pointer">
@@ -196,11 +202,12 @@
<Button <Button
onclick={handleSubmit} onclick={handleSubmit}
disabled={saving} disabled={saving}
class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90" class="cursor-pointer bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
> >
{saving ? $_("admin.common.creating") : $_("admin.article_form.create")} {saving ? $_("admin.common.creating") : $_("admin.article_form.create")}
</Button> </Button>
<Button variant="outline" href="/admin/articles">{$_("common.cancel")}</Button> <Button variant="outline" href="/admin/articles">{$_("common.cancel")}</Button>
</div> </div>
</div> </CardContent>
</Card>
</div> </div>

View File

@@ -56,7 +56,7 @@
<Meta title={$_("admin.comments.title")} description={null} /> <Meta title={$_("admin.comments.title")} description={null} />
<div class="py-3 sm:py-6 sm:pl-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center justify-between mb-6 px-3 sm:px-0"> <div class="flex items-center justify-between mb-6 px-3 sm:px-0">
<h1 class="text-2xl font-bold">{$_("admin.comments.title")}</h1> <h1 class="text-2xl font-bold">{$_("admin.comments.title")}</h1>
<span class="text-sm text-muted-foreground" <span class="text-sm text-muted-foreground"

View File

@@ -127,7 +127,7 @@
<Meta title={$_("admin.queues.title")} description={null} /> <Meta title={$_("admin.queues.title")} description={null} />
<div class="py-3 sm:py-6 sm:pl-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center justify-between mb-6 px-3 sm:px-0"> <div class="flex items-center justify-between mb-6 px-3 sm:px-0">
<h1 class="text-2xl font-bold">{$_("admin.queues.title")}</h1> <h1 class="text-2xl font-bold">{$_("admin.queues.title")}</h1>
</div> </div>

View File

@@ -66,7 +66,7 @@
<Meta title={$_("admin.recordings.title")} description={null} /> <Meta title={$_("admin.recordings.title")} description={null} />
<div class="py-3 sm:py-6 sm:pl-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center justify-between mb-6 px-3 sm:px-0"> <div class="flex items-center justify-between mb-6 px-3 sm:px-0">
<h1 class="text-2xl font-bold">{$_("admin.recordings.title")}</h1> <h1 class="text-2xl font-bold">{$_("admin.recordings.title")}</h1>
<span class="text-sm text-muted-foreground" <span class="text-sm text-muted-foreground"

View File

@@ -87,7 +87,7 @@
<Meta title={$_("admin.users.title")} description={null} /> <Meta title={$_("admin.users.title")} description={null} />
<div class="py-3 sm:py-6 sm:pl-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center justify-between mb-6 px-3 sm:px-0"> <div class="flex items-center justify-between mb-6 px-3 sm:px-0">
<h1 class="text-2xl font-bold">{$_("admin.users.title")}</h1> <h1 class="text-2xl font-bold">{$_("admin.users.title")}</h1>
<span class="text-sm text-muted-foreground" <span class="text-sm text-muted-foreground"

View File

@@ -13,6 +13,7 @@
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { Input } from "$lib/components/ui/input"; import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label"; import { Label } from "$lib/components/ui/label";
import { Card, CardContent } from "$lib/components/ui/card";
import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone"; import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone";
import Meta from "$lib/components/meta/meta.svelte"; import Meta from "$lib/components/meta/meta.svelte";
@@ -128,9 +129,9 @@
<Meta title={data.user.artist_name || data.user.email} description={null} /> <Meta title={data.user.artist_name || data.user.email} description={null} />
<div class="p-3 sm:p-6 max-w-2xl"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center gap-4 mb-6"> <div class="flex items-center gap-4 mb-6">
<Button variant="ghost" href="/admin/users" size="sm"> <Button variant="ghost" href="/admin/users" size="sm" class="shrink-0">
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>{$_("common.back")} <span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>{$_("common.back")}
</Button> </Button>
<div> <div>
@@ -143,25 +144,38 @@
</div> </div>
</div> </div>
<div class="space-y-6"> <div class="space-y-6 max-w-2xl">
<!-- Basic info --> <!-- Profile & files card -->
<Card class="bg-card/50 border-primary/20">
<CardContent class="space-y-5 pt-6">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="firstName">{$_("admin.user_edit.first_name")}</Label> <Label for="firstName">{$_("admin.user_edit.first_name")}</Label>
<Input id="firstName" bind:value={firstName} /> <Input
id="firstName"
bind:value={firstName}
class="bg-background/50 border-primary/20 focus:border-primary"
/>
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="lastName">{$_("admin.user_edit.last_name")}</Label> <Label for="lastName">{$_("admin.user_edit.last_name")}</Label>
<Input id="lastName" bind:value={lastName} /> <Input
id="lastName"
bind:value={lastName}
class="bg-background/50 border-primary/20 focus:border-primary"
/>
</div> </div>
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="artistName">{$_("admin.user_edit.artist_name")}</Label> <Label for="artistName">{$_("admin.user_edit.artist_name")}</Label>
<Input id="artistName" bind:value={artistName} /> <Input
id="artistName"
bind:value={artistName}
class="bg-background/50 border-primary/20 focus:border-primary"
/>
</div> </div>
<!-- Avatar -->
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.user_edit.avatar")}</Label> <Label>{$_("admin.user_edit.avatar")}</Label>
{#if avatarId} {#if avatarId}
@@ -174,7 +188,6 @@
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleAvatarUpload} /> <FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleAvatarUpload} />
</div> </div>
<!-- Banner -->
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.user_edit.banner")}</Label> <Label>{$_("admin.user_edit.banner")}</Label>
{#if bannerId} {#if bannerId}
@@ -187,7 +200,6 @@
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleBannerUpload} /> <FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleBannerUpload} />
</div> </div>
<!-- Model photo (used in cards & model page, not for avatar/comments) -->
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.user_edit.model_photo")}</Label> <Label>{$_("admin.user_edit.model_photo")}</Label>
<p class="text-xs text-muted-foreground">{$_("admin.user_edit.model_photo_hint")}</p> <p class="text-xs text-muted-foreground">{$_("admin.user_edit.model_photo_hint")}</p>
@@ -201,7 +213,6 @@
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handlePhotoUpload2} /> <FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handlePhotoUpload2} />
</div> </div>
<!-- Admin flag -->
<label <label
class="flex items-center gap-3 rounded-lg border border-border/40 px-4 py-3 cursor-pointer hover:bg-muted/20 transition-colors" class="flex items-center gap-3 rounded-lg border border-border/40 px-4 py-3 cursor-pointer hover:bg-muted/20 transition-colors"
> >
@@ -216,18 +227,19 @@
</div> </div>
</label> </label>
<div class="flex gap-3">
<Button <Button
onclick={handleSave} onclick={handleSave}
disabled={saving} disabled={saving}
class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90" class="cursor-pointer w-full bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
> >
{saving ? $_("admin.common.saving") : $_("admin.common.save_changes")} {saving ? $_("admin.common.saving") : $_("admin.common.save_changes")}
</Button> </Button>
</div> </CardContent>
</Card>
<!-- Photo gallery --> <!-- Photo gallery card -->
<div class="space-y-3 pt-4 border-t border-border/40"> <Card class="bg-card/50 border-primary/20">
<CardContent class="space-y-4 pt-6">
<Label>{$_("admin.user_edit.photos")}</Label> <Label>{$_("admin.user_edit.photos")}</Label>
{#if data.user.photos && data.user.photos.length > 0} {#if data.user.photos && data.user.photos.length > 0}
@@ -255,6 +267,7 @@
{/if} {/if}
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handlePhotoUpload} /> <FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handlePhotoUpload} />
</div> </CardContent>
</Card>
</div> </div>
</div> </div>

View File

@@ -64,7 +64,7 @@
<Meta title={$_("admin.videos.title")} description={null} /> <Meta title={$_("admin.videos.title")} description={null} />
<div class="py-3 sm:py-6 sm:pl-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center justify-between mb-6 px-3 sm:px-0"> <div class="flex items-center justify-between mb-6 px-3 sm:px-0">
<h1 class="text-2xl font-bold">{$_("admin.videos.title")}</h1> <h1 class="text-2xl font-bold">{$_("admin.videos.title")}</h1>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">

View File

@@ -10,6 +10,7 @@
import { Textarea } from "$lib/components/ui/textarea"; import { Textarea } from "$lib/components/ui/textarea";
import { TagsInput } from "$lib/components/ui/tags-input"; import { TagsInput } from "$lib/components/ui/tags-input";
import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone"; import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone";
import { Card, CardContent } from "$lib/components/ui/card";
import { getAssetUrl } from "$lib/api"; import { getAssetUrl } from "$lib/api";
import { Select, SelectContent, SelectItem, SelectTrigger } from "$lib/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger } from "$lib/components/ui/select";
import { DatePicker } from "$lib/components/ui/date-picker"; import { DatePicker } from "$lib/components/ui/date-picker";
@@ -105,15 +106,16 @@
<Meta title={$_("admin.video_form.edit_title")} description={null} /> <Meta title={$_("admin.video_form.edit_title")} description={null} />
<div class="p-3 sm:p-6 max-w-2xl"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center gap-4 mb-6"> <div class="flex items-center gap-4 mb-6">
<Button variant="ghost" href="/admin/videos" size="sm"> <Button variant="ghost" href="/admin/videos" size="sm" class="shrink-0">
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>{$_("common.back")} <span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>{$_("common.back")}
</Button> </Button>
<h1 class="text-2xl font-bold">{$_("admin.video_form.edit_title")}</h1> <h1 class="text-2xl font-bold">{$_("admin.video_form.edit_title")}</h1>
</div> </div>
<div class="space-y-5"> <Card class="bg-card/50 border-primary/20 max-w-2xl">
<CardContent class="space-y-5 pt-6">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="title">{$_("admin.common.title_field")}</Label> <Label for="title">{$_("admin.common.title_field")}</Label>
@@ -121,11 +123,17 @@
id="title" id="title"
bind:value={title} bind:value={title}
placeholder={$_("admin.video_form.title_placeholder")} placeholder={$_("admin.video_form.title_placeholder")}
class="bg-background/50 border-primary/20 focus:border-primary"
/> />
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="slug">{$_("admin.common.slug_field")}</Label> <Label for="slug">{$_("admin.common.slug_field")}</Label>
<Input id="slug" bind:value={slug} placeholder={$_("admin.video_form.slug_placeholder")} /> <Input
id="slug"
bind:value={slug}
placeholder={$_("admin.video_form.slug_placeholder")}
class="bg-background/50 border-primary/20 focus:border-primary"
/>
</div> </div>
</div> </div>
@@ -136,6 +144,7 @@
bind:value={description} bind:value={description}
placeholder={$_("admin.video_form.description_placeholder")} placeholder={$_("admin.video_form.description_placeholder")}
rows={3} rows={3}
class="bg-background/50 border-primary/20 focus:border-primary"
/> />
</div> </div>
@@ -168,7 +177,7 @@
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.common.tags")}</Label> <Label>{$_("admin.common.tags")}</Label>
<TagsInput bind:value={tags} /> <TagsInput bind:value={tags} class="bg-background/50 border-primary/20 focus:border-primary" />
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
@@ -195,7 +204,7 @@
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.video_form.models")}</Label> <Label>{$_("admin.video_form.models")}</Label>
<Select type="multiple" bind:value={selectedModelIds}> <Select type="multiple" bind:value={selectedModelIds}>
<SelectTrigger class="w-full"> <SelectTrigger class="w-full bg-background/50 border-primary/20">
{#if selectedModelIds.length} {#if selectedModelIds.length}
{$_("admin.video_form.models_selected", { {$_("admin.video_form.models_selected", {
values: { count: selectedModelIds.length }, values: { count: selectedModelIds.length },
@@ -226,11 +235,12 @@
<Button <Button
onclick={handleSubmit} onclick={handleSubmit}
disabled={saving} disabled={saving}
class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90" class="cursor-pointer bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
> >
{saving ? $_("admin.common.saving") : $_("admin.common.save_changes")} {saving ? $_("admin.common.saving") : $_("admin.common.save_changes")}
</Button> </Button>
<Button variant="outline" href="/admin/videos">{$_("common.cancel")}</Button> <Button variant="outline" href="/admin/videos">{$_("common.cancel")}</Button>
</div> </div>
</div> </CardContent>
</Card>
</div> </div>

View File

@@ -10,6 +10,7 @@
import { TagsInput } from "$lib/components/ui/tags-input"; import { TagsInput } from "$lib/components/ui/tags-input";
import { DatePicker } from "$lib/components/ui/date-picker"; import { DatePicker } from "$lib/components/ui/date-picker";
import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone"; import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone";
import { Card, CardContent } from "$lib/components/ui/card";
import Meta from "$lib/components/meta/meta.svelte"; import Meta from "$lib/components/meta/meta.svelte";
const { data } = $props(); const { data } = $props();
@@ -100,15 +101,16 @@
<Meta title={$_("admin.video_form.new_title")} description={null} /> <Meta title={$_("admin.video_form.new_title")} description={null} />
<div class="p-3 sm:p-6 max-w-2xl"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center gap-4 mb-6"> <div class="flex items-center gap-4 mb-6">
<Button variant="ghost" href="/admin/videos" size="sm"> <Button variant="ghost" href="/admin/videos" size="sm" class="shrink-0">
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>{$_("common.back")} <span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>{$_("common.back")}
</Button> </Button>
<h1 class="text-2xl font-bold">{$_("admin.video_form.new_title")}</h1> <h1 class="text-2xl font-bold">{$_("admin.video_form.new_title")}</h1>
</div> </div>
<div class="space-y-5"> <Card class="bg-card/50 border-primary/20 max-w-2xl">
<CardContent class="space-y-5 pt-6">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="title">{$_("admin.common.title_field")}</Label> <Label for="title">{$_("admin.common.title_field")}</Label>
@@ -119,11 +121,17 @@
if (!slug) slug = generateSlug(title); if (!slug) slug = generateSlug(title);
}} }}
placeholder={$_("admin.video_form.title_placeholder")} placeholder={$_("admin.video_form.title_placeholder")}
class="bg-background/50 border-primary/20 focus:border-primary"
/> />
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label for="slug">{$_("admin.common.slug_field")}</Label> <Label for="slug">{$_("admin.common.slug_field")}</Label>
<Input id="slug" bind:value={slug} placeholder={$_("admin.video_form.slug_placeholder")} /> <Input
id="slug"
bind:value={slug}
placeholder={$_("admin.video_form.slug_placeholder")}
class="bg-background/50 border-primary/20 focus:border-primary"
/>
</div> </div>
</div> </div>
@@ -134,28 +142,29 @@
bind:value={description} bind:value={description}
placeholder={$_("admin.video_form.description_placeholder")} placeholder={$_("admin.video_form.description_placeholder")}
rows={3} rows={3}
class="bg-background/50 border-primary/20 focus:border-primary"
/> />
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.common.cover_image")}</Label> <Label>{$_("admin.common.cover_image")}</Label>
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} /> <FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} />
{#if imageId}<p class="text-xs text-green-600 mt-1"> {#if imageId}
{$_("admin.common.image_uploaded")} <p class="text-xs text-green-600 mt-1">{$_("admin.common.image_uploaded")}</p>
</p>{/if} {/if}
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.video_form.video_file")}</Label> <Label>{$_("admin.video_form.video_file")}</Label>
<FileDropZone accept="video/*" maxFileSize={2000 * MEGABYTE} onUpload={handleVideoUpload} /> <FileDropZone accept="video/*" maxFileSize={2000 * MEGABYTE} onUpload={handleVideoUpload} />
{#if movieId}<p class="text-xs text-green-600 mt-1"> {#if movieId}
{$_("admin.video_form.video_uploaded")} <p class="text-xs text-green-600 mt-1">{$_("admin.video_form.video_uploaded")}</p>
</p>{/if} {/if}
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<Label>{$_("admin.common.tags")}</Label> <Label>{$_("admin.common.tags")}</Label>
<TagsInput bind:value={tags} /> <TagsInput bind:value={tags} class="bg-background/50 border-primary/20 focus:border-primary" />
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
@@ -180,7 +189,7 @@
{#if data.models.length > 0} {#if data.models.length > 0}
<div class="space-y-2"> <div class="space-y-2">
<Label>Models</Label> <Label>{$_("admin.video_form.models")}</Label>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
{#each data.models as model (model.id)} {#each data.models as model (model.id)}
<Button <Button
@@ -204,11 +213,12 @@
<Button <Button
onclick={handleSubmit} onclick={handleSubmit}
disabled={saving} disabled={saving}
class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90" class="cursor-pointer bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
> >
{saving ? $_("admin.common.creating") : $_("admin.video_form.create")} {saving ? $_("admin.common.creating") : $_("admin.video_form.create")}
</Button> </Button>
<Button variant="outline" href="/admin/videos">{$_("common.cancel")}</Button> <Button variant="outline" href="/admin/videos">{$_("common.cancel")}</Button>
</div> </div>
</div> </CardContent>
</Card>
</div> </div>

View File

@@ -44,9 +44,10 @@
<div class="flex items-center gap-1 overflow-x-auto py-2 scrollbar-none"> <div class="flex items-center gap-1 overflow-x-auto py-2 scrollbar-none">
<a <a
href="/" href="/"
class="shrink-0 text-xs text-muted-foreground hover:text-foreground transition-colors px-2" class="shrink-0 flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors px-2"
> >
{$_("me.nav.back_mobile")} <span class="icon-[ri--arrow-left-line] h-4 w-4"></span>
<span class="hidden sm:inline">{$_("me.nav.back_mobile")}</span>
</a> </a>
{#each navLinks as link (link.href)} {#each navLinks as link (link.href)}
<a <a
@@ -69,7 +70,8 @@
<!-- Sidebar (desktop only) --> <!-- Sidebar (desktop only) -->
<aside class="hidden lg:flex w-56 shrink-0 flex-col border-r border-border/40"> <aside class="hidden lg:flex w-56 shrink-0 flex-col border-r border-border/40">
<div class="px-4 py-5 border-b border-border/40"> <div class="px-4 py-5 border-b border-border/40">
<a href="/" class="text-xs text-muted-foreground hover:text-foreground transition-colors"> <a href="/" class="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors">
<span class="icon-[ri--arrow-left-line] h-3.5 w-3.5"></span>
{$_("me.nav.back_to_site")} {$_("me.nav.back_to_site")}
</a> </a>
<div class="mt-3 flex items-center gap-3"> <div class="mt-3 flex items-center gap-3">

View File

@@ -14,15 +14,15 @@
<Meta title={$_("me.analytics.title")} /> <Meta title={$_("me.analytics.title")} />
<div class="py-3 sm:py-6 sm:pl-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center justify-between mb-6 px-3 sm:px-0"> <div class="flex items-center justify-between mb-6">
<div> <div>
<h1 class="text-2xl font-bold">{$_("me.analytics.title")}</h1> <h1 class="text-2xl font-bold">{$_("me.analytics.title")}</h1>
<p class="text-sm text-muted-foreground mt-0.5">{$_("me.analytics.description")}</p> <p class="text-sm text-muted-foreground mt-0.5">{$_("me.analytics.description")}</p>
</div> </div>
</div> </div>
<div class="px-3 sm:px-0 space-y-6"> <div class="space-y-6">
{#if data.analytics} {#if data.analytics}
<!-- Overview Stats --> <!-- Overview Stats -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6"> <div class="grid grid-cols-1 md:grid-cols-3 gap-6">

View File

@@ -2,7 +2,7 @@
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import { invalidateAll } from "$app/navigation"; import { invalidateAll } from "$app/navigation";
import { untrack } from "svelte"; import { untrack } from "svelte";
import { getAssetUrl, isModel } from "$lib/api"; import { getAssetUrl } from "$lib/api";
import { toast } from "svelte-sonner"; import { toast } from "svelte-sonner";
import { updateProfile, uploadFile, removeFile } from "$lib/services"; import { updateProfile, uploadFile, removeFile } from "$lib/services";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
@@ -129,17 +129,11 @@
<Meta title={$_("me.settings.profile_title")} /> <Meta title={$_("me.settings.profile_title")} />
<div class="py-3 sm:py-6 sm:pl-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center justify-between mb-6 px-3 sm:px-0"> <div class="mb-6">
<h1 class="text-2xl font-bold">{$_("me.settings.profile_title")}</h1> <h1 class="text-2xl font-bold">{$_("me.settings.profile_title")}</h1>
{#if isModel(data.authStatus.user!)}
<Button href={`/models/${data.authStatus.user!.slug}`} variant="outline">
{$_("me.view_profile")}
</Button>
{/if}
</div> </div>
<div class="px-3 sm:px-0">
<Card class="bg-card/50 border-primary/20 max-w-2xl"> <Card class="bg-card/50 border-primary/20 max-w-2xl">
<CardHeader> <CardHeader>
<CardTitle>{$_("me.settings.profile_title")}</CardTitle> <CardTitle>{$_("me.settings.profile_title")}</CardTitle>
@@ -288,4 +282,3 @@
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
</div>

View File

@@ -5,7 +5,7 @@
import { toast } from "svelte-sonner"; import { toast } from "svelte-sonner";
import { deleteRecording } from "$lib/services"; import { deleteRecording } from "$lib/services";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { Card, CardContent } from "$lib/components/ui/card"; import * as Empty from "$lib/components/ui/empty";
import * as Dialog from "$lib/components/ui/dialog"; import * as Dialog from "$lib/components/ui/dialog";
import RecordingCard from "$lib/components/recording-card/recording-card.svelte"; import RecordingCard from "$lib/components/recording-card/recording-card.svelte";
import Meta from "$lib/components/meta/meta.svelte"; import Meta from "$lib/components/meta/meta.svelte";
@@ -45,8 +45,8 @@
<Meta title={$_("me.recordings.title")} /> <Meta title={$_("me.recordings.title")} />
<div class="py-3 sm:py-6 sm:pl-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center justify-between mb-6 px-3 sm:px-0"> <div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold">{$_("me.recordings.title")}</h1> <h1 class="text-2xl font-bold">{$_("me.recordings.title")}</h1>
<Button <Button
href="/play" href="/play"
@@ -57,20 +57,16 @@
</Button> </Button>
</div> </div>
<div class="px-3 sm:px-0">
{#if recordings.length === 0} {#if recordings.length === 0}
<Card class="bg-card/50 border-primary/20"> <Empty.Root>
<CardContent class="py-12"> <Empty.Header>
<div class="flex flex-col items-center justify-center text-center"> <Empty.Media variant="icon">
<div class="mb-4 p-4 rounded-full bg-muted/30 border border-border/30"> <span class="icon-[ri--play-list-2-line] w-8 h-8"></span>
<span class="icon-[ri--play-list-2-line] w-12 h-12 text-muted-foreground"></span> </Empty.Media>
</div> <Empty.Title>{$_("me.recordings.no_recordings")}</Empty.Title>
<h3 class="text-xl font-semibold mb-2"> <Empty.Description>{$_("me.recordings.no_recordings_description")}</Empty.Description>
{$_("me.recordings.no_recordings")} </Empty.Header>
</h3> <Empty.Content>
<p class="text-muted-foreground mb-6 max-w-md">
{$_("me.recordings.no_recordings_description")}
</p>
<Button <Button
href="/play" href="/play"
class="cursor-pointer bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90" class="cursor-pointer bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
@@ -78,9 +74,8 @@
<span class="icon-[ri--rocket-line] w-4 h-4 mr-2"></span> <span class="icon-[ri--rocket-line] w-4 h-4 mr-2"></span>
{$_("me.recordings.go_to_play")} {$_("me.recordings.go_to_play")}
</Button> </Button>
</div> </Empty.Content>
</CardContent> </Empty.Root>
</Card>
{:else} {:else}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{#each recordings as recording (recording.id)} {#each recordings as recording (recording.id)}
@@ -93,7 +88,6 @@
</div> </div>
{/if} {/if}
</div> </div>
</div>
<Dialog.Root bind:open={deleteOpen}> <Dialog.Root bind:open={deleteOpen}>
<Dialog.Content> <Dialog.Content>

View File

@@ -57,12 +57,11 @@
<Meta title={$_("me.settings.privacy_title")} /> <Meta title={$_("me.settings.privacy_title")} />
<div class="py-3 sm:py-6 sm:pl-6"> <div class="py-3 sm:py-6 lg:pl-6">
<div class="flex items-center justify-between mb-6 px-3 sm:px-0"> <div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold">{$_("me.settings.privacy_title")}</h1> <h1 class="text-2xl font-bold">{$_("me.settings.privacy_title")}</h1>
</div> </div>
<div class="px-3 sm:px-0">
<Card class="bg-card/50 border-primary/20 max-w-2xl"> <Card class="bg-card/50 border-primary/20 max-w-2xl">
<CardHeader> <CardHeader>
<CardTitle>{$_("me.settings.privacy_title")}</CardTitle> <CardTitle>{$_("me.settings.privacy_title")}</CardTitle>
@@ -145,9 +144,8 @@
{/if} {/if}
<Button <Button
variant="outline"
type="submit" type="submit"
class="cursor-pointer w-full border-primary/20 hover:bg-primary/10" class="cursor-pointer w-full bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
disabled={isSecurityLoading} disabled={isSecurityLoading}
> >
{#if isSecurityLoading} {#if isSecurityLoading}
@@ -163,4 +161,3 @@
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
</div>