Files
bookhoard/templates/setup_templ.go
T
john-okeefe a075a5061f feat(ui): refresh auth, entry, admin, and secondary pages
Apply the new design-system primitives across the remaining pages so the
whole app shares one visual language:

- Auth/entry: redesigned index, login, register (centered card layout,
  SVG icons, semantic tokens)
- Admin: sidebar nav with icons, cohesive admin dashboard/users/library/
  processing-issues/settings (tables use border-line + hover states;
  scan/websocket attributes preserved)
- Secondary: analytics (stat cards), devices, progress, conflicts, queue,
  profile + form/modal all migrated to .card/.btn/.input primitives and
  SVG action icons
- Docs + API explorer + setup wizard + collection/rules/modals +
  unlinked books + book-detail modals: consistent chrome, modal scrims
  use --surface-overlay, close buttons use icon-btn
- reader.templ and error.templ intentionally left unchanged
2026-08-05 16:01:22 -04:00

49 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func Setup() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Setup - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script type=\"module\" src=\"/static/main.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body class=\"theme-tokyo-night min-h-screen\" x-data=\"setupWizard\" x-init=\"initSetup()\"><div class=\"container mx-auto px-4 py-8 max-w-2xl\"><div class=\"text-center mb-8\"><h1 class=\"text-2xl font-bold tracking-tight text-content mb-2\">Welcome to Bookhoard</h1><p class=\"text-lg text-content-muted\">Let's get your library set up.</p></div><!-- Step indicator --><div class=\"flex items-center justify-center mb-8 gap-2\"><template x-for=\"n in 4\" :key=\"n\"><div class=\"flex items-center gap-2\"><div class=\"w-10 h-10 rounded-full flex items-center justify-center font-bold text-sm transition-all\" :class=\"currentStep >= n ? 'bg-[var(--accent)] text-[var(--bg-primary)]' : 'bg-[var(--bg-secondary)] text-[var(--text-secondary)] border border-[var(--border)]'\"><span x-text=\"currentStep > n ? '✓' : n\"></span></div><div x-text=\"['Admin', 'Libraries', 'Folders', 'Scan'][n-1]\" class=\"text-sm hidden sm:inline\" :style=\"currentStep >= n ? 'color: var(--text-primary)' : 'color: var(--text-secondary)'\"></div><div x-show=\"n < 4\" class=\"w-8 h-0.5 mx-1\" :class=\"currentStep > n ? 'bg-[var(--accent)]' : 'bg-[var(--border)]'\"></div></div></template></div><!-- Error display --><div x-show=\"errorMsg\" x-cloak class=\"mb-4 p-4 rounded-lg border border-red-500/50 bg-red-500/10 text-red-400 text-sm\"><span x-text=\"errorMsg\"></span></div><!-- ==================== STEP 1: Create Admin ==================== --><div x-show=\"currentStep === 1\" x-cloak class=\"card p-6\"><h2 class=\"text-2xl font-bold tracking-tight text-content mb-2\">Create Administrator Account</h2><p class=\"mb-6 text-content-muted\">This first account will have full admin privileges.</p><form @submit.prevent=\"submitAdmin()\"><div class=\"mb-6 p-4 rounded-lg border border-line bg-surface\"><label class=\"block text-xs font-semibold uppercase tracking-wide text-content-muted mb-1\">Server URL (Base URL)</label> <input type=\"url\" x-model=\"admin.baseUrl\" placeholder=\"https://books.example.com\" class=\"input w-full mt-2\" required><p class=\"text-sm mt-2 text-content-muted\">The public URL of your Bookhoard instance. Used for device sync, OPDS feed, and API endpoints.</p></div><div class=\"mb-4\"><label class=\"block text-xs font-semibold uppercase tracking-wide text-content-muted mb-1\">Email</label> <input type=\"email\" id=\"email\" x-model=\"admin.email\" class=\"input w-full\" required></div><div class=\"mb-4\"><label class=\"block text-xs font-semibold uppercase tracking-wide text-content-muted mb-1\">Username</label> <input type=\"text\" x-model=\"admin.username\" id=\"username\" class=\"input w-full\" required></div><div class=\"grid grid-cols-2 gap-4 mb-4\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide text-content-muted mb-1\">First Name</label> <input type=\"text\" x-model=\"admin.firstName\" placeholder=\"Optional\" class=\"input w-full\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide text-content-muted mb-1\">Last Name</label> <input type=\"text\" x-model=\"admin.lastName\" placeholder=\"Optional\" class=\"input w-full\"></div></div><div id=\"password-requirements\" class=\"mb-4 p-4 rounded-lg border border-line bg-surface text-sm\"><p class=\"font-semibold mb-2 text-content\">Password Requirements:</p><ul class=\"space-y-1 text-content-muted\"><li id=\"req-length\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> At least 8 characters</li><li id=\"req-upper\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> One uppercase letter</li><li id=\"req-lower\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> One lowercase letter</li><li id=\"req-number\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> One number</li><li id=\"req-special\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> One special character</li><li id=\"req-match\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> Passwords match</li></ul></div><div class=\"mb-4\"><label class=\"block text-xs font-semibold uppercase tracking-wide text-content-muted mb-1\">Password</label> <input type=\"password\" x-model=\"admin.password\" id=\"password\" class=\"input w-full\" required></div><div class=\"mb-6\"><label class=\"block text-xs font-semibold uppercase tracking-wide text-content-muted mb-1\">Confirm Password</label> <input type=\"password\" x-model=\"admin.confirmPassword\" id=\"confirm-password\" class=\"input w-full\" required></div><button type=\"submit\" id=\"register-btn\" class=\"btn btn-primary w-full opacity-50 cursor-not-allowed\" :disabled=\"loading\" x-text=\"loading ? 'Creating...' : 'Create Admin Account'\"></button></form></div><!-- ==================== STEP 2: Create Libraries ==================== --><div x-show=\"currentStep === 2\" x-cloak class=\"card p-6\"><h2 class=\"text-2xl font-bold tracking-tight text-content mb-2\">Create Libraries</h2><p class=\"mb-6 text-content-muted\">Add one or more media libraries. You can always add more later.</p><!-- Created libraries list --><div x-show=\"libraries.length > 0\" x-cloak class=\"mb-6 space-y-2\"><template x-for=\"(lib, idx) in libraries\" :key=\"lib.id\"><div class=\"p-3 rounded-lg border border-line bg-surface flex items-center justify-between\"><div class=\"flex items-center gap-3\"><span class=\"text-xl\" x-text=\"lib.type === 'ebooks' ? '📚' : lib.type === 'comics' ? '📖' : '🗾'\"></span><div><p class=\"font-medium text-content\" x-text=\"lib.name\"></p><p class=\"text-xs capitalize text-content-muted\" x-text=\"lib.type\"></p></div></div><button type=\"button\" @click=\"removeLibrary(idx)\" class=\"text-xs text-danger hover:underline\">Remove</button></div></template></div><!-- Add library form --><form @submit.prevent=\"addLibrary()\" class=\"space-y-4 p-4 rounded-lg border border-line bg-surface\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide text-content-muted mb-1\">Library Name</label> <input type=\"text\" x-model=\"newLibrary.name\" placeholder=\"My Ebook Library\" class=\"input w-full\" required></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide text-content-muted mb-1\">Library Type</label> <select x-model=\"newLibrary.type\" class=\"input w-full\"><option value=\"ebooks\">📚 Ebooks</option> <option value=\"comics\">📖 Comics</option> <option value=\"manga\">🗾 Manga</option></select></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide text-content-muted mb-1\">Description (optional)</label> <input type=\"text\" x-model=\"newLibrary.description\" placeholder=\"Optional\" class=\"input w-full\"></div><button type=\"submit\" class=\"btn btn-secondary w-full\" :disabled=\"loading || !newLibrary.name.trim()\" x-text=\"loading ? 'Creating...' : '+ Add This Library'\"></button></form><div class=\"flex justify-between mt-6\"><button type=\"button\" @click=\"currentStep = 1\" class=\"btn btn-secondary\">Back</button> <button type=\"button\" @click=\"currentStep = 3\" class=\"btn btn-primary\"><span x-text=\"libraries.length > 0 ? 'Next: Configure Folders' : 'Skip Continue'\"></span></button></div></div><!-- ==================== STEP 3: Configure Folders ==================== --><div x-show=\"currentStep === 3\" x-cloak class=\"card p-6\"><h2 class=\"text-2xl font-bold tracking-tight text-content mb-2\">Configure Library Folders</h2><p class=\"mb-6 text-content-muted\">Add the filesystem folders where your media files are stored.</p><div x-show=\"libraries.length === 0\" x-cloak class=\"text-center py-8 text-content-muted\"><p>No libraries created. You can add folders later from the admin panel.</p></div><div class=\"space-y-6\"><template x-for=\"lib in libraries\" :key=\"lib.id\"><div class=\"p-4 rounded-lg border border-line bg-surface\"><div class=\"flex items-center gap-2 mb-3\"><span class=\"text-xl\" x-text=\"lib.type === 'ebooks' ? '📚' : lib.type === 'comics' ? '📖' : '🗾'\"></span><h4 class=\"font-semibold text-content\" x-text=\"lib.name\"></h4></div><!-- Existing folders for this library --><div class=\"space-y-1 mb-3\" x-show=\"lib.folders && lib.folders.length > 0\"><template x-for=\"(folder, fIdx) in (lib.folders || [])\" :key=\"folder\"><div class=\"flex items-center justify-between p-2 rounded text-sm border border-line bg-surface-raised\"><span class=\"text-content\" x-text=\"folder\"></span> <button type=\"button\" @click=\"removeFolder(lib.id, folder, fIdx)\" class=\"text-xs text-danger hover:underline\">Remove</button></div></template></div><!-- Add folder --><div class=\"flex gap-2\"><input type=\"text\" :placeholder=\"'/path/to/' + lib.type\" :id=\"'folderInput-' + lib.id\" class=\"input flex-1 text-sm\"> <button type=\"button\" @click=\"showFolderBrowser('folderInput-' + lib.id)\" class=\"btn btn-secondary text-xs\">Browse</button> <button type=\"button\" @click=\"addFolderToLibrary(lib)\" class=\"btn btn-primary text-xs\">Add</button></div></div></template></div><div class=\"flex justify-between mt-6\"><button type=\"button\" @click=\"currentStep = 2\" class=\"btn btn-secondary\">Back</button> <button type=\"button\" @click=\"currentStep = 4\" class=\"btn btn-primary\">Next: Scan</button></div></div><!-- ==================== STEP 4: Scan & Finish ==================== --><div x-show=\"currentStep === 4\" x-cloak class=\"card p-6\"><h2 class=\"text-2xl font-bold tracking-tight text-content mb-2\">Scan & Finish</h2><p class=\"mb-6 text-content-muted\">Review your configuration and start the initial scan.</p><!-- Summary --><div class=\"space-y-3 mb-6\"><div class=\"p-4 rounded-lg border border-line bg-surface\"><p class=\"text-sm font-medium mb-1 text-content-muted\">Server URL</p><p class=\"text-content\" x-text=\"admin.baseUrl\"></p></div><div class=\"p-4 rounded-lg border border-line bg-surface\"><p class=\"text-sm font-medium mb-1 text-content-muted\">Administrator</p><p class=\"text-content\" x-text=\"admin.username + ' (' + admin.email + ')'\"></p></div><div class=\"p-4 rounded-lg border border-line bg-surface\"><p class=\"text-sm font-medium mb-2 text-content-muted\">Libraries (<span x-text=\"libraries.length\"></span>)</p><template x-for=\"lib in libraries\" :key=\"lib.id\"><div class=\"flex items-start justify-between text-sm py-1\"><div><span class=\"text-content\" x-text=\"lib.name\"></span> <span class=\"ml-2 text-xs px-2 py-0.5 rounded bg-brand text-surface\" x-text=\"lib.type\"></span></div><div class=\"text-right text-content-muted\"><span x-text=\"(lib.folders || []).length + ' folder(s)'\"></span></div></div></template></div></div><!-- Scan progress --><div x-show=\"scanStarted\" x-cloak class=\"mb-6 space-y-3\"><h3 class=\"font-semibold text-content\">Scan Progress</h3><div id=\"library-progress-list\" class=\"space-y-3\"><template x-for=\"job in scanJobs\" :key=\"job.jobId\"><div class=\"p-3 rounded border border-line bg-surface\"><div class=\"flex justify-between items-center mb-2\"><span class=\"font-medium text-content\" x-text=\"job.libraryName\"></span> <span class=\"text-sm text-content-muted\" x-text=\"job.statusText\"></span></div><div class=\"w-full rounded-full h-2 bg-surface-raised\"><div class=\"h-2 rounded-full transition-all duration-500\" :style=\"'width: ' + job.progress + '%; background-color: var(--accent);'\"></div></div></div></template></div><div x-show=\"scanComplete\" x-cloak class=\"p-4 rounded-lg border border-green-500/50 bg-green-500/10 text-green-400 text-sm\">Initial scan complete! Your libraries are ready to use.</div></div><div class=\"flex justify-between mt-6\"><button type=\"button\" @click=\"currentStep = 3\" class=\"btn btn-secondary\" :disabled=\"loading\">Back</button><div class=\"flex gap-3\"><button x-show=\"!scanStarted\" x-cloak type=\"button\" @click=\"startScan()\" class=\"btn btn-primary\" :disabled=\"loading\">Start Scan</button> <button x-show=\"scanStarted && !scanComplete\" x-cloak type=\"button\" @click=\"finishSetup()\" class=\"btn btn-secondary\" :disabled=\"loading\">Skip to Dashboard</button> <button x-show=\"scanComplete\" x-cloak type=\"button\" @click=\"finishSetup()\" class=\"btn btn-primary\" :disabled=\"loading\" x-text=\"loading ? 'Finishing...' : 'Go to Dashboard'\"></button></div></div></div></div><!-- Folder Browser Modal (reused from admin library) --><div id=\"folder-browser-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: var(--surface-overlay);\"><div class=\"card p-6 w-full max-w-md mx-4\"><div class=\"flex justify-between items-center mb-4\"><h2 class=\"text-xl font-bold text-content\">Browse Folders</h2><button type=\"button\" @click=\"hideFolderBrowser()\" class=\"icon-btn\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Icon("close", "h-5 w-5").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</button></div><div id=\"folder-browser-content\"><!-- Directory listings rendered by Alpine --></div></div></div></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate