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:
@@ -38,7 +38,7 @@
|
||||
isMobileMenuOpen = false;
|
||||
}
|
||||
|
||||
function isActiveLink(link: { name: string; href: string }) {
|
||||
function isActiveLink(link: { name?: string; href: string }) {
|
||||
return (
|
||||
(page.url.pathname === "/" && link === navLinks[0]) ||
|
||||
(page.url.pathname.startsWith(link.href) && link !== navLinks[0])
|
||||
@@ -80,20 +80,6 @@
|
||||
{#if authStatus.authenticated}
|
||||
<div class="w-full flex items-center justify-end">
|
||||
<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
|
||||
variant="link"
|
||||
size="icon"
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
23
packages/frontend/src/lib/components/ui/empty/empty.svelte
Normal file
23
packages/frontend/src/lib/components/ui/empty/empty.svelte
Normal 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>
|
||||
22
packages/frontend/src/lib/components/ui/empty/index.ts
Normal file
22
packages/frontend/src/lib/components/ui/empty/index.ts
Normal 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,
|
||||
};
|
||||
@@ -96,8 +96,8 @@ export default {
|
||||
security: "Security",
|
||||
recordings: "Recordings",
|
||||
analytics: "Analytics",
|
||||
back_to_site: "← Back to site",
|
||||
back_mobile: "← Back",
|
||||
back_to_site: "Back to site",
|
||||
back_mobile: "Back",
|
||||
},
|
||||
analytics: {
|
||||
title: "Analytics",
|
||||
@@ -922,8 +922,8 @@ export default {
|
||||
},
|
||||
admin: {
|
||||
nav: {
|
||||
back_to_site: "← Back to site",
|
||||
back_mobile: "← Back",
|
||||
back_to_site: "Back to site",
|
||||
back_mobile: "Back",
|
||||
title: "Admin",
|
||||
users: "Users",
|
||||
videos: "Videos",
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/state";
|
||||
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([
|
||||
{ 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">
|
||||
<a
|
||||
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>
|
||||
{#each navLinks as link (link.href)}
|
||||
<a
|
||||
@@ -58,10 +68,27 @@
|
||||
<!-- Sidebar (desktop only) -->
|
||||
<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">
|
||||
<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")}
|
||||
</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>
|
||||
|
||||
<nav class="flex-1 p-3 space-y-1">
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
<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">
|
||||
<h1 class="text-2xl font-bold">{$_("admin.articles.title")}</h1>
|
||||
<div class="flex items-center gap-3">
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { Textarea } from "$lib/components/ui/textarea";
|
||||
import { TagsInput } from "$lib/components/ui/tags-input";
|
||||
import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone";
|
||||
import { Card, CardContent } from "$lib/components/ui/card";
|
||||
import { getAssetUrl } from "$lib/api";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "$lib/components/ui/select";
|
||||
import { DatePicker } from "$lib/components/ui/date-picker";
|
||||
@@ -96,29 +97,43 @@
|
||||
|
||||
<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">
|
||||
<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")}
|
||||
</Button>
|
||||
<h1 class="text-2xl font-bold">{$_("admin.article_form.edit_title")}</h1>
|
||||
</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="space-y-1.5">
|
||||
<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 class="space-y-1.5">
|
||||
<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 class="space-y-1.5">
|
||||
<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>
|
||||
|
||||
<!-- Markdown editor with live preview -->
|
||||
@@ -143,7 +158,7 @@
|
||||
<div class="sm:grid sm:grid-cols-2 sm:gap-4 min-h-96">
|
||||
<Textarea
|
||||
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
|
||||
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} />
|
||||
</div>
|
||||
|
||||
<!-- Author -->
|
||||
<div class="space-y-1.5">
|
||||
<Label>{$_("admin.article_form.author")}</Label>
|
||||
<Select type="single" bind:value={authorId}>
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectTrigger class="w-full bg-background/50 border-primary/20">
|
||||
{#if selectedAuthor}
|
||||
{#if selectedAuthor.avatar}
|
||||
<img
|
||||
@@ -210,7 +224,11 @@
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-1.5">
|
||||
<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 class="space-y-1.5">
|
||||
<Label>{$_("admin.common.publish_date")}</Label>
|
||||
@@ -220,7 +238,7 @@
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label>{$_("admin.common.tags")}</Label>
|
||||
<TagsInput bind:value={tags} />
|
||||
<TagsInput bind:value={tags} class="bg-background/50 border-primary/20 focus:border-primary" />
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
@@ -232,11 +250,12 @@
|
||||
<Button
|
||||
onclick={handleSubmit}
|
||||
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")}
|
||||
</Button>
|
||||
<Button variant="outline" href="/admin/articles">{$_("common.cancel")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { TagsInput } from "$lib/components/ui/tags-input";
|
||||
import { DatePicker } from "$lib/components/ui/date-picker";
|
||||
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";
|
||||
|
||||
let title = $state("");
|
||||
@@ -78,15 +79,16 @@
|
||||
|
||||
<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">
|
||||
<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")}
|
||||
</Button>
|
||||
<h1 class="text-2xl font-bold">{$_("admin.article_form.new_title")}</h1>
|
||||
</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="space-y-1.5">
|
||||
<Label for="title">{$_("admin.common.title_field")}</Label>
|
||||
@@ -97,6 +99,7 @@
|
||||
if (!slug) slug = generateSlug(title);
|
||||
}}
|
||||
placeholder={$_("admin.article_form.title_placeholder")}
|
||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
@@ -105,6 +108,7 @@
|
||||
id="slug"
|
||||
bind:value={slug}
|
||||
placeholder={$_("admin.article_form.slug_placeholder")}
|
||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,6 +120,7 @@
|
||||
bind:value={excerpt}
|
||||
placeholder={$_("admin.article_form.excerpt_placeholder")}
|
||||
rows={2}
|
||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -143,7 +148,7 @@
|
||||
<Textarea
|
||||
bind:value={content}
|
||||
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
|
||||
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">
|
||||
<Label>{$_("admin.common.cover_image")}</Label>
|
||||
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} />
|
||||
{#if imageId}<p class="text-xs text-green-600 mt-1">
|
||||
{$_("admin.common.image_uploaded")} ✓
|
||||
</p>{/if}
|
||||
{#if imageId}
|
||||
<p class="text-xs text-green-600 mt-1">{$_("admin.common.image_uploaded")} ✓</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
@@ -174,6 +179,7 @@
|
||||
id="category"
|
||||
bind:value={category}
|
||||
placeholder={$_("admin.article_form.category_placeholder")}
|
||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
@@ -184,7 +190,7 @@
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label>{$_("admin.common.tags")}</Label>
|
||||
<TagsInput bind:value={tags} />
|
||||
<TagsInput bind:value={tags} class="bg-background/50 border-primary/20 focus:border-primary" />
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
@@ -196,11 +202,12 @@
|
||||
<Button
|
||||
onclick={handleSubmit}
|
||||
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")}
|
||||
</Button>
|
||||
<Button variant="outline" href="/admin/articles">{$_("common.cancel")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
<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">
|
||||
<h1 class="text-2xl font-bold">{$_("admin.comments.title")}</h1>
|
||||
<span class="text-sm text-muted-foreground"
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
|
||||
<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">
|
||||
<h1 class="text-2xl font-bold">{$_("admin.queues.title")}</h1>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
<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">
|
||||
<h1 class="text-2xl font-bold">{$_("admin.recordings.title")}</h1>
|
||||
<span class="text-sm text-muted-foreground"
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
|
||||
<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">
|
||||
<h1 class="text-2xl font-bold">{$_("admin.users.title")}</h1>
|
||||
<span class="text-sm text-muted-foreground"
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
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 Meta from "$lib/components/meta/meta.svelte";
|
||||
|
||||
@@ -128,9 +129,9 @@
|
||||
|
||||
<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">
|
||||
<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")}
|
||||
</Button>
|
||||
<div>
|
||||
@@ -143,25 +144,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Basic info -->
|
||||
<div class="space-y-6 max-w-2xl">
|
||||
<!-- 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="space-y-1.5">
|
||||
<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 class="space-y-1.5">
|
||||
<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 class="space-y-1.5">
|
||||
<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>
|
||||
|
||||
<!-- Avatar -->
|
||||
<div class="space-y-1.5">
|
||||
<Label>{$_("admin.user_edit.avatar")}</Label>
|
||||
{#if avatarId}
|
||||
@@ -174,7 +188,6 @@
|
||||
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleAvatarUpload} />
|
||||
</div>
|
||||
|
||||
<!-- Banner -->
|
||||
<div class="space-y-1.5">
|
||||
<Label>{$_("admin.user_edit.banner")}</Label>
|
||||
{#if bannerId}
|
||||
@@ -187,7 +200,6 @@
|
||||
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleBannerUpload} />
|
||||
</div>
|
||||
|
||||
<!-- Model photo (used in cards & model page, not for avatar/comments) -->
|
||||
<div class="space-y-1.5">
|
||||
<Label>{$_("admin.user_edit.model_photo")}</Label>
|
||||
<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} />
|
||||
</div>
|
||||
|
||||
<!-- Admin flag -->
|
||||
<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"
|
||||
>
|
||||
@@ -216,18 +227,19 @@
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<Button
|
||||
onclick={handleSave}
|
||||
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")}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- Photo gallery -->
|
||||
<div class="space-y-3 pt-4 border-t border-border/40">
|
||||
<!-- Photo gallery card -->
|
||||
<Card class="bg-card/50 border-primary/20">
|
||||
<CardContent class="space-y-4 pt-6">
|
||||
<Label>{$_("admin.user_edit.photos")}</Label>
|
||||
|
||||
{#if data.user.photos && data.user.photos.length > 0}
|
||||
@@ -255,6 +267,7 @@
|
||||
{/if}
|
||||
|
||||
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handlePhotoUpload} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
<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">
|
||||
<h1 class="text-2xl font-bold">{$_("admin.videos.title")}</h1>
|
||||
<div class="flex items-center gap-3">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { Textarea } from "$lib/components/ui/textarea";
|
||||
import { TagsInput } from "$lib/components/ui/tags-input";
|
||||
import { FileDropZone, MEGABYTE } from "$lib/components/ui/file-drop-zone";
|
||||
import { Card, CardContent } from "$lib/components/ui/card";
|
||||
import { getAssetUrl } from "$lib/api";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "$lib/components/ui/select";
|
||||
import { DatePicker } from "$lib/components/ui/date-picker";
|
||||
@@ -105,15 +106,16 @@
|
||||
|
||||
<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">
|
||||
<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")}
|
||||
</Button>
|
||||
<h1 class="text-2xl font-bold">{$_("admin.video_form.edit_title")}</h1>
|
||||
</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="space-y-1.5">
|
||||
<Label for="title">{$_("admin.common.title_field")}</Label>
|
||||
@@ -121,11 +123,17 @@
|
||||
id="title"
|
||||
bind:value={title}
|
||||
placeholder={$_("admin.video_form.title_placeholder")}
|
||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<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>
|
||||
|
||||
@@ -136,6 +144,7 @@
|
||||
bind:value={description}
|
||||
placeholder={$_("admin.video_form.description_placeholder")}
|
||||
rows={3}
|
||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -168,7 +177,7 @@
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<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 class="space-y-1.5">
|
||||
@@ -195,7 +204,7 @@
|
||||
<div class="space-y-1.5">
|
||||
<Label>{$_("admin.video_form.models")}</Label>
|
||||
<Select type="multiple" bind:value={selectedModelIds}>
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectTrigger class="w-full bg-background/50 border-primary/20">
|
||||
{#if selectedModelIds.length}
|
||||
{$_("admin.video_form.models_selected", {
|
||||
values: { count: selectedModelIds.length },
|
||||
@@ -226,11 +235,12 @@
|
||||
<Button
|
||||
onclick={handleSubmit}
|
||||
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")}
|
||||
</Button>
|
||||
<Button variant="outline" href="/admin/videos">{$_("common.cancel")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { TagsInput } from "$lib/components/ui/tags-input";
|
||||
import { DatePicker } from "$lib/components/ui/date-picker";
|
||||
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";
|
||||
|
||||
const { data } = $props();
|
||||
@@ -100,15 +101,16 @@
|
||||
|
||||
<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">
|
||||
<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")}
|
||||
</Button>
|
||||
<h1 class="text-2xl font-bold">{$_("admin.video_form.new_title")}</h1>
|
||||
</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="space-y-1.5">
|
||||
<Label for="title">{$_("admin.common.title_field")}</Label>
|
||||
@@ -119,11 +121,17 @@
|
||||
if (!slug) slug = generateSlug(title);
|
||||
}}
|
||||
placeholder={$_("admin.video_form.title_placeholder")}
|
||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<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>
|
||||
|
||||
@@ -134,28 +142,29 @@
|
||||
bind:value={description}
|
||||
placeholder={$_("admin.video_form.description_placeholder")}
|
||||
rows={3}
|
||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label>{$_("admin.common.cover_image")}</Label>
|
||||
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} />
|
||||
{#if imageId}<p class="text-xs text-green-600 mt-1">
|
||||
{$_("admin.common.image_uploaded")} ✓
|
||||
</p>{/if}
|
||||
{#if imageId}
|
||||
<p class="text-xs text-green-600 mt-1">{$_("admin.common.image_uploaded")} ✓</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label>{$_("admin.video_form.video_file")}</Label>
|
||||
<FileDropZone accept="video/*" maxFileSize={2000 * MEGABYTE} onUpload={handleVideoUpload} />
|
||||
{#if movieId}<p class="text-xs text-green-600 mt-1">
|
||||
{$_("admin.video_form.video_uploaded")} ✓
|
||||
</p>{/if}
|
||||
{#if movieId}
|
||||
<p class="text-xs text-green-600 mt-1">{$_("admin.video_form.video_uploaded")} ✓</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<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 class="space-y-1.5">
|
||||
@@ -180,7 +189,7 @@
|
||||
|
||||
{#if data.models.length > 0}
|
||||
<div class="space-y-2">
|
||||
<Label>Models</Label>
|
||||
<Label>{$_("admin.video_form.models")}</Label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each data.models as model (model.id)}
|
||||
<Button
|
||||
@@ -204,11 +213,12 @@
|
||||
<Button
|
||||
onclick={handleSubmit}
|
||||
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")}
|
||||
</Button>
|
||||
<Button variant="outline" href="/admin/videos">{$_("common.cancel")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -44,9 +44,10 @@
|
||||
<div class="flex items-center gap-1 overflow-x-auto py-2 scrollbar-none">
|
||||
<a
|
||||
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>
|
||||
{#each navLinks as link (link.href)}
|
||||
<a
|
||||
@@ -69,7 +70,8 @@
|
||||
<!-- Sidebar (desktop only) -->
|
||||
<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">
|
||||
<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")}
|
||||
</a>
|
||||
<div class="mt-3 flex items-center gap-3">
|
||||
|
||||
@@ -14,15 +14,15 @@
|
||||
|
||||
<Meta title={$_("me.analytics.title")} />
|
||||
|
||||
<div class="py-3 sm:py-6 sm:pl-6">
|
||||
<div class="flex items-center justify-between mb-6 px-3 sm:px-0">
|
||||
<div class="py-3 sm:py-6 lg:pl-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">{$_("me.analytics.title")}</h1>
|
||||
<p class="text-sm text-muted-foreground mt-0.5">{$_("me.analytics.description")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-3 sm:px-0 space-y-6">
|
||||
<div class="space-y-6">
|
||||
{#if data.analytics}
|
||||
<!-- Overview Stats -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { _ } from "svelte-i18n";
|
||||
import { invalidateAll } from "$app/navigation";
|
||||
import { untrack } from "svelte";
|
||||
import { getAssetUrl, isModel } from "$lib/api";
|
||||
import { getAssetUrl } from "$lib/api";
|
||||
import { toast } from "svelte-sonner";
|
||||
import { updateProfile, uploadFile, removeFile } from "$lib/services";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
@@ -129,17 +129,11 @@
|
||||
|
||||
<Meta title={$_("me.settings.profile_title")} />
|
||||
|
||||
<div class="py-3 sm:py-6 sm:pl-6">
|
||||
<div class="flex items-center justify-between mb-6 px-3 sm:px-0">
|
||||
<div class="py-3 sm:py-6 lg:pl-6">
|
||||
<div class="mb-6">
|
||||
<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 class="px-3 sm:px-0">
|
||||
<Card class="bg-card/50 border-primary/20 max-w-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle>{$_("me.settings.profile_title")}</CardTitle>
|
||||
@@ -288,4 +282,3 @@
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { toast } from "svelte-sonner";
|
||||
import { deleteRecording } from "$lib/services";
|
||||
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 RecordingCard from "$lib/components/recording-card/recording-card.svelte";
|
||||
import Meta from "$lib/components/meta/meta.svelte";
|
||||
@@ -45,8 +45,8 @@
|
||||
|
||||
<Meta title={$_("me.recordings.title")} />
|
||||
|
||||
<div class="py-3 sm:py-6 sm:pl-6">
|
||||
<div class="flex items-center justify-between mb-6 px-3 sm:px-0">
|
||||
<div class="py-3 sm:py-6 lg:pl-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold">{$_("me.recordings.title")}</h1>
|
||||
<Button
|
||||
href="/play"
|
||||
@@ -57,20 +57,16 @@
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="px-3 sm:px-0">
|
||||
{#if recordings.length === 0}
|
||||
<Card class="bg-card/50 border-primary/20">
|
||||
<CardContent class="py-12">
|
||||
<div class="flex flex-col items-center justify-center text-center">
|
||||
<div class="mb-4 p-4 rounded-full bg-muted/30 border border-border/30">
|
||||
<span class="icon-[ri--play-list-2-line] w-12 h-12 text-muted-foreground"></span>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-2">
|
||||
{$_("me.recordings.no_recordings")}
|
||||
</h3>
|
||||
<p class="text-muted-foreground mb-6 max-w-md">
|
||||
{$_("me.recordings.no_recordings_description")}
|
||||
</p>
|
||||
<Empty.Root>
|
||||
<Empty.Header>
|
||||
<Empty.Media variant="icon">
|
||||
<span class="icon-[ri--play-list-2-line] w-8 h-8"></span>
|
||||
</Empty.Media>
|
||||
<Empty.Title>{$_("me.recordings.no_recordings")}</Empty.Title>
|
||||
<Empty.Description>{$_("me.recordings.no_recordings_description")}</Empty.Description>
|
||||
</Empty.Header>
|
||||
<Empty.Content>
|
||||
<Button
|
||||
href="/play"
|
||||
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>
|
||||
{$_("me.recordings.go_to_play")}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Empty.Content>
|
||||
</Empty.Root>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{#each recordings as recording (recording.id)}
|
||||
@@ -93,7 +88,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Root bind:open={deleteOpen}>
|
||||
<Dialog.Content>
|
||||
|
||||
@@ -57,12 +57,11 @@
|
||||
|
||||
<Meta title={$_("me.settings.privacy_title")} />
|
||||
|
||||
<div class="py-3 sm:py-6 sm:pl-6">
|
||||
<div class="flex items-center justify-between mb-6 px-3 sm:px-0">
|
||||
<div class="py-3 sm:py-6 lg:pl-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold">{$_("me.settings.privacy_title")}</h1>
|
||||
</div>
|
||||
|
||||
<div class="px-3 sm:px-0">
|
||||
<Card class="bg-card/50 border-primary/20 max-w-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle>{$_("me.settings.privacy_title")}</CardTitle>
|
||||
@@ -145,9 +144,8 @@
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
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}
|
||||
>
|
||||
{#if isSecurityLoading}
|
||||
@@ -163,4 +161,3 @@
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user