feat: mobile-optimize admin section

- Layout: sidebar hidden on mobile, replaced with horizontal top nav strip
- Tables: overflow-x-auto + hide secondary columns (email/category/dates/
  plays/likes) on small screens; show email inline under name on mobile
- Forms: grid-cols-2 → grid-cols-1 sm:grid-cols-2 on all admin forms
- Markdown editor: Write/Preview tab toggle on mobile, side-by-side on sm+
- Padding: p-3 sm:p-6 on all admin pages for tighter mobile layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 14:36:52 +01:00
parent a7fafaf7c5
commit 648123fab5
9 changed files with 105 additions and 46 deletions

View File

@@ -16,9 +16,31 @@
<div class="min-h-screen bg-background">
<div class="container mx-auto px-4">
<!-- Mobile top nav -->
<div class="lg:hidden flex items-center gap-2 py-3 border-b border-border/40">
<a href="/" class="text-xs text-muted-foreground hover:text-foreground transition-colors shrink-0 mr-2">
← Back
</a>
{#each navLinks as link (link.href)}
<a
href={link.href}
class={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${
isActive(link.href)
? "bg-primary/10 text-primary"
: "text-muted-foreground hover:text-foreground hover:bg-muted/50"
}`}
>
<span class={`${link.icon} h-4 w-4`}></span>
{link.name}
</a>
{/each}
</div>
<!-- Desktop layout -->
<div class="flex min-h-screen">
<!-- Sidebar -->
<aside class="w-56 shrink-0 flex flex-col">
<!-- 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">
← Back to site
@@ -44,7 +66,7 @@
</aside>
<!-- Main content -->
<main class="flex-1 overflow-auto">
<main class="flex-1 min-w-0">
{@render children()}
</main>
</div>

View File

@@ -38,7 +38,7 @@
}
</script>
<div class="p-6">
<div class="p-3 sm:p-6">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold">Articles</h1>
<Button href="/admin/articles/new">
@@ -46,13 +46,13 @@
</Button>
</div>
<div class="rounded-lg border border-border/40 overflow-hidden">
<div class="rounded-lg border border-border/40 overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-muted/30">
<tr>
<th class="px-4 py-3 text-left font-medium text-muted-foreground">Article</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground">Category</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground">Published</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground hidden sm:table-cell">Category</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground hidden sm:table-cell">Published</th>
<th class="px-4 py-3 text-right font-medium text-muted-foreground">Actions</th>
</tr>
</thead>
@@ -85,8 +85,8 @@
</div>
</div>
</td>
<td class="px-4 py-3 text-muted-foreground capitalize">{article.category ?? "—"}</td>
<td class="px-4 py-3 text-muted-foreground">
<td class="px-4 py-3 text-muted-foreground capitalize hidden sm:table-cell">{article.category ?? "—"}</td>
<td class="px-4 py-3 text-muted-foreground hidden sm:table-cell">
{timeAgo.format(new Date(article.publish_date))}
</td>
<td class="px-4 py-3 text-right">

View File

@@ -25,6 +25,7 @@
);
let imageId = $state<string | null>(data.article.image ?? null);
let saving = $state(false);
let editorTab = $state<"write" | "preview">("write");
let preview = $derived(content ? (marked.parse(content) as string) : "");
@@ -67,7 +68,7 @@
}
</script>
<div class="p-6">
<div class="p-3 sm:p-6">
<div class="flex items-center gap-4 mb-6">
<Button variant="ghost" href="/admin/articles" size="sm">
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>Back
@@ -76,7 +77,7 @@
</div>
<div class="space-y-5 max-w-4xl">
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5">
<Label for="title">Title *</Label>
<Input id="title" bind:value={title} />
@@ -94,11 +95,28 @@
<!-- Markdown editor with live preview -->
<div class="space-y-1.5">
<Label>Content (Markdown)</Label>
<div class="grid grid-cols-2 gap-4 min-h-96">
<Textarea bind:value={content} class="h-full min-h-96 font-mono text-sm resize-none" />
<div class="flex items-center justify-between">
<Label>Content (Markdown)</Label>
<div class="flex rounded-lg border border-border/40 overflow-hidden text-xs sm:hidden">
<button
type="button"
class={`px-3 py-1 transition-colors ${editorTab === "write" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
onclick={() => (editorTab = "write")}
>Write</button>
<button
type="button"
class={`px-3 py-1 transition-colors ${editorTab === "preview" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
onclick={() => (editorTab = "preview")}
>Preview</button>
</div>
</div>
<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" : ""}`}
/>
<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"
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" : ""}`}
>
{#if preview}
{@html preview}
@@ -121,7 +139,7 @@
<FileDropZone accept="image/*" maxFileSize={10 * MEGABYTE} onUpload={handleImageUpload} />
</div>
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5">
<Label for="category">Category</Label>
<Input id="category" bind:value={category} />

View File

@@ -20,6 +20,7 @@
let publishDate = $state("");
let imageId = $state<string | null>(null);
let saving = $state(false);
let editorTab = $state<"write" | "preview">("write");
let preview = $derived(content ? (marked.parse(content) as string) : "");
@@ -72,7 +73,7 @@
}
</script>
<div class="p-6">
<div class="p-3 sm:p-6">
<div class="flex items-center gap-4 mb-6">
<Button variant="ghost" href="/admin/articles" size="sm">
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>Back
@@ -81,7 +82,7 @@
</div>
<div class="space-y-5 max-w-4xl">
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5">
<Label for="title">Title *</Label>
<Input
@@ -106,15 +107,30 @@
<!-- Markdown editor with live preview -->
<div class="space-y-1.5">
<Label>Content (Markdown)</Label>
<div class="grid grid-cols-2 gap-4 min-h-96">
<div class="flex items-center justify-between">
<Label>Content (Markdown)</Label>
<div class="flex rounded-lg border border-border/40 overflow-hidden text-xs sm:hidden">
<button
type="button"
class={`px-3 py-1 transition-colors ${editorTab === "write" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
onclick={() => (editorTab = "write")}
>Write</button>
<button
type="button"
class={`px-3 py-1 transition-colors ${editorTab === "preview" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
onclick={() => (editorTab = "preview")}
>Preview</button>
</div>
</div>
<!-- Mobile: single pane toggled; Desktop: side by side -->
<div class="sm:grid sm:grid-cols-2 sm:gap-4 min-h-96">
<Textarea
bind:value={content}
placeholder="Write in Markdown…"
class="h-full min-h-96 font-mono text-sm resize-none"
class={`h-full min-h-96 font-mono text-sm resize-none ${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"
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" : ""}`}
>
{#if preview}
{@html preview}
@@ -131,7 +147,7 @@
{#if imageId}<p class="text-xs text-green-600 mt-1">Image uploaded ✓</p>{/if}
</div>
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5">
<Label for="category">Category</Label>
<Input id="category" bind:value={category} placeholder="e.g. news, tutorial…" />

View File

@@ -82,7 +82,7 @@
}
</script>
<div class="p-6">
<div class="p-3 sm:p-6">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold">Users</h1>
<span class="text-sm text-muted-foreground">{data.total} total</span>
@@ -114,14 +114,14 @@
</div>
<!-- Table -->
<div class="rounded-lg border border-border/40 overflow-hidden">
<div class="rounded-lg border border-border/40 overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-muted/30">
<tr>
<th class="px-4 py-3 text-left font-medium text-muted-foreground">User</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground">Email</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground hidden sm:table-cell">Email</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground">Role</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground">Joined</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground hidden md:table-cell">Joined</th>
<th class="px-4 py-3 text-right font-medium text-muted-foreground">Actions</th>
</tr>
</thead>
@@ -134,19 +134,22 @@
<img
src={getAssetUrl(user.avatar, "mini")}
alt=""
class="h-8 w-8 rounded-full object-cover"
class="h-8 w-8 rounded-full object-cover shrink-0"
/>
{:else}
<div
class="h-8 w-8 rounded-full bg-primary/20 flex items-center justify-center text-xs font-semibold text-primary"
class="h-8 w-8 rounded-full bg-primary/20 flex items-center justify-center text-xs font-semibold text-primary shrink-0"
>
{(user.artist_name || user.email)[0].toUpperCase()}
</div>
{/if}
<span class="font-medium">{user.artist_name || user.first_name || "—"}</span>
<div class="min-w-0">
<span class="font-medium block truncate">{user.artist_name || user.first_name || "—"}</span>
<span class="text-xs text-muted-foreground sm:hidden truncate block">{user.email}</span>
</div>
</div>
</td>
<td class="px-4 py-3 text-muted-foreground">{user.email}</td>
<td class="px-4 py-3 text-muted-foreground hidden sm:table-cell">{user.email}</td>
<td class="px-4 py-3">
<Select
type="single"
@@ -164,7 +167,7 @@
</SelectContent>
</Select>
</td>
<td class="px-4 py-3 text-muted-foreground">{formatDate(user.date_created)}</td>
<td class="px-4 py-3 text-muted-foreground hidden md:table-cell">{formatDate(user.date_created)}</td>
<td class="px-4 py-3 text-right">
<div class="flex items-center justify-end gap-1">
<Button size="sm" variant="ghost" href="/admin/users/{user.id}">

View File

@@ -96,7 +96,7 @@
}
</script>
<div class="p-6 max-w-2xl">
<div class="p-3 sm:p-6 max-w-2xl">
<div class="flex items-center gap-4 mb-6">
<Button variant="ghost" href="/admin/users" size="sm">
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>Back
@@ -109,7 +109,7 @@
<div class="space-y-6">
<!-- Basic info -->
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5">
<Label for="firstName">First name</Label>
<Input id="firstName" bind:value={firstName} />

View File

@@ -35,7 +35,7 @@
}
</script>
<div class="p-6">
<div class="p-3 sm:p-6">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold">Videos</h1>
<Button href="/admin/videos/new">
@@ -43,14 +43,14 @@
</Button>
</div>
<div class="rounded-lg border border-border/40 overflow-hidden">
<div class="rounded-lg border border-border/40 overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-muted/30">
<tr>
<th class="px-4 py-3 text-left font-medium text-muted-foreground">Video</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground">Badges</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground">Plays</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground">Likes</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground hidden sm:table-cell">Badges</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground hidden md:table-cell">Plays</th>
<th class="px-4 py-3 text-left font-medium text-muted-foreground hidden md:table-cell">Likes</th>
<th class="px-4 py-3 text-right font-medium text-muted-foreground">Actions</th>
</tr>
</thead>
@@ -78,7 +78,7 @@
</div>
</div>
</td>
<td class="px-4 py-3">
<td class="px-4 py-3 hidden sm:table-cell">
<div class="flex gap-1">
{#if video.premium}
<span
@@ -93,8 +93,8 @@
{/if}
</div>
</td>
<td class="px-4 py-3 text-muted-foreground">{video.plays_count ?? 0}</td>
<td class="px-4 py-3 text-muted-foreground">{video.likes_count ?? 0}</td>
<td class="px-4 py-3 text-muted-foreground hidden md:table-cell">{video.plays_count ?? 0}</td>
<td class="px-4 py-3 text-muted-foreground hidden md:table-cell">{video.likes_count ?? 0}</td>
<td class="px-4 py-3 text-right">
<div class="flex items-center justify-end gap-1">
<Button size="sm" variant="ghost" href="/admin/videos/{video.id}">

View File

@@ -88,7 +88,7 @@
}
</script>
<div class="p-6 max-w-2xl">
<div class="p-3 sm:p-6 max-w-2xl">
<div class="flex items-center gap-4 mb-6">
<Button variant="ghost" href="/admin/videos" size="sm">
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>Back
@@ -97,7 +97,7 @@
</div>
<div class="space-y-5">
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5">
<Label for="title">Title *</Label>
<Input id="title" bind:value={title} placeholder="Video title" />

View File

@@ -95,7 +95,7 @@
}
</script>
<div class="p-6 max-w-2xl">
<div class="p-3 sm:p-6 max-w-2xl">
<div class="flex items-center gap-4 mb-6">
<Button variant="ghost" href="/admin/videos" size="sm">
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>Back
@@ -104,7 +104,7 @@
</div>
<div class="space-y-5">
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5">
<Label for="title">Title *</Label>
<Input