222 lines
6.9 KiB
Plaintext
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>
|
|
}
|
|
}
|