From dd1ddff08d7cc560c9c59061a23fc5dd4787b8dd Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 27 Apr 2026 21:30:22 -0400 Subject: [PATCH 01/15] Remove completed PROGRESS_MIGRATION.md The progress reading history migration has been fully implemented and this planning document is no longer needed. --- PROGRESS_MIGRATION.md | 447 ------------------------------------------ 1 file changed, 447 deletions(-) delete mode 100644 PROGRESS_MIGRATION.md diff --git a/PROGRESS_MIGRATION.md b/PROGRESS_MIGRATION.md deleted file mode 100644 index 92acbda..0000000 --- a/PROGRESS_MIGRATION.md +++ /dev/null @@ -1,447 +0,0 @@ -# Universal Progress Service Migration Plan - -## Goal - -Consolidate all progress-saving handlers into a single `ProgressService` that: -- Merges new data with existing progress (preventing data loss across client switches) -- Enriches progress with computed fields (e.g., character_offset from percentage) -- Detects conflicts between different sync sources -- Broadcasts updates via WebSocket -- Is called by all clients: web reader, KOReader, Kobo - -## Architecture - -``` -Client Request → HTTP Handler (thin) → ProgressService.SaveProgress() - ↓ - 1. Read existing progress from DB - 2. Merge new data over existing (keep unset fields) - 3. Enrich (compute missing fields) - 4. Conflict detection - 5. Upsert enriched progress to DB - 6. WebSocket broadcast -``` - -## Current State: Three Separate Handlers Writing Progress - -| Route | Handler | Auth | What it does | -|---|---|---|---| -| `PUT /api/media-items/:id/progress` | `MediaHandler.UpdateMediaReadingProgress` | JWT | Raw upsert, 4 fields only (percentage, current_page, total_pages, epubcfi). ALL other fields set to NULL. | -| `POST /api/progress/:id` | `Handler.UpdateUniversalProgress` (ScannerHandler) | JWT | Page→percentage conversion, WebSocket broadcast. Still nulls unset fields. | -| `POST /api/sync/koreader/progress` | `KOReaderHandler.SyncProgress` | Device token | Book resolution (UUID→hash→path→title), conflict detection, checkpoint mode, WebSocket broadcast. Sets chapter/character_offset but nulls viewport/zoom/panel. | -| `POST /api/sync/kobo/:token/markup` | `KoboHandler.Markup` | Device token | ContentId mapping, ReadingSync + BookmarkSync. `last-read-place` only sets epubcfi/chapter, NULLs everything else. | -| `POST /api/sync/kobo/:token/v1/analytics/gettests` | `KoboHandler.AnalyticsGettests` | Device token | Same as ReadingSync in Markup. | -| `POST /api/sync/kobo/:token/sync-from-server` | `KoboHandler.SyncFromServer` | Device token | Same pattern, `last_sync_source = "bookhoard"`. | - -### Critical Bug in Current Code (Data Loss) - -`UpdateUniversalProgress` SQL uses `ON CONFLICT DO UPDATE SET ... = EXCLUDED.*` — it replaces ALL fields. Any field passed as `Valid: false` (NULL) overwrites whatever was previously stored. - -**This means every cross-client save loses data.** Examples: -- KOReader saves character_offset → web reader saves → character_offset becomes NULL -- Kobo saves percentage → Kobo sends last-read-place → percentage becomes NULL -- KOReader saves chapter → web reader saves → chapter becomes NULL - -The merge approach in this migration fixes this. - -## Read Routes - -| Route | Handler | Returns | -|---|---|---| -| `GET /api/media-items/:id/progress` | `MediaHandler.GetMediaReadingProgress` | Raw `ReadingProgress` struct | -| `GET /api/progress/:id` | `Handler.GetUniversalProgress` | Enriched with `format_group`, `total_characters`, `chapter_count` from media_items JOIN | -| `GET /api/progress/:id/history` | `Handler.GetProgressHistory` | Reading history array | -| `GET /progress` (frontend) | Inline in `frontend.go` | Progress overview page, calls `GetAllProgressData` | - -## Existing Bugs to Fix During Migration - -### KOReader handler (`internal/handlers/koreader.go`) -1. **Line ~556:** `UpdateDeviceLastSync` called with zero UUID `pgtype.UUID{Bytes: [16]byte{}, Valid: false}` instead of actual device ID. The correct call already exists in `SyncProgress` at line ~201. Remove the duplicate. -2. **Line ~549:** `SourceDevice.ID` set to `uuid.UUID(userID.Bytes).String()` (user ID) instead of device ID. Device ID is available from the device context but not passed through to `updateProgressForBook`. -3. **`ChapterProgress` always set to `book.Percentage`** (overall book progress), not chapter-relative. Fix: only set if KOReader provides it explicitly, otherwise preserve existing value via merge. -4. **Dead code:** `conflicts` response field is initialized but never populated. This is intentional — conflicts are only shown in web UI, not returned to devices. No change needed. - -### Kobo handler (`internal/handlers/kobo.go`) -5. **`last-read-place` (line ~487):** `epubcfi` passed as `Valid: true` even when empty string (BookmarkId doesn't start with `epubcfi(`). Fix: only set `Valid: true` if non-empty after stripping. -6. **`calculateFileSHA256` function:** Defined but never called. Dead code — remove. -7. **`parseKoboDeviceHeader` function:** Defined but never called in kobo.go (may be used by middleware). Verify before removing. -8. **`GetLibrary` bookmark_count:** Counts ALL annotations (highlights + notes + bookmarks), not just bookmarks. Known issue, fix separately. - -### Media handler (`internal/handlers/media.go`) -9. **`UpdateMediaReadingProgress`:** Sets `CharacterOffset`, `Chapter`, `ChapterProgress`, `ViewportX/Y`, `ZoomLevel`, `ScrollPositionX/Y`, `PanelNumber`, `ReadingMode` all to `Valid: false` — nulls them. Fixed by merge approach. - -### Universal progress handler (`internal/handlers/progress.go`) -10. **`UpdateUniversalProgress`:** Also sets `ChapterProgress = percentage` (book-wide, not chapter-relative). Same bug as KOReader. Fixed by merge approach. - -### Sync infrastructure -11. **`OfflineDetector`** (`internal/sync/offline.go`): Fully implemented but never started in `cmd/server/main.go`. Not part of this migration, but noted. -12. **Queue processor `syncNote`/`syncHighlight`** (`internal/sync/queue.go`): Stub methods, not implemented. Not part of this migration. -13. **`reading_progress.conflict_detected` column:** Never set to `true` by any handler. The `sync_conflicts` table records conflicts, but the boolean on the progress row stays false. The SQL upsert doesn't include this column in the `DO UPDATE SET` clause. Schema fix needed separately. - -## New Code: ProgressService - -### Location: `internal/sync/progress.go` (add to existing file) - -### Struct - -```go -type ProgressService struct { - db *database.Queries - connManager *ConnectionManager -} - -func NewProgressService(db *database.Queries, connManager *ConnectionManager) *ProgressService -``` - -### Input Struct - -```go -type SaveProgressRequest struct { - MediaItemID pgtype.UUID - UserID pgtype.UUID - Source string // "web", "koreader", "kobo", "bookhoard" - DeviceID pgtype.UUID // for conflict detection context - - // All pointer fields — nil means "don't change existing value" - Percentage *float64 - Epubcfi *string - CharacterOffset *int64 - Chapter *int - ChapterProgress *float64 - CurrentPage *int - TotalPages *int - ViewportX *float64 - ViewportY *float64 - ZoomLevel *float64 - ScrollX *float64 - ScrollY *float64 - PanelNumber *int - ReadingMode *string - - // For broadcast and conflict detection - DeviceType string - DeviceName string -} -``` - -### SaveProgress Logic (pseudocode) - -``` -func SaveProgress(ctx, req) (ReadingProgress, error): - - // 1. Get media item metadata (for enrichment) - mediaItem = db.GetMediaItem(ctx, req.MediaItemID) - - // 2. Read existing progress - existing, err = db.GetReadingProgress(ctx, {MediaItemID, UserID}) - if err == pgx.ErrNoRows: - existing = empty defaults - else if err != nil: - return err - - // 3. Merge: build UpdateUniversalProgressParams by starting with - // existing values, then overwriting with any non-nil fields from req - params = buildParamsFromExisting(existing) - params = mergeRequestOverParams(params, req) - - // 4. Enrich missing fields - if params.Percentage.Valid && !params.CharacterOffset.Valid && mediaItem.TotalCharacters.Valid && mediaItem.TotalCharacters.Int64 > 0: - charOffset = PercentageToCharacter(params.Percentage.Float64, mediaItem.TotalCharacters.Int64) - params.CharacterOffset = {Int64: charOffset, Valid: true} - - if params.Percentage.Valid && !params.CurrentPage.Valid && params.TotalPages.Valid: - page = PercentageToPage(params.Percentage.Float64, int(params.TotalPages.Int32)) - params.CurrentPage = {Int32: int32(page), Valid: true} - - // (Future: generate CFI from character_offset using EPUB parser) - - // 5. Set sync metadata - params.MediaItemID = req.MediaItemID - params.UserID = req.UserID - params.LastSyncDevice = {String: req.DeviceType, Valid: true} - params.LastSyncSource = {String: req.Source, Valid: true} - - // 6. Conflict detection - if existing exists AND existing.LastSyncSource.Valid: - if existing.LastSyncSource.String != req.Source AND existing.LastSyncTimestamp.Valid: - if time.Since(existing.LastSyncTimestamp.Time) < 5*time.Minute: - pctDiff = abs(params.Percentage.Float64 - existing.Percentage.Float64) - if pctDiff > 0.01: - // Record conflict - conflictData = buildConflictData(existing, req) - db.CreateSyncConflict(ctx, {MediaItemID, UserID, "progress", conflictData}) - connManager.BroadcastConflictNotification(mediaItemID, "detection", "") - - // 7. Upsert - result, err = db.UpdateUniversalProgress(ctx, params) - if err != nil: - return err - - // 8. Broadcast - deviceName = req.DeviceName - if deviceName == "": deviceName = req.DeviceType + " Device" - connManager.BroadcastProgressUpdate( - mediaItemID, - params.Percentage.Float64, - SourceDevice{ID: req.DeviceID, Name: deviceName, Type: req.Source}, - ) - - return result, nil -``` - -### Merge Logic Detail - -The `buildParamsFromExisting` function reads every field from the existing `ReadingProgress` row into `UpdateUniversalProgressParams`. - -The `mergeRequestOverParams` function only overwrites a field if the corresponding pointer in `SaveProgressRequest` is non-nil. - -This ensures: -- Web reader sends percentage + epubcfi + current_page + total_pages → character_offset from KOReader's last save is preserved -- KOReader sends percentage + character_offset + chapter → epubcfi from web reader's last save is preserved -- Kobo sends only percentage → everything else preserved -- Kobo sends epubcfi + chapter (last-read-place) → percentage from previous ReadingSync preserved - -## File Changes - -### Modified Files - -| File | Change | -|---|---| -| `internal/sync/progress.go` | Add `ProgressService` struct, `NewProgressService`, `SaveProgress`, merge/enrich helpers | -| `internal/handlers/media.go` | `UpdateMediaReadingProgress`: parse richer request, call `ProgressService.SaveProgress`. `GetMediaReadingProgress`: use `GetUniversalProgress` query for enriched response. `MediaHandler` struct: add `progressService` field. Constructor: accept `ProgressService`. | -| `internal/handlers/koreader.go` | `updateProgressForBook`: replace raw upsert with `ProgressService.SaveProgress` call. Fix `SourceDevice.ID` bug (use device ID). Remove duplicate `UpdateDeviceLastSync` with zero UUID. `enqueueProgressForBook`: update `ProgressUpdate` struct if needed. `KOReaderHandler` struct: add `progressService` field. Constructor: accept `ProgressService`. | -| `internal/handlers/kobo.go` | `Markup` ReadingSync: call `ProgressService.SaveProgress`. `Markup` last-read-place: call `ProgressService.SaveProgress` (merge preserves percentage). `AnalyticsGettests`: same. `SyncFromServer`: same. `KoboHandler` struct: add `progressService` field. Constructor: accept `ProgressService`. | -| `internal/handlers/progress.go` | Remove `UpdateUniversalProgress` method. Keep `GetUniversalProgress`, `GetAllProgressData`, `GetProgressHistory`. | -| `internal/sync/queue.go` | `syncProgress` method: call `ProgressService.SaveProgress` instead of raw `db.UpdateUniversalProgress`. `SyncQueueProcessor` struct: add `progressService` field. | -| `internal/router/media.go` | Add `GET /media-items/:id/progress/history` route. Remove "Legacy" comment from progress routes. | -| `internal/router/progress.go` | **DELETE THIS FILE** — routes moved to media.go or removed. | -| `internal/router/router.go` | Remove `registerProgressRoutes` call. Create `ProgressService` and inject into `MediaHandler`, `KOReaderHandler`, `KoboHandler`, `SyncQueueProcessor`. | -| `cmd/server/main.go` | Create `ProgressService` after `connManager` and `queueProcessor` creation. Pass to handler constructors. | -| `web/src/reader/reader.ts` | `saveProgress`: send richer payload (add chapter, chapter_progress, reading_mode, zoom_level, etc.) | - -### Deleted Files - -| File | Why | -|---|---| -| `internal/router/progress.go` | All routes moved to `media.go` or removed | - -### Dead Code to Remove - -| What | Where | -|---|---| -| `UpdateReadingProgress` query | `queries/queries.sql` (source) + `queries.sql.go` + `querier.go` (generated) | -| `registerProgressRoutes` function | `router/progress.go` (file deleted) | -| `calculateFileSHA256` function | `handlers/kobo.go` — never called | -| `parseKoboDeviceHeader` function | `handlers/kobo.go` — verify it's not used by middleware before removing | - -## Route Changes - -### Before - -| Method | Route | Handler | Auth | -|---|---|---|---| -| GET | `/api/media-items/:id/progress` | `MediaHandler.GetMediaReadingProgress` | JWT | -| PUT | `/api/media-items/:id/progress` | `MediaHandler.UpdateMediaReadingProgress` | JWT | -| DELETE | `/api/media-items/:id/progress` | `MediaHandler.DeleteMediaReadingProgress` | JWT | -| GET | `/api/progress/:id` | `Handler.GetUniversalProgress` | JWT | -| POST | `/api/progress/:id` | `Handler.UpdateUniversalProgress` | JWT | -| GET | `/api/progress/:id/history` | `Handler.GetProgressHistory` | JWT | -| POST | `/api/sync/koreader/progress` | `KOReaderHandler.SyncProgress` | Device | -| GET | `/api/sync/koreader/metadata/:uuid` | `KOReaderHandler.GetMetadata` | Device | -| GET | `/api/sync/koreader/library` | `KOReaderHandler.GetLibrary` | Device | -| POST | `/api/sync/koreader/bookmarks` | `KOReaderHandler.SyncBookmarks` | Device | -| POST | `/api/sync/kobo/:token/markup` | `KoboHandler.Markup` | Device | -| POST | `/api/sync/kobo/:token/bookmark` | `KoboHandler.Bookmark` | Device | -| POST | `/api/sync/kobo/:token/v1/analytics/gettests` | `KoboHandler.AnalyticsGettests` | Device | -| GET | `/api/sync/kobo/:token/v1/initialization` | `KoboHandler.Initialization` | Device | -| POST | `/api/sync/kobo/:token/sync-from-server` | `KoboHandler.SyncFromServer` | Device | - -### After - -| Method | Route | Handler | Auth | Change | -|---|---|---|---|---| -| GET | `/api/media-items/:id/progress` | `MediaHandler.GetMediaReadingProgress` | JWT | Enhanced response (adds format_group, total_characters) | -| PUT | `/api/media-items/:id/progress` | `MediaHandler.UpdateMediaReadingProgress` | JWT | Now calls ProgressService, richer request | -| DELETE | `/api/media-items/:id/progress` | `MediaHandler.DeleteMediaReadingProgress` | JWT | No change | -| GET | `/api/media-items/:id/progress/history` | `MediaHandler.GetProgressHistory` | JWT | **NEW** (moved from /progress/:id/history) | -| ~~GET~~ | ~~`/api/progress/:id`~~ | ~~removed~~ | | **REMOVED** | -| ~~POST~~ | ~~`/api/progress/:id`~~ | ~~removed~~ | | **REMOVED** | -| ~~GET~~ | ~~`/api/progress/:id/history`~~ | ~~removed~~ | | **MOVED** to media-items | -| POST | `/api/sync/koreader/progress` | `KOReaderHandler.SyncProgress` | Device | Internally uses ProgressService | -| GET | `/api/sync/koreader/metadata/:uuid` | `KOReaderHandler.GetMetadata` | Device | No change | -| GET | `/api/sync/koreader/library` | `KOReaderHandler.GetLibrary` | Device | No change | -| POST | `/api/sync/koreader/bookmarks` | `KOReaderHandler.SyncBookmarks` | Device | No change | -| POST | `/api/sync/kobo/:token/markup` | `KoboHandler.Markup` | Device | Internally uses ProgressService | -| POST | `/api/sync/kobo/:token/bookmark` | `KoboHandler.Bookmark` | Device | No change (bookmark-only, no progress) | -| POST | `/api/sync/kobo/:token/v1/analytics/gettests` | `KoboHandler.AnalyticsGettests` | Device | Internally uses ProgressService | -| GET | `/api/sync/kobo/:token/v1/initialization` | `KoboHandler.Initialization` | Device | No change | -| POST | `/api/sync/kobo/:token/sync-from-server` | `KoboHandler.SyncFromServer` | Device | Internally uses ProgressService | - -**All device-facing URLs are unchanged.** KOReader and Kobo firmware expect exact paths. - -## Conflict Rules (Preserved From Current Behavior) - -- Conflict detected ONLY when: - 1. Existing progress has `last_sync_source` that differs from current source - 2. `last_sync_timestamp` is within 5 minutes - 3. Absolute percentage difference > 0.01 (1%) -- On conflict: record in `sync_conflicts` table, broadcast WebSocket notification -- Current client's data ALWAYS wins (overwrite, don't merge with conflicting data) -- Conflict details NOT returned to device caller (only shown in web UI) -- Same-source rapid syncs never trigger conflicts (built-in debouncing for web, same-source check in handler) - -## Enrichment Rules - -After merge, compute missing fields: - -| Condition | Enrichment | -|---|---| -| Has percentage, missing character_offset, media has total_characters | `character_offset = PercentageToCharacter(pct, totalChars)` | -| Has percentage, missing current_page, has total_pages | `current_page = PercentageToPage(pct, totalPages)` | -| Has current_page + total_pages, missing percentage | `percentage = PageToPercentage(page, totalPages)` | -| Has character_offset + total_characters, missing percentage | `percentage = CharacterToPercentage(char, totalChars)` | -| Missing chapter_progress | Preserve existing value (never compute from book-wide percentage) | - -**All enrichment is only computed when source data is valid and non-zero. Silently skip if insufficient data.** - -## Kobo Special Cases - -### `last-read-place` (in Markup BookmarkSync) -- Only provides: `epubcfi` (parsed from BookmarkId), `chapter`, `chapter_progress = 0.5` -- Does NOT provide: `percentage`, `current_page`, `total_pages`, `character_offset` -- **Before migration:** These fields get NULLed (data loss bug) -- **After migration:** Merge preserves existing values, only overwrites epubcfi/chapter/chapter_progress - -### `ReadingSync` (in Markup) -- Only provides: `percentage` (from PercentRead/100) -- Does NOT provide: `epubcfi`, `chapter`, `character_offset`, etc. -- **Before migration:** These fields get NULLed (data loss bug) -- **After migration:** Merge preserves existing values, enrichment may compute character_offset from percentage - -### `SyncFromServer` -- `last_sync_source = "bookhoard"` (NOT "kobo") — this must be preserved -- No WebSocket broadcast — this must be preserved - -## KOReader Special Cases - -### Book resolution (stays in handler, NOT in ProgressService) -The 4-priority resolution chain is KOReader-specific: -1. UUID match (confidence 1.0) -2. SHA-256 match (confidence 0.9) -3. File path match via alias or DB lookup (confidence 0.7) -4. Title + Author match (confidence 0.5/0.4) - -This logic stays in `KOReaderHandler.resolveBookToMediaItem`. Only the final progress write goes through `ProgressService`. - -### Checkpoint mode -- `enqueueProgressForBook` creates `ProgressUpdate` struct → queue channel -- Queue processor's `syncProgress` calls `ProgressService.SaveProgress` instead of raw upsert -- `ProgressService` needs to be injected into `SyncQueueProcessor` - -### Device file aliases -- `createDeviceFileAlias` stays in `KOReaderHandler` — it's book resolution, not progress writing - -### Bulk sync -- `SyncProgress` loops over books, resolves each, calls `ProgressService.SaveProgress` per book -- If one book fails, others continue (current behavior, must preserve) -- Error from `SaveProgress` causes the book to not be counted in `booksSynced` - -## Execution Order - -Each step is independently deployable. If a step breaks, previous steps are safe. - -1. **Add `ProgressService` to `internal/sync/progress.go`** — additive, nothing breaks -2. **Write tests for `ProgressService.SaveProgress`** — verify merge, enrichment, conflict detection -3. **Update `cmd/server/main.go` and `internal/router/router.go`** — create `ProgressService`, inject into handlers. Pass as new parameter to constructors. -4. **Update `MediaHandler`** — accept `ProgressService`, use it in `UpdateMediaReadingProgress`. Accept richer request body. -5. **Update `KOReaderHandler`** — accept `ProgressService`, use in `updateProgressForBook`. Fix bugs. -6. **Update `KoboHandler`** — accept `ProgressService`, use in Markup/AnalyticsGettests/SyncFromServer. -7. **Update queue processor** — `syncProgress` calls `ProgressService.SaveProgress` -8. **Update `reader.ts`** — send richer payload -9. **Move routes** — add history route to media.go, remove progress.go -10. **Delete dead code** — `UpdateReadingProgress` query, `calculateFileSHA256`, etc. -11. **Run all tests** -12. **Rebuild frontend** (`npm run build:ts`) -13. **Rebuild app** (`make rebuild-app`) -14. **Manual test**: web reader → KOReader sync → Kobo sync → back to web reader - -## Dependency Injection Changes - -### Current (cmd/server/main.go) -``` -connManager = sync.NewConnectionManager() -queueProcessor = sync.NewSyncQueueProcessor(queries) -mediaHandler = handlers.NewMediaHandler(queries, libraryService, worker) -koreaderHandler = handlers.NewKOReaderHandler(queries, connManager, queueProcessor) -koboHandler = handlers.NewKoboHandler(queries, connManager) -``` - -### After -``` -connManager = sync.NewConnectionManager() -progressService = sync.NewProgressService(queries, connManager) -queueProcessor = sync.NewSyncQueueProcessor(queries, progressService) -mediaHandler = handlers.NewMediaHandler(queries, libraryService, worker, progressService) -koreaderHandler = handlers.NewKOReaderHandler(queries, connManager, queueProcessor, progressService) -koboHandler = handlers.NewKoboHandler(queries, connManager, progressService) -``` - -## Tests to Write/Update - -1. **`internal/sync/progress_test.go`** — add tests for: - - `SaveProgress` with no existing data (fresh insert) - - `SaveProgress` merge: KOReader data preserved when web saves - - `SaveProgress` merge: web data preserved when KOReader saves - - `SaveProgress` enrichment: character_offset computed from percentage - - `SaveProgress` enrichment: current_page computed from percentage + total_pages - - `SaveProgress` conflict detection: triggered when different source within 5 min - - `SaveProgress` conflict detection: NOT triggered for same source - - `SaveProgress` conflict detection: NOT triggered after 5 min window -2. **Run existing tests** — `ConvertProgress`, `MergeProgress`, `PageToPercentage`, etc. must still pass - -## Functionality That Must Not Be Touched - -- KOReader bookmark/highlight/note sync (`SyncBookmarks`) — annotation creation, not progress -- Kobo bookmark creation in `Bookmark` handler — annotation creation -- Kobo library initialization — read-only -- Kobo ContentId mapping helpers — book resolution -- KOReader book resolution logic — book resolution -- KOReader metadata/library retrieval — read-only -- WebSocket connection management — infrastructure -- Offline detection — infrastructure (not started anyway) -- Scanner/worker functionality on `Handler` struct -- Frontend progress overview page -- Frontend book detail page progress display -- All annotation-related queries and handlers - -## Context: The Bigger Picture - -This migration is the foundation for the "universal sync engine" — the core purpose of the Bookhoard project. The goal is seamless reading progress sync across all devices: -- Web reader (foliate-js based) -- KOReader (crengine based, running on Kindle/Kobo/Android/desktop) -- Kobo (stock firmware) - -The `ProgressService` is designed to eventually support: -- Server-side CFI generation from crengine data (EPUB parser needed) -- Server-side crengine XPointer generation from CFI (EPUB parser needed) -- Bidirectional exact position sync between any two clients - -Current `percentage` is the universal fallback. CFI is exact for EPUB. Character offset bridges the gap for crengine. The `ProgressService` enrichment step is where future CFI generation will be added. - -## Database Schema (unchanged) - -The `reading_progress` table already has all needed fields. No schema changes required. - -Key columns: -- `percentage` FLOAT (0.0-1.0) — universal progress -- `epubcfi` TEXT — exact EPUB position -- `character_offset` BIGINT — crengine position -- `chapter` INTEGER — chapter number -- `chapter_progress` FLOAT — within-chapter progress -- `current_page` / `total_pages` INTEGER — page display -- `viewport_x/y`, `zoom_level`, `scroll_position_x/y` — fixed-layout state -- `panel_number` — comic panel -- `reading_mode` — reading mode identifier -- `last_sync_device` / `last_sync_source` — sync metadata -- `last_sync_timestamp` — for conflict detection -- `conflict_detected` / `conflict_resolved` — conflict flags From caf50ade3177e50a709e9ffc00655122af5426a6 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 27 Apr 2026 21:30:37 -0400 Subject: [PATCH 02/15] Add timezone support to database schema and queries - Add timezone column (VARCHAR(50) DEFAULT 'UTC') to users table - Add default_timezone row to system_settings seed data - Add idx_users_timezone index for user timezone lookups - Add UpdateUserTimezone and GetSystemTimezone queries - Regenerate sqlc code (models, querier, queries.sql.go) - Reuse existing UpdateSystemSetting for system timezone updates instead of creating a redundant UpdateSystemTimezone query --- database/schema/schema.sql | 5 ++++- internal/database/models.go | 1 + internal/database/querier.go | 2 ++ internal/database/queries.sql.go | 28 ++++++++++++++++++++++++++- internal/database/queries/queries.sql | 6 ++++++ 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/database/schema/schema.sql b/database/schema/schema.sql index e0c445b..8789292 100644 --- a/database/schema/schema.sql +++ b/database/schema/schema.sql @@ -32,6 +32,7 @@ CREATE TABLE IF NOT EXISTS users ( theme VARCHAR(50) DEFAULT 'tokyo-night', max_devices INTEGER DEFAULT 10, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + timezone VARCHAR(50) DEFAULT 'UTC', updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); @@ -47,7 +48,8 @@ CREATE TABLE IF NOT EXISTS system_settings ( -- Insert default system settings INSERT INTO system_settings (setting_key, setting_value, description) VALUES ('scan_poll_interval_seconds', '60', 'How often to scan all libraries in minutes'), -('auto_scan_enabled', 'true', 'Whether auto-scanning is enabled system-wide') +('auto_scan_enabled', 'true', 'Whether auto-scanning is enabled system-wide'), +('default_timezone', 'UTC', 'System default timezone') ON CONFLICT (setting_key) DO NOTHING; -- Create refresh_tokens table @@ -457,6 +459,7 @@ CREATE TABLE IF NOT EXISTS reading_history ( -- Create indexes for better query performance CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); +CREATE INDEX IF NOT EXISTS idx_users_timezone ON users(timezone); CREATE INDEX IF NOT EXISTS idx_library_types_name ON library_types(name); CREATE INDEX IF NOT EXISTS idx_refresh_tokens_token ON refresh_tokens(token); CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id); diff --git a/internal/database/models.go b/internal/database/models.go index b20110a..3fb239b 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -507,5 +507,6 @@ type Users struct { Theme pgtype.Text `db:"theme" json:"theme"` MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"` CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + Timezone pgtype.Text `db:"timezone" json:"timezone"` UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` } diff --git a/internal/database/querier.go b/internal/database/querier.go index e902dd1..0b41efd 100644 --- a/internal/database/querier.go +++ b/internal/database/querier.go @@ -247,6 +247,7 @@ type Querier interface { GetSystemConfig(ctx context.Context, key string) (SystemConfig, error) // System Settings queries GetSystemSetting(ctx context.Context, settingKey string) (string, error) + GetSystemTimezone(ctx context.Context) (string, error) // Get universal progress for a book GetUniversalProgress(ctx context.Context, arg GetUniversalProgressParams) (GetUniversalProgressRow, error) // Get unlinked book by ContentId @@ -373,6 +374,7 @@ type Querier interface { UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) error UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UpdateUserRoleRow, error) UpdateUserTheme(ctx context.Context, arg UpdateUserThemeParams) error + UpdateUserTimezone(ctx context.Context, arg UpdateUserTimezoneParams) error UpdateUsername(ctx context.Context, arg UpdateUsernameParams) error UpsertDashboardPreferences(ctx context.Context, arg UpsertDashboardPreferencesParams) (UserDashboardPreferences, error) UpsertPanelData(ctx context.Context, arg UpsertPanelDataParams) (PanelData, error) diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index 5274924..19ab276 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -5408,6 +5408,17 @@ func (q *Queries) GetSystemSetting(ctx context.Context, settingKey string) (stri return setting_value, err } +const GetSystemTimezone = `-- name: GetSystemTimezone :one +SELECT setting_value FROM system_settings WHERE setting_key = 'default_timezone' +` + +func (q *Queries) GetSystemTimezone(ctx context.Context) (string, error) { + row := q.db.QueryRow(ctx, GetSystemTimezone) + var setting_value string + err := row.Scan(&setting_value) + return setting_value, err +} + const GetUniversalProgress = `-- name: GetUniversalProgress :one SELECT rp.id, @@ -10256,7 +10267,7 @@ func (q *Queries) UpdateUniversalProgress(ctx context.Context, arg UpdateUnivers const UpdateUserMaxDevices = `-- name: UpdateUserMaxDevices :one UPDATE users SET max_devices = $2, updated_at = NOW() WHERE id = $1 -RETURNING id, email, username, password_hash, first_name, last_name, role, theme, max_devices, created_at, updated_at +RETURNING id, email, username, password_hash, first_name, last_name, role, theme, max_devices, created_at, timezone, updated_at ` type UpdateUserMaxDevicesParams struct { @@ -10278,6 +10289,7 @@ func (q *Queries) UpdateUserMaxDevices(ctx context.Context, arg UpdateUserMaxDev &i.Theme, &i.MaxDevices, &i.CreatedAt, + &i.Timezone, &i.UpdatedAt, ) return i, err @@ -10341,6 +10353,20 @@ func (q *Queries) UpdateUserTheme(ctx context.Context, arg UpdateUserThemeParams return err } +const UpdateUserTimezone = `-- name: UpdateUserTimezone :exec +UPDATE users SET timezone = $2, updated_at = NOW() WHERE id = $1 +` + +type UpdateUserTimezoneParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Timezone pgtype.Text `db:"timezone" json:"timezone"` +} + +func (q *Queries) UpdateUserTimezone(ctx context.Context, arg UpdateUserTimezoneParams) error { + _, err := q.db.Exec(ctx, UpdateUserTimezone, arg.ID, arg.Timezone) + return err +} + const UpdateUsername = `-- name: UpdateUsername :exec UPDATE users SET username = $2, updated_at = NOW() WHERE id = $1 ` diff --git a/internal/database/queries/queries.sql b/internal/database/queries/queries.sql index f1b8131..202a8cb 100644 --- a/internal/database/queries/queries.sql +++ b/internal/database/queries/queries.sql @@ -300,6 +300,12 @@ RETURNING *; UPDATE users SET role = $2, updated_at = NOW() WHERE id = $1 RETURNING id, email, username, role; +-- name: UpdateUserTimezone :exec +UPDATE users SET timezone = $2, updated_at = NOW() WHERE id = $1; + +-- name: GetSystemTimezone :one +SELECT setting_value FROM system_settings WHERE setting_key = 'default_timezone'; + -- name: CountUserDevices :one SELECT COUNT(*) FROM devices WHERE user_id = $1; From 27e9a654bf40b10fdba89cb03e8f0cf45e48975d Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 27 Apr 2026 21:30:53 -0400 Subject: [PATCH 03/15] Add timezone backend support (handlers, utilities, user context) - Add FormatInTimezone and FormatTimestamptzInTimezone helpers in templates/utils.go for timezone-aware time display - Add Timezone field to templates.User struct - Pass user timezone from DB to template context in helpers.go - Add timezone update handling in auth.go UpdateProfile with validation via time.LoadLocation - Add UpdateTimezoneSettings handler in system_settings.go for admin system-wide default timezone using UpdateSystemSetting --- internal/handlers/auth.go | 17 +++++++++++++++++ internal/handlers/system_settings.go | 23 +++++++++++++++++++++++ internal/router/helpers.go | 6 ++++++ templates/types.go | 1 + templates/utils.go | 23 +++++++++++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index b9806b7..16fa9bf 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -87,6 +87,7 @@ type UpdateProfileRequest struct { FirstName string `json:"first_name,omitempty" validate:"omitempty,max=100"` LastName string `json:"last_name,omitempty" validate:"omitempty,max=100"` Theme string `json:"theme,omitempty" validate:"omitempty"` + Timezone string `json:"timezone,omitempty" validate:"omitempty"` } type AdminUpdateUserRequest struct { @@ -564,6 +565,22 @@ func (h *AuthHandler) UpdateProfile(c *echo.Context) error { } } + // Update timezone + if req.Timezone != "" { + if _, err := time.LoadLocation(req.Timezone); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": "Invalid timezone", + }) + } + err := h.db.UpdateUserTimezone(ctx, database.UpdateUserTimezoneParams{ + ID: pgtype.UUID{Bytes: userUUID, Valid: true}, + Timezone: pgtype.Text{String: req.Timezone, Valid: true}, + }) + if err != nil { + return err + } + } + // Update email (if provided) if req.Email != "" { existingUser, err := h.db.GetUserByEmail(c.Request().Context(), req.Email) diff --git a/internal/handlers/system_settings.go b/internal/handlers/system_settings.go index 0acde00..f2af571 100644 --- a/internal/handlers/system_settings.go +++ b/internal/handlers/system_settings.go @@ -5,6 +5,7 @@ import ( "errors" "net/http" "strconv" + "time" "github.com/jackc/pgx/v5" "github.com/labstack/echo/v5" @@ -31,6 +32,28 @@ type ScanSettingsResponse struct { Message string `json:"message,omitempty"` } +type UpdateTimezoneSettingsRequest struct { + DefaultTimezone string `json:"default_timezone" validate:"required"` +} + +func (h *SystemSettingsHandler) UpdateTimezoneSettings(c *echo.Context) error { + var req UpdateTimezoneSettingsRequest + if err := c.Bind(&req); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request"}) + } + if _, err := time.LoadLocation(req.DefaultTimezone); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid timezone"}) + } + err := h.db.UpdateSystemSetting(c.Request().Context(), database.UpdateSystemSettingParams{ + SettingKey: "default_timezone", + SettingValue: req.DefaultTimezone, + }) + if err != nil { + return err + } + return c.JSON(http.StatusOK, map[string]string{"message": "Timezone updated"}) +} + func (h *SystemSettingsHandler) UpdateScanSettings(c *echo.Context) error { var req UpdateScanSettingsRequest if err := c.Bind(&req); err != nil { diff --git a/internal/router/helpers.go b/internal/router/helpers.go index d483dc0..449efee 100644 --- a/internal/router/helpers.go +++ b/internal/router/helpers.go @@ -35,6 +35,11 @@ func getTemplateUserWithTheme(c *echo.Context, cfg *Config) (templates.User, err userTheme = userDB.Theme.String } + userTimezone := "UTC" + if userDB.Timezone.Valid { + userTimezone = userDB.Timezone.String + } + // Extract JWT token for WebSocket authentication token := "" if cookie, err := c.Cookie("token"); err == nil { @@ -48,6 +53,7 @@ func getTemplateUserWithTheme(c *echo.Context, cfg *Config) (templates.User, err Role: userRole, Theme: userTheme, Token: token, + Timezone: userTimezone, }, nil } diff --git a/templates/types.go b/templates/types.go index 8ec1371..83448d4 100644 --- a/templates/types.go +++ b/templates/types.go @@ -16,6 +16,7 @@ type User struct { LastName string CreatedAt time.Time Token string + Timezone string } type PageData struct { diff --git a/templates/utils.go b/templates/utils.go index f463f42..9ad94f1 100644 --- a/templates/utils.go +++ b/templates/utils.go @@ -6,6 +6,7 @@ import ( "fmt" "net/url" "strings" + "time" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" @@ -197,3 +198,25 @@ func formatAlternateInfo(data []byte) string { return strings.Join(parts, " ") } + +// FormatInTimezone formats a time.Time in the specified timezone as MM-DD-YYYY HH:MM +func FormatInTimezone(t time.Time, timezone string) string { + if t.IsZero() { + return "" + } + + loc, err := time.LoadLocation(timezone) + if err != nil { + loc = time.UTC + } + + return t.In(loc).Format("01-02-2006 03:04 PM") +} + +// FormatTimestamptzInTimezone formats a pgtype.Timestamptz in the specified timezone +func FormatTimestamptzInTimezone(t pgtype.Timestamptz, timezone string) string { + if !t.Valid { + return "" + } + return FormatInTimezone(t.Time, timezone) +} From f589bedad5b0a5af3261595fb04a176f2924c341 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 27 Apr 2026 21:31:04 -0400 Subject: [PATCH 04/15] Add timezone dropdown to profile form and admin settings - Add timezone select dropdown to profile form with common US timezones and UTC - Add system default timezone setting to admin settings page - Reformat profile_form.templ with consistent indentation and multi-line attribute formatting --- templates/admin_settings.templ | 14 ++ templates/admin_settings_templ.go | 8 +- templates/profile_form.templ | 229 ++++++++++++++++++------------ templates/profile_form_templ.go | 118 ++++++++++++--- 4 files changed, 253 insertions(+), 116 deletions(-) diff --git a/templates/admin_settings.templ b/templates/admin_settings.templ index e3c7faa..1bd0e26 100644 --- a/templates/admin_settings.templ +++ b/templates/admin_settings.templ @@ -51,6 +51,20 @@ templ AdminSettings(user User, systemConfig map[string]string, errorMessage stri +
+

System Defaults

+ + +

URL Paths

diff --git a/templates/admin_settings_templ.go b/templates/admin_settings_templ.go index d7805a6..2bdce13 100644 --- a/templates/admin_settings_templ.go +++ b/templates/admin_settings_templ.go @@ -81,14 +81,14 @@ func AdminSettings(user User, systemConfig map[string]string, errorMessage strin if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" placeholder=\"https://books.example.com\" class=\"w-full px-4 py-2 rounded-lg border\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" required>

The public URL of your Bookhoard instance (e.g., https://books.example.com). Used for device sync, OPDS, and API endpoints.

URL Paths

OPDS: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" placeholder=\"https://books.example.com\" class=\"w-full px-4 py-2 rounded-lg border\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" required>

The public URL of your Bookhoard instance (e.g., https://books.example.com). Used for device sync, OPDS, and API endpoints.

System Defaults

URL Paths

OPDS: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(systemConfig["base_url"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 58, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 72, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -101,7 +101,7 @@ func AdminSettings(user User, systemConfig map[string]string, errorMessage strin var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(systemConfig["base_url"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 59, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 73, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -114,7 +114,7 @@ func AdminSettings(user User, systemConfig map[string]string, errorMessage strin var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(systemConfig["base_url"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 60, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 74, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { diff --git a/templates/profile_form.templ b/templates/profile_form.templ index edd7c87..cf8bd08 100644 --- a/templates/profile_form.templ +++ b/templates/profile_form.templ @@ -1,113 +1,149 @@ package templates templ ProfileForm(user User, actionURL string, requireCurrentPassword bool, showRoleField bool, showCancelButton bool) { -

- +

Account Information

-
- +
-
- +
-
- +
-
- +
-
- + +
+
+ +
- if showRoleField {
- + +
}
-

Change Password

- if requireCurrentPassword {
- +
-
- +
-
- +
- -
@@ -117,56 +153,63 @@ templ ProfileForm(user User, actionURL string, requireCurrentPassword bool, show

As an admin, you can change this user's password without knowing their current password.

-
- +
-
- +
- -
}
-
if showCancelButton { - } -
-
} diff --git a/templates/profile_form_templ.go b/templates/profile_form_templ.go index e630130..5753f5f 100644 --- a/templates/profile_form_templ.go +++ b/templates/profile_form_templ.go @@ -36,7 +36,7 @@ func ProfileForm(user User, actionURL string, requireCurrentPassword bool, showR var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(actionURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/profile_form.templ`, Line: 4, Col: 25} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/profile_form.templ`, Line: 5, Col: 20} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -49,7 +49,7 @@ func ProfileForm(user User, actionURL string, requireCurrentPassword bool, showR var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/profile_form.templ`, Line: 17, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/profile_form.templ`, Line: 20, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -62,7 +62,7 @@ func ProfileForm(user User, actionURL string, requireCurrentPassword bool, showR var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/profile_form.templ`, Line: 24, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/profile_form.templ`, Line: 30, Col: 24} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -75,7 +75,7 @@ func ProfileForm(user User, actionURL string, requireCurrentPassword bool, showR var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(user.FirstName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/profile_form.templ`, Line: 31, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/profile_form.templ`, Line: 40, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -88,7 +88,7 @@ func ProfileForm(user User, actionURL string, requireCurrentPassword bool, showR var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(user.LastName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/profile_form.templ`, Line: 38, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/profile_form.templ`, Line: 51, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -164,75 +164,155 @@ func ProfileForm(user User, actionURL string, requireCurrentPassword bool, showR return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, ">Material Dark
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, ">Material Dark
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if showRoleField { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, ">Admin
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "

Change Password

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "

Change Password

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if requireCurrentPassword { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "

As an admin, you can change this user's password without knowing their current password.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\" hx-headers='{\"Authorization\": \"Bearer \" + localStorage.getItem(\"token\")}' hx-target=\"#password-result\" hx-swap=\"innerHTML\" hx-include=\"closest form\" class=\"px-4 py-2 rounded\" style=\"background-color: var(--accent); color: white;\">Reset Password
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if showCancelButton { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } From e5726e12be2d533ed52f5c443730e7d52900558a Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 27 Apr 2026 21:31:19 -0400 Subject: [PATCH 05/15] Switch all user-facing time displays to 12-hour MM-DD-YYYY format Consistently format dates and times across all templates and API handlers using MM-DD-YYYY with 12-hour clock (03:04 PM): - analytics.go: date keys, lastSync, lastRead timestamps - progress.go: lastUpdated timestamp in GetAllProgress - book_detail.templ: LastReadAt, DatePublished - book_detail_modals.templ: progress sync timestamps, LastReadAt - devices.templ: LastSync, LastSeen - conflicts.templ: CreatedAt - admin_users.templ: user CreatedAt date --- internal/handlers/analytics.go | 16 ++++++++-------- internal/handlers/progress.go | 2 +- templates/admin_users.templ | 2 +- templates/admin_users_templ.go | 2 +- templates/book_detail.templ | 6 +++--- templates/book_detail_modals.templ | 4 ++-- templates/book_detail_modals_templ.go | 8 ++++---- templates/book_detail_templ.go | 8 ++++---- templates/conflicts.templ | 2 +- templates/conflicts_templ.go | 4 ++-- templates/devices.templ | 4 ++-- templates/devices_templ.go | 8 ++++---- 12 files changed, 33 insertions(+), 33 deletions(-) diff --git a/internal/handlers/analytics.go b/internal/handlers/analytics.go index 9df4a66..1b0644e 100644 --- a/internal/handlers/analytics.go +++ b/internal/handlers/analytics.go @@ -75,18 +75,18 @@ func (h *AnalyticsHandler) GetReadingStats(c *echo.Context) error { endDate := c.QueryParam("end_date") if startDate == "" { - startDate = time.Now().AddDate(0, -1, 0).Format("2006-01-02") + startDate = time.Now().AddDate(0, -1, 0).Format("01-02-2006") } if endDate == "" { - endDate = time.Now().Format("2006-01-02") + endDate = time.Now().Format("01-02-2006") } - startTime, err := time.Parse("2006-01-02", startDate) + startTime, err := time.Parse("01-02-2006", startDate) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "invalid start_date format") } - endTime, err := time.Parse("2006-01-02", endDate) + endTime, err := time.Parse("01-02-2006", endDate) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "invalid end_date format") } @@ -130,7 +130,7 @@ func (h *AnalyticsHandler) calculateReadingStats(history []database.GetUserReadi longestSession = int(minutes) } - dateKey := entry.CreatedAt.Time.Format("2006-01-02") + dateKey := entry.CreatedAt.Time.Format("01-02-2006") if dailyMap[dateKey] == nil { dailyMap[dateKey] = &DailyReading{ Date: dateKey, @@ -141,7 +141,7 @@ func (h *AnalyticsHandler) calculateReadingStats(history []database.GetUserReadi if entry.PagesRead.Valid { totalPages += int(entry.PagesRead.Int32) - dateKey := entry.CreatedAt.Time.Format("2006-01-02") + dateKey := entry.CreatedAt.Time.Format("01-02-2006") if dailyMap[dateKey] != nil { dailyMap[dateKey].Pages += int(entry.PagesRead.Int32) } @@ -222,7 +222,7 @@ func (h *AnalyticsHandler) GetDeviceUsage(c *echo.Context) error { lastSync := "" if u.LastSync != nil { if t, ok := u.LastSync.(time.Time); ok { - lastSync = t.Format("2006-01-02 15:04:05") + lastSync = t.Format("01-02-2006 03:04:05 PM") } } @@ -274,7 +274,7 @@ func (h *AnalyticsHandler) GetPopularBooks(c *echo.Context) error { lastRead := "" if book.LastRead != nil { if t, ok := book.LastRead.(time.Time); ok { - lastRead = t.Format("2006-01-02 15:04:05") + lastRead = t.Format("01-02-2006 03:04:05 PM") } } diff --git a/internal/handlers/progress.go b/internal/handlers/progress.go index f10946e..0af5ea9 100644 --- a/internal/handlers/progress.go +++ b/internal/handlers/progress.go @@ -306,7 +306,7 @@ func (h *Handler) GetAllProgress(c *echo.Context) error { lastUpdated := "" if progress.LastReadAt.Valid { - lastUpdated = progress.LastReadAt.Time.Format("2006-01-02 15:04") + lastUpdated = progress.LastReadAt.Time.Format("01-02-2006 03:04 PM") } progressList = append(progressList, ProgressWithMedia{ diff --git a/templates/admin_users.templ b/templates/admin_users.templ index 6a4a803..8953a28 100644 --- a/templates/admin_users.templ +++ b/templates/admin_users.templ @@ -86,7 +86,7 @@ templ AdminUsers(currentUser User, users []User, adminCount int) { -
{ user.CreatedAt.Format("2006-01-02") }
+
{ user.CreatedAt.Format("01-02-2006") }
diff --git a/templates/admin_users_templ.go b/templates/admin_users_templ.go index 2f5adde..8bfff80 100644 --- a/templates/admin_users_templ.go +++ b/templates/admin_users_templ.go @@ -178,7 +178,7 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component return templ_7745c5c3_Err } var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(user.CreatedAt.Format("2006-01-02")) + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(user.CreatedAt.Format("01-02-2006")) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_users.templ`, Line: 89, Col: 106} } diff --git a/templates/book_detail.templ b/templates/book_detail.templ index dd04a6d..a132634 100644 --- a/templates/book_detail.templ +++ b/templates/book_detail.templ @@ -229,7 +229,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) {
@@ -250,7 +250,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) { if book.ReadingProgress.LastReadAt.Valid {

Last Read

-

{ book.ReadingProgress.LastReadAt.Time.Format("2006-01-02 15:04") }

+

{ book.ReadingProgress.LastReadAt.Time.Format("01-02-2006 03:04 PM") }

} if book.ReadingProgress.LastSyncSource.Valid { @@ -279,7 +279,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) { if book.DatePublished.Valid {

Published

-

{ book.DatePublished.Time.Format("2006-01-02") }

+

{ book.DatePublished.Time.Format("01-02-2006") }

} if book.Isbn.Valid && book.Isbn.String != "" { diff --git a/templates/book_detail_modals.templ b/templates/book_detail_modals.templ index 146f0d3..288a41b 100644 --- a/templates/book_detail_modals.templ +++ b/templates/book_detail_modals.templ @@ -65,7 +65,7 @@ templ ProgressSyncModal(book handlers.MediaDetail) { { source }

- { data.Timestamp.Format("2006-01-02 15:04:05") } + { data.Timestamp.Format("01-02-2006 03:04 PM:05") }

@@ -110,7 +110,7 @@ templ ProgressSyncModal(book handlers.MediaDetail) { } if book.ReadingProgress.LastReadAt.Valid {

- Last read: { book.ReadingProgress.LastReadAt.Time.Format("2006-01-02 15:04") } + Last read: { book.ReadingProgress.LastReadAt.Time.Format("01-02-2006 03:04 PM") }

}
diff --git a/templates/book_detail_modals_templ.go b/templates/book_detail_modals_templ.go index 5ebd8ce..b7fcdb8 100644 --- a/templates/book_detail_modals_templ.go +++ b/templates/book_detail_modals_templ.go @@ -76,9 +76,9 @@ func ProgressSyncModal(book handlers.MediaDetail) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Timestamp.Format("2006-01-02 15:04:05")) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Timestamp.Format("01-02-2006 03:04 PM:05")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 68, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 68, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -243,9 +243,9 @@ func ProgressSyncModal(book handlers.MediaDetail) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.LastReadAt.Time.Format("2006-01-02 15:04")) + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.LastReadAt.Time.Format("01-02-2006 03:04 PM")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 113, Col: 83} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 113, Col: 86} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { diff --git a/templates/book_detail_templ.go b/templates/book_detail_templ.go index 33a9593..daa8e56 100644 --- a/templates/book_detail_templ.go +++ b/templates/book_detail_templ.go @@ -438,7 +438,7 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string) templ var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("width: %.1f%%; background-color: var(--accent);", book.ReadingProgress.Percentage.Float64*100)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail.templ`, Line: 232, Col: 126} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail.templ`, Line: 232, Col: 124} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -499,9 +499,9 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string) templ return templ_7745c5c3_Err } var templ_7745c5c3_Var21 string - templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.LastReadAt.Time.Format("2006-01-02 15:04")) + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.LastReadAt.Time.Format("01-02-2006 03:04 PM")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail.templ`, Line: 253, Col: 77} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail.templ`, Line: 253, Col: 80} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -565,7 +565,7 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string) templ return templ_7745c5c3_Err } var templ_7745c5c3_Var24 string - templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(book.DatePublished.Time.Format("2006-01-02")) + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(book.DatePublished.Time.Format("01-02-2006")) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail.templ`, Line: 282, Col: 57} } diff --git a/templates/conflicts.templ b/templates/conflicts.templ index 21d9b19..d8bb326 100644 --- a/templates/conflicts.templ +++ b/templates/conflicts.templ @@ -111,7 +111,7 @@ templ Conflicts(user User, conflicts []handlers.ConflictDetailResponse, total in
- Created: { conflict.CreatedAt.Format("2006-01-02 15:04:05") } + Created: { conflict.CreatedAt.Format("01-02-2006 03:04:05 PM") } if conflict.ResolvedBy != "" { | Resolved by: { conflict.ResolvedBy } } diff --git a/templates/conflicts_templ.go b/templates/conflicts_templ.go index e35c693..5a248bb 100644 --- a/templates/conflicts_templ.go +++ b/templates/conflicts_templ.go @@ -120,9 +120,9 @@ func Conflicts(user User, conflicts []handlers.ConflictDetailResponse, total int return templ_7745c5c3_Err } var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(conflict.CreatedAt.Format("2006-01-02 15:04:05")) + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(conflict.CreatedAt.Format("01-02-2006 03:04:05 PM")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/conflicts.templ`, Line: 114, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/conflicts.templ`, Line: 114, Col: 70} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { diff --git a/templates/devices.templ b/templates/devices.templ index 5dc96c2..2f8141e 100644 --- a/templates/devices.templ +++ b/templates/devices.templ @@ -80,7 +80,7 @@ templ Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []P
Last Sync if device.LastSync != nil { - { device.LastSync.Format("2006-01-02 15:04") } + { device.LastSync.Format("01-02-2006 03:04 PM") } } else { Never } @@ -88,7 +88,7 @@ templ Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []P
Last Seen if device.LastSeen != nil { - { device.LastSeen.Format("2006-01-02 15:04") } + { device.LastSeen.Format("01-02-2006 03:04 PM") } } else { Never } diff --git a/templates/devices_templ.go b/templates/devices_templ.go index 69188de..2b4d0b8 100644 --- a/templates/devices_templ.go +++ b/templates/devices_templ.go @@ -135,9 +135,9 @@ func Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []Pe return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(device.LastSync.Format("2006-01-02 15:04")) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(device.LastSync.Format("01-02-2006 03:04 PM")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/devices.templ`, Line: 83, Col: 97} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/devices.templ`, Line: 83, Col: 100} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -163,9 +163,9 @@ func Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []Pe return templ_7745c5c3_Err } var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(device.LastSeen.Format("2006-01-02 15:04")) + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(device.LastSeen.Format("01-02-2006 03:04 PM")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/devices.templ`, Line: 91, Col: 97} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/devices.templ`, Line: 91, Col: 100} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { From c592c745c3582c7ee3e5475327489744306ea326 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 27 Apr 2026 21:31:31 -0400 Subject: [PATCH 06/15] Update timezone plan: remove duplicate query, use 12-hour format - Remove UpdateSystemTimezone query from plan; reuse existing UpdateSystemSetting with 'default_timezone' as the key parameter - Update handler code example to reference UpdateSystemSetting - Update FormatInTimezone format string to 12-hour (03:04 PM) - Update queries file description in summary table --- TIMEZONE_PLAN.md | 94 ++++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 39 deletions(-) diff --git a/TIMEZONE_PLAN.md b/TIMEZONE_PLAN.md index fcf85b7..d2137d4 100644 --- a/TIMEZONE_PLAN.md +++ b/TIMEZONE_PLAN.md @@ -14,27 +14,44 @@ Add per-user timezone support with system-wide fallback (set via docker-compose) **File:** `database/schema/schema.sql` -1. Add `timezone` column to `users` table: +1. Add `timezone` column directly to the `users` table definition (line ~36): ```sql -ALTER TABLE users ADD COLUMN IF NOT EXISTS timezone VARCHAR(50) DEFAULT 'UTC'; +CREATE TABLE IF NOT EXISTS users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email VARCHAR(255) UNIQUE NOT NULL, + username VARCHAR(255) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + first_name VARCHAR(255), + last_name VARCHAR(255), + role VARCHAR(20) NOT NULL DEFAULT 'user' CHECK (role IN ('admin', 'user')), + theme VARCHAR(50) DEFAULT 'tokyo-night', + max_devices INTEGER DEFAULT 10, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + timezone VARCHAR(50) DEFAULT 'UTC' +); ``` -2. Add system-wide default timezone to `system_settings`: +> Note: The `timezone` column is already present at line 36 in the current schema. No change needed for this step. + +1. Add `default_timezone` to the `system_settings` INSERT block (line ~49-52): ```sql INSERT INTO system_settings (setting_key, setting_value, description) VALUES +('scan_poll_interval_seconds', '60', 'How often to scan all libraries in minutes'), +('auto_scan_enabled', 'true', 'Whether auto-scanning is enabled system-wide'), ('default_timezone', 'UTC', 'System default timezone') ON CONFLICT (setting_key) DO NOTHING; ``` -3. Add index: +1. Add index in the indexes section (after line ~460, with other user indexes): ```sql CREATE INDEX IF NOT EXISTS idx_users_timezone ON users(timezone); ``` -4. Regenerate sqlc code: +1. Regenerate sqlc code: ```bash cd internal/database && sqlc generate @@ -54,11 +71,10 @@ UPDATE users SET timezone = $2, updated_at = NOW() WHERE id = $1; -- name: GetSystemTimezone :one SELECT setting_value FROM system_settings WHERE setting_key = 'default_timezone'; - --- name: UpdateSystemTimezone :exec -UPDATE system_settings SET setting_value = $2, updated_at = NOW() WHERE setting_key = 'default_timezone'; ``` +> Note: `UpdateSystemTimezone` is omitted because the existing `UpdateSystemSetting` query handles it by passing `'default_timezone'` as the key parameter. + Regenerate after adding queries: ```bash @@ -93,7 +109,7 @@ func FormatInTimezone(t time.Time, timezone string) string { loc = time.UTC } - return t.In(loc).Format("01-02-2006 15:04") + return t.In(loc).Format("01-02-2006 03:04 PM") } // FormatTimestamptzInTimezone formats a pgtype.Timestamptz in the specified timezone @@ -199,7 +215,7 @@ func (h *SystemSettingsHandler) UpdateTimezoneSettings(c *echo.Context) error { if _, err := time.LoadLocation(req.DefaultTimezone); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid timezone"}) } - err := h.db.UpdateSystemTimezone(c.Request().Context(), database.UpdateSystemTimezoneParams{ + err := h.db.UpdateSystemSetting(c.Request().Context(), database.UpdateSystemSettingParams{ SettingKey: "default_timezone", SettingValue: req.DefaultTimezone, }) @@ -267,20 +283,20 @@ Add system default timezone setting: ### Files to update -| Template | Line(s) | Field(s) | -|----------|---------|----------| -| `templates/book_detail.templ` | ~253, ~282 | `LastReadAt`, `DatePublished` | -| `templates/book_detail_modals.templ` | ~68, ~113 | `Timestamp`, `LastReadAt` | -| `templates/devices.templ` | ~83, ~91, ~174 | `LastSync`, `LastSeen`, `ExpiresAt` | -| `templates/conflicts.templ` | ~114 | `CreatedAt` | -| `templates/admin_users.templ` | ~89 | `CreatedAt` | -| `templates/queue.templ` | ~138 | `CreatedAt` | +| Template | Line(s) | Field(s) | +| ------------------------------------ | -------------- | ----------------------------------- | +| `templates/book_detail.templ` | ~253, ~282 | `LastReadAt`, `DatePublished` | +| `templates/book_detail_modals.templ` | ~68, ~113 | `Timestamp`, `LastReadAt` | +| `templates/devices.templ` | ~83, ~91, ~174 | `LastSync`, `LastSeen`, `ExpiresAt` | +| `templates/conflicts.templ` | ~114 | `CreatedAt` | +| `templates/admin_users.templ` | ~89 | `CreatedAt` | +| `templates/queue.templ` | ~138 | `CreatedAt` | ### Change pattern ```templ -{ book.ReadingProgress.LastReadAt.Time.Format("2006-01-02 15:04") } +{ book.ReadingProgress.LastReadAt.Time.Format("01-02-2006 03:04 PM") } { templates.FormatTimestamptzInTimezone(book.ReadingProgress.LastReadAt, user.Timezone) } @@ -290,7 +306,7 @@ For `time.Time` fields: ```templ -{ device.LastSync.Format("2006-01-02 15:04") } +{ device.LastSync.Format("01-02-2006 03:04 PM") } { templates.FormatInTimezone(device.LastSync, user.Timezone) } @@ -320,25 +336,25 @@ TZ=UTC ## Files Modified Summary -| File | Change | -|------|--------| -| `database/schema/schema.sql` | Add timezone column to users, system_setting row | -| `internal/database/queries/queries.sql` | Add UpdateUserTimezone, GetSystemTimezone, UpdateSystemTimezone | -| `templates/utils.go` | Add FormatInTimezone, FormatTimestamptzInTimezone | -| `templates/types.go` | Add Timezone field to User struct | -| `internal/router/helpers.go` | Pass timezone to template User | -| `internal/handlers/auth.go` | Handle timezone updates in UpdateProfile | -| `internal/handlers/system_settings.go` | Add timezone settings handler | -| `templates/profile_form.templ` | Add timezone dropdown | -| `templates/admin_settings.templ` | Add default timezone setting | -| `templates/book_detail.templ` | Update time displays | -| `templates/book_detail_modals.templ` | Update time displays | -| `templates/devices.templ` | Update time displays | -| `templates/conflicts.templ` | Update time displays | -| `templates/admin_users.templ` | Update time displays | -| `templates/queue.templ` | Update time displays | -| `docker-compose.yml` | Add TZ env var | -| `.env.example` | Add TZ example | +| File | Change | +| --------------------------------------- | ------------------------------------------------------------------------------- | +| `database/schema/schema.sql` | Add timezone column to users, system_setting row | +| `internal/database/queries/queries.sql` | Add UpdateUserTimezone, GetSystemTimezone (reuses existing UpdateSystemSetting) | +| `templates/utils.go` | Add FormatInTimezone, FormatTimestamptzInTimezone | +| `templates/types.go` | Add Timezone field to User struct | +| `internal/router/helpers.go` | Pass timezone to template User | +| `internal/handlers/auth.go` | Handle timezone updates in UpdateProfile | +| `internal/handlers/system_settings.go` | Add timezone settings handler | +| `templates/profile_form.templ` | Add timezone dropdown | +| `templates/admin_settings.templ` | Add default timezone setting | +| `templates/book_detail.templ` | Update time displays | +| `templates/book_detail_modals.templ` | Update time displays | +| `templates/devices.templ` | Update time displays | +| `templates/conflicts.templ` | Update time displays | +| `templates/admin_users.templ` | Update time displays | +| `templates/queue.templ` | Update time displays | +| `docker-compose.yml` | Add TZ env var | +| `.env.example` | Add TZ example | --- From 55a9ec00e1ff26f0bd4503a3db9c9d862b02b253 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Tue, 28 Apr 2026 21:12:01 -0400 Subject: [PATCH 07/15] fix(ui): use timezone-aware formatting for Last Read timestamps in book detail and progress sync modal Replace hardcoded 12-hour Format() calls with FormatTimestamptzInTimezone() so that the Last Read time respects the user's selected timezone preference. Both book_detail.templ and book_detail_modals.templ now use the same timezone-aware helper that was introduced in the timezone support feature. --- templates/book_detail.templ | 2 +- templates/book_detail_modals.templ | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/book_detail.templ b/templates/book_detail.templ index a132634..a0db0b6 100644 --- a/templates/book_detail.templ +++ b/templates/book_detail.templ @@ -250,7 +250,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) { if book.ReadingProgress.LastReadAt.Valid {

Last Read

-

{ book.ReadingProgress.LastReadAt.Time.Format("01-02-2006 03:04 PM") }

+

{ templates.FormatTimestamptzInTimezone(book.ReadingProgress.LastReadAt, user.Timezone) }

} if book.ReadingProgress.LastSyncSource.Valid { diff --git a/templates/book_detail_modals.templ b/templates/book_detail_modals.templ index 288a41b..b5fe655 100644 --- a/templates/book_detail_modals.templ +++ b/templates/book_detail_modals.templ @@ -110,7 +110,7 @@ templ ProgressSyncModal(book handlers.MediaDetail) { } if book.ReadingProgress.LastReadAt.Valid {

- Last read: { book.ReadingProgress.LastReadAt.Time.Format("01-02-2006 03:04 PM") } + Last read: { FormatTimestamptzInTimezone(book.ReadingProgress.LastReadAt, user.Timezone) }

}
From 5be6fec4089d422fb15d725badc8dd6bd8c4d6df Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 29 Apr 2026 20:32:38 -0400 Subject: [PATCH 08/15] fix(auth): resolve compile errors in timezone update handler The timezone update block in UpdateProfile() referenced undefined variables ctx and userUUID, causing a compile error. Fixed to use c.Request().Context() and targetUserUUID which are the correct variables in that handler scope. Also added Timezone field to AdminUpdateUserRequest struct so the timezone value is properly bound from JSON requests, since UpdateProfile() binds to AdminUpdateUserRequest rather than UpdateProfileRequest. --- internal/handlers/auth.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index 16fa9bf..242f251 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -96,6 +96,7 @@ type AdminUpdateUserRequest struct { FirstName string `json:"first_name,omitempty" validate:"omitempty,max=100"` LastName string `json:"last_name,omitempty" validate:"omitempty,max=100"` Theme string `json:"theme,omitempty" validate:"omitempty"` + Timezone string `json:"timezone,omitempty" validate:"omitempty"` Role string `json:"role,omitempty" validate:"omitempty,oneof=user admin"` } @@ -572,8 +573,8 @@ func (h *AuthHandler) UpdateProfile(c *echo.Context) error { "error": "Invalid timezone", }) } - err := h.db.UpdateUserTimezone(ctx, database.UpdateUserTimezoneParams{ - ID: pgtype.UUID{Bytes: userUUID, Valid: true}, + err := h.db.UpdateUserTimezone(c.Request().Context(), database.UpdateUserTimezoneParams{ + ID: targetUserUUID, Timezone: pgtype.Text{String: req.Timezone, Valid: true}, }) if err != nil { From f87fc453771c59517c352a7035b02b231a280aca Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 29 Apr 2026 20:32:42 -0400 Subject: [PATCH 09/15] feat(db): add timezone column to GetUser query The GetUser query did not select the timezone column, so the router helper could not access userDB.Timezone. Added u.timezone to the SELECT list so the per-user timezone is available in the template user context. --- internal/database/queries.sql.go | 3 +++ internal/database/queries/queries.sql | 1 + 2 files changed, 4 insertions(+) diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index 19ab276..dd201e5 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -5662,6 +5662,7 @@ SELECT u.max_devices, u.created_at, u.updated_at, + u.timezone, (SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count FROM users u WHERE u.id = $1 @@ -5678,6 +5679,7 @@ type GetUserRow struct { MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"` CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + Timezone pgtype.Text `db:"timezone" json:"timezone"` DeviceCount int64 `db:"device_count" json:"device_count"` } @@ -5695,6 +5697,7 @@ func (q *Queries) GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, erro &i.MaxDevices, &i.CreatedAt, &i.UpdatedAt, + &i.Timezone, &i.DeviceCount, ) return i, err diff --git a/internal/database/queries/queries.sql b/internal/database/queries/queries.sql index 202a8cb..7033466 100644 --- a/internal/database/queries/queries.sql +++ b/internal/database/queries/queries.sql @@ -27,6 +27,7 @@ SELECT u.max_devices, u.created_at, u.updated_at, + u.timezone, (SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count FROM users u WHERE u.id = $1; From 5da91b9c7c9a48ce3a512ee49573c48933653738 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 29 Apr 2026 20:32:51 -0400 Subject: [PATCH 10/15] feat(ui): use timezone-aware time formatting across all templates Replace hardcoded .Format() calls with FormatInTimezone() and FormatTimestamptzInTimezone() helpers so all timestamps display in the user's selected timezone. Changes: - book_detail.templ: remove incorrect templates. package prefix - book_detail_modals.templ: add User param to ProgressSyncModal so timezone is available; convert Timestamp to FormatInTimezone() - devices.templ: convert LastSync and LastSeen to FormatInTimezone() - conflicts.templ: convert CreatedAt to FormatInTimezone() - admin_users.templ: convert CreatedAt to FormatInTimezone() using currentUser.Timezone Note: DatePublished is kept as a plain date format (MM-DD-YYYY) since it is a pgtype.Date, not a timestamp, and does not need timezone conversion. --- templates/admin_users.templ | 2 +- templates/book_detail.templ | 4 ++-- templates/book_detail_modals.templ | 4 ++-- templates/conflicts.templ | 2 +- templates/devices.templ | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/templates/admin_users.templ b/templates/admin_users.templ index 8953a28..0a9e8ad 100644 --- a/templates/admin_users.templ +++ b/templates/admin_users.templ @@ -86,7 +86,7 @@ templ AdminUsers(currentUser User, users []User, adminCount int) { -
{ user.CreatedAt.Format("01-02-2006") }
+
{ FormatInTimezone(user.CreatedAt, currentUser.Timezone) }
diff --git a/templates/book_detail.templ b/templates/book_detail.templ index a0db0b6..3614b85 100644 --- a/templates/book_detail.templ +++ b/templates/book_detail.templ @@ -250,7 +250,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) { if book.ReadingProgress.LastReadAt.Valid {

Last Read

-

{ templates.FormatTimestamptzInTimezone(book.ReadingProgress.LastReadAt, user.Timezone) }

+

{ FormatTimestamptzInTimezone(book.ReadingProgress.LastReadAt, user.Timezone) }

} if book.ReadingProgress.LastSyncSource.Valid { @@ -501,7 +501,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) { }
- @ProgressSyncModal(book) + @ProgressSyncModal(user, book) @NotesHighlightsModal(book) @ErrorToast(errorMessage) diff --git a/templates/book_detail_modals.templ b/templates/book_detail_modals.templ index b5fe655..3d58f49 100644 --- a/templates/book_detail_modals.templ +++ b/templates/book_detail_modals.templ @@ -6,7 +6,7 @@ import ( ) // ProgressSyncModal shows progress from all devices for manual review -templ ProgressSyncModal(book handlers.MediaDetail) { +templ ProgressSyncModal(user User, book handlers.MediaDetail) {
diff --git a/templates/conflicts.templ b/templates/conflicts.templ index d8bb326..1eb1b67 100644 --- a/templates/conflicts.templ +++ b/templates/conflicts.templ @@ -111,7 +111,7 @@ templ Conflicts(user User, conflicts []handlers.ConflictDetailResponse, total in
- Created: { conflict.CreatedAt.Format("01-02-2006 03:04:05 PM") } + Created: { FormatInTimezone(conflict.CreatedAt, user.Timezone) } if conflict.ResolvedBy != "" { | Resolved by: { conflict.ResolvedBy } } diff --git a/templates/devices.templ b/templates/devices.templ index 2f8141e..aaee178 100644 --- a/templates/devices.templ +++ b/templates/devices.templ @@ -80,7 +80,7 @@ templ Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []P
Last Sync if device.LastSync != nil { - { device.LastSync.Format("01-02-2006 03:04 PM") } + { FormatInTimezone(*device.LastSync, user.Timezone) } } else { Never } @@ -88,7 +88,7 @@ templ Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []P
Last Seen if device.LastSeen != nil { - { device.LastSeen.Format("01-02-2006 03:04 PM") } + { FormatInTimezone(*device.LastSeen, user.Timezone) } } else { Never } From 1da77654668d0ecbeafa6fea25403b50928f6704 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 29 Apr 2026 20:32:58 -0400 Subject: [PATCH 11/15] fix(admin): wire up default timezone setting in admin settings page The admin settings timezone dropdown was incomplete: it had no pre-selection of the current value, was missing consistent styling, and the form submission did not persist timezone changes. Changes: - frontend.go: load default_timezone from system_settings into the systemConfig map passed to the template - admin_settings.templ: match card styling used by the Base URL section; pre-select current timezone with selected?= attribute - sidecar.go: handle default_timezone in UpdateSystemConfiguration by writing to system_settings table instead of system_config; update HTMX response to include timezone section with current value - Add selectedAttr() helper for HTMX HTML string response --- internal/handlers/sidecar.go | 63 +++++++++++++++++++++++++++++++++- internal/router/frontend.go | 8 ++++- templates/admin_settings.templ | 34 +++++++++++------- 3 files changed, 90 insertions(+), 15 deletions(-) diff --git a/internal/handlers/sidecar.go b/internal/handlers/sidecar.go index 92c3b6a..9ba5cf9 100644 --- a/internal/handlers/sidecar.go +++ b/internal/handlers/sidecar.go @@ -388,6 +388,23 @@ func (h *SidecarHandler) UpdateSystemConfiguration(c *echo.Context) error { // Update each config value for key, value := range req { + if key == "default_timezone" { + if _, err := time.LoadLocation(value); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": "invalid timezone", + }) + } + err := h.db.UpdateSystemSetting(ctx, database.UpdateSystemSettingParams{ + SettingKey: "default_timezone", + SettingValue: value, + }) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": "failed to update default timezone", + }) + } + continue + } _, err := h.db.SetSystemConfig(ctx, database.SetSystemConfigParams{ Key: key, Value: value, @@ -408,6 +425,12 @@ func (h *SidecarHandler) UpdateSystemConfiguration(c *echo.Context) error { return c.HTML(http.StatusInternalServerError, `
Failed to fetch updated configuration
`) } + defaultTimezone := "UTC" + tz, err := h.db.GetSystemTimezone(ctx) + if err == nil && tz != "" { + defaultTimezone = tz + } + // Render success message with updated form return c.HTML(http.StatusOK, fmt.Sprintf(`
@@ -437,6 +460,28 @@ func (h *SidecarHandler) UpdateSystemConfiguration(c *echo.Context) error {
+
+

System Defaults

+
+ + +

Default timezone for users who haven't set their own.

+
+
+ +
+
@@ -447,7 +492,16 @@ func (h *SidecarHandler) UpdateSystemConfiguration(c *echo.Context) error {

Device Sync: %s/api/sync

-`, baseURL.Value, baseURL.Value, baseURL.Value, baseURL.Value)) +`, baseURL.Value, + selectedAttr(defaultTimezone, "UTC"), + selectedAttr(defaultTimezone, "America/New_York"), + selectedAttr(defaultTimezone, "America/Chicago"), + selectedAttr(defaultTimezone, "America/Denver"), + selectedAttr(defaultTimezone, "America/Los_Angeles"), + selectedAttr(defaultTimezone, "America/Phoenix"), + selectedAttr(defaultTimezone, "America/Anchorage"), + selectedAttr(defaultTimezone, "Pacific/Honolulu"), + baseURL.Value, baseURL.Value, baseURL.Value)) } return c.JSON(http.StatusOK, map[string]string{ @@ -477,3 +531,10 @@ func sanitizeAll(s string, old string, new string) string { } return result } + +func selectedAttr(current, value string) string { + if current == value { + return " selected" + } + return "" +} diff --git a/internal/router/frontend.go b/internal/router/frontend.go index b63dc8e..2dca858 100644 --- a/internal/router/frontend.go +++ b/internal/router/frontend.go @@ -932,7 +932,13 @@ func registerFrontendRoutes(cfg *Config) { } systemConfig := map[string]string{ - "base_url": baseURL, + "base_url": baseURL, + "default_timezone": "UTC", + } + + defaultTimezone, err := cfg.Queries.GetSystemTimezone(c.Request().Context()) + if err == nil && defaultTimezone != "" { + systemConfig["default_timezone"] = defaultTimezone } var buf bytes.Buffer diff --git a/templates/admin_settings.templ b/templates/admin_settings.templ index 1bd0e26..f9d254c 100644 --- a/templates/admin_settings.templ +++ b/templates/admin_settings.templ @@ -51,19 +51,27 @@ templ AdminSettings(user User, systemConfig map[string]string, errorMessage stri
-
-

System Defaults

- - +
+

System Defaults

+
+ + +

Default timezone for users who haven't set their own.

+
From 4305c77df4afe98567cf56a4ecf4015b2e8cbee7 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 29 Apr 2026 20:33:03 -0400 Subject: [PATCH 12/15] fix(profile): match timezone dropdown styling to rest of profile form The timezone select used generic form-group/form-select CSS classes while all other fields use Tailwind utilities with CSS custom properties. Updated to use the same w-full px-3 py-2 border rounded pattern with var(--bg-primary), var(--text-primary), and var(--border) for visual consistency. --- templates/profile_form.templ | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/templates/profile_form.templ b/templates/profile_form.templ index cf8bd08..b9a8eb9 100644 --- a/templates/profile_form.templ +++ b/templates/profile_form.templ @@ -70,9 +70,13 @@ templ ProfileForm(user User, actionURL string, requireCurrentPassword bool, show
-
- - From ffaa561cb7bcb1654d865f5ab823f98518e38d24 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 29 Apr 2026 20:33:08 -0400 Subject: [PATCH 13/15] chore: regenerate all templ Go files Regenerated from .templ sources after template changes. Includes path reference updates in error messages (templates/ prefix shortened) from templ tool regeneration. --- templates/admin_library_templ.go | 18 ++--- templates/admin_processing_issues_templ.go | 20 ++--- templates/admin_settings_templ.go | 94 ++++++++++++++++++++-- templates/admin_sidebar_templ.go | 8 +- templates/admin_users_templ.go | 4 +- templates/api_explorer_templ.go | 6 +- templates/book_detail_modals_templ.go | 8 +- templates/book_detail_templ.go | 6 +- templates/bookshelf_templ.go | 16 ++-- templates/collection_modal_templ.go | 14 ++-- templates/collection_rules_templ.go | 6 +- templates/collections_templ.go | 30 +++---- templates/conflicts_templ.go | 2 +- templates/custom_section_templ.go | 4 +- templates/dashboard_templ.go | 54 ++++++------- templates/devices_templ.go | 8 +- templates/docs_templ.go | 30 +++---- templates/error_templ.go | 4 +- templates/filter_item_templ.go | 4 +- templates/header_templ.go | 2 +- templates/profile_form_templ.go | 4 +- templates/profile_modal_templ.go | 4 +- templates/progress_templ.go | 28 +++---- templates/queue_templ.go | 26 +++--- templates/reader_templ.go | 20 ++--- templates/unlinked_books_templ.go | 14 ++-- 26 files changed, 257 insertions(+), 177 deletions(-) diff --git a/templates/admin_library_templ.go b/templates/admin_library_templ.go index 3ea24b6..27d8715 100644 --- a/templates/admin_library_templ.go +++ b/templates/admin_library_templ.go @@ -63,7 +63,7 @@ func AdminLibrary(user User, libraries []LibraryData, users []User) templ.Compon var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(library.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_library.templ`, Line: 44, Col: 89} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 44, Col: 89} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -81,7 +81,7 @@ func AdminLibrary(user User, libraries []LibraryData, users []User) templ.Compon var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(library.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_library.templ`, Line: 46, Col: 92} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 46, Col: 92} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -99,7 +99,7 @@ func AdminLibrary(user User, libraries []LibraryData, users []User) templ.Compon var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(library.TypeName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_library.templ`, Line: 49, Col: 33} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 49, Col: 33} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -112,7 +112,7 @@ func AdminLibrary(user User, libraries []LibraryData, users []User) templ.Compon var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(library.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_library.templ`, Line: 53, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 53, Col: 50} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -125,7 +125,7 @@ func AdminLibrary(user User, libraries []LibraryData, users []User) templ.Compon var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(library.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_library.templ`, Line: 54, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 54, Col: 50} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -138,7 +138,7 @@ func AdminLibrary(user User, libraries []LibraryData, users []User) templ.Compon var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(library.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_library.templ`, Line: 55, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 55, Col: 50} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -151,7 +151,7 @@ func AdminLibrary(user User, libraries []LibraryData, users []User) templ.Compon var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs("library-folders-" + library.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_library.templ`, Line: 58, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 58, Col: 53} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -175,7 +175,7 @@ func AdminLibrary(user User, libraries []LibraryData, users []User) templ.Compon var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_library.templ`, Line: 81, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 81, Col: 53} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -188,7 +188,7 @@ func AdminLibrary(user User, libraries []LibraryData, users []User) templ.Compon var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_library.templ`, Line: 81, Col: 69} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 81, Col: 69} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { diff --git a/templates/admin_processing_issues_templ.go b/templates/admin_processing_issues_templ.go index f1ad199..066f7bb 100644 --- a/templates/admin_processing_issues_templ.go +++ b/templates/admin_processing_issues_templ.go @@ -55,7 +55,7 @@ func AdminProcessingIssues(user User, libraryID string, issues []ProcessingIssue var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(stats.ErrorCount) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_processing_issues.templ`, Line: 32, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_processing_issues.templ`, Line: 32, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -74,7 +74,7 @@ func AdminProcessingIssues(user User, libraryID string, issues []ProcessingIssue var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(stats.WarningCount) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_processing_issues.templ`, Line: 38, Col: 58} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_processing_issues.templ`, Line: 38, Col: 58} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -93,7 +93,7 @@ func AdminProcessingIssues(user User, libraryID string, issues []ProcessingIssue var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(stats.InfoCount) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_processing_issues.templ`, Line: 44, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_processing_issues.templ`, Line: 44, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -127,7 +127,7 @@ func AdminProcessingIssues(user User, libraryID string, issues []ProcessingIssue var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(issue.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_processing_issues.templ`, Line: 60, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_processing_issues.templ`, Line: 60, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -140,7 +140,7 @@ func AdminProcessingIssues(user User, libraryID string, issues []ProcessingIssue var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(issue.IssueDescription) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_processing_issues.templ`, Line: 61, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_processing_issues.templ`, Line: 61, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -153,7 +153,7 @@ func AdminProcessingIssues(user User, libraryID string, issues []ProcessingIssue var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(issue.IssueType) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_processing_issues.templ`, Line: 63, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_processing_issues.templ`, Line: 63, Col: 54} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -166,7 +166,7 @@ func AdminProcessingIssues(user User, libraryID string, issues []ProcessingIssue var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(issue.FormatGroup) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_processing_issues.templ`, Line: 64, Col: 58} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_processing_issues.templ`, Line: 64, Col: 58} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -179,7 +179,7 @@ func AdminProcessingIssues(user User, libraryID string, issues []ProcessingIssue var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(issue.FilePath) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_processing_issues.templ`, Line: 65, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_processing_issues.templ`, Line: 65, Col: 53} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -192,7 +192,7 @@ func AdminProcessingIssues(user User, libraryID string, issues []ProcessingIssue var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(issue.LibraryTypeName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_processing_issues.templ`, Line: 66, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_processing_issues.templ`, Line: 66, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -205,7 +205,7 @@ func AdminProcessingIssues(user User, libraryID string, issues []ProcessingIssue var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(issue.Severity) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_processing_issues.templ`, Line: 74, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_processing_issues.templ`, Line: 74, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { diff --git a/templates/admin_settings_templ.go b/templates/admin_settings_templ.go index 2bdce13..6afc9e6 100644 --- a/templates/admin_settings_templ.go +++ b/templates/admin_settings_templ.go @@ -81,46 +81,126 @@ func AdminSettings(user User, systemConfig map[string]string, errorMessage strin if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" placeholder=\"https://books.example.com\" class=\"w-full px-4 py-2 rounded-lg border\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" required>

The public URL of your Bookhoard instance (e.g., https://books.example.com). Used for device sync, OPDS, and API endpoints.

System Defaults

URL Paths

OPDS: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" placeholder=\"https://books.example.com\" class=\"w-full px-4 py-2 rounded-lg border\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" required>

The public URL of your Bookhoard instance (e.g., https://books.example.com). Used for device sync, OPDS, and API endpoints.

System Defaults

Default timezone for users who haven't set their own.

URL Paths

OPDS: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(systemConfig["base_url"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 72, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 80, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "/opds

API: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "/opds

API: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(systemConfig["base_url"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 73, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 81, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "/api

Device Sync: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "/api

Device Sync: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(systemConfig["base_url"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 74, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 82, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "/api/sync

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "/api/sync

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/admin_sidebar_templ.go b/templates/admin_sidebar_templ.go index 1142d81..5e14651 100644 --- a/templates/admin_sidebar_templ.go +++ b/templates/admin_sidebar_templ.go @@ -45,7 +45,7 @@ func AdminSidebar(user User, currentPath string) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_sidebar.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_sidebar.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -67,7 +67,7 @@ func AdminSidebar(user User, currentPath string) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_sidebar.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_sidebar.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -89,7 +89,7 @@ func AdminSidebar(user User, currentPath string) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_sidebar.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_sidebar.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -111,7 +111,7 @@ func AdminSidebar(user User, currentPath string) templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_sidebar.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_sidebar.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { diff --git a/templates/admin_users_templ.go b/templates/admin_users_templ.go index 8bfff80..9da31c8 100644 --- a/templates/admin_users_templ.go +++ b/templates/admin_users_templ.go @@ -178,9 +178,9 @@ func AdminUsers(currentUser User, users []User, adminCount int) templ.Component return templ_7745c5c3_Err } var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(user.CreatedAt.Format("01-02-2006")) + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(FormatInTimezone(user.CreatedAt, currentUser.Timezone)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_users.templ`, Line: 89, Col: 106} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_users.templ`, Line: 89, Col: 125} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { diff --git a/templates/api_explorer_templ.go b/templates/api_explorer_templ.go index 4df685e..e0d8d52 100644 --- a/templates/api_explorer_templ.go +++ b/templates/api_explorer_templ.go @@ -77,7 +77,7 @@ func APIExplorer(explorer APIExplorerData) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(explorer.Endpoint.RequestBody) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/api_explorer.templ`, Line: 21, Col: 141} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `api_explorer.templ`, Line: 21, Col: 141} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -90,7 +90,7 @@ func APIExplorer(explorer APIExplorerData) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(explorer.Endpoint.Response) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/api_explorer.templ`, Line: 25, Col: 138} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `api_explorer.templ`, Line: 25, Col: 138} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -148,7 +148,7 @@ func APIExplorer(explorer APIExplorerData) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(explorer.Endpoint.RequestBody) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/api_explorer.templ`, Line: 49, Col: 274} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `api_explorer.templ`, Line: 49, Col: 274} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/templates/book_detail_modals_templ.go b/templates/book_detail_modals_templ.go index b7fcdb8..5493c5a 100644 --- a/templates/book_detail_modals_templ.go +++ b/templates/book_detail_modals_templ.go @@ -14,7 +14,7 @@ import ( ) // ProgressSyncModal shows progress from all devices for manual review -func ProgressSyncModal(book handlers.MediaDetail) templ.Component { +func ProgressSyncModal(user User, book handlers.MediaDetail) 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 { @@ -76,7 +76,7 @@ func ProgressSyncModal(book handlers.MediaDetail) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Timestamp.Format("01-02-2006 03:04 PM:05")) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(FormatInTimezone(data.Timestamp, user.Timezone)) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 68, Col: 59} } @@ -243,9 +243,9 @@ func ProgressSyncModal(book handlers.MediaDetail) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.LastReadAt.Time.Format("01-02-2006 03:04 PM")) + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(FormatTimestamptzInTimezone(book.ReadingProgress.LastReadAt, user.Timezone)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 113, Col: 86} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 113, Col: 95} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { diff --git a/templates/book_detail_templ.go b/templates/book_detail_templ.go index daa8e56..41f00d7 100644 --- a/templates/book_detail_templ.go +++ b/templates/book_detail_templ.go @@ -499,9 +499,9 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string) templ return templ_7745c5c3_Err } var templ_7745c5c3_Var21 string - templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(book.ReadingProgress.LastReadAt.Time.Format("01-02-2006 03:04 PM")) + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(FormatTimestamptzInTimezone(book.ReadingProgress.LastReadAt, user.Timezone)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail.templ`, Line: 253, Col: 80} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail.templ`, Line: 253, Col: 89} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -1126,7 +1126,7 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string) templ if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = ProgressSyncModal(book).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = ProgressSyncModal(user, book).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/bookshelf_templ.go b/templates/bookshelf_templ.go index 2928b38..977c092 100644 --- a/templates/bookshelf_templ.go +++ b/templates/bookshelf_templ.go @@ -71,7 +71,7 @@ func BookShelf( var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(lib.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 58, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 58, Col: 34} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -84,7 +84,7 @@ func BookShelf( var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(lib.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 58, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 58, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -102,7 +102,7 @@ func BookShelf( var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(lib.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 60, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 60, Col: 34} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -115,7 +115,7 @@ func BookShelf( var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(lib.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 60, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 60, Col: 47} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -140,7 +140,7 @@ func BookShelf( var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(uuidToString(filter.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 277, Col: 173} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 277, Col: 173} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -153,7 +153,7 @@ func BookShelf( var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(filter.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 279, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 279, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -194,7 +194,7 @@ func BookShelf( var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(errorMessage) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 313, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 313, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -227,7 +227,7 @@ func BookShelf( var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(offset/limit + 1) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/bookshelf.templ`, Line: 332, Col: 32} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `bookshelf.templ`, Line: 332, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { diff --git a/templates/collection_modal_templ.go b/templates/collection_modal_templ.go index 7473533..0037498 100644 --- a/templates/collection_modal_templ.go +++ b/templates/collection_modal_templ.go @@ -56,7 +56,7 @@ func CollectionModal(collection CollectionData) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/api/collections/" + collection.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_modal.templ`, Line: 16, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collection_modal.templ`, Line: 16, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -69,7 +69,7 @@ func CollectionModal(collection CollectionData) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(collection.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_modal.templ`, Line: 19, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collection_modal.templ`, Line: 19, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -82,7 +82,7 @@ func CollectionModal(collection CollectionData) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_modal.templ`, Line: 25, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collection_modal.templ`, Line: 25, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -95,7 +95,7 @@ func CollectionModal(collection CollectionData) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_modal.templ`, Line: 40, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collection_modal.templ`, Line: 40, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -108,7 +108,7 @@ func CollectionModal(collection CollectionData) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_modal.templ`, Line: 56, Col: 83} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collection_modal.templ`, Line: 56, Col: 83} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -121,7 +121,7 @@ func CollectionModal(collection CollectionData) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Color) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_modal.templ`, Line: 75, Col: 86} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collection_modal.templ`, Line: 75, Col: 86} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -139,7 +139,7 @@ func CollectionModal(collection CollectionData) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_modal.templ`, Line: 122, Col: 83} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collection_modal.templ`, Line: 122, Col: 83} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { diff --git a/templates/collection_rules_templ.go b/templates/collection_rules_templ.go index 4a7c016..7e2dec8 100644 --- a/templates/collection_rules_templ.go +++ b/templates/collection_rules_templ.go @@ -36,7 +36,7 @@ func CollectionRules(user User, collection CollectionData) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 9, Col: 46} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collection_rules.templ`, Line: 9, Col: 46} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -57,7 +57,7 @@ func CollectionRules(user User, collection CollectionData) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 21, Col: 81} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collection_rules.templ`, Line: 21, Col: 81} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -70,7 +70,7 @@ func CollectionRules(user User, collection CollectionData) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 23, Col: 90} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collection_rules.templ`, Line: 23, Col: 90} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/templates/collections_templ.go b/templates/collections_templ.go index a3dce1e..ac7e13f 100644 --- a/templates/collections_templ.go +++ b/templates/collections_templ.go @@ -57,7 +57,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 62, Col: 82} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 62, Col: 82} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -70,7 +70,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(col.Color) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 66, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 66, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -83,7 +83,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(col.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 69, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 69, Col: 41} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -96,7 +96,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID + "/edit-modal") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 72, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 72, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -109,7 +109,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("/api/collections/" + col.ID + "") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 81, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 81, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -122,7 +122,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(col.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 91, Col: 92} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 91, Col: 92} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -135,7 +135,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(col.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 92, Col: 86} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 92, Col: 86} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -190,7 +190,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 109, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 109, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -211,7 +211,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 121, Col: 81} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 121, Col: 81} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -224,7 +224,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 123, Col: 90} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 123, Col: 90} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -237,7 +237,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 124, Col: 71} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 124, Col: 71} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -261,7 +261,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var14 templ.SafeURL templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinURLErrs("/media/" + book.MediaItemID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 166, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 166, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -274,7 +274,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(book.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 185, Col: 23} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 185, Col: 23} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -292,7 +292,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(book.Author) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 192, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 192, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -315,7 +315,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(book.CoverImagePath) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 200, Col: 37} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 200, Col: 37} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { diff --git a/templates/conflicts_templ.go b/templates/conflicts_templ.go index 5a248bb..f65859a 100644 --- a/templates/conflicts_templ.go +++ b/templates/conflicts_templ.go @@ -120,7 +120,7 @@ func Conflicts(user User, conflicts []handlers.ConflictDetailResponse, total int return templ_7745c5c3_Err } var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(conflict.CreatedAt.Format("01-02-2006 03:04:05 PM")) + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(FormatInTimezone(conflict.CreatedAt, user.Timezone)) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/conflicts.templ`, Line: 114, Col: 70} } diff --git a/templates/custom_section_templ.go b/templates/custom_section_templ.go index a309c63..c84683f 100644 --- a/templates/custom_section_templ.go +++ b/templates/custom_section_templ.go @@ -49,7 +49,7 @@ func CustomSectionBuilder(user User, libraries []LibraryData, errorMessage strin var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(lib.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/custom_section.templ`, Line: 68, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `custom_section.templ`, Line: 68, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -62,7 +62,7 @@ func CustomSectionBuilder(user User, libraries []LibraryData, errorMessage strin var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(lib.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/custom_section.templ`, Line: 68, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `custom_section.templ`, Line: 68, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { diff --git a/templates/dashboard_templ.go b/templates/dashboard_templ.go index 6540089..e6f788e 100644 --- a/templates/dashboard_templ.go +++ b/templates/dashboard_templ.go @@ -55,7 +55,7 @@ func Dashboard(user User, sections []handlers.SectionData, allSections []handler var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(lib.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 33, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 33, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -68,7 +68,7 @@ func Dashboard(user User, sections []handlers.SectionData, allSections []handler var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(lib.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 33, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 33, Col: 53} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -86,7 +86,7 @@ func Dashboard(user User, sections []handlers.SectionData, allSections []handler var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(lib.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 35, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 35, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -99,7 +99,7 @@ func Dashboard(user User, sections []handlers.SectionData, allSections []handler var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(lib.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 35, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 35, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -169,7 +169,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 83, Col: 33} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 83, Col: 33} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -182,7 +182,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(section.IsSystem) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 84, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 84, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -195,7 +195,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(section.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 89, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 89, Col: 41} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -208,7 +208,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component { var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(section.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 91, Col: 85} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 91, Col: 85} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -226,7 +226,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component { var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(section.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 93, Col: 83} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 93, Col: 83} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -249,7 +249,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component { var templ_7745c5c3_Var12 templ.SafeURL templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(section.ViewAllURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 99, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 99, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -267,7 +267,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 115, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 115, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -280,7 +280,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs("carousel-track-" + section.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 122, Col: 39} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 122, Col: 39} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -309,7 +309,7 @@ func CollectionCarousel(section handlers.SectionData) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 143, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 143, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -351,7 +351,7 @@ func BookCard(item handlers.BookInfo) templ.Component { var templ_7745c5c3_Var17 templ.SafeURL templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinURLErrs("/media/" + item.MediaItemID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 154, Col: 39} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 154, Col: 39} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -364,7 +364,7 @@ func BookCard(item handlers.BookInfo) templ.Component { var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("View " + item.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 160, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 160, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -382,7 +382,7 @@ func BookCard(item handlers.BookInfo) templ.Component { var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(item.CoverImagePath) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 168, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 168, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -395,7 +395,7 @@ func BookCard(item handlers.BookInfo) templ.Component { var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 169, Col: 22} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 169, Col: 22} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { @@ -413,7 +413,7 @@ func BookCard(item handlers.BookInfo) templ.Component { var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 177, Col: 22} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 177, Col: 22} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -431,7 +431,7 @@ func BookCard(item handlers.BookInfo) templ.Component { var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 184, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 184, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { @@ -449,7 +449,7 @@ func BookCard(item handlers.BookInfo) templ.Component { var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(item.Author) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 188, Col: 19} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 188, Col: 19} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { @@ -501,7 +501,7 @@ func DashboardSettingsModal(sections []handlers.SectionData, hiddenCollections [ var templ_7745c5c3_Var25 string templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 224, Col: 37} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 224, Col: 37} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { @@ -514,7 +514,7 @@ func DashboardSettingsModal(sections []handlers.SectionData, hiddenCollections [ var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", section.IsSystem)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 225, Col: 58} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 225, Col: 58} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { @@ -527,7 +527,7 @@ func DashboardSettingsModal(sections []handlers.SectionData, hiddenCollections [ var templ_7745c5c3_Var27 string templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(section.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 231, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 231, Col: 43} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { @@ -540,7 +540,7 @@ func DashboardSettingsModal(sections []handlers.SectionData, hiddenCollections [ var templ_7745c5c3_Var28 string templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(section.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 233, Col: 85} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 233, Col: 85} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { @@ -568,7 +568,7 @@ func DashboardSettingsModal(sections []handlers.SectionData, hiddenCollections [ var templ_7745c5c3_Var29 string templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(section.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 243, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 243, Col: 42} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) if templ_7745c5c3_Err != nil { @@ -601,7 +601,7 @@ func DashboardSettingsModal(sections []handlers.SectionData, hiddenCollections [ var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(itemsPerSection) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 274, Col: 93} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 274, Col: 93} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { @@ -614,7 +614,7 @@ func DashboardSettingsModal(sections []handlers.SectionData, hiddenCollections [ var templ_7745c5c3_Var31 string templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(itemsPerSection) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 281, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard.templ`, Line: 281, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { diff --git a/templates/devices_templ.go b/templates/devices_templ.go index 2b4d0b8..5e11d93 100644 --- a/templates/devices_templ.go +++ b/templates/devices_templ.go @@ -135,9 +135,9 @@ func Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []Pe return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(device.LastSync.Format("01-02-2006 03:04 PM")) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(FormatInTimezone(*device.LastSync, user.Timezone)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/devices.templ`, Line: 83, Col: 100} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/devices.templ`, Line: 83, Col: 104} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -163,9 +163,9 @@ func Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []Pe return templ_7745c5c3_Err } var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(device.LastSeen.Format("01-02-2006 03:04 PM")) + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(FormatInTimezone(*device.LastSeen, user.Timezone)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/devices.templ`, Line: 91, Col: 100} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/devices.templ`, Line: 91, Col: 104} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { diff --git a/templates/docs_templ.go b/templates/docs_templ.go index a207ca5..912da32 100644 --- a/templates/docs_templ.go +++ b/templates/docs_templ.go @@ -38,7 +38,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(doc.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 11, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 11, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -60,7 +60,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 1, Col: 0} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 1, Col: 0} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -86,7 +86,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(section.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 47, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 47, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -109,7 +109,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var6 templ.SafeURL templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinURLErrs(item.URL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 55, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 55, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -122,7 +122,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(item.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 56, Col: 40} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 56, Col: 40} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -135,7 +135,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 56, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 56, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -163,7 +163,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var9 templ.SafeURL templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinURLErrs(item.URL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 63, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 63, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -176,7 +176,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(item.Icon) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 64, Col: 40} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 64, Col: 40} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -189,7 +189,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 64, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 64, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -233,7 +233,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var12 templ.SafeURL templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(crumb.URL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 81, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 81, Col: 26} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -246,7 +246,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(crumb.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 81, Col: 91} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 81, Col: 91} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -269,7 +269,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(doc.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 86, Col: 69} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 86, Col: 69} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -292,7 +292,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var15 templ.SafeURL templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinURLErrs("#" + item.Anchor) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 96, Col: 32} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 96, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -305,7 +305,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("margin-left: " + fmt.Sprintf("%drem", item.Level)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 98, Col: 66} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 98, Col: 66} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -318,7 +318,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/docs.templ`, Line: 100, Col: 20} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `docs.templ`, Line: 100, Col: 20} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { diff --git a/templates/error_templ.go b/templates/error_templ.go index 6bfa573..956b94f 100644 --- a/templates/error_templ.go +++ b/templates/error_templ.go @@ -36,7 +36,7 @@ func ErrorPage(message string, errorType string) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(message) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/error.templ`, Line: 83, Col: 37} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `error.templ`, Line: 83, Col: 37} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -49,7 +49,7 @@ func ErrorPage(message string, errorType string) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(errorType) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/error.templ`, Line: 89, Col: 75} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `error.templ`, Line: 89, Col: 75} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { diff --git a/templates/filter_item_templ.go b/templates/filter_item_templ.go index f1b7b86..38ec7a8 100644 --- a/templates/filter_item_templ.go +++ b/templates/filter_item_templ.go @@ -37,7 +37,7 @@ func FilterItem(id string, name string) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(id) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/filter_item.templ`, Line: 8, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `filter_item.templ`, Line: 8, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -50,7 +50,7 @@ func FilterItem(id string, name string) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/filter_item.templ`, Line: 16, Col: 9} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `filter_item.templ`, Line: 16, Col: 9} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { diff --git a/templates/header_templ.go b/templates/header_templ.go index f4d22d1..bad3391 100644 --- a/templates/header_templ.go +++ b/templates/header_templ.go @@ -41,7 +41,7 @@ func Header(user User, currentPath string) templ.Component { var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/header.templ`, Line: 211, Col: 96} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `header.templ`, Line: 211, Col: 96} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { diff --git a/templates/profile_form_templ.go b/templates/profile_form_templ.go index 5753f5f..65a6d66 100644 --- a/templates/profile_form_templ.go +++ b/templates/profile_form_templ.go @@ -164,7 +164,7 @@ func ProfileForm(user User, actionURL string, requireCurrentPassword bool, showR return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, ">Material Dark
- - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + +

Default timezone for users who haven't set their own.

@@ -494,13 +510,29 @@ func (h *SidecarHandler) UpdateSystemConfiguration(c *echo.Context) error { `, baseURL.Value, selectedAttr(defaultTimezone, "UTC"), - selectedAttr(defaultTimezone, "America/New_York"), - selectedAttr(defaultTimezone, "America/Chicago"), - selectedAttr(defaultTimezone, "America/Denver"), - selectedAttr(defaultTimezone, "America/Los_Angeles"), - selectedAttr(defaultTimezone, "America/Phoenix"), - selectedAttr(defaultTimezone, "America/Anchorage"), selectedAttr(defaultTimezone, "Pacific/Honolulu"), + selectedAttr(defaultTimezone, "America/Anchorage"), + selectedAttr(defaultTimezone, "America/Los_Angeles"), + selectedAttr(defaultTimezone, "America/Denver"), + selectedAttr(defaultTimezone, "America/Phoenix"), + selectedAttr(defaultTimezone, "America/Chicago"), + selectedAttr(defaultTimezone, "America/New_York"), + selectedAttr(defaultTimezone, "America/Sao_Paulo"), + selectedAttr(defaultTimezone, "Europe/London"), + selectedAttr(defaultTimezone, "Europe/Paris"), + selectedAttr(defaultTimezone, "Europe/Helsinki"), + selectedAttr(defaultTimezone, "Europe/Moscow"), + selectedAttr(defaultTimezone, "Asia/Tehran"), + selectedAttr(defaultTimezone, "Asia/Dubai"), + selectedAttr(defaultTimezone, "Asia/Karachi"), + selectedAttr(defaultTimezone, "Asia/Kolkata"), + selectedAttr(defaultTimezone, "Asia/Dhaka"), + selectedAttr(defaultTimezone, "Asia/Bangkok"), + selectedAttr(defaultTimezone, "Asia/Shanghai"), + selectedAttr(defaultTimezone, "Asia/Tokyo"), + selectedAttr(defaultTimezone, "Australia/Darwin"), + selectedAttr(defaultTimezone, "Australia/Sydney"), + selectedAttr(defaultTimezone, "Pacific/Auckland"), baseURL.Value, baseURL.Value, baseURL.Value)) } diff --git a/templates/admin_settings.templ b/templates/admin_settings.templ index f9d254c..feacf7d 100644 --- a/templates/admin_settings.templ +++ b/templates/admin_settings.templ @@ -61,14 +61,30 @@ templ AdminSettings(user User, systemConfig map[string]string, errorMessage stri class="w-full px-4 py-2 rounded-lg border" style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);" > - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + +

Default timezone for users who haven't set their own.

diff --git a/templates/admin_settings_templ.go b/templates/admin_settings_templ.go index 6afc9e6..a41d2b4 100644 --- a/templates/admin_settings_templ.go +++ b/templates/admin_settings_templ.go @@ -91,47 +91,47 @@ func AdminSettings(user User, systemConfig map[string]string, errorMessage strin return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, ">UTC (Coordinated Universal Time)

Default timezone for users who haven't set their own.

URL Paths

OPDS: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, ">Eastern (UTC-5/-4)

Default timezone for users who haven't set their own.

URL Paths

OPDS: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(systemConfig["base_url"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 80, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 96, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "/opds

API: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "/opds

API: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(systemConfig["base_url"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 81, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 97, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "/api

Device Sync: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "/api

Device Sync: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(systemConfig["base_url"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 82, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/admin_settings.templ`, Line: 98, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "/api/sync

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "/api/sync

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/profile_form.templ b/templates/profile_form.templ index b9a8eb9..fe00f1e 100644 --- a/templates/profile_form.templ +++ b/templates/profile_form.templ @@ -77,14 +77,30 @@ templ ProfileForm(user User, actionURL string, requireCurrentPassword bool, show class="w-full px-3 py-2 border rounded" style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);" > - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + if showRoleField { diff --git a/templates/profile_form_templ.go b/templates/profile_form_templ.go index 65a6d66..f6aae65 100644 --- a/templates/profile_form_templ.go +++ b/templates/profile_form_templ.go @@ -174,47 +174,47 @@ func ProfileForm(user User, actionURL string, requireCurrentPassword bool, showR return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, ">UTC (Coordinated Universal Time) ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, ">Eastern (UTC-5/-4) ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if showRoleField { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, ">Admin") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "

Change Password

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "

Change Password

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if requireCurrentPassword { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "

As an admin, you can change this user's password without knowing their current password.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "\" hx-headers='{\"Authorization\": \"Bearer \" + localStorage.getItem(\"token\")}' hx-target=\"#password-result\" hx-swap=\"innerHTML\" hx-include=\"closest form\" class=\"px-4 py-2 rounded\" style=\"background-color: var(--accent); color: white;\">Reset Password
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if showCancelButton { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }