Skip to content

feat(hosted-events): dashboard handler, routes, UI templates (Phase D + E)#45

Closed
Ceesaxp wants to merge 1 commit into
feature/hosted-event-servicefrom
feature/hosted-events-ui
Closed

feat(hosted-events): dashboard handler, routes, UI templates (Phase D + E)#45
Ceesaxp wants to merge 1 commit into
feature/hosted-event-servicefrom
feature/hosted-events-ui

Conversation

@Ceesaxp

@Ceesaxp Ceesaxp commented May 6, 2026

Copy link
Copy Markdown
Owner

Summary

Phase D + E of host-driven event scheduling. Builds on `HostedEventService` from #44 (this PR is targeted at that branch so the diff stays scoped to the new handler/UI work; once #43 and #44 merge, this PR auto-rebases through to main).

This is the user-visible end of the work — host opens "Schedule Event" in the sidebar, picks a time + attendees + location, hits submit, attendees get an invite. Edit-after-send works the same way.

What's in the PR

12 dashboard routes (`cmd/server/main.go`)

Method Path Purpose
GET `/dashboard/events` list (?archived=true to include)
GET `/dashboard/events/new` create form (?template=ID prefills)
POST `/dashboard/events` create
GET `/dashboard/events/check-conflicts` HTMX: busy-time merge for the form
GET `/dashboard/events/attendee-search` HTMX: contact autocomplete
GET `/dashboard/events/{id}/details` modal partial (mirrors booking)
GET `/dashboard/events/{id}/edit` full edit form
POST `/dashboard/events/{id}/edit` update
POST `/dashboard/events/{id}/cancel` cancel
POST `/dashboard/events/{id}/archive` archive
POST `/dashboard/events/{id}/unarchive` unarchive
POST `/dashboard/events/{id}/retry-calendar` retry calendar event creation

The two HTMX partial routes use `/events/check-conflicts` and `/events/attendee-search` so they don't shadow `{id}`.

Handler (`internal/handlers/dashboard_events.go`)

`DashboardEventsHandler` wired on the `Handlers` struct alongside `Dashboard`. Notes:

  • Form parsing uses parallel-indexed repeated fields (`attendee_email` / `_name` / `_contact_id`), matching the templates handler's existing `r.Form["durations"]` pattern.
  • `parseDateTimeInTZ` combines the form's date + time + timezone fields into a UTC `time.Time` before handing to the service.
  • Update flow uses pointer fields on `UpdateHostedEventInput` so the form can distinguish "unchanged" from "set to empty".

Templates (Phase E)

Six new templates, all reusing existing primitives — `.section`, `.section-header`, `.form-input`, `.form-select`, `.form-textarea`, `.form-label`, `.form-hint`, `.btn .btn-primary/.btn-secondary/.btn-outline`, `.btn-sm`, `.badge`, `.table-container/.table`, `.duration-chips`, `.radio-cards`, modal classes, `.empty-state`, `.alert/.alert-warning/.alert-error`. No new CSS files; the attendee chip styling is a small inline block confined to the form.

Path Purpose
`templates/pages/dashboard_events.html` List + filter view
`templates/pages/dashboard_event_form.html` Create + edit form (single page reused)
`templates/partials/events_table_partial.html` Table body
`templates/partials/event_attendee_picker_results.html` Autocomplete results
`templates/partials/event_conflict_warning.html` Soft conflict banner
`templates/partials/dashboard_event_detail_partial.html` Modal detail

Form sections in order: Template prefill (new mode only) → Basics → When (date/time/duration chips/timezone with HTMX conflict check) → Conflict banner → Location (radio cards) → Calendar → Attendees (chip-based picker with contact autocomplete + free-form email entry).

Modal pattern matches the booking flow's fetch-based approach (post-#41 — fetch HTML, inject into `#event-modal`, ESC to close).

Sidebar nav (`templates/layouts/dashboard.html`)

Added "Schedule Event" between "Meeting Types" and "Bookings".

Tests

`internal/handlers/dashboard_events_test.go` — 9 cases following the existing `template_test.go` pattern (load real templates, render to `bytes.Buffer`, assert strings). I render the `{{define "content"}}` block directly to sidestep the layout-loading dependency.

  • `TestEventAttendeePickerResults_RendersContacts` / `_EmptyShowsFreeFormHint` / `_EmptyAndNoQuery_RendersNothing`
  • `TestEventConflictWarning_RendersConflicts` (pluralisation + soft-warning copy)
  • `TestEventConflictWarning_NoConflicts_RendersEmpty`
  • `TestEventsTablePartial_RendersScheduledAndCancelled` (status-routed badges + actions)
  • `TestEventForm_NewMode_RendersTitleAndAttendeePicker`
  • `TestEventForm_EditMode_PrefillsAndCarriesExcludeEventID` (zoom card + 45-min chip + self-exclusion)
  • `TestEventForm_FormErrorRendersHumanCopy`

Verification

  • `make test` — all packages green (handlers, services, repository, etc.)
  • `go vet ./...` clean
  • `go build ./cmd/server` clean
  • Manual smoke (post-merge of the chain): expected to render at `/dashboard/events`, prefill correctly when `?template=` is passed, conflict banner appears when overlapping with existing busy/booking/event windows, attendee chip + autocomplete behave per design.

Note on `/frontend-design:frontend-design`

The plan called for invoking that skill. I deliberately chose not to load it because its description anchors on producing distinctive new designs, which would conflict with the user's explicit constraint of "following the existing design language". Instead I read the existing booking + template + contacts pages thoroughly, catalogued every primitive in use, and reused them rigorously here. The visual language should match seamlessly — happy to revisit if anything looks off in the rendered UI.

Test plan

  • Visual review of the list page (empty + with events)
  • Visual review of the create form (radio cards, duration chips, attendee chip picker)
  • Visual review of the edit form (prefill behaviour, including zoom + 45-min cases)
  • HTMX paths: attendee autocomplete, conflict warning trigger
  • Modal: detail view + ESC to close
  • Sidebar entry placement + active state on /dashboard/events

🤖 Generated with Claude Code

…se D + E)

Phase D + E of host-driven event scheduling. Builds on the
HostedEventService introduced in #44 and exposes it through 12 dashboard
routes plus 6 HTML templates. Sidebar gets a "Schedule Event" item between
Templates and Bookings.

## Routes (cmd/server/main.go)

- GET  /dashboard/events                       — list (?archived=true to include)
- GET  /dashboard/events/new                   — create form (?template=ID prefills)
- POST /dashboard/events                       — create
- GET  /dashboard/events/check-conflicts       — HTMX partial; busy-time merge
- GET  /dashboard/events/attendee-search       — HTMX partial; contact autocomplete
- GET  /dashboard/events/{id}/details          — modal partial
- GET  /dashboard/events/{id}/edit             — full edit form
- POST /dashboard/events/{id}/edit             — update
- POST /dashboard/events/{id}/cancel           — cancel
- POST /dashboard/events/{id}/archive          — archive
- POST /dashboard/events/{id}/unarchive        — unarchive
- POST /dashboard/events/{id}/retry-calendar   — retry calendar event creation

The HTMX partial routes use the /events/check-conflicts and
/events/attendee-search paths so they don't shadow {id}.

## Handler (internal/handlers/dashboard_events.go)

DashboardEventsHandler wired on the Handlers struct alongside Dashboard.
Form parsing uses parallel-indexed repeated form fields
(attendee_email/_name/_contact_id), matching the existing pattern used by
the templates handler. parseDateTimeInTZ combines the form's date + time +
timezone fields into a UTC time.Time before handing to the service. Update
flow uses pointer fields on UpdateHostedEventInput so the form can express
"unchanged" vs "set to empty".

## Templates (Phase E)

All six new templates reuse the existing primitives — `.section`,
`.section-header`, `.form-input`, `.form-select`, `.form-textarea`,
`.form-label`, `.form-hint`, `.btn .btn-primary/.btn-secondary/.btn-outline`,
`.btn-sm`, `.badge`, `.table-container/.table`, `.duration-chips`,
`.radio-cards`, `.modal-overlay/.modal-content/.modal-header/.modal-body`,
`.empty-state`, `.alert/.alert-warning/.alert-error`. No new CSS files; the
attendee chip styling is a small inline block confined to the form template.

- templates/pages/dashboard_events.html              — list + filter view
- templates/pages/dashboard_event_form.html          — create + edit form
- templates/partials/events_table_partial.html       — table body
- templates/partials/event_attendee_picker_results.html — autocomplete results
- templates/partials/event_conflict_warning.html     — soft conflict banner
- templates/partials/dashboard_event_detail_partial.html — modal detail

The form has 7 sections (Template prefill (new only) → Basics → When →
Location → Calendar → Attendees) with HTMX-driven conflict warnings and
contact autocomplete. Modal pattern matches the booking flow's recent
fetch-based approach (#41 pattern).

## Tests

internal/handlers/dashboard_events_test.go — 9 cases:

- TestEventAttendeePickerResults_RendersContacts
- TestEventAttendeePickerResults_EmptyShowsFreeFormHint
- TestEventAttendeePickerResults_EmptyAndNoQuery_RendersNothing
- TestEventConflictWarning_RendersConflicts (asserts pluralisation + soft-warning copy)
- TestEventConflictWarning_NoConflicts_RendersEmpty
- TestEventsTablePartial_RendersScheduledAndCancelled (status-routed badges + actions)
- TestEventForm_NewMode_RendersTitleAndAttendeePicker
- TestEventForm_EditMode_PrefillsAndCarriesExcludeEventID (zoom + 45-min + exclude_event_id)
- TestEventForm_FormErrorRendersHumanCopy (at_least_one_attendee → human copy)

Pattern matches internal/handlers/template_test.go: load real template
files, render to bytes.Buffer, assert strings. Tests render the {{define
"content"}} blocks directly to avoid the layout-loading dependency.

`make test`, `go vet ./...`, `go build ./cmd/server` all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Ceesaxp
Ceesaxp deleted the branch feature/hosted-event-service May 7, 2026 13:43
@Ceesaxp Ceesaxp closed this May 7, 2026
@Ceesaxp
Ceesaxp deleted the feature/hosted-events-ui branch May 7, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant