Compare commits
2 commits
6eb72c5091
...
4d3da023fc
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d3da023fc | |||
| a60ef7d25b |
4 changed files with 137 additions and 68 deletions
25
README.md
25
README.md
|
|
@ -1,21 +1,21 @@
|
||||||
# Turnpike
|
# Turnpike
|
||||||
|
|
||||||
Self-hosted event attendee and volunteer management. One instance, one event.
|
Self-hosted event ticketing and volunteer management. One instance, one event.
|
||||||
|
|
||||||
Turnpike handles gate check-in, volunteer scheduling, and department coordination for events ranging from a single evening to a multi-day festival. It works offline — gate volunteers can check people in without a network connection and sync when connectivity returns.
|
Turnpike handles gate check-in, volunteer scheduling, and department coordination for events ranging from a single evening to a multi-day festival. It works offline — gate volunteers can check people in without a network connection and sync when connectivity returns.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Attendee management** — CSV import (CrowdWork/Zeffy auto-detected), party-size tracking, search, check-in
|
- **Participant & ticket management** — CSV import (CrowdWork/Zeffy auto-detected), search, check-in
|
||||||
- **Volunteer scheduling** — departments, shifts with capacity, conflict detection, reordering
|
- **Volunteer scheduling** — departments, shifts with capacity, conflict detection, reordering
|
||||||
- **Public volunteer signup** — self-registration form with email confirmation, auto-attendee linking
|
- **Public volunteer signup** — self-registration form with email confirmation, auto-participant linking
|
||||||
- **Volunteer kiosk** — token-authenticated self-service shift signup, no login required
|
- **Volunteer kiosk** — code-authenticated self-service shift signup, no login required
|
||||||
- **Gate check-in** — full-screen UI with QR scanner, party check-in ("2/3 checked in"), volunteer dual check-in
|
- **Gate check-in** — full-screen UI with QR scanner, volunteer dual check-in
|
||||||
- **Schedule** — create shifts, assign volunteers, manage assignments with conflict awareness
|
- **Schedule** — create shifts, assign volunteers, manage assignments with conflict awareness
|
||||||
- **Role-based access** — admin, coordinator, volunteer lead (department-scoped), gate
|
- **Role-based access** — admin, ticketing, staffing, colead (department-scoped), gatekeeper
|
||||||
- **Offline-first PWA** — installs on phones/tablets, full offline check-in with background sync
|
- **Offline-first PWA** — installs on phones/tablets, full offline check-in with background sync
|
||||||
- **Real-time** — check-ins and changes broadcast live via SSE
|
- **Real-time** — check-ins and changes broadcast live via SSE
|
||||||
- **SMTP email** — send volunteer token links directly or export CSV for bulk email platforms
|
- **SMTP email** — volunteer confirmation emails, kiosk link distribution when shift signups open
|
||||||
- **Single binary** — Go backend embeds the frontend; no runtime dependencies
|
- **Single binary** — Go backend embeds the frontend; no runtime dependencies
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
@ -60,10 +60,11 @@ See [docs/INSTALLATION.md](docs/INSTALLATION.md) for systemd, Docker, NixOS, and
|
||||||
|
|
||||||
| Role | Access |
|
| Role | Access |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| `admin` | Full access: attendee import, user management, SMTP settings, all departments and shifts |
|
| `admin` | Full access: participant import, user management, SMTP settings, all departments and shifts |
|
||||||
| `coordinator` | All departments: volunteers, shifts, schedule. No user management or settings |
|
| `ticketing` | Participants, tickets, import. No user management |
|
||||||
| `volunteer_lead` | Own department only: volunteers and shifts scoped to assigned department |
|
| `staffing` | All departments: volunteers, shifts, schedule. No user management or settings |
|
||||||
| `gate` | Full-screen check-in UI with QR scanner. No access to other pages |
|
| `colead` | Own department only: volunteers and shifts scoped to assigned department(s) |
|
||||||
|
| `gatekeeper` | Full-screen check-in UI with QR scanner. No access to other pages |
|
||||||
|
|
||||||
See [docs/USAGE.md](docs/USAGE.md) for detailed workflow documentation.
|
See [docs/USAGE.md](docs/USAGE.md) for detailed workflow documentation.
|
||||||
|
|
||||||
|
|
@ -91,7 +92,7 @@ The Vite dev server runs on `:5173` and proxies `/api` requests to the Go server
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [Usage Guide](docs/USAGE.md) — event setup, attendee import, volunteer signup, volunteer kiosk, gate check-in, schedule
|
- [Usage Guide](docs/USAGE.md) — event setup, participant import, volunteer signup, volunteer kiosk, gate check-in, schedule
|
||||||
- [Installation Guide](docs/INSTALLATION.md) — building, deploying, systemd, Docker, NixOS, reverse proxy, backup
|
- [Installation Guide](docs/INSTALLATION.md) — building, deploying, systemd, Docker, NixOS, reverse proxy, backup
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
||||||
|
|
@ -105,23 +105,27 @@ docker run -p 8180:8180 \
|
||||||
|
|
||||||
## NixOS
|
## NixOS
|
||||||
|
|
||||||
Turnpike builds with `buildGoModule` using the pure-Go SQLite driver (no CGO):
|
Turnpike builds with `buildGoModule` + `buildNpmPackage` (pure-Go SQLite, no CGO). The frontend is built separately and copied into the Go build:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
|
frontendDist = pkgs.buildNpmPackage {
|
||||||
|
pname = "turnpike-frontend";
|
||||||
|
src = "${src}/frontend";
|
||||||
|
npmDepsHash = "sha256-...";
|
||||||
|
buildPhase = "npm run build";
|
||||||
|
installPhase = "cp -r dist $out";
|
||||||
|
};
|
||||||
|
|
||||||
turnpike = pkgs.buildGoModule {
|
turnpike = pkgs.buildGoModule {
|
||||||
pname = "turnpike";
|
pname = "turnpike";
|
||||||
version = "0.1.0";
|
src = fetchgit { url = "..."; rev = "v2.0.0"; hash = "sha256-..."; };
|
||||||
src = ./path/to/turnpike; # must include vendor/ and frontend/dist/
|
vendorHash = "sha256-...";
|
||||||
vendorHash = null;
|
|
||||||
env.CGO_ENABLED = 0;
|
env.CGO_ENABLED = 0;
|
||||||
|
preBuild = "cp -r ${frontendDist} frontend/dist";
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
The source directory must contain:
|
A complete NixOS module with `DynamicUser`, `StateDirectory`, and secrets is in the project's `homelab/turnpike.nix`.
|
||||||
- Go source files and `vendor/` (run `go mod vendor`)
|
|
||||||
- Pre-built frontend at `frontend/dist/` (run `cd frontend && npm run build`)
|
|
||||||
|
|
||||||
A complete NixOS module example with `DynamicUser`, `StateDirectory`, and agenix secrets is in the project's `homelab/turnpike.nix`.
|
|
||||||
|
|
||||||
## Reverse Proxy
|
## Reverse Proxy
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,23 +12,22 @@ After logging in, create accounts for your team under **Users**. Each user gets
|
||||||
|
|
||||||
| Role | What they see | What they can do |
|
| Role | What they see | What they can do |
|
||||||
|------|--------------|------------------|
|
|------|--------------|------------------|
|
||||||
| **admin** | All pages + Settings | Everything: attendee import, user management, SMTP config, departments, shifts, volunteers |
|
| **admin** | All pages + Settings | Everything: participant import, user management, SMTP config, departments, shifts, volunteers |
|
||||||
| **coordinator** | Dashboard, Schedule, Volunteers, Departments | Manage volunteers, departments, and shifts across all departments. Cannot manage users or settings |
|
| **ticketing** | Participants, Tickets, Import | Manage participants and tickets, run CSV imports |
|
||||||
| **volunteer_lead** | Schedule, Volunteers, Departments | Manage volunteers and shifts within their assigned department only |
|
| **staffing** | Dashboard, Schedule, Volunteers, Departments | Manage volunteers, departments, and shifts across all departments. No user management or settings |
|
||||||
| **gate** | Full-screen Gate UI | Check in attendees (search + QR scan). No access to other pages |
|
| **colead** | Dashboard, Schedule, Volunteers | Manage volunteers and shifts within their assigned department(s) only |
|
||||||
|
| **gatekeeper** | Full-screen Gate UI | Check in ticket holders (search + QR scan). No access to other pages |
|
||||||
|
|
||||||
Ticketing and ops staff should use the **admin** role. The `ticketing` role exists in the codebase but is effectively unused — admin covers all ticketing functions.
|
Coleads are scoped to one or more departments. When creating a colead user, assign their department(s).
|
||||||
|
|
||||||
Volunteer leads are scoped to a single department. When creating a volunteer_lead user, assign their department.
|
|
||||||
|
|
||||||
## Event Setup
|
## Event Setup
|
||||||
|
|
||||||
1. **Configure your event** — go to the Dashboard and set the event name and dates.
|
1. **Configure your event** — go to **Settings** and set the event name, venue, dates, and timezone. These appear on the Dashboard and volunteer signup page.
|
||||||
2. **Create departments** — under Departments, add each department your event needs (e.g., Gate, Greeters, Rangers, Build, LNT).
|
2. **Create departments** — under Departments, add each department your event needs (e.g., Gate, Greeters, Rangers, Build, LNT).
|
||||||
3. **Import attendees** — see next section.
|
3. **Import participants** — see next section.
|
||||||
4. **Create shifts** — under Schedule, create shifts for each department with day, start/end time, and capacity.
|
4. **Create shifts** — under Schedule, create shifts for each department with day, start/end time, and capacity.
|
||||||
|
|
||||||
## Importing Attendees
|
## Importing Participants
|
||||||
|
|
||||||
Go to **Import** and upload a CSV file. Turnpike auto-detects two formats:
|
Go to **Import** and upload a CSV file. Turnpike auto-detects two formats:
|
||||||
|
|
||||||
|
|
@ -36,7 +35,7 @@ Go to **Import** and upload a CSV file. Turnpike auto-detects two formats:
|
||||||
|
|
||||||
| Column | Maps to |
|
| Column | Maps to |
|
||||||
|--------|---------|
|
|--------|---------|
|
||||||
| `Patron Name` | Name |
|
| `Patron Name` | Ticket name |
|
||||||
| `Patron Email` | Email |
|
| `Patron Email` | Email |
|
||||||
| `Order Number` | Ticket ID |
|
| `Order Number` | Ticket ID |
|
||||||
| `Tier Name` | Ticket type |
|
| `Tier Name` | Ticket type |
|
||||||
|
|
@ -45,7 +44,7 @@ Go to **Import** and upload a CSV file. Turnpike auto-detects two formats:
|
||||||
|
|
||||||
| Column | Maps to |
|
| Column | Maps to |
|
||||||
|--------|---------|
|
|--------|---------|
|
||||||
| `name` (required) | Name |
|
| `name` (required) | Ticket name |
|
||||||
| `email` | Email |
|
| `email` | Email |
|
||||||
| `ticket_id` | Ticket ID |
|
| `ticket_id` | Ticket ID |
|
||||||
| `ticket_type` | Ticket type |
|
| `ticket_type` | Ticket type |
|
||||||
|
|
@ -53,27 +52,21 @@ Go to **Import** and upload a CSV file. Turnpike auto-detects two formats:
|
||||||
|
|
||||||
Column matching is case-insensitive. Extra columns are ignored. BOM-encoded files (Windows Excel exports) are handled automatically.
|
Column matching is case-insensitive. Extra columns are ignored. BOM-encoded files (Windows Excel exports) are handled automatically.
|
||||||
|
|
||||||
### Party-size dedup
|
### Participants and tickets
|
||||||
|
|
||||||
CrowdWork exports one row per ticket, even when the same person bought multiple tickets in one order. Turnpike handles this automatically:
|
Each row in the CSV creates one **ticket**. Participants are deduplicated by email — multiple tickets with the same email address are linked to a single participant record. The import result shows `inserted` (new tickets) and `skipped` (exact duplicates).
|
||||||
|
|
||||||
- First row for "Titania Fairweather" (order 1234) creates a record with `party_size=1`
|
Re-importing the same CSV is safe — exact duplicates are skipped, not duplicated.
|
||||||
- Subsequent rows with the same name + order number increment `party_size` (no duplicate record)
|
|
||||||
- Result: one attendee record, `party_size=3` if three tickets were purchased
|
|
||||||
|
|
||||||
The import result shows `inserted` (new records), `grouped` (merged into existing party), and `skipped` (exact duplicates).
|
|
||||||
|
|
||||||
Re-importing the same CSV is safe — existing records are skipped, not duplicated.
|
|
||||||
|
|
||||||
## Volunteer Signup
|
## Volunteer Signup
|
||||||
|
|
||||||
Turnpike provides a public signup form for volunteers at `/#/volunteer-signup`. No login is required.
|
Turnpike provides a public signup form for volunteers at `/volunteer-signup`. No login is required.
|
||||||
|
|
||||||
### Signup flow
|
### Signup flow
|
||||||
|
|
||||||
1. Volunteer visits the signup form and fills in: preferred name (required), ticket name, email (required), pronouns, phone, department preference, and an optional note.
|
1. Volunteer visits the signup form and fills in: preferred name (required), ticket name, email (required), pronouns, phone, department preference, and an optional note.
|
||||||
2. Turnpike creates a volunteer record and auto-links it to an existing attendee by email match, or creates a new attendee record.
|
2. Turnpike creates a volunteer record and auto-links it to an existing participant by email match, or creates a new participant record.
|
||||||
3. A confirmation email is sent with a unique link (`/#/confirm/{token}`).
|
3. A confirmation email is sent with a unique link (`/confirm/{token}`).
|
||||||
4. The volunteer clicks the link to confirm their email.
|
4. The volunteer clicks the link to confirm their email.
|
||||||
5. If shift signups are already open, the confirmation page includes a link to the kiosk for shift selection.
|
5. If shift signups are already open, the confirmation page includes a link to the kiosk for shift selection.
|
||||||
|
|
||||||
|
|
@ -90,7 +83,7 @@ In **Settings**, the "Volunteer Signup" card controls:
|
||||||
|
|
||||||
In **Settings**, the "Shift Signups" card has an open/close toggle:
|
In **Settings**, the "Shift Signups" card has an open/close toggle:
|
||||||
|
|
||||||
- **Opening** signups generates kiosk tokens for all confirmed volunteers and emails them their shift signup links. A confirmation dialog warns before sending.
|
- **Opening** signups generates kiosk codes for all confirmed volunteers and emails them their shift signup links. A confirmation dialog warns before sending.
|
||||||
- **Closing** signups prevents new kiosk links from being issued on confirmation, but existing links continue to work.
|
- **Closing** signups prevents new kiosk links from being issued on confirmation, but existing links continue to work.
|
||||||
|
|
||||||
If a volunteer confirms their email while signups are already open, they receive their kiosk link immediately in the confirmation response and via email.
|
If a volunteer confirms their email while signups are already open, they receive their kiosk link immediately in the confirmation response and via email.
|
||||||
|
|
@ -100,11 +93,11 @@ If a volunteer confirms their email while signups are already open, they receive
|
||||||
Under **Volunteers**, you can:
|
Under **Volunteers**, you can:
|
||||||
|
|
||||||
- Create volunteers manually (name, email, department)
|
- Create volunteers manually (name, email, department)
|
||||||
- Link a volunteer to an existing attendee record (for dual check-in at the gate)
|
|
||||||
- Assign volunteers to departments
|
- Assign volunteers to departments
|
||||||
|
- Mark volunteers as co-leads
|
||||||
- Check in volunteers
|
- Check in volunteers
|
||||||
|
|
||||||
Volunteers are separate from attendees. A person can be both an attendee (ticket holder) and a volunteer (shift worker). Linking them enables the gate team to check in both records simultaneously.
|
Volunteers are separate from participants. A person can be both a ticket holder and a volunteer. When a volunteer signs up via the public form, they are automatically linked to their participant record by email.
|
||||||
|
|
||||||
## Shift Scheduling
|
## Shift Scheduling
|
||||||
|
|
||||||
|
|
@ -128,15 +121,17 @@ The kiosk lets volunteers self-select shifts without logging in.
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
1. **Generate tokens** — on the Attendees page, click "Generate Tokens." This creates a unique 8-character code for every attendee that doesn't have one.
|
Kiosk links are generated and distributed automatically through the volunteer signup flow:
|
||||||
2. **Distribute tokens** — two options:
|
|
||||||
- **Export CSV** — downloads a file with columns `Email Address`, `First Name`, `Token`, `Signup Link`. Import this into MailChimp, Zeffy, or any email platform.
|
1. Volunteers sign up via the public signup form (`/volunteer-signup`) and confirm their email.
|
||||||
- **Email directly** — if SMTP is configured (see below), use "Email All" to send token links, or email individually per attendee.
|
2. In **Settings**, open shift signups. This generates kiosk codes for all confirmed volunteers and emails them their links. A confirmation dialog warns before sending.
|
||||||
3. **Set base URL** — in Settings, set the public base URL (e.g., `https://turnpike.example.com`). Token links use this URL.
|
3. If a volunteer confirms their email while signups are already open, they receive their kiosk link immediately.
|
||||||
|
|
||||||
|
**Set base URL** — in Settings, set the public base URL (e.g., `https://turnpike.example.com`). Kiosk links use this URL.
|
||||||
|
|
||||||
### Volunteer experience
|
### Volunteer experience
|
||||||
|
|
||||||
Each volunteer receives a link like `https://turnpike.example.com/#/v/ABC12345`. This opens a mobile-friendly page showing:
|
Each volunteer receives a link like `https://turnpike.example.com/v/ABC12345`. This opens a mobile-friendly page showing:
|
||||||
|
|
||||||
- Their name and department
|
- Their name and department
|
||||||
- Currently assigned shifts
|
- Currently assigned shifts
|
||||||
|
|
@ -144,20 +139,19 @@ Each volunteer receives a link like `https://turnpike.example.com/#/v/ABC12345`.
|
||||||
|
|
||||||
Claiming a shift checks for time conflicts. If a conflict exists, the volunteer sees which shifts overlap and can confirm to proceed anyway.
|
Claiming a shift checks for time conflicts. If a conflict exists, the volunteer sees which shifts overlap and can confirm to proceed anyway.
|
||||||
|
|
||||||
No login is required. The 8-character token authenticates the request.
|
No login is required. The kiosk code authenticates the request.
|
||||||
|
|
||||||
### Token format
|
### Code format
|
||||||
|
|
||||||
Tokens use the character set `A-Z, 2-9` (excluding 0/O, 1/I/L to avoid ambiguity when reading aloud or on printed badges).
|
Kiosk codes use the character set `A-Z, 2-9` (excluding 0/O, 1/I/L to avoid ambiguity when reading aloud or on printed badges).
|
||||||
|
|
||||||
## Gate Check-In
|
## Gate Check-In
|
||||||
|
|
||||||
Users with the **gate** role see a dedicated full-screen UI:
|
Users with the **gatekeeper** role see a dedicated full-screen UI:
|
||||||
|
|
||||||
- **QR scanner** — uses the device camera via the BarcodeDetector API. Scanned codes populate the search field.
|
- **QR scanner** — uses the device camera via the BarcodeDetector API. Scanned codes populate the search field.
|
||||||
- **Search** — type a name to filter attendees in real-time (searches local IndexedDB, works offline).
|
- **Search** — type a name to filter tickets in real-time (searches local IndexedDB, works offline).
|
||||||
- **Party check-in** — for attendees with `party_size > 1`, the gate UI shows progress ("2/3 checked in") and offers "Check in 1" or "Check in all remaining."
|
- **Volunteer dual check-in** — if a ticket holder is also a volunteer, the gate UI shows their volunteer status and offers to check in both simultaneously.
|
||||||
- **Volunteer dual check-in** — if an attendee is linked to a volunteer record, the gate UI shows their volunteer status and offers to check in both simultaneously.
|
|
||||||
- **Recent check-ins** — the last 10 check-ins are shown for quick reference.
|
- **Recent check-ins** — the last 10 check-ins are shown for quick reference.
|
||||||
|
|
||||||
Gate devices should install Turnpike as a PWA (Add to Home Screen) for the best experience. Check-ins are stored locally and sync when connectivity is available.
|
Gate devices should install Turnpike as a PWA (Add to Home Screen) for the best experience. Check-ins are stored locally and sync when connectivity is available.
|
||||||
|
|
@ -170,7 +164,7 @@ The Schedule page is the primary UI for managing shifts and volunteer assignment
|
||||||
- Each shift card shows: name, time, capacity (used/total), assigned volunteers
|
- Each shift card shows: name, time, capacity (used/total), assigned volunteers
|
||||||
- Conflict badges when a volunteer has overlapping shifts on the same day
|
- Conflict badges when a volunteer has overlapping shifts on the same day
|
||||||
|
|
||||||
**Coordinators and admins** see all departments. **Volunteer leads** see only their assigned department.
|
**Admins and staffing** see all departments. **Coleads** see only their assigned department(s).
|
||||||
|
|
||||||
Actions available:
|
Actions available:
|
||||||
- Create new shifts (+ Add shift button)
|
- Create new shifts (+ Add shift button)
|
||||||
|
|
@ -182,7 +176,7 @@ Actions available:
|
||||||
|
|
||||||
## SMTP Configuration
|
## SMTP Configuration
|
||||||
|
|
||||||
SMTP enables token email distribution and test emails. Configure in **Settings** (admin only):
|
SMTP enables volunteer confirmation emails, kiosk link distribution, and test emails. Configure in **Settings** (admin only):
|
||||||
|
|
||||||
| Field | Description |
|
| Field | Description |
|
||||||
|-------|-------------|
|
|-------|-------------|
|
||||||
|
|
@ -203,13 +197,13 @@ Turnpike is a Progressive Web App (PWA). After the first load, it works offline:
|
||||||
|
|
||||||
- **Gate check-ins** are stored in the browser's IndexedDB and sync when connectivity returns.
|
- **Gate check-ins** are stored in the browser's IndexedDB and sync when connectivity returns.
|
||||||
- **Real-time updates** use Server-Sent Events (SSE). When the connection drops, the client reconnects automatically.
|
- **Real-time updates** use Server-Sent Events (SSE). When the connection drops, the client reconnects automatically.
|
||||||
- **Sync** pulls all changes from the server on startup and periodically thereafter. Local changes are queued in an outbox and flushed in order.
|
- **Sync** pulls all changes from the server on startup and periodically thereafter.
|
||||||
|
|
||||||
Install Turnpike as a PWA (Add to Home Screen on mobile, or Install App in desktop Chrome) for the best offline experience.
|
Install Turnpike as a PWA (Add to Home Screen on mobile, or Install App in desktop Chrome) for the best offline experience.
|
||||||
|
|
||||||
## CSV Exports
|
## CSV Exports
|
||||||
|
|
||||||
Two CSV exports are available from the Attendees page:
|
CSV exports are available from the Participants page:
|
||||||
|
|
||||||
- **Attendee export** — all attendee records with check-in status
|
- **Participant export** — all participant records with check-in status
|
||||||
- **Token link export** — columns: `Email Address`, `First Name`, `Token`, `Signup Link`. Only includes attendees with tokens. Compatible with MailChimp and Zeffy for bulk email campaigns.
|
- **Ticket export** — all ticket records with codes and check-in status
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { api } from '../api.js'
|
import { api } from '../api.js'
|
||||||
|
import { db } from '../db.js'
|
||||||
|
|
||||||
let loading = $state(true)
|
let loading = $state(true)
|
||||||
let saving = $state(false)
|
let saving = $state(false)
|
||||||
|
let savingEvent = $state(false)
|
||||||
let testing = $state(false)
|
let testing = $state(false)
|
||||||
let error = $state('')
|
let error = $state('')
|
||||||
let success = $state('')
|
let success = $state('')
|
||||||
|
|
@ -18,10 +20,23 @@
|
||||||
let testEmail = $state('')
|
let testEmail = $state('')
|
||||||
let noteLabel = $state('Additional note')
|
let noteLabel = $state('Additional note')
|
||||||
let noteRequired = $state(false)
|
let noteRequired = $state(false)
|
||||||
|
let eventName = $state('')
|
||||||
|
let eventVenue = $state('')
|
||||||
|
let eventStartDate = $state('')
|
||||||
|
let eventEndDate = $state('')
|
||||||
|
let eventTimezone = $state('')
|
||||||
let shiftSignupsOpen = $state(false)
|
let shiftSignupsOpen = $state(false)
|
||||||
let togglingSignups = $state(false)
|
let togglingSignups = $state(false)
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
const ev = await api.event.get()
|
||||||
|
eventName = ev.name ?? ''
|
||||||
|
eventVenue = ev.venue ?? ''
|
||||||
|
eventStartDate = ev.start_date ?? ''
|
||||||
|
eventEndDate = ev.end_date ?? ''
|
||||||
|
eventTimezone = ev.timezone ?? ''
|
||||||
|
} catch {}
|
||||||
try {
|
try {
|
||||||
const s = await api.settings.get()
|
const s = await api.settings.get()
|
||||||
smtpHost = s.smtp_host ?? ''
|
smtpHost = s.smtp_host ?? ''
|
||||||
|
|
@ -41,6 +56,28 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function saveEvent(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
savingEvent = true
|
||||||
|
error = ''
|
||||||
|
success = ''
|
||||||
|
try {
|
||||||
|
const updated = await api.event.update({
|
||||||
|
name: eventName,
|
||||||
|
venue: eventVenue,
|
||||||
|
start_date: eventStartDate,
|
||||||
|
end_date: eventEndDate,
|
||||||
|
timezone: eventTimezone,
|
||||||
|
})
|
||||||
|
await db.event.put({ ...updated, id: 1 })
|
||||||
|
success = 'Event saved.'
|
||||||
|
} catch (err) {
|
||||||
|
error = err.message
|
||||||
|
} finally {
|
||||||
|
savingEvent = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function save(e) {
|
async function save(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
saving = true
|
saving = true
|
||||||
|
|
@ -127,6 +164,39 @@
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="text-muted">Loading…</div>
|
<div class="text-muted">Loading…</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
<form onsubmit={saveEvent}>
|
||||||
|
<div class="card" style="margin-bottom:1.5rem">
|
||||||
|
<h2 style="font-size:0.95rem;font-weight:700;margin-bottom:1rem">Event</h2>
|
||||||
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">
|
||||||
|
<div class="form-group" style="grid-column:1/-1">
|
||||||
|
<label for="e-name">Event Name *</label>
|
||||||
|
<input id="e-name" bind:value={eventName} required placeholder="My Event 2026" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="grid-column:1/-1">
|
||||||
|
<label for="e-venue">Venue</label>
|
||||||
|
<input id="e-venue" bind:value={eventVenue} placeholder="Location name" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="e-start">Start Date *</label>
|
||||||
|
<input id="e-start" type="date" bind:value={eventStartDate} required />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="e-end">End Date *</label>
|
||||||
|
<input id="e-end" type="date" bind:value={eventEndDate} required />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="grid-column:1/-1">
|
||||||
|
<label for="e-tz">Timezone</label>
|
||||||
|
<input id="e-tz" bind:value={eventTimezone} placeholder="America/Los_Angeles" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button type="submit" class="btn btn-primary" disabled={savingEvent}>
|
||||||
|
{savingEvent ? 'Saving…' : 'Save Event'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
<form onsubmit={save}>
|
<form onsubmit={save}>
|
||||||
<div class="card" style="margin-bottom:1.5rem">
|
<div class="card" style="margin-bottom:1.5rem">
|
||||||
<h2 style="font-size:0.95rem;font-weight:700;margin-bottom:1rem">SMTP Email</h2>
|
<h2 style="font-size:0.95rem;font-weight:700;margin-bottom:1rem">SMTP Email</h2>
|
||||||
|
|
@ -160,7 +230,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="s-url">Base URL <span class="text-muted" style="font-weight:400">(for volunteer token links)</span></label>
|
<label for="s-url">Base URL <span class="text-muted" style="font-weight:400">(for kiosk links in emails)</span></label>
|
||||||
<input id="s-url" bind:value={baseURL} placeholder="https://events.example.com" />
|
<input id="s-url" bind:value={baseURL} placeholder="https://events.example.com" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue