From 8e28dec20bb224248e82d36368e0efe446217a38 Mon Sep 17 00:00:00 2001 From: SG Command Date: Mon, 24 Aug 2026 09:35:49 +0200 Subject: [PATCH] fix(ui): stabilize theme colors and calendar end time --- PLAN.md | 2 +- README.md | 2 +- config.example.yaml | 1 + server/internal/api/handlers.go | 1 + server/internal/config/config.go | 4 ++++ ui/src/lib/api.ts | 1 + ui/src/lib/config.ts | 1 + ui/src/lib/wedding-info.svelte.ts | 6 ++++++ ui/src/routes/+layout.svelte | 2 +- ui/src/routes/details/+page.svelte | 2 +- 10 files changed, 18 insertions(+), 4 deletions(-) diff --git a/PLAN.md b/PLAN.md index 28d663c..4e7d2a5 100644 --- a/PLAN.md +++ b/PLAN.md @@ -52,7 +52,7 @@ RSVP is a party: one or more guest names attached to a single RSVP + optional sh | Method | Path | Body | Notes | |---|---|---|---| | POST | `/api/rsvp` | `{names: string[], message?}` | at least one non-blank name required; insert party + guests, email couple | -| GET | `/api/config` | — | `{weddingDate, ceremonyTime, rsvpByDate, venueName, venueAddress, rsvpPhone}` (all RFC3339 where dates) — single source of truth for countdowns, gate, and displayed details | +| GET | `/api/config` | — | `{weddingDate, eventEnd, ceremonyTime, rsvpByDate, venueName, venueAddress, rsvpPhone}` (all RFC3339 where dates) — single source of truth for countdowns, calendar, gate, and displayed details | | POST | `/api/photos/upload` | multipart: `name, message?, email?, phone?, files[]` | rejected with 403 if `now < weddingDate`, **enforced server-side** regardless of client clock | | GET | `/healthz` | — | liveness | diff --git a/README.md b/README.md index 865c3f8..1a76501 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ docker-compose.yml wedding app, published to 127.0.0.1:8080 only ``` ## Config -Copy `config.example.yaml` to `config.yaml` and fill in the wedding date, SMTP settings, storage path, and (if enabled) the Origin service key. Path is resolved from `CONFIG_PATH` (default `./config.yaml`) — that's the only env var; everything else lives in the file. +Copy `config.example.yaml` to `config.yaml` and fill in the wedding date, event end time, SMTP settings, storage path, and (if enabled) the Origin service key. `wedding.event_end` is the explicit local RFC3339 end time used for calendar downloads. Path is resolved from `CONFIG_PATH` (default `./config.yaml`) — that's the only env var; everything else lives in the file. When `origin.service_key` is configured, the server registers itself with Origin as the `generic` service type named `ZoeShaldon` immediately at startup and repeats the check-in every 24 hours. Registration failures are logged without taking down the wedding site. Leave the key empty to disable registration. diff --git a/config.example.yaml b/config.example.yaml index 877b0a5..ded75ab 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -11,6 +11,7 @@ site: wedding: date: "2026-11-07T16:00:00+02:00" # RFC3339, guest arrival time; drives countdown + photo upload gate + event_end: "2026-11-07T23:30:00+02:00" # RFC3339, local end time used by calendar downloads ceremony_time: "16:00 for 16:30" # display text rsvp_by: "2026-10-01T08:00:00+02:00" # RFC3339, RSVP deadline; drives the RSVP countdown venue_name: "Rivier Plaas Wedding Venue" diff --git a/server/internal/api/handlers.go b/server/internal/api/handlers.go index 98a9773..c39ee21 100644 --- a/server/internal/api/handlers.go +++ b/server/internal/api/handlers.go @@ -58,6 +58,7 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]string{ "siteUrl": s.cfg.Site.URL, "weddingDate": s.cfg.Wedding.Date.Format(time.RFC3339), + "eventEnd": s.cfg.Wedding.EventEnd.Format(time.RFC3339), "ceremonyTime": s.cfg.Wedding.CeremonyTime, "rsvpByDate": s.cfg.Wedding.RSVPBy.Format(time.RFC3339), "venueName": s.cfg.Wedding.VenueName, diff --git a/server/internal/config/config.go b/server/internal/config/config.go index a44b513..fc91460 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -35,6 +35,7 @@ type SiteConfig struct { type WeddingConfig struct { Date time.Time `yaml:"date"` + EventEnd time.Time `yaml:"event_end"` CeremonyTime string `yaml:"ceremony_time"` RSVPBy time.Time `yaml:"rsvp_by"` VenueName string `yaml:"venue_name"` @@ -112,6 +113,9 @@ func Load(path string) (*Config, error) { if cfg.Wedding.RSVPBy.IsZero() { return nil, fmt.Errorf("wedding.rsvp_by is required in %s", path) } + if cfg.Wedding.EventEnd.IsZero() { + return nil, fmt.Errorf("wedding.event_end is required in %s", path) + } return &cfg, nil } diff --git a/ui/src/lib/api.ts b/ui/src/lib/api.ts index bfe0141..a03569f 100644 --- a/ui/src/lib/api.ts +++ b/ui/src/lib/api.ts @@ -1,6 +1,7 @@ export interface WeddingConfig { siteUrl: string; weddingDate: string; + eventEnd: string; ceremonyTime: string; rsvpByDate: string; venueName: string; diff --git a/ui/src/lib/config.ts b/ui/src/lib/config.ts index fe4e7ad..824797a 100644 --- a/ui/src/lib/config.ts +++ b/ui/src/lib/config.ts @@ -2,6 +2,7 @@ // fallback while /api/config loads (and when developing the UI without the Go server). export const FALLBACK_SITE_URL = "https://zoeshaldon.warky.info"; export const FALLBACK_WEDDING_DATE = "2026-11-07T16:00:00+02:00"; +export const FALLBACK_EVENT_END = "2026-11-07T23:30:00+02:00"; export const FALLBACK_CEREMONY_TIME = "16:00 for 16:30"; export const FALLBACK_RSVP_BY_DATE = "2026-10-17T00:00:00+02:00"; export const FALLBACK_VENUE_NAME = "Rivier Plaas Wedding Venue"; diff --git a/ui/src/lib/wedding-info.svelte.ts b/ui/src/lib/wedding-info.svelte.ts index a6d7da9..a54880c 100644 --- a/ui/src/lib/wedding-info.svelte.ts +++ b/ui/src/lib/wedding-info.svelte.ts @@ -3,6 +3,7 @@ import { fetchConfig } from './api'; import { FALLBACK_SITE_URL, FALLBACK_WEDDING_DATE, + FALLBACK_EVENT_END, FALLBACK_CEREMONY_TIME, FALLBACK_RSVP_BY_DATE, FALLBACK_VENUE_NAME, @@ -13,6 +14,7 @@ import { export function createWeddingInfo() { let siteUrl = $state(FALLBACK_SITE_URL); let date = $state(new Date(FALLBACK_WEDDING_DATE)); + let eventEnd = $state(new Date(FALLBACK_EVENT_END)); let ceremonyTime = $state(FALLBACK_CEREMONY_TIME); let rsvpByDate = $state(new Date(FALLBACK_RSVP_BY_DATE)); let venueName = $state(FALLBACK_VENUE_NAME); @@ -25,6 +27,7 @@ export function createWeddingInfo() { .then((config) => { siteUrl = config.siteUrl; date = new Date(config.weddingDate); + eventEnd = new Date(config.eventEnd); ceremonyTime = config.ceremonyTime; rsvpByDate = new Date(config.rsvpByDate); venueName = config.venueName; @@ -46,6 +49,9 @@ export function createWeddingInfo() { get ceremonyTime() { return ceremonyTime; }, + get eventEnd() { + return eventEnd; + }, get rsvpByDate() { return rsvpByDate; }, diff --git a/ui/src/routes/+layout.svelte b/ui/src/routes/+layout.svelte index 75334d6..270eea9 100644 --- a/ui/src/routes/+layout.svelte +++ b/ui/src/routes/+layout.svelte @@ -128,7 +128,7 @@ {/if}
{@render children()}
diff --git a/ui/src/routes/details/+page.svelte b/ui/src/routes/details/+page.svelte index 8450e15..6024b99 100644 --- a/ui/src/routes/details/+page.svelte +++ b/ui/src/routes/details/+page.svelte @@ -8,7 +8,7 @@ const icsUrl = $derived( buildIcsDataUrl({ start: wedding.date, - end: new Date(wedding.date.getTime() + 7.5 * 60 * 60 * 1000), + end: wedding.eventEnd, summary: "Zoé & Shaldon's Wedding", location: `${wedding.venueName}, ${wedding.venueAddress}`, description: `Join us as we say I do! ${wedding.ceremonyTime}.`,