chore: lint and format
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,6 @@
|
|||||||
const seconds = totalSeconds % 60;
|
const seconds = totalSeconds % 60;
|
||||||
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="empty-content"
|
data-slot="empty-content"
|
||||||
class={cn(
|
class={cn(
|
||||||
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="empty-description"
|
data-slot="empty-description"
|
||||||
class={cn(
|
class={cn(
|
||||||
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4",
|
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="empty-header"
|
data-slot="empty-header"
|
||||||
class={cn("flex max-w-sm flex-col items-center gap-2 text-center", className)}
|
class={cn("flex max-w-sm flex-col items-center gap-2 text-center", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,41 +1,41 @@
|
|||||||
<script lang="ts" module>
|
<script lang="ts" module>
|
||||||
import { tv, type VariantProps } from "tailwind-variants";
|
import { tv, type VariantProps } from "tailwind-variants";
|
||||||
|
|
||||||
export const emptyMediaVariants = tv({
|
export const emptyMediaVariants = tv({
|
||||||
base: "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
base: "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-transparent",
|
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",
|
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export type EmptyMediaVariant = VariantProps<typeof emptyMediaVariants>["variant"];
|
export type EmptyMediaVariant = VariantProps<typeof emptyMediaVariants>["variant"];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
...restProps
|
...restProps
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & { variant?: EmptyMediaVariant } = $props();
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & { variant?: EmptyMediaVariant } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="empty-icon"
|
data-slot="empty-icon"
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
class={cn(emptyMediaVariants({ variant }), className)}
|
class={cn(emptyMediaVariants({ variant }), className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="empty-title"
|
data-slot="empty-title"
|
||||||
class={cn("text-lg font-medium tracking-tight", className)}
|
class={cn("text-lg font-medium tracking-tight", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="empty"
|
data-slot="empty"
|
||||||
class={cn(
|
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",
|
"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
|
className,
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ import Description from "./empty-description.svelte";
|
|||||||
import Content from "./empty-content.svelte";
|
import Content from "./empty-content.svelte";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Root,
|
Root,
|
||||||
Header,
|
Header,
|
||||||
Media,
|
Media,
|
||||||
Title,
|
Title,
|
||||||
Description,
|
Description,
|
||||||
Content,
|
Content,
|
||||||
//
|
//
|
||||||
Root as Empty,
|
Root as Empty,
|
||||||
Header as EmptyHeader,
|
Header as EmptyHeader,
|
||||||
Media as EmptyMedia,
|
Media as EmptyMedia,
|
||||||
Title as EmptyTitle,
|
Title as EmptyTitle,
|
||||||
Description as EmptyDescription,
|
Description as EmptyDescription,
|
||||||
Content as EmptyContent,
|
Content as EmptyContent,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -912,20 +912,14 @@ const UPDATE_RECORDING_MUTATION = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export async function updateRecording(
|
export async function updateRecording(id: string, fields: { status?: string; public?: boolean }) {
|
||||||
id: string,
|
return loggedApiCall("updateRecording", async () => {
|
||||||
fields: { status?: string; public?: boolean },
|
const data = await getGraphQLClient().request<{ updateRecording: Recording }>(
|
||||||
) {
|
UPDATE_RECORDING_MUTATION,
|
||||||
return loggedApiCall(
|
{ id, ...fields },
|
||||||
"updateRecording",
|
);
|
||||||
async () => {
|
return data.updateRecording;
|
||||||
const data = await getGraphQLClient().request<{ updateRecording: Recording }>(
|
});
|
||||||
UPDATE_RECORDING_MUTATION,
|
|
||||||
{ id, ...fields },
|
|
||||||
);
|
|
||||||
return data.updateRecording;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DELETE_RECORDING_MUTATION = gql`
|
const DELETE_RECORDING_MUTATION = gql`
|
||||||
|
|||||||
@@ -68,7 +68,10 @@
|
|||||||
<!-- 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="flex items-center gap-1 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>
|
<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>
|
||||||
@@ -80,8 +83,11 @@
|
|||||||
{getUserInitials(displayName)}
|
{getUserInitials(displayName)}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</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
|
||||||
<span class="icon-[ri--shield-keyhole-fill] h-2.5 w-2.5 text-primary-foreground"></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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
|
|||||||
@@ -101,7 +101,10 @@
|
|||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-2xl font-bold">{data.article.title}</h1>
|
<h1 class="text-2xl font-bold">{data.article.title}</h1>
|
||||||
<p class="text-xs text-muted-foreground mt-0.5">
|
<p class="text-xs text-muted-foreground mt-0.5">
|
||||||
{data.article.slug}{data.article.category ? " · " + data.article.category : ""}{data.article.author ? " · " + data.article.author.artist_name : ""}
|
{data.article.slug}{data.article.category ? " · " + data.article.category : ""}{data.article
|
||||||
|
.author
|
||||||
|
? " · " + data.article.author.artist_name
|
||||||
|
: ""}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -238,7 +241,10 @@
|
|||||||
|
|
||||||
<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} class="bg-background/50 border-primary/20 focus:border-primary" />
|
<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">
|
||||||
|
|||||||
@@ -187,7 +187,10 @@
|
|||||||
|
|
||||||
<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} class="bg-background/50 border-primary/20 focus:border-primary" />
|
<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">
|
||||||
|
|||||||
@@ -182,7 +182,11 @@
|
|||||||
class="h-20 w-20 rounded-full object-cover mb-2"
|
class="h-20 w-20 rounded-full object-cover mb-2"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleAvatarUpload} />
|
<FileDropZone
|
||||||
|
accept="image/*"
|
||||||
|
maxFileSize={10 * MEGABYTE}
|
||||||
|
onUpload={handleAvatarUpload}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
@@ -194,7 +198,11 @@
|
|||||||
class="w-full h-24 rounded object-cover mb-2"
|
class="w-full h-24 rounded object-cover mb-2"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleBannerUpload} />
|
<FileDropZone
|
||||||
|
accept="image/*"
|
||||||
|
maxFileSize={10 * MEGABYTE}
|
||||||
|
onUpload={handleBannerUpload}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
@@ -207,7 +215,11 @@
|
|||||||
class="w-full h-48 rounded object-cover mb-2"
|
class="w-full h-48 rounded object-cover mb-2"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handlePhotoUpload2} />
|
<FileDropZone
|
||||||
|
accept="image/*"
|
||||||
|
maxFileSize={10 * MEGABYTE}
|
||||||
|
onUpload={handlePhotoUpload2}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -110,7 +110,9 @@
|
|||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-2xl font-bold">{data.video.title}</h1>
|
<h1 class="text-2xl font-bold">{data.video.title}</h1>
|
||||||
<p class="text-xs text-muted-foreground mt-0.5">
|
<p class="text-xs text-muted-foreground mt-0.5">
|
||||||
{data.video.slug}{data.video.premium ? " · premium" : ""}{data.video.featured ? " · featured" : ""}
|
{data.video.slug}{data.video.premium ? " · premium" : ""}{data.video.featured
|
||||||
|
? " · featured"
|
||||||
|
: ""}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -177,7 +179,10 @@
|
|||||||
|
|
||||||
<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} class="bg-background/50 border-primary/20 focus:border-primary" />
|
<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">
|
||||||
|
|||||||
@@ -161,7 +161,10 @@
|
|||||||
|
|
||||||
<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} class="bg-background/50 border-primary/20 focus:border-primary" />
|
<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">
|
||||||
|
|||||||
@@ -65,7 +65,10 @@
|
|||||||
<!-- 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="flex items-center gap-1 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>
|
<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>
|
||||||
|
|||||||
@@ -135,150 +135,150 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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>
|
||||||
<CardDescription>{$_("me.settings.profile_subtitle")}</CardDescription>
|
<CardDescription>{$_("me.settings.profile_subtitle")}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="space-y-4">
|
<CardContent class="space-y-4">
|
||||||
<form onsubmit={handleProfileSubmit} class="space-y-4">
|
<form onsubmit={handleProfileSubmit} class="space-y-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label>{$_("me.settings.avatar")}</Label>
|
<Label>{$_("me.settings.avatar")}</Label>
|
||||||
<div class="flex items-center gap-5">
|
<div class="flex items-center gap-5">
|
||||||
<FileDropZone
|
<FileDropZone
|
||||||
id="avatar"
|
id="avatar"
|
||||||
fileCount={0}
|
fileCount={0}
|
||||||
maxFiles={1}
|
maxFiles={1}
|
||||||
maxFileSize={2 * MEGABYTE}
|
maxFileSize={2 * MEGABYTE}
|
||||||
onUpload={handleFilesUpload}
|
onUpload={handleFilesUpload}
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
class="h-auto w-auto shrink-0 border-none p-0 rounded-full hover:bg-transparent"
|
class="h-auto w-auto shrink-0 border-none p-0 rounded-full hover:bg-transparent"
|
||||||
>
|
>
|
||||||
<div class="relative group cursor-pointer w-24 h-24">
|
<div class="relative group cursor-pointer w-24 h-24">
|
||||||
{#if avatar}
|
|
||||||
<img
|
|
||||||
src={avatar.url}
|
|
||||||
alt={avatar.name}
|
|
||||||
class="w-24 h-24 rounded-full object-cover ring-4 ring-primary/20 group-hover:ring-primary/50 transition-all"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="absolute inset-0 rounded-full bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<span class="icon-[ri--camera-line] w-7 h-7 text-white"></span>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div
|
|
||||||
class="w-24 h-24 rounded-full border-2 border-dashed border-primary/30 group-hover:border-primary/60 bg-primary/5 group-hover:bg-primary/10 transition-all flex flex-col items-center justify-center gap-1"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="icon-[ri--camera-line] w-7 h-7 text-primary/50 group-hover:text-primary/80 transition-colors"
|
|
||||||
></span>
|
|
||||||
<span class="text-xs text-muted-foreground">Upload</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</FileDropZone>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<p class="text-sm text-muted-foreground">JPG, PNG · max 2 MB</p>
|
|
||||||
<p class="text-xs text-muted-foreground/70">
|
|
||||||
Click or drop to {avatar ? "change" : "upload"}
|
|
||||||
</p>
|
|
||||||
{#if avatar}
|
{#if avatar}
|
||||||
<Button
|
<img
|
||||||
variant="ghost"
|
src={avatar.url}
|
||||||
size="sm"
|
alt={avatar.name}
|
||||||
onclick={handleAvatarRemove}
|
class="w-24 h-24 rounded-full object-cover ring-4 ring-primary/20 group-hover:ring-primary/50 transition-all"
|
||||||
class="cursor-pointer w-fit mt-1 px-2 h-7 text-xs text-muted-foreground hover:text-destructive hover:bg-destructive/10"
|
/>
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 rounded-full bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<span class="icon-[ri--delete-bin-line] w-3.5 h-3.5 mr-1"></span>
|
<span class="icon-[ri--camera-line] w-7 h-7 text-white"></span>
|
||||||
Remove
|
</div>
|
||||||
</Button>
|
{:else}
|
||||||
|
<div
|
||||||
|
class="w-24 h-24 rounded-full border-2 border-dashed border-primary/30 group-hover:border-primary/60 bg-primary/5 group-hover:bg-primary/10 transition-all flex flex-col items-center justify-center gap-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon-[ri--camera-line] w-7 h-7 text-primary/50 group-hover:text-primary/80 transition-colors"
|
||||||
|
></span>
|
||||||
|
<span class="text-xs text-muted-foreground">Upload</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</FileDropZone>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<p class="text-sm text-muted-foreground">JPG, PNG · max 2 MB</p>
|
||||||
|
<p class="text-xs text-muted-foreground/70">
|
||||||
|
Click or drop to {avatar ? "change" : "upload"}
|
||||||
|
</p>
|
||||||
|
{#if avatar}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onclick={handleAvatarRemove}
|
||||||
|
class="cursor-pointer w-fit mt-1 px-2 h-7 text-xs text-muted-foreground hover:text-destructive hover:bg-destructive/10"
|
||||||
|
>
|
||||||
|
<span class="icon-[ri--delete-bin-line] w-3.5 h-3.5 mr-1"></span>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div class="space-y-2">
|
|
||||||
<Label for="firstName">{$_("me.settings.first_name")}</Label>
|
|
||||||
<Input
|
|
||||||
id="firstName"
|
|
||||||
placeholder={$_("me.settings.first_name_placeholder")}
|
|
||||||
bind:value={firstName}
|
|
||||||
required
|
|
||||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<Label for="lastName">{$_("me.settings.last_name")}</Label>
|
|
||||||
<Input
|
|
||||||
id="lastName"
|
|
||||||
placeholder={$_("me.settings.last_name_placeholder")}
|
|
||||||
bind:value={lastName}
|
|
||||||
required
|
|
||||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="artistName">{$_("me.settings.artist_name")}</Label>
|
<Label for="firstName">{$_("me.settings.first_name")}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="artistName"
|
id="firstName"
|
||||||
placeholder={$_("me.settings.artist_name_placeholder")}
|
placeholder={$_("me.settings.first_name_placeholder")}
|
||||||
bind:value={artistName}
|
bind:value={firstName}
|
||||||
required
|
required
|
||||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="description">{$_("me.settings.description")}</Label>
|
<Label for="lastName">{$_("me.settings.last_name")}</Label>
|
||||||
<Textarea
|
<Input
|
||||||
id="description"
|
id="lastName"
|
||||||
bind:value={description}
|
placeholder={$_("me.settings.last_name_placeholder")}
|
||||||
placeholder={$_("me.settings.description_placeholder")}
|
bind:value={lastName}
|
||||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
required
|
||||||
rows={3}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-2">
|
|
||||||
<Label for="tags">{$_("me.settings.tags")}</Label>
|
|
||||||
<TagsInput
|
|
||||||
id="tags"
|
|
||||||
bind:value={tags}
|
|
||||||
placeholder={$_("me.settings.tags_placeholder")}
|
|
||||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if isProfileError}
|
<div class="space-y-2">
|
||||||
<div class="grid w-full items-start gap-4">
|
<Label for="artistName">{$_("me.settings.artist_name")}</Label>
|
||||||
<Alert.Root variant="destructive">
|
<Input
|
||||||
<Alert.Title class="items-center flex">
|
id="artistName"
|
||||||
<span class="icon-[ri--alert-line] inline-block w-4 h-4 mr-1"></span>
|
placeholder={$_("me.settings.artist_name_placeholder")}
|
||||||
{$_("me.settings.error")}
|
bind:value={artistName}
|
||||||
</Alert.Title>
|
required
|
||||||
<Alert.Description>{profileError}</Alert.Description>
|
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||||
</Alert.Root>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="description">{$_("me.settings.description")}</Label>
|
||||||
|
<Textarea
|
||||||
|
id="description"
|
||||||
|
bind:value={description}
|
||||||
|
placeholder={$_("me.settings.description_placeholder")}
|
||||||
|
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="tags">{$_("me.settings.tags")}</Label>
|
||||||
|
<TagsInput
|
||||||
|
id="tags"
|
||||||
|
bind:value={tags}
|
||||||
|
placeholder={$_("me.settings.tags_placeholder")}
|
||||||
|
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if isProfileError}
|
||||||
|
<div class="grid w-full items-start gap-4">
|
||||||
|
<Alert.Root variant="destructive">
|
||||||
|
<Alert.Title class="items-center flex">
|
||||||
|
<span class="icon-[ri--alert-line] inline-block w-4 h-4 mr-1"></span>
|
||||||
|
{$_("me.settings.error")}
|
||||||
|
</Alert.Title>
|
||||||
|
<Alert.Description>{profileError}</Alert.Description>
|
||||||
|
</Alert.Root>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
class="cursor-pointer w-full bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
|
||||||
|
disabled={isProfileLoading}
|
||||||
|
>
|
||||||
|
{#if isProfileLoading}
|
||||||
|
<div
|
||||||
|
class="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2"
|
||||||
|
></div>
|
||||||
|
{$_("me.settings.updating_profile")}
|
||||||
|
{:else}
|
||||||
|
{$_("me.settings.update_profile")}
|
||||||
{/if}
|
{/if}
|
||||||
|
</Button>
|
||||||
<Button
|
</form>
|
||||||
type="submit"
|
</CardContent>
|
||||||
class="cursor-pointer w-full bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
|
</Card>
|
||||||
disabled={isProfileLoading}
|
|
||||||
>
|
|
||||||
{#if isProfileLoading}
|
|
||||||
<div
|
|
||||||
class="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2"
|
|
||||||
></div>
|
|
||||||
{$_("me.settings.updating_profile")}
|
|
||||||
{:else}
|
|
||||||
{$_("me.settings.update_profile")}
|
|
||||||
{/if}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,101 +63,101 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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>
|
||||||
<CardDescription>{$_("me.settings.privacy_subtitle")}</CardDescription>
|
<CardDescription>{$_("me.settings.privacy_subtitle")}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="space-y-4">
|
<CardContent class="space-y-4">
|
||||||
<form onsubmit={handleSecuritySubmit} class="space-y-4">
|
<form onsubmit={handleSecuritySubmit} class="space-y-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="email">{$_("me.settings.email")}</Label>
|
<Label for="email">{$_("me.settings.email")}</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder={$_("me.settings.email_placeholder")}
|
||||||
|
bind:value={email}
|
||||||
|
required
|
||||||
|
class="bg-background/50 border-primary/20 focus:border-primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="password">{$_("me.settings.password")}</Label>
|
||||||
|
<div class="relative">
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
id="password"
|
||||||
type="email"
|
type={showPassword ? "text" : "password"}
|
||||||
placeholder={$_("me.settings.email_placeholder")}
|
placeholder={$_("me.settings.password_placeholder")}
|
||||||
bind:value={email}
|
bind:value={password}
|
||||||
required
|
required
|
||||||
class="bg-background/50 border-primary/20 focus:border-primary"
|
class="bg-background/50 border-primary/20 focus:border-primary pr-10"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => (showPassword = !showPassword)}
|
||||||
|
class="cursor-pointer absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||||
|
>
|
||||||
|
{#if showPassword}
|
||||||
|
<span class="icon-[ri--eye-off-line] w-4 h-4"></span>
|
||||||
|
{:else}
|
||||||
|
<span class="icon-[ri--eye-line] w-4 h-4"></span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="password">{$_("me.settings.password")}</Label>
|
<Label for="confirmPassword">{$_("me.settings.confirm_password")}</Label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<Input
|
<Input
|
||||||
id="password"
|
id="confirmPassword"
|
||||||
type={showPassword ? "text" : "password"}
|
type={showConfirmPassword ? "text" : "password"}
|
||||||
placeholder={$_("me.settings.password_placeholder")}
|
placeholder={$_("me.settings.confirm_password_placeholder")}
|
||||||
bind:value={password}
|
bind:value={confirmPassword}
|
||||||
required
|
required
|
||||||
class="bg-background/50 border-primary/20 focus:border-primary pr-10"
|
class="bg-background/50 border-primary/20 focus:border-primary pr-10"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => (showPassword = !showPassword)}
|
onclick={() => (showConfirmPassword = !showConfirmPassword)}
|
||||||
class="cursor-pointer absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
class="cursor-pointer absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
{#if showPassword}
|
{#if showConfirmPassword}
|
||||||
<span class="icon-[ri--eye-off-line] w-4 h-4"></span>
|
<span class="icon-[ri--eye-off-line] w-4 h-4"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="icon-[ri--eye-line] w-4 h-4"></span>
|
<span class="icon-[ri--eye-line] w-4 h-4"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
{#if isSecurityError}
|
||||||
<Label for="confirmPassword">{$_("me.settings.confirm_password")}</Label>
|
<div class="grid w-full items-start gap-4">
|
||||||
<div class="relative">
|
<Alert.Root variant="destructive">
|
||||||
<Input
|
<Alert.Title class="items-center flex">
|
||||||
id="confirmPassword"
|
<span class="icon-[ri--alert-line] inline-block w-4 h-4 mr-1"></span>
|
||||||
type={showConfirmPassword ? "text" : "password"}
|
{$_("me.settings.error")}
|
||||||
placeholder={$_("me.settings.confirm_password_placeholder")}
|
</Alert.Title>
|
||||||
bind:value={confirmPassword}
|
<Alert.Description>{securityError}</Alert.Description>
|
||||||
required
|
</Alert.Root>
|
||||||
class="bg-background/50 border-primary/20 focus:border-primary pr-10"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={() => (showConfirmPassword = !showConfirmPassword)}
|
|
||||||
class="cursor-pointer absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
||||||
>
|
|
||||||
{#if showConfirmPassword}
|
|
||||||
<span class="icon-[ri--eye-off-line] w-4 h-4"></span>
|
|
||||||
{:else}
|
|
||||||
<span class="icon-[ri--eye-line] w-4 h-4"></span>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if isSecurityError}
|
<Button
|
||||||
<div class="grid w-full items-start gap-4">
|
type="submit"
|
||||||
<Alert.Root variant="destructive">
|
class="cursor-pointer w-full bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
|
||||||
<Alert.Title class="items-center flex">
|
disabled={isSecurityLoading}
|
||||||
<span class="icon-[ri--alert-line] inline-block w-4 h-4 mr-1"></span>
|
>
|
||||||
{$_("me.settings.error")}
|
{#if isSecurityLoading}
|
||||||
</Alert.Title>
|
<div
|
||||||
<Alert.Description>{securityError}</Alert.Description>
|
class="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2"
|
||||||
</Alert.Root>
|
></div>
|
||||||
</div>
|
{$_("me.settings.updating_security")}
|
||||||
|
{:else}
|
||||||
|
{$_("me.settings.update_security")}
|
||||||
{/if}
|
{/if}
|
||||||
|
</Button>
|
||||||
<Button
|
</form>
|
||||||
type="submit"
|
</CardContent>
|
||||||
class="cursor-pointer w-full bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
|
</Card>
|
||||||
disabled={isSecurityLoading}
|
|
||||||
>
|
|
||||||
{#if isSecurityLoading}
|
|
||||||
<div
|
|
||||||
class="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2"
|
|
||||||
></div>
|
|
||||||
{$_("me.settings.updating_security")}
|
|
||||||
{:else}
|
|
||||||
{$_("me.settings.update_security")}
|
|
||||||
{/if}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,9 +9,24 @@
|
|||||||
const { children, data } = $props();
|
const { children, data } = $props();
|
||||||
|
|
||||||
const navLinks = $derived([
|
const navLinks = $derived([
|
||||||
{ name: $_("play.nav.play"), href: "/play/buttplug", icon: "icon-[ri--rocket-line]", exact: false },
|
{
|
||||||
{ name: $_("play.nav.recordings"), href: "/play/recordings", icon: "icon-[ri--play-list-2-line]", exact: false },
|
name: $_("play.nav.play"),
|
||||||
{ name: $_("play.nav.leaderboard"), href: "/play/leaderboard", icon: "icon-[ri--trophy-line]", exact: false },
|
href: "/play/buttplug",
|
||||||
|
icon: "icon-[ri--rocket-line]",
|
||||||
|
exact: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: $_("play.nav.recordings"),
|
||||||
|
href: "/play/recordings",
|
||||||
|
icon: "icon-[ri--play-list-2-line]",
|
||||||
|
exact: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: $_("play.nav.leaderboard"),
|
||||||
|
href: "/play/leaderboard",
|
||||||
|
icon: "icon-[ri--trophy-line]",
|
||||||
|
exact: false,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function isActive(link: { href: string; exact: boolean }) {
|
function isActive(link: { href: string; exact: boolean }) {
|
||||||
|
|||||||
Reference in New Issue
Block a user