Files
mal/web/templates/admin.templ

222 lines
6.9 KiB
Plaintext

package templates
import (
"fmt"
"mal/internal/db"
"mal/web/shared/layout"
)
templ AdminPage(users []database.User) {
@layout.Layout("mal - admin", true) {
<div class="grid gap-6">
<h1 class="text-xl font-semibold">Admin Panel</h1>
<!-- Add User Section -->
<div class="rounded bg-(--surface-search) p-4">
<h2 class="mb-4 text-lg">Add New User</h2>
<form
hx-post="/admin/users"
hx-target="#users-list"
hx-swap="outerHTML"
class="flex flex-wrap gap-3"
>
<input
type="email"
name="username"
placeholder="Email"
required
class="h-9 rounded bg-(--bg) px-3 text-(--text) placeholder:text-(--text-faint) focus:outline-none"
/>
<input
type="password"
name="password"
placeholder="Password"
required
class="h-9 rounded bg-(--bg) px-3 text-(--text) placeholder:text-(--text-faint) focus:outline-none"
/>
<button
type="submit"
class="h-9 cursor-pointer rounded bg-(--surface-button) px-4 text-sm text-(--text) transition-opacity duration-150 hover:opacity-80"
>
Add User
</button>
</form>
</div>
<!-- Users List -->
<div>
<h2 class="mb-4 text-lg">Users</h2>
@AdminUsersList(users)
</div>
</div>
}
}
templ AdminUsersList(users []database.User) {
<div id="users-list" class="grid gap-2">
for _, user := range users {
<div class="flex items-center justify-between rounded bg-(--surface-search) p-3">
<div>
<div class="text-sm font-medium">{ user.Username }</div>
<div class="text-xs text-(--text-muted)">ID: { user.ID }</div>
<div class="text-xs text-(--text-faint)">Created: { user.CreatedAt.Format("2006-01-02") }</div>
</div>
<div class="flex gap-2">
<a
href={ templ.URL(fmt.Sprintf("/admin/users/%s", user.ID)) }
class="rounded bg-(--panel-soft) px-3 py-1 text-xs text-(--text) hover:bg-(--panel)"
>
View
</a>
</div>
</div>
}
</div>
}
templ AdminImpersonatePage(user database.User) {
@layout.Layout("mal - admin - user view", true) {
<div class="grid gap-6">
<div class="flex items-center justify-between">
<div>
<h1 class="text-xl font-semibold">Viewing User</h1>
<p class="text-sm text-(--text-muted)">{ user.Username }</p>
</div>
<a
href="/admin"
class="rounded bg-(--surface-search) px-4 py-2 text-sm text-(--text) hover:bg-(--panel-soft)"
>
← Back to Admin
</a>
</div>
<div class="grid gap-4 md:grid-cols-2">
<!-- Watchlist Card -->
<a
href={ templ.URL(fmt.Sprintf("/admin/users/%s/watchlist", user.ID)) }
class="block rounded bg-(--surface-search) p-4 hover:bg-(--panel-soft)"
>
<h2 class="mb-2 text-lg">Watchlist</h2>
<p class="text-sm text-(--text-muted)">View user's anime watchlist</p>
</a>
<!-- Continue Watching Card -->
<a
href={ templ.URL(fmt.Sprintf("/admin/users/%s/continue-watching", user.ID)) }
class="block rounded bg-(--surface-search) p-4 hover:bg-(--panel-soft)"
>
<h2 class="mb-2 text-lg">Continue Watching</h2>
<p class="text-sm text-(--text-muted)">View user's watch progress</p>
</a>
</div>
<div class="mt-4 rounded border border-(--danger) p-4 text-sm text-(--danger)">
<strong>Read-only mode:</strong> You are viewing this user's data. Changes cannot be made from this view.
</div>
</div>
}
}
templ AdminUserWatchlist(entries []database.GetUserWatchListRow) {
@layout.Layout("mal - admin - watchlist", true) {
<div class="grid gap-6">
<div class="flex items-center justify-between">
<h1 class="text-xl font-semibold">User Watchlist</h1>
<a
href="/admin"
class="rounded bg-(--surface-search) px-4 py-2 text-sm text-(--text) hover:bg-(--panel-soft)"
>
← Back to Admin
</a>
</div>
if len(entries) == 0 {
<div class="rounded bg-(--surface-search) p-8 text-center text-(--text-muted)">
No watchlist entries
</div>
} else {
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
for _, entry := range entries {
<div class="rounded bg-(--surface-search) p-3">
<div class="mb-2 aspect-2/3 overflow-hidden rounded bg-(--bg)">
if entry.ImageUrl != "" {
<img
src={ entry.ImageUrl }
alt={ entry.TitleOriginal }
class="h-full w-full object-cover"
loading="lazy"
/>
} else {
<div class="flex h-full items-center justify-center text-xs text-(--text-faint)">No image</div>
}
</div>
<div class="text-sm line-clamp-2">{ entry.TitleOriginal }</div>
<div class="mt-1 text-xs text-(--text-muted)">Status: { entry.Status }</div>
if entry.CurrentEpisode.Valid {
<div class="text-xs text-(--text-faint)">Episode: { fmt.Sprintf("%d", entry.CurrentEpisode.Int64) }</div>
}
</div>
}
</div>
}
<div class="mt-4 rounded border border-(--danger) p-4 text-sm text-(--danger)">
<strong>Read-only mode:</strong> You are viewing this user's data. Changes cannot be made from this view.
</div>
</div>
}
}
templ AdminUserContinueWatching(entries []database.GetContinueWatchingEntriesRow) {
@layout.Layout("mal - admin - continue watching", true) {
<div class="grid gap-6">
<div class="flex items-center justify-between">
<h1 class="text-xl font-semibold">Continue Watching</h1>
<a
href="/admin"
class="rounded bg-(--surface-search) px-4 py-2 text-sm text-(--text) hover:bg-(--panel-soft)"
>
← Back to Admin
</a>
</div>
if len(entries) == 0 {
<div class="rounded bg-(--surface-search) p-8 text-center text-(--text-muted)">
No continue watching entries
</div>
} else {
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
for _, entry := range entries {
<div class="rounded bg-(--surface-search) p-3">
<div class="mb-2 aspect-2/3 overflow-hidden rounded bg-(--bg)">
if entry.ImageUrl != "" {
<img
src={ entry.ImageUrl }
alt={ entry.TitleOriginal }
class="h-full w-full object-cover"
loading="lazy"
/>
} else {
<div class="flex h-full items-center justify-center text-xs text-(--text-faint)">No image</div>
}
</div>
<div class="text-sm line-clamp-2">{ entry.TitleOriginal }</div>
if entry.CurrentEpisode.Valid {
<div class="mt-1 text-xs text-(--text-muted)">Episode: { fmt.Sprintf("%d", entry.CurrentEpisode.Int64) }</div>
}
if entry.CurrentTimeSeconds > 0 {
<div class="text-xs text-(--text-faint)">Time: { fmt.Sprintf("%.0fs", entry.CurrentTimeSeconds) }</div>
}
</div>
}
</div>
}
<div class="mt-4 rounded border border-(--danger) p-4 text-sm text-(--danger)">
<strong>Read-only mode:</strong> You are viewing this user's data. Changes cannot be made from this view.
</div>
</div>
}
}