Rename all 1,169 components to path-based names with namespace support

Component names now reflect filesystem location using / as path separator
and : as namespace separator for shared components:
  ~sx-header → ~layouts/header
  ~layout-app-body → ~shared:layout/app-body
  ~blog-admin-dashboard → ~admin/dashboard

209 files, 4,941 replacements across all services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 22:00:12 +00:00
parent de80d921e9
commit b0920a1121
209 changed files with 4620 additions and 4620 deletions

View File

@@ -1,6 +1,6 @@
;; Events admin components
(defcomp ~events-calendar-admin-panel (&key description-content csrf description)
(defcomp ~admin/calendar-admin-panel (&key description-content csrf description)
(section :class "max-w-3xl mx-auto p-4 space-y-10"
(div
(h2 :class "text-xl font-semibold" "Calendar configuration")
@@ -19,45 +19,45 @@
(div (button :class "px-3 py-2 rounded bg-stone-800 text-white" "Save"))))
(hr :class "border-stone-200")))
(defcomp ~events-entry-admin-link (&key href)
(defcomp ~admin/entry-admin-link (&key href)
(a :href href :class "inline-flex items-center gap-1 px-2 py-1 text-xs text-stone-500 hover:text-stone-700 hover:bg-stone-100 rounded"
(i :class "fa fa-cog" :aria-hidden "true") " Admin"))
(defcomp ~events-entry-field (&key label content)
(defcomp ~admin/entry-field (&key label content)
(div :class "flex flex-col mb-4"
(div :class "text-xs font-semibold uppercase tracking-wide text-stone-500" label)
content))
(defcomp ~events-entry-name-field (&key name)
(defcomp ~admin/entry-name-field (&key name)
(div :class "mt-1 text-lg font-medium" name))
(defcomp ~events-entry-slot-assigned (&key slot-name flex-label)
(defcomp ~admin/entry-slot-assigned (&key slot-name flex-label)
(div :class "mt-1"
(span :class "px-2 py-1 rounded text-sm bg-blue-100 text-blue-700" slot-name)
(span :class "ml-2 text-xs text-stone-500" flex-label)))
(defcomp ~events-entry-slot-none ()
(defcomp ~admin/entry-slot-none ()
(div :class "mt-1" (span :class "text-sm text-stone-400" "No slot assigned")))
(defcomp ~events-entry-time-field (&key time-str)
(defcomp ~admin/entry-time-field (&key time-str)
(div :class "mt-1" time-str))
(defcomp ~events-entry-state-field (&key entry-id badge)
(defcomp ~admin/entry-state-field (&key entry-id badge)
(div :class "mt-1" (div :id (str "entry-state-" entry-id) badge)))
(defcomp ~events-entry-cost-field (&key cost)
(defcomp ~admin/entry-cost-field (&key cost)
(div :class "mt-1" (span :class "font-medium text-green-600" cost)))
(defcomp ~events-entry-tickets-field (&key entry-id tickets-config)
(defcomp ~admin/entry-tickets-field (&key entry-id tickets-config)
(div :class "mt-1" :id (str "entry-tickets-" entry-id) tickets-config))
(defcomp ~events-entry-date-field (&key date-str)
(defcomp ~admin/entry-date-field (&key date-str)
(div :class "mt-1" date-str))
(defcomp ~events-entry-posts-field (&key entry-id posts-panel)
(defcomp ~admin/entry-posts-field (&key entry-id posts-panel)
(div :class "mt-1" :id (str "entry-posts-" entry-id) posts-panel))
(defcomp ~events-entry-panel (&key entry-id list-container name slot time state cost
(defcomp ~admin/entry-panel (&key entry-id list-container name slot time state cost
tickets buy date posts options pre-action edit-url)
(section :id (str "entry-" entry-id) :class list-container
name slot time state cost
@@ -68,21 +68,21 @@
:sx-get edit-url :sx-target (str "#entry-" entry-id) :sx-swap "outerHTML"
"Edit"))))
(defcomp ~events-entry-title (&key name badge)
(defcomp ~admin/entry-title (&key name badge)
(<> (i :class "fa fa-clock") " " name " " badge))
(defcomp ~events-entry-times (&key time-str)
(defcomp ~admin/entry-times (&key time-str)
(div :class "text-sm text-gray-600" time-str))
(defcomp ~events-entry-optioned-oob (&key entry-id title state)
(defcomp ~admin/entry-optioned-oob (&key entry-id title state)
(<> (div :id (str "entry-title-" entry-id) :sx-swap-oob "innerHTML" title)
(div :id (str "entry-state-" entry-id) :sx-swap-oob "innerHTML" state)))
(defcomp ~events-entry-options (&key entry-id buttons)
(defcomp ~admin/entry-options (&key entry-id buttons)
(div :id (str "calendar_entry_options_" entry-id) :class "flex flex-col md:flex-row gap-1"
buttons))
(defcomp ~events-entry-option-button (&key url target csrf btn-type action-btn confirm-title confirm-text
(defcomp ~admin/entry-option-button (&key url target csrf btn-type action-btn confirm-title confirm-text
label is-btn)
(form :sx-post url :sx-select target :sx-target target :sx-swap "outerHTML"
:sx-trigger (if is-btn "confirmed" nil)

View File

@@ -1,34 +1,34 @@
;; Events calendar components
(defcomp ~events-calendar-nav-arrow (&key (pill-cls :as string) (href :as string) (label :as string))
(defcomp ~calendar/nav-arrow (&key (pill-cls :as string) (href :as string) (label :as string))
(a :class (str pill-cls " text-xl") :href href
:sx-get href :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" label))
(defcomp ~events-calendar-month-label (&key (month-name :as string) (year :as string))
(defcomp ~calendar/month-label (&key (month-name :as string) (year :as string))
(div :class "px-3 font-medium" (str month-name " " year)))
(defcomp ~events-calendar-weekday (&key (name :as string))
(defcomp ~calendar/weekday (&key (name :as string))
(div :class "py-1" name))
(defcomp ~events-calendar-day-short (&key (day-str :as string))
(defcomp ~calendar/day-short (&key (day-str :as string))
(span :class "sm:hidden text-[16px] text-stone-500" day-str))
(defcomp ~events-calendar-day-num (&key (pill-cls :as string) (href :as string) (num :as string))
(defcomp ~calendar/day-num (&key (pill-cls :as string) (href :as string) (num :as string))
(a :class pill-cls :href href :sx-get href :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" num))
(defcomp ~events-calendar-entry-badge (&key (bg-cls :as string) (name :as string) (state-label :as string))
(defcomp ~calendar/entry-badge (&key (bg-cls :as string) (name :as string) (state-label :as string))
(div :class (str "flex items-center justify-between gap-1 text-[11px] rounded px-1 py-0.5 " bg-cls)
(span :class "truncate" name)
(span :class "shrink-0 text-[10px] font-semibold uppercase tracking-tight" state-label)))
(defcomp ~events-calendar-cell (&key (cell-cls :as string) day-short day-num badges)
(defcomp ~calendar/cell (&key (cell-cls :as string) day-short day-num badges)
(div :class cell-cls
(div :class "flex justify-between items-center"
(div :class "flex flex-col" day-short day-num))
(div :class "mt-1 space-y-0.5" badges)))
(defcomp ~events-calendar-grid (&key arrows weekdays cells)
(defcomp ~calendar/grid (&key arrows weekdays cells)
(section :class "bg-orange-100"
(header :class "flex items-center justify-center mt-2"
(nav :class "flex items-center gap-2 text-2xl" arrows))
@@ -37,36 +37,36 @@
(div :class "grid grid-cols-1 sm:grid-cols-7 gap-px bg-stone-200 rounded-xl overflow-hidden" cells))))
;; Calendar grid from data — all iteration in sx
(defcomp ~events-calendar-grid-from-data (&key (pill-cls :as string) (month-name :as string) (year :as string)
(defcomp ~calendar/grid-from-data (&key (pill-cls :as string) (month-name :as string) (year :as string)
(prev-year-href :as string) (prev-month-href :as string)
(next-month-href :as string) (next-year-href :as string)
(weekday-names :as list) (cells :as list))
(~events-calendar-grid
(~calendar/grid
:arrows (<>
(~events-calendar-nav-arrow :pill-cls pill-cls :href prev-year-href :label "\u00ab")
(~events-calendar-nav-arrow :pill-cls pill-cls :href prev-month-href :label "\u2039")
(~events-calendar-month-label :month-name month-name :year year)
(~events-calendar-nav-arrow :pill-cls pill-cls :href next-month-href :label "\u203a")
(~events-calendar-nav-arrow :pill-cls pill-cls :href next-year-href :label "\u00bb"))
:weekdays (<> (map (lambda (wd) (~events-calendar-weekday :name wd))
(~calendar/nav-arrow :pill-cls pill-cls :href prev-year-href :label "\u00ab")
(~calendar/nav-arrow :pill-cls pill-cls :href prev-month-href :label "\u2039")
(~calendar/month-label :month-name month-name :year year)
(~calendar/nav-arrow :pill-cls pill-cls :href next-month-href :label "\u203a")
(~calendar/nav-arrow :pill-cls pill-cls :href next-year-href :label "\u00bb"))
:weekdays (<> (map (lambda (wd) (~calendar/weekday :name wd))
(or weekday-names (list))))
:cells (<> (map (lambda (cell)
(~events-calendar-cell
(~calendar/cell
:cell-cls (get cell "cell-cls")
:day-short (when (get cell "day-str")
(~events-calendar-day-short :day-str (get cell "day-str")))
(~calendar/day-short :day-str (get cell "day-str")))
:day-num (when (get cell "day-href")
(~events-calendar-day-num :pill-cls pill-cls
(~calendar/day-num :pill-cls pill-cls
:href (get cell "day-href") :num (get cell "day-num")))
:badges (when (get cell "badges")
(<> (map (lambda (b)
(~events-calendar-entry-badge
(~calendar/entry-badge
:bg-cls (get b "bg-cls") :name (get b "name")
:state-label (get b "state-label")))
(get cell "badges"))))))
(or cells (list))))))
(defcomp ~events-calendar-description-display (&key (description :as string?) (edit-url :as string))
(defcomp ~calendar/description-display (&key (description :as string?) (edit-url :as string))
(div :id "calendar-description"
(if description
(p :class "text-stone-700 whitespace-pre-line break-all" description)
@@ -75,12 +75,12 @@
:sx-get edit-url :sx-target "#calendar-description" :sx-swap "outerHTML"
(i :class "fas fa-edit"))))
(defcomp ~events-calendar-description-title-oob (&key (description :as string))
(defcomp ~calendar/description-title-oob (&key (description :as string))
(div :id "calendar-description-title" :sx-swap-oob "outerHTML"
:class "text-base font-normal break-words whitespace-normal min-w-0 break-all w-full text-center block"
description))
(defcomp ~events-calendar-description-edit-form (&key (save-url :as string) (cancel-url :as string) (csrf :as string) (description :as string?))
(defcomp ~calendar/description-edit-form (&key (save-url :as string) (cancel-url :as string) (csrf :as string) (description :as string?))
(div :id "calendar-description"
(form :sx-post save-url :sx-target "#calendar-description" :sx-swap "outerHTML"
(input :type "hidden" :name "csrf_token" :value csrf)

View File

@@ -1,18 +1,18 @@
;; Events day components
(defcomp ~events-day-entry-link (&key (href :as string) (name :as string) (time-str :as string))
(defcomp ~day/entry-link (&key (href :as string) (name :as string) (time-str :as string))
(a :href href :class "flex items-center gap-2 px-3 py-2 hover:bg-stone-100 rounded transition text-sm border sm:whitespace-nowrap sm:flex-shrink-0"
(div :class "flex-1 min-w-0"
(div :class "font-medium truncate" name)
(div :class "text-xs text-stone-600 truncate" time-str))))
(defcomp ~events-day-entries-nav (&key inner)
(defcomp ~day/entries-nav (&key inner)
(div :class "flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
:id "day-entries-nav-wrapper"
(div :class "flex overflow-x-auto gap-1 scrollbar-thin"
inner)))
(defcomp ~events-day-table (&key (list-container :as string) rows (pre-action :as string) (add-url :as string))
(defcomp ~day/table (&key (list-container :as string) rows (pre-action :as string) (add-url :as string))
(section :id "day-entries" :class list-container
(table :class "w-full text-sm border table-fixed"
(thead :class "bg-stone-100"
@@ -29,95 +29,95 @@
:sx-get add-url :sx-target "#entry-add-container" :sx-swap "innerHTML"
"+ Add entry"))))
(defcomp ~events-day-empty-row ()
(defcomp ~day/empty-row ()
(tr (td :colspan "6" :class "p-3 text-stone-500" "No entries yet.")))
(defcomp ~events-day-row-name (&key (href :as string) (pill-cls :as string) (name :as string))
(defcomp ~day/row-name (&key (href :as string) (pill-cls :as string) (name :as string))
(td :class "p-2 align-top w-2/6" (div :class "font-medium"
(a :href href :class pill-cls :sx-get href :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" name))))
(defcomp ~events-day-row-slot (&key (href :as string) (pill-cls :as string) (slot-name :as string) (time-str :as string))
(defcomp ~day/row-slot (&key (href :as string) (pill-cls :as string) (slot-name :as string) (time-str :as string))
(td :class "p-2 align-top w-1/6" (div :class "text-xs font-medium"
(a :href href :class pill-cls :sx-get href :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" slot-name)
(span :class "text-stone-600 font-normal" time-str))))
(defcomp ~events-day-row-time (&key (start :as string) (end :as string))
(defcomp ~day/row-time (&key (start :as string) (end :as string))
(td :class "p-2 align-top w-1/6" (div :class "text-xs text-stone-600" (str start end))))
(defcomp ~events-day-row-state (&key (state-id :as string) badge)
(defcomp ~day/row-state (&key (state-id :as string) badge)
(td :class "p-2 align-top w-1/6" (div :id state-id badge)))
(defcomp ~events-day-row-cost (&key (cost-str :as string))
(defcomp ~day/row-cost (&key (cost-str :as string))
(td :class "p-2 align-top w-1/6" (span :class "font-medium text-green-600" cost-str)))
(defcomp ~events-day-row-tickets (&key (price-str :as string) (count-str :as string))
(defcomp ~day/row-tickets (&key (price-str :as string) (count-str :as string))
(td :class "p-2 align-top w-1/6" (div :class "text-xs space-y-1"
(div :class "font-medium text-green-600" price-str)
(div :class "text-stone-600" count-str))))
(defcomp ~events-day-row-no-tickets ()
(defcomp ~day/row-no-tickets ()
(td :class "p-2 align-top w-1/6" (span :class "text-xs text-stone-400" "No tickets")))
(defcomp ~events-day-row-actions ()
(defcomp ~day/row-actions ()
(td :class "p-2 align-top w-1/6"))
(defcomp ~events-day-row (&key (tr-cls :as string) name slot state cost tickets actions)
(defcomp ~day/row (&key (tr-cls :as string) name slot state cost tickets actions)
(tr :class tr-cls name slot state cost tickets actions))
(defcomp ~events-day-admin-panel ()
(defcomp ~day/admin-panel ()
(div :class "p-4 text-sm text-stone-500" "Admin options"))
(defcomp ~events-day-entries-nav-oob-empty ()
(defcomp ~day/entries-nav-oob-empty ()
(div :id "day-entries-nav-wrapper" :sx-swap-oob "true"))
(defcomp ~events-day-entries-nav-oob (&key items)
(defcomp ~day/entries-nav-oob (&key items)
(div :class "flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
:id "day-entries-nav-wrapper" :sx-swap-oob "true"
(div :class "flex overflow-x-auto gap-1 scrollbar-thin" items)))
(defcomp ~events-day-nav-entry (&key (href :as string) (nav-btn :as string) (name :as string) (time-str :as string))
(defcomp ~day/nav-entry (&key (href :as string) (nav-btn :as string) (name :as string) (time-str :as string))
(a :href href :class nav-btn
(div :class "flex-1 min-w-0"
(div :class "font-medium truncate" name)
(div :class "text-xs text-stone-600 truncate" time-str))))
;; Day table from data — all row iteration in sx
(defcomp ~events-day-table-from-data (&key (list-container :as string) (pre-action :as string) (add-url :as string) (tr-cls :as string) (pill-cls :as string) (rows :as list?))
(~events-day-table
(defcomp ~day/table-from-data (&key (list-container :as string) (pre-action :as string) (add-url :as string) (tr-cls :as string) (pill-cls :as string) (rows :as list?))
(~day/table
:list-container list-container
:rows (if (empty? (or rows (list)))
(~events-day-empty-row)
(~day/empty-row)
(<> (map (lambda (r)
(~events-day-row
(~day/row
:tr-cls tr-cls
:name (~events-day-row-name
:name (~day/row-name
:href (get r "href") :pill-cls pill-cls :name (get r "name"))
:slot (if (get r "slot-name")
(~events-day-row-slot
(~day/row-slot
:href (get r "slot-href") :pill-cls pill-cls
:slot-name (get r "slot-name") :time-str (get r "slot-time"))
(~events-day-row-time :start (get r "start") :end (get r "end")))
:state (~events-day-row-state
(~day/row-time :start (get r "start") :end (get r "end")))
:state (~day/row-state
:state-id (get r "state-id")
:badge (~entry-state-badge :state (get r "state")))
:cost (~events-day-row-cost :cost-str (get r "cost-str"))
:badge (~entries/entry-state-badge :state (get r "state")))
:cost (~day/row-cost :cost-str (get r "cost-str"))
:tickets (if (get r "has-tickets")
(~events-day-row-tickets
(~day/row-tickets
:price-str (get r "price-str") :count-str (get r "count-str"))
(~events-day-row-no-tickets))
:actions (~events-day-row-actions)))
(~day/row-no-tickets))
:actions (~day/row-actions)))
(or rows (list)))))
:pre-action pre-action :add-url add-url))
;; Day entries nav OOB from data
(defcomp ~events-day-entries-nav-oob-from-data (&key (nav-btn :as string) (entries :as list?))
(defcomp ~day/entries-nav-oob-from-data (&key (nav-btn :as string) (entries :as list?))
(if (empty? (or entries (list)))
(~events-day-entries-nav-oob-empty)
(~events-day-entries-nav-oob
(~day/entries-nav-oob-empty)
(~day/entries-nav-oob
:items (<> (map (lambda (e)
(~events-day-nav-entry
(~day/nav-entry
:href (get e "href") :nav-btn nav-btn
:name (get e "name") :time-str (get e "time-str")))
entries)))))

View File

@@ -4,8 +4,8 @@
;; State badges — cond maps state string to class + label
;; ---------------------------------------------------------------------------
(defcomp ~entry-state-badge (&key state)
(~badge
(defcomp ~entries/entry-state-badge (&key state)
(~shared:misc/badge
:cls (cond
((= state "confirmed") "bg-emerald-100 text-emerald-800")
((= state "provisional") "bg-amber-100 text-amber-800")
@@ -21,7 +21,7 @@
((= state "declined") "Declined")
(true (or state "Unknown")))))
(defcomp ~entry-state-badge-lg (&key state)
(defcomp ~entries/entry-state-badge-lg (&key state)
(span :class (str "inline-flex items-center rounded-full px-3 py-1 text-sm font-medium "
(cond
((= state "confirmed") "bg-emerald-100 text-emerald-800")
@@ -38,8 +38,8 @@
((= state "declined") "Declined")
(true (or state "Unknown")))))
(defcomp ~ticket-state-badge (&key state)
(~badge
(defcomp ~entries/ticket-state-badge (&key state)
(~shared:misc/badge
:cls (cond
((= state "confirmed") "bg-emerald-100 text-emerald-800")
((= state "checked_in") "bg-blue-100 text-blue-800")
@@ -53,7 +53,7 @@
((= state "cancelled") "Cancelled")
(true (or state "Unknown")))))
(defcomp ~ticket-state-badge-lg (&key state)
(defcomp ~entries/ticket-state-badge-lg (&key state)
(span :class (str "inline-flex items-center rounded-full px-3 py-1 text-sm font-medium "
(cond
((= state "confirmed") "bg-emerald-100 text-emerald-800")
@@ -73,36 +73,36 @@
;; Entry card components
;; ---------------------------------------------------------------------------
(defcomp ~events-entry-title-linked (&key href name)
(defcomp ~entries/entry-title-linked (&key href name)
(a :href href :class "hover:text-emerald-700"
(h2 :class "text-lg font-semibold text-stone-900" name)))
(defcomp ~events-entry-title-plain (&key name)
(defcomp ~entries/entry-title-plain (&key name)
(h2 :class "text-lg font-semibold text-stone-900" name))
(defcomp ~events-entry-title-tile-linked (&key href name)
(defcomp ~entries/entry-title-tile-linked (&key href name)
(a :href href :class "hover:text-emerald-700"
(h2 :class "text-base font-semibold text-stone-900 line-clamp-2" name)))
(defcomp ~events-entry-title-tile-plain (&key name)
(defcomp ~entries/entry-title-tile-plain (&key name)
(h2 :class "text-base font-semibold text-stone-900 line-clamp-2" name))
(defcomp ~events-entry-page-badge (&key href title)
(defcomp ~entries/entry-page-badge (&key href title)
(a :href href :class "inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-amber-100 text-amber-800 hover:bg-amber-200" title))
(defcomp ~events-entry-cal-badge (&key name)
(defcomp ~entries/entry-cal-badge (&key name)
(span :class "inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-sky-100 text-sky-700" name))
(defcomp ~events-entry-time-linked (&key href date-str)
(defcomp ~entries/entry-time-linked (&key href date-str)
(<> (a :href href :class "hover:text-stone-700" date-str) " · "))
(defcomp ~events-entry-time-plain (&key date-str)
(defcomp ~entries/entry-time-plain (&key date-str)
(<> (span date-str) " · "))
(defcomp ~events-entry-cost (&key cost)
(defcomp ~entries/entry-cost (&key cost)
(div :class "mt-1 text-sm font-medium text-green-600" cost))
(defcomp ~events-entry-card (&key title badges time-parts cost widget)
(defcomp ~entries/entry-card (&key title badges time-parts cost widget)
(article :class "rounded-xl bg-white shadow-sm border border-stone-200 p-4"
(div :class "flex flex-col sm:flex-row sm:items-start justify-between gap-3"
(div :class "flex-1 min-w-0"
@@ -112,7 +112,7 @@
cost)
widget)))
(defcomp ~events-entry-card-tile (&key title badges time cost widget)
(defcomp ~entries/entry-card-tile (&key title badges time cost widget)
(article :class "rounded-xl bg-white shadow-sm border border-stone-200 overflow-hidden"
(div :class "p-3"
title
@@ -121,20 +121,20 @@
cost)
widget))
(defcomp ~events-entry-tile-widget-wrapper (&key widget)
(defcomp ~entries/entry-tile-widget-wrapper (&key widget)
(div :class "border-t border-stone-100 px-3 py-2" widget))
(defcomp ~events-entry-widget-wrapper (&key widget)
(defcomp ~entries/entry-widget-wrapper (&key widget)
(div :class "shrink-0" widget))
(defcomp ~events-date-separator (&key date-str)
(defcomp ~entries/date-separator (&key date-str)
(div :class "pt-2 pb-1"
(h3 :class "text-sm font-semibold text-stone-500 uppercase tracking-wide" date-str)))
(defcomp ~events-grid (&key grid-cls cards)
(defcomp ~entries/grid (&key grid-cls cards)
(div :class grid-cls cards))
(defcomp ~events-main-panel-body (&key toggle body)
(defcomp ~entries/main-panel-body (&key toggle body)
(<> toggle body (div :class "pb-8")))
@@ -143,46 +143,46 @@
;; ---------------------------------------------------------------------------
;; Ticket widget from data — replaces _ticket_widget_html Python composition
(defcomp ~events-tw-widget-from-data (&key entry-id price qty ticket-url csrf)
(~events-tw-widget :entry-id (str entry-id) :price price
(defcomp ~entries/tw-widget-from-data (&key entry-id price qty ticket-url csrf)
(~page/tw-widget :entry-id (str entry-id) :price price
:inner (if (= (or qty 0) 0)
(~events-tw-form :ticket-url ticket-url :target (str "#page-ticket-" entry-id)
(~page/tw-form :ticket-url ticket-url :target (str "#page-ticket-" entry-id)
:csrf csrf :entry-id (str entry-id) :count-val "1"
:btn (~events-tw-cart-plus))
:btn (~page/tw-cart-plus))
(<>
(~events-tw-form :ticket-url ticket-url :target (str "#page-ticket-" entry-id)
(~page/tw-form :ticket-url ticket-url :target (str "#page-ticket-" entry-id)
:csrf csrf :entry-id (str entry-id) :count-val (str (- qty 1))
:btn (~events-tw-minus))
(~events-tw-cart-icon :qty (str qty))
(~events-tw-form :ticket-url ticket-url :target (str "#page-ticket-" entry-id)
:btn (~page/tw-minus))
(~page/tw-cart-icon :qty (str qty))
(~page/tw-form :ticket-url ticket-url :target (str "#page-ticket-" entry-id)
:csrf csrf :entry-id (str entry-id) :count-val (str (+ qty 1))
:btn (~events-tw-plus))))))
:btn (~page/tw-plus))))))
;; Entry card (list view) from data
(defcomp ~events-entry-card-from-data (&key entry-href name day-href
(defcomp ~entries/entry-card-from-data (&key entry-href name day-href
page-badge-href page-badge-title cal-name
date-str start-time end-time is-page-scoped
cost has-ticket ticket-data)
(~events-entry-card
(~entries/entry-card
:title (if entry-href
(~events-entry-title-linked :href entry-href :name name)
(~events-entry-title-plain :name name))
(~entries/entry-title-linked :href entry-href :name name)
(~entries/entry-title-plain :name name))
:badges (<>
(when page-badge-title
(~events-entry-page-badge :href page-badge-href :title page-badge-title))
(~entries/entry-page-badge :href page-badge-href :title page-badge-title))
(when cal-name
(~events-entry-cal-badge :name cal-name)))
(~entries/entry-cal-badge :name cal-name)))
:time-parts (<>
(when (and day-href (not is-page-scoped))
(~events-entry-time-linked :href day-href :date-str date-str))
(~entries/entry-time-linked :href day-href :date-str date-str))
(when (and (not day-href) (not is-page-scoped) date-str)
(~events-entry-time-plain :date-str date-str))
(~entries/entry-time-plain :date-str date-str))
start-time
(when end-time (str " \u2013 " end-time)))
:cost (when cost (~events-entry-cost :cost cost))
:cost (when cost (~entries/entry-cost :cost cost))
:widget (when has-ticket
(~events-entry-widget-wrapper
:widget (~events-tw-widget-from-data
(~entries/entry-widget-wrapper
:widget (~entries/tw-widget-from-data
:entry-id (get ticket-data "entry-id")
:price (get ticket-data "price")
:qty (get ticket-data "qty")
@@ -190,24 +190,24 @@
:csrf (get ticket-data "csrf"))))))
;; Entry card (tile view) from data
(defcomp ~events-entry-card-tile-from-data (&key entry-href name day-href
(defcomp ~entries/entry-card-tile-from-data (&key entry-href name day-href
page-badge-href page-badge-title cal-name
date-str time-str
cost has-ticket ticket-data)
(~events-entry-card-tile
(~entries/entry-card-tile
:title (if entry-href
(~events-entry-title-tile-linked :href entry-href :name name)
(~events-entry-title-tile-plain :name name))
(~entries/entry-title-tile-linked :href entry-href :name name)
(~entries/entry-title-tile-plain :name name))
:badges (<>
(when page-badge-title
(~events-entry-page-badge :href page-badge-href :title page-badge-title))
(~entries/entry-page-badge :href page-badge-href :title page-badge-title))
(when cal-name
(~events-entry-cal-badge :name cal-name)))
(~entries/entry-cal-badge :name cal-name)))
:time time-str
:cost (when cost (~events-entry-cost :cost cost))
:cost (when cost (~entries/entry-cost :cost cost))
:widget (when has-ticket
(~events-entry-tile-widget-wrapper
:widget (~events-tw-widget-from-data
(~entries/entry-tile-widget-wrapper
:widget (~entries/tw-widget-from-data
:entry-id (get ticket-data "entry-id")
:price (get ticket-data "price")
:qty (get ticket-data "qty")
@@ -215,13 +215,13 @@
:csrf (get ticket-data "csrf"))))))
;; Entry cards list (with date separators + sentinel) from data
(defcomp ~events-entry-cards-from-data (&key items view page has-more next-url)
(defcomp ~entries/entry-cards-from-data (&key items view page has-more next-url)
(<>
(map (lambda (item)
(if (get item "is-separator")
(~events-date-separator :date-str (get item "date-str"))
(~entries/date-separator :date-str (get item "date-str"))
(if (= view "tile")
(~events-entry-card-tile-from-data
(~entries/entry-card-tile-from-data
:entry-href (get item "entry-href") :name (get item "name")
:day-href (get item "day-href")
:page-badge-href (get item "page-badge-href")
@@ -230,7 +230,7 @@
:date-str (get item "date-str") :time-str (get item "time-str")
:cost (get item "cost") :has-ticket (get item "has-ticket")
:ticket-data (get item "ticket-data"))
(~events-entry-card-from-data
(~entries/entry-card-from-data
:entry-href (get item "entry-href") :name (get item "name")
:day-href (get item "day-href")
:page-badge-href (get item "page-badge-href")
@@ -243,20 +243,20 @@
:ticket-data (get item "ticket-data")))))
(or items (list)))
(when has-more
(~sentinel-simple :id (str "sentinel-" page) :next-url next-url))))
(~shared:misc/sentinel-simple :id (str "sentinel-" page) :next-url next-url))))
;; Events main panel (toggle + cards grid) from data
(defcomp ~events-main-panel-from-data (&key toggle items view page has-more next-url)
(~events-main-panel-body
(defcomp ~entries/main-panel-from-data (&key toggle items view page has-more next-url)
(~entries/main-panel-body
:toggle toggle
:body (if items
(~events-grid
(~entries/grid
:grid-cls (if (= view "tile")
"max-w-full px-3 py-3 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4"
"max-w-full px-3 py-3 space-y-3")
:cards (~events-entry-cards-from-data
:cards (~entries/entry-cards-from-data
:items items :view view :page page
:has-more has-more :next-url next-url))
(~empty-state :icon "fa fa-calendar-xmark"
(~shared:misc/empty-state :icon "fa fa-calendar-xmark"
:message "No upcoming events"
:cls "px-3 py-12 text-center text-stone-400"))))

View File

@@ -5,25 +5,25 @@
;; Slot picker option (shared by entry-edit and entry-add)
;; ---------------------------------------------------------------------------
(defcomp ~events-slot-option (&key value data-start data-end data-flexible data-cost selected label)
(defcomp ~forms/slot-option (&key value data-start data-end data-flexible data-cost selected label)
(option :value value :data-start data-start :data-end data-end
:data-flexible data-flexible :data-cost data-cost
:selected selected
label))
(defcomp ~events-slot-picker (&key id options)
(defcomp ~forms/slot-picker (&key id options)
(select :id id :name "slot_id" :class "w-full border p-2 rounded"
:data-slot-picker "" :required "required"
options))
(defcomp ~events-no-slots ()
(defcomp ~forms/no-slots ()
(div :class "text-sm text-stone-500" "No slots defined for this day."))
;; ---------------------------------------------------------------------------
;; Entry edit form (_types/entry/_edit.html)
;; ---------------------------------------------------------------------------
(defcomp ~events-entry-edit-form (&key entry-id list-container put-url cancel-url csrf
(defcomp ~forms/entry-edit-form (&key entry-id list-container put-url cancel-url csrf
name-val slot-picker
start-val end-val cost-display
ticket-price-val ticket-count-val
@@ -115,7 +115,7 @@
;; Post search results (_types/entry/_post_search_results.html)
;; ---------------------------------------------------------------------------
(defcomp ~events-post-search-item (&key post-url entry-id csrf post-id
(defcomp ~forms/post-search-item (&key post-url entry-id csrf post-id
img title)
(form :sx-post post-url :sx-target (str "#entry-posts-" entry-id) :sx-swap "innerHTML"
:class "p-2 hover:bg-stone-50 cursor-pointer rounded text-sm border-b"
@@ -129,7 +129,7 @@
:data-confirm-cancel-text "Cancel"
img (span title))))
(defcomp ~events-post-search-sentinel (&key page next-url)
(defcomp ~forms/post-search-sentinel (&key page next-url)
(div :id (str "post-search-sentinel-" page)
:sx-get next-url
:sx-trigger "intersect once delay:250ms, sentinel:retry"
@@ -172,7 +172,7 @@
(div :class "text-xs text-center text-stone-400 js-loading" "Loading more...")
(div :class "text-xs text-center text-stone-400 js-neterr hidden" "Connection error. Retrying...")))
(defcomp ~events-post-search-end ()
(defcomp ~forms/post-search-end ()
(div :class "py-2 text-xs text-center text-stone-400" "End of results"))
@@ -180,17 +180,17 @@
;; Slot edit form (_types/slot/_edit.html)
;; ---------------------------------------------------------------------------
(defcomp ~events-day-checkbox (&key name label checked)
(defcomp ~forms/day-checkbox (&key name label checked)
(label :class "flex items-center gap-1 px-2 py-1 rounded-full bg-slate-100"
(input :type "checkbox" :name name :value "1" :data-day name :checked checked)
(span label)))
(defcomp ~events-day-all-checkbox (&key checked)
(defcomp ~forms/day-all-checkbox (&key checked)
(label :class "flex items-center gap-1 px-2 py-1 rounded-full bg-slate-200"
(input :type "checkbox" :data-day-all "" :checked checked)
(span "All")))
(defcomp ~events-slot-edit-form (&key slot-id list-container put-url cancel-url csrf
(defcomp ~forms/slot-edit-form (&key slot-id list-container put-url cancel-url csrf
name-val cost-val start-val end-val desc-val
days flexible-checked
action-btn cancel-btn)
@@ -271,7 +271,7 @@
;; Slot add form (_types/slots/_add.html)
;; ---------------------------------------------------------------------------
(defcomp ~events-slot-add-form (&key post-url csrf days action-btn cancel-btn cancel-url)
(defcomp ~forms/slot-add-form (&key post-url csrf days action-btn cancel-btn cancel-url)
(form :sx-post post-url :sx-target "#slots-table" :sx-select "#slots-table"
:sx-disinherit "sx-select" :sx-swap "outerHTML"
:sx-headers csrf :class "space-y-3"
@@ -312,7 +312,7 @@
:data-confirm-cancel-text "Cancel"
(i :class "fa fa-save") " Save slot"))))
(defcomp ~events-slot-add-button (&key pre-action add-url)
(defcomp ~forms/slot-add-button (&key pre-action add-url)
(button :type "button" :class pre-action
:sx-get add-url :sx-target "#slot-add-container" :sx-swap "innerHTML"
"+ Add slot"))
@@ -323,20 +323,20 @@
;; ---------------------------------------------------------------------------
;; Day checkboxes from data — replaces Python loop
(defcomp ~events-day-checkboxes-from-data (&key days-data all-checked)
(defcomp ~forms/day-checkboxes-from-data (&key days-data all-checked)
(<>
(~events-day-all-checkbox :checked (when all-checked "checked"))
(~forms/day-all-checkbox :checked (when all-checked "checked"))
(map (lambda (d)
(~events-day-checkbox
(~forms/day-checkbox
:name (get d "name")
:label (get d "label")
:checked (when (get d "checked") "checked")))
(or days-data (list)))))
;; Slot options from data — replaces _slot_options_html Python loop
(defcomp ~events-slot-options-from-data (&key slots)
(defcomp ~forms/slot-options-from-data (&key slots)
(<> (map (lambda (s)
(~events-slot-option
(~forms/slot-option
:value (get s "value")
:data-start (get s "data-start")
:data-end (get s "data-end")
@@ -347,32 +347,32 @@
(or slots (list)))))
;; Slot picker from data — wraps picker + options
(defcomp ~events-slot-picker-from-data (&key id slots)
(defcomp ~forms/slot-picker-from-data (&key id slots)
(if (empty? (or slots (list)))
(~events-no-slots)
(~events-slot-picker
(~forms/no-slots)
(~forms/slot-picker
:id id
:options (~events-slot-options-from-data :slots slots))))
:options (~forms/slot-options-from-data :slots slots))))
;; Slot edit form from data
(defcomp ~events-slot-edit-form-from-data (&key slot-id list-container put-url cancel-url csrf
(defcomp ~forms/slot-edit-form-from-data (&key slot-id list-container put-url cancel-url csrf
name-val cost-val start-val end-val desc-val
days-data all-checked flexible-checked
action-btn cancel-btn)
(~events-slot-edit-form
(~forms/slot-edit-form
:slot-id slot-id :list-container list-container
:put-url put-url :cancel-url cancel-url :csrf csrf
:name-val name-val :cost-val cost-val :start-val start-val
:end-val end-val :desc-val desc-val
:days (~events-day-checkboxes-from-data :days-data days-data :all-checked all-checked)
:days (~forms/day-checkboxes-from-data :days-data days-data :all-checked all-checked)
:flexible-checked flexible-checked
:action-btn action-btn :cancel-btn cancel-btn))
;; Slot add form from data
(defcomp ~events-slot-add-form-from-data (&key post-url csrf days-data action-btn cancel-btn cancel-url)
(~events-slot-add-form
(defcomp ~forms/slot-add-form-from-data (&key post-url csrf days-data action-btn cancel-btn cancel-url)
(~forms/slot-add-form
:post-url post-url :csrf csrf
:days (~events-day-checkboxes-from-data :days-data days-data)
:days (~forms/day-checkboxes-from-data :days-data days-data)
:action-btn action-btn :cancel-btn cancel-btn :cancel-url cancel-url))
@@ -380,7 +380,7 @@
;; Entry add form (_types/day/_add.html)
;; ---------------------------------------------------------------------------
(defcomp ~events-entry-add-form (&key post-url csrf slot-picker
(defcomp ~forms/entry-add-form (&key post-url csrf slot-picker
action-btn cancel-btn cancel-url)
(<>
(div :id "entry-errors" :class "mt-2 text-sm text-red-600")
@@ -446,7 +446,7 @@
:data-confirm-cancel-text "Cancel"
(i :class "fa fa-save") " Save entry")))))
(defcomp ~events-entry-add-button (&key pre-action add-url)
(defcomp ~forms/entry-add-button (&key pre-action add-url)
(button :type "button" :class pre-action
:sx-get add-url :sx-target "#entry-add-container" :sx-swap "innerHTML"
"+ Add entry"))
@@ -456,7 +456,7 @@
;; Ticket type edit form (_types/ticket_type/_edit.html)
;; ---------------------------------------------------------------------------
(defcomp ~events-ticket-type-edit-form (&key ticket-id list-container put-url cancel-url csrf
(defcomp ~forms/ticket-type-edit-form (&key ticket-id list-container put-url cancel-url csrf
name-val cost-val count-val
action-btn cancel-btn)
(section :id (str "ticket-" ticket-id) :class list-container
@@ -509,7 +509,7 @@
;; Ticket type add form (_types/ticket_types/_add.html)
;; ---------------------------------------------------------------------------
(defcomp ~events-ticket-type-add-form (&key post-url csrf action-btn cancel-btn cancel-url)
(defcomp ~forms/ticket-type-add-form (&key post-url csrf action-btn cancel-btn cancel-url)
(form :sx-post post-url :sx-target "#tickets-table" :sx-select "#tickets-table"
:sx-disinherit "sx-select" :sx-swap "outerHTML"
:sx-headers csrf :class "space-y-3"
@@ -540,7 +540,7 @@
:data-confirm-cancel-text "Cancel"
(i :class "fa fa-save") " Save ticket type"))))
(defcomp ~events-ticket-type-add-button (&key action-btn add-url)
(defcomp ~forms/ticket-type-add-button (&key action-btn add-url)
(button :class action-btn
:sx-get add-url :sx-target "#ticket-add-container" :sx-swap "innerHTML"
(i :class "fa fa-plus") " Add ticket type"))
@@ -550,6 +550,6 @@
;; Entry admin nav — placeholder
;; ---------------------------------------------------------------------------
(defcomp ~events-admin-placeholder-nav ()
(defcomp ~forms/admin-placeholder-nav ()
(div :class "relative nav-group"
(span :class "block px-3 py-2 text-stone-400 text-sm italic" "Admin options")))

View File

@@ -5,14 +5,14 @@
;; Container cards entries (fragments/container_cards_entries.html)
;; ---------------------------------------------------------------------------
(defcomp ~events-frag-entry-card (&key href name date-str time-str)
(defcomp ~fragments/frag-entry-card (&key href name date-str time-str)
(a :href href
:class "flex flex-col gap-1 px-3 py-2 bg-stone-50 hover:bg-stone-100 rounded border border-stone-200 transition text-sm whitespace-nowrap flex-shrink-0 min-w-[180px]"
(div :class "font-medium text-stone-900 truncate" name)
(div :class "text-xs text-stone-600" date-str)
(div :class "text-xs text-stone-500" time-str)))
(defcomp ~events-frag-entries-widget (&key cards)
(defcomp ~fragments/frag-entries-widget (&key cards)
(div :class "mt-4 mb-2"
(h3 :class "text-sm font-semibold text-stone-700 mb-2 px-2" "Events:")
(div :class "overflow-x-auto scrollbar-hide" :style "scroll-behavior: smooth;"
@@ -23,7 +23,7 @@
;; Account page tickets (fragments/account_page_tickets.html)
;; ---------------------------------------------------------------------------
(defcomp ~events-frag-ticket-item (&key href entry-name date-str calendar-name type-name badge)
(defcomp ~fragments/frag-ticket-item (&key href entry-name date-str calendar-name type-name badge)
(div :class "py-4 first:pt-0 last:pb-0"
(div :class "flex items-start justify-between gap-4"
(div :class "min-w-0 flex-1"
@@ -35,13 +35,13 @@
type-name))
(div :class "flex-shrink-0" badge))))
(defcomp ~events-frag-tickets-panel (&key items)
(defcomp ~fragments/frag-tickets-panel (&key items)
(div :class "w-full max-w-3xl mx-auto px-4 py-6"
(div :class "bg-white/70 backdrop-blur rounded-2xl shadow border border-stone-200 p-6 sm:p-8 space-y-6"
(h1 :class "text-xl font-semibold tracking-tight" "Tickets")
items)))
(defcomp ~events-frag-tickets-list (&key items)
(defcomp ~fragments/frag-tickets-list (&key items)
(div :class "divide-y divide-stone-100" items))
@@ -49,7 +49,7 @@
;; Account page bookings (fragments/account_page_bookings.html)
;; ---------------------------------------------------------------------------
(defcomp ~events-frag-booking-item (&key name date-str calendar-name cost-str badge)
(defcomp ~fragments/frag-booking-item (&key name date-str calendar-name cost-str badge)
(div :class "py-4 first:pt-0 last:pb-0"
(div :class "flex items-start justify-between gap-4"
(div :class "min-w-0 flex-1"
@@ -60,13 +60,13 @@
cost-str))
(div :class "flex-shrink-0" badge))))
(defcomp ~events-frag-bookings-panel (&key items)
(defcomp ~fragments/frag-bookings-panel (&key items)
(div :class "w-full max-w-3xl mx-auto px-4 py-6"
(div :class "bg-white/70 backdrop-blur rounded-2xl shadow border border-stone-200 p-6 sm:p-8 space-y-6"
(h1 :class "text-xl font-semibold tracking-tight" "Bookings")
items)))
(defcomp ~events-frag-bookings-list (&key items)
(defcomp ~fragments/frag-bookings-list (&key items)
(div :class "divide-y divide-stone-100" items))
@@ -75,12 +75,12 @@
;; ---------------------------------------------------------------------------
;; Container cards: list of widgets, each with entries
(defcomp ~events-frag-container-cards-from-data (&key widgets)
(defcomp ~fragments/frag-container-cards-from-data (&key widgets)
(<> (map (lambda (w)
(if (get w "entries")
(~events-frag-entries-widget
(~fragments/frag-entries-widget
:cards (<> (map (lambda (e)
(~events-frag-entry-card
(~fragments/frag-entry-card
:href (get e "href") :name (get e "name")
:date-str (get e "date-str") :time-str (get e "time-str")))
(get w "entries"))))
@@ -88,43 +88,43 @@
(or widgets (list)))))
;; Ticket item from data — composes badge + optional spans
(defcomp ~events-frag-ticket-item-from-data (&key href entry-name date-str calendar-name type-name state)
(~events-frag-ticket-item
(defcomp ~fragments/frag-ticket-item-from-data (&key href entry-name date-str calendar-name type-name state)
(~fragments/frag-ticket-item
:href href :entry-name entry-name :date-str date-str
:calendar-name (when calendar-name (span "\u00b7 " calendar-name))
:type-name (when type-name (span "\u00b7 " type-name))
:badge (~status-pill :status state)))
:badge (~shared:controls/status-pill :status state)))
;; Tickets panel from data — full panel with list iteration
(defcomp ~events-frag-tickets-panel-from-data (&key tickets)
(~events-frag-tickets-panel
(defcomp ~fragments/frag-tickets-panel-from-data (&key tickets)
(~fragments/frag-tickets-panel
:items (if (empty? (or tickets (list)))
(~empty-state :message "No tickets yet." :cls "text-sm text-stone-500")
(~events-frag-tickets-list
(~shared:misc/empty-state :message "No tickets yet." :cls "text-sm text-stone-500")
(~fragments/frag-tickets-list
:items (<> (map (lambda (t)
(~events-frag-ticket-item-from-data
(~fragments/frag-ticket-item-from-data
:href (get t "href") :entry-name (get t "entry-name")
:date-str (get t "date-str") :calendar-name (get t "calendar-name")
:type-name (get t "type-name") :state (get t "state")))
tickets))))))
;; Booking item from data — composes badge + optional spans
(defcomp ~events-frag-booking-item-from-data (&key name date-str end-time calendar-name cost-str state)
(~events-frag-booking-item
(defcomp ~fragments/frag-booking-item-from-data (&key name date-str end-time calendar-name cost-str state)
(~fragments/frag-booking-item
:name name
:date-str (<> date-str (when end-time (span "\u2013 " end-time)))
:calendar-name (when calendar-name (span "\u00b7 " calendar-name))
:cost-str (when cost-str (span "\u00b7 \u00a3" cost-str))
:badge (~status-pill :status state)))
:badge (~shared:controls/status-pill :status state)))
;; Bookings panel from data — full panel with list iteration
(defcomp ~events-frag-bookings-panel-from-data (&key bookings)
(~events-frag-bookings-panel
(defcomp ~fragments/frag-bookings-panel-from-data (&key bookings)
(~fragments/frag-bookings-panel
:items (if (empty? (or bookings (list)))
(~empty-state :message "No bookings yet." :cls "text-sm text-stone-500")
(~events-frag-bookings-list
(~shared:misc/empty-state :message "No bookings yet." :cls "text-sm text-stone-500")
(~fragments/frag-bookings-list
:items (<> (map (lambda (b)
(~events-frag-booking-item-from-data
(~fragments/frag-booking-item-from-data
:href (get b "href") :name (get b "name")
:date-str (get b "date-str") :end-time (get b "end-time")
:calendar-name (get b "calendar-name") :cost-str (get b "cost-str")

View File

@@ -8,12 +8,12 @@
(nav-class (or (get styles "nav_button") ""))
(hx-select "#main-panel, #search-mobile, #search-count-mobile, #search-desktop, #search-count-desktop, #menu-items-nav-wrapper"))
(<>
(~nav-group-link
(~shared:misc/nav-group-link
:href (app-url "account" "/tickets/")
:hx-select hx-select
:nav-class nav-class
:label "tickets")
(~nav-group-link
(~shared:misc/nav-group-link
:href (app-url "account" "/bookings/")
:hx-select hx-select
:nav-class nav-class

View File

@@ -10,13 +10,13 @@
(cond
(= slug "tickets")
(let ((tickets (service "calendar" "user-tickets" :user-id uid)))
(~events-frag-tickets-panel
(~fragments/frag-tickets-panel
:items (if (empty? tickets)
(~empty-state :message "No tickets yet."
(~shared:misc/empty-state :message "No tickets yet."
:cls "text-sm text-stone-500")
(~events-frag-tickets-list
(~fragments/frag-tickets-list
:items (<> (map (fn (t)
(~events-frag-ticket-item
(~fragments/frag-ticket-item
:href (app-url "events"
(str "/tickets/" (get t "code") "/"))
:entry-name (get t "entry_name")
@@ -25,18 +25,18 @@
(span (str "\u00b7 " (get t "calendar_name"))))
:type-name (when (get t "ticket_type_name")
(span (str "\u00b7 " (get t "ticket_type_name"))))
:badge (~status-pill :status (or (get t "state") ""))))
:badge (~shared:controls/status-pill :status (or (get t "state") ""))))
tickets))))))
(= slug "bookings")
(let ((bookings (service "calendar" "user-bookings" :user-id uid)))
(~events-frag-bookings-panel
(~fragments/frag-bookings-panel
:items (if (empty? bookings)
(~empty-state :message "No bookings yet."
(~shared:misc/empty-state :message "No bookings yet."
:cls "text-sm text-stone-500")
(~events-frag-bookings-list
(~fragments/frag-bookings-list
:items (<> (map (fn (b)
(~events-frag-booking-item
(~fragments/frag-booking-item
:name (get b "name")
:date-str (str (format-date (get b "start_at") "%d %b %Y, %H:%M")
(if (get b "end_at")
@@ -46,5 +46,5 @@
(span (str "\u00b7 " (get b "calendar_name"))))
:cost-str (when (get b "cost")
(span (str "\u00b7 \u00a3" (get b "cost"))))
:badge (~status-pill :status (or (get b "state") ""))))
:badge (~shared:controls/status-pill :status (or (get b "state") ""))))
bookings))))))))))

View File

@@ -19,13 +19,13 @@
(post-slug (or (nth slugs i) "")))
(<> (str "<!-- card-widget:" pid " -->")
(when (not (empty? entries))
(~events-frag-entries-widget
(~fragments/frag-entries-widget
:cards (<> (map (fn (e)
(let ((time-str (str (format-date (get e "start_at") "%H:%M")
(if (get e "end_at")
(str " \u2013 " (format-date (get e "end_at") "%H:%M"))
""))))
(~events-frag-entry-card
(~fragments/frag-entry-card
:href (app-url "events"
(str "/" post-slug
"/" (get e "calendar_slug")

View File

@@ -53,7 +53,7 @@
(if (get entry "end_at")
(str " " (format-date (get entry "end_at") "%H:%M"))
""))))
(~calendar-entry-nav
(~shared:navigation/calendar-entry-nav
:href (app-url "events" entry-path)
:name (get entry "name")
:date-str date-str
@@ -61,7 +61,7 @@
;; Infinite scroll sentinel
(when (and has-more (not (empty? purl)))
(~htmx-sentinel
(~shared:misc/htmx-sentinel
:id (str "entries-load-sentinel-" pg)
:hx-get (str purl "?page=" (+ pg 1))
:hx-trigger "intersect once"
@@ -74,7 +74,7 @@
(is-selected (if (not (empty? cur-cal))
(= (get cal "slug") cur-cal)
false)))
(~calendar-link-nav
(~shared:navigation/calendar-link-nav
:href href
:name (get cal "name")
:nav-class nav-class

View File

@@ -16,7 +16,7 @@
:container-type "page"
:container-id (get post "id")))
(cal-names (join ", " (map (fn (c) (get c "name")) calendars))))
(~link-card
(~shared:fragments/link-card
:title (get post "title")
:image (get post "feature_image")
:subtitle cal-names
@@ -28,7 +28,7 @@
:container-type "page"
:container-id (get post "id")))
(cal-names (join ", " (map (fn (c) (get c "name")) calendars))))
(~link-card
(~shared:fragments/link-card
:title (get post "title")
:image (get post "feature_image")
:subtitle cal-names

View File

@@ -1,12 +1,12 @@
;; Events header components
(defcomp ~events-calendars-label ()
(defcomp ~header/calendars-label ()
(<> (i :class "fa fa-calendar" :aria-hidden "true") (div "Calendars")))
(defcomp ~events-markets-label ()
(defcomp ~header/markets-label ()
(<> (i :class "fa fa-shopping-bag" :aria-hidden "true") (div "Markets")))
(defcomp ~events-calendar-label (&key name description)
(defcomp ~header/calendar-label (&key name description)
(div :class "flex flex-col md:flex-row md:gap-2 items-center min-w-0"
(div :class "flex flex-row items-center gap-2"
(i :class "fa fa-calendar")
@@ -15,16 +15,16 @@
:class "text-base font-normal break-words whitespace-normal min-w-0 break-all w-full text-center block"
description)))
(defcomp ~events-day-label (&key date-str)
(defcomp ~header/day-label (&key date-str)
(div :class "flex gap-1 items-center"
(i :class "fa fa-calendar-day")
(span date-str)))
(defcomp ~events-entry-label (&key entry-id title times)
(defcomp ~header/entry-label (&key entry-id title times)
(div :id (str "entry-title-" entry-id) :class "flex gap-1 items-center"
title times))
(defcomp ~events-slot-label (&key name description)
(defcomp ~header/slot-label (&key name description)
(div :class "flex flex-col md:flex-row md:gap-2 items-center"
(div :class "flex flex-row items-center gap-2"
(i :class "fa fa-clock")

View File

@@ -11,20 +11,20 @@
(let ((__cal (events-calendar-ctx))
(__sc (select-colours)))
(when (get __cal "slug")
(~menu-row-sx :id "calendar-row" :level 3
(~shared:layout/menu-row-sx :id "calendar-row" :level 3
:link-href (url-for "calendar.get"
:calendar-slug (get __cal "slug"))
:link-label-content (~events-calendar-label
:link-label-content (~header/calendar-label
:name (get __cal "name")
:description (get __cal "description"))
:nav (<>
(~nav-link :href (url-for "defpage_slots_listing"
(~shared:layout/nav-link :href (url-for "defpage_slots_listing"
:calendar-slug (get __cal "slug"))
:icon "fa fa-clock" :label "Slots"
:select-colours __sc)
(let ((__rights (app-rights)))
(when (get __rights "admin")
(~nav-link :href (url-for "defpage_calendar_admin"
(~shared:layout/nav-link :href (url-for "defpage_calendar_admin"
:calendar-slug (get __cal "slug"))
:icon "fa fa-cog"
:select-colours __sc))))
@@ -37,13 +37,13 @@
(let ((__cal (events-calendar-ctx))
(__sc (select-colours)))
(when (get __cal "slug")
(~menu-row-sx :id "calendar-admin-row" :level 4
(~shared:layout/menu-row-sx :id "calendar-admin-row" :level 4
:link-label "admin" :icon "fa fa-cog"
:nav (<>
(~nav-link :href (url-for "defpage_slots_listing"
(~shared:layout/nav-link :href (url-for "defpage_slots_listing"
:calendar-slug (get __cal "slug"))
:label "slots" :select-colours __sc)
(~nav-link :href (url-for "calendar.admin.calendar_description_edit"
(~shared:layout/nav-link :href (url-for "calendar.admin.calendar_description_edit"
:calendar-slug (get __cal "slug"))
:label "description" :select-colours __sc))
:child-id "calendar-admin-header-child"
@@ -55,13 +55,13 @@
(let ((__day (events-day-ctx))
(__cal (events-calendar-ctx)))
(when (get __day "date-str")
(~menu-row-sx :id "day-row" :level 4
(~shared:layout/menu-row-sx :id "day-row" :level 4
:link-href (url-for "calendar.day.show_day"
:calendar-slug (get __cal "slug")
:year (get __day "year")
:month (get __day "month")
:day (get __day "day"))
:link-label-content (~events-day-label
:link-label-content (~header/day-label
:date-str (get __day "date-str"))
:nav (get __day "nav")
:child-id "day-header-child"
@@ -73,7 +73,7 @@
(let ((__day (events-day-ctx))
(__cal (events-calendar-ctx)))
(when (get __day "date-str")
(~menu-row-sx :id "day-admin-row" :level 5
(~shared:layout/menu-row-sx :id "day-admin-row" :level 5
:link-href (url-for "defpage_day_admin"
:calendar-slug (get __cal "slug")
:year (get __day "year")
@@ -88,12 +88,12 @@
(quasiquote
(let ((__ectx (events-entry-ctx)))
(when (get __ectx "id")
(~menu-row-sx :id "entry-row" :level 5
(~shared:layout/menu-row-sx :id "entry-row" :level 5
:link-href (get __ectx "link-href")
:link-label-content (~events-entry-label
:link-label-content (~header/entry-label
:entry-id (get __ectx "id")
:title (~events-entry-title :name (get __ectx "name"))
:times (~events-entry-times :time-str (get __ectx "time-str")))
:title (~admin/entry-title :name (get __ectx "name"))
:times (~admin/entry-times :time-str (get __ectx "time-str")))
:nav (get __ectx "nav")
:child-id "entry-header-child"
:oob (unquote oob))))))
@@ -103,11 +103,11 @@
(quasiquote
(let ((__ectx (events-entry-ctx)))
(when (get __ectx "id")
(~menu-row-sx :id "entry-admin-row" :level 6
(~shared:layout/menu-row-sx :id "entry-admin-row" :level 6
:link-href (get __ectx "admin-href")
:link-label "admin" :icon "fa fa-cog"
:nav (when (get __ectx "is-admin")
(~nav-link :href (get __ectx "ticket-types-href")
(~shared:layout/nav-link :href (get __ectx "ticket-types-href")
:label "ticket_types"
:select-colours (get __ectx "select-colours")))
:child-id "entry-admin-header-child"
@@ -118,8 +118,8 @@
(quasiquote
(let ((__slot (events-slot-ctx)))
(when (get __slot "name")
(~menu-row-sx :id "slot-row" :level 5
:link-label-content (~events-slot-label
(~shared:layout/menu-row-sx :id "slot-row" :level 5
:link-label-content (~header/slot-label
:name (get __slot "name")
:description (get __slot "description"))
:child-id "slot-header-child"
@@ -131,12 +131,12 @@
(let ((__ectx (events-entry-ctx))
(__cal (events-calendar-ctx)))
(when (get __ectx "id")
(~menu-row-sx :id "ticket_types-row" :level 7
(~shared:layout/menu-row-sx :id "ticket_types-row" :level 7
:link-href (get __ectx "ticket-types-href")
:link-label-content (<>
(i :class "fa fa-ticket")
(div :class "shrink-0" "ticket types"))
:nav (~events-admin-placeholder-nav)
:nav (~forms/admin-placeholder-nav)
:child-id "ticket_type-header-child"
:oob (unquote oob))))))
@@ -145,22 +145,22 @@
(quasiquote
(let ((__tt (events-ticket-type-ctx)))
(when (get __tt "id")
(~menu-row-sx :id "ticket_type-row" :level 8
(~shared:layout/menu-row-sx :id "ticket_type-row" :level 8
:link-href (get __tt "link-href")
:link-label-content (div :class "flex flex-col md:flex-row md:gap-2 items-center"
(div :class "flex flex-row items-center gap-2"
(i :class "fa fa-ticket")
(div :class "shrink-0" (get __tt "name"))))
:nav (~events-admin-placeholder-nav)
:nav (~forms/admin-placeholder-nav)
:child-id "ticket_type-header-child-inner"
:oob (unquote oob))))))
(defmacro ~events-markets-header-auto (oob)
"Markets section header row."
(quasiquote
(~menu-row-sx :id "markets-row" :level 3
(~shared:layout/menu-row-sx :id "markets-row" :level 3
:link-href (url-for "defpage_events_markets")
:link-label-content (~events-markets-label)
:link-label-content (~header/markets-label)
:child-id "markets-header-child"
:oob (unquote oob))))
@@ -168,218 +168,218 @@
;; OOB clear helpers — clear deeper header rows not present at this level
;; ---------------------------------------------------------------------------
(defcomp ~events-clear-oob-cal-admin ()
(defcomp ~layouts/clear-oob-cal-admin ()
"Clear OOB divs for cal-admin level (keeps down to calendar-admin)."
(<>
(~clear-oob-div :id "entry-admin-row")
(~clear-oob-div :id "entry-admin-header-child")
(~clear-oob-div :id "entry-row")
(~clear-oob-div :id "entry-header-child")
(~clear-oob-div :id "day-admin-row")
(~clear-oob-div :id "day-admin-header-child")
(~clear-oob-div :id "day-row")
(~clear-oob-div :id "day-header-child")
(~clear-oob-div :id "calendars-row")
(~clear-oob-div :id "calendars-header-child")))
(~shared:layout/clear-oob-div :id "entry-admin-row")
(~shared:layout/clear-oob-div :id "entry-admin-header-child")
(~shared:layout/clear-oob-div :id "entry-row")
(~shared:layout/clear-oob-div :id "entry-header-child")
(~shared:layout/clear-oob-div :id "day-admin-row")
(~shared:layout/clear-oob-div :id "day-admin-header-child")
(~shared:layout/clear-oob-div :id "day-row")
(~shared:layout/clear-oob-div :id "day-header-child")
(~shared:layout/clear-oob-div :id "calendars-row")
(~shared:layout/clear-oob-div :id "calendars-header-child")))
(defcomp ~events-clear-oob-slot ()
(defcomp ~layouts/clear-oob-slot ()
"Clear OOB divs for slot level."
(<>
(~clear-oob-div :id "entry-admin-row")
(~clear-oob-div :id "entry-admin-header-child")
(~clear-oob-div :id "entry-row")
(~clear-oob-div :id "entry-header-child")
(~clear-oob-div :id "day-admin-row")
(~clear-oob-div :id "day-admin-header-child")
(~clear-oob-div :id "day-row")
(~clear-oob-div :id "day-header-child")
(~clear-oob-div :id "calendars-row")
(~clear-oob-div :id "calendars-header-child")))
(~shared:layout/clear-oob-div :id "entry-admin-row")
(~shared:layout/clear-oob-div :id "entry-admin-header-child")
(~shared:layout/clear-oob-div :id "entry-row")
(~shared:layout/clear-oob-div :id "entry-header-child")
(~shared:layout/clear-oob-div :id "day-admin-row")
(~shared:layout/clear-oob-div :id "day-admin-header-child")
(~shared:layout/clear-oob-div :id "day-row")
(~shared:layout/clear-oob-div :id "day-header-child")
(~shared:layout/clear-oob-div :id "calendars-row")
(~shared:layout/clear-oob-div :id "calendars-header-child")))
(defcomp ~events-clear-oob-day-admin ()
(defcomp ~layouts/clear-oob-day-admin ()
"Clear OOB divs for day-admin level."
(<>
(~clear-oob-div :id "entry-admin-row")
(~clear-oob-div :id "entry-admin-header-child")
(~clear-oob-div :id "entry-row")
(~clear-oob-div :id "entry-header-child")
(~clear-oob-div :id "calendars-row")
(~clear-oob-div :id "calendars-header-child")))
(~shared:layout/clear-oob-div :id "entry-admin-row")
(~shared:layout/clear-oob-div :id "entry-admin-header-child")
(~shared:layout/clear-oob-div :id "entry-row")
(~shared:layout/clear-oob-div :id "entry-header-child")
(~shared:layout/clear-oob-div :id "calendars-row")
(~shared:layout/clear-oob-div :id "calendars-header-child")))
(defcomp ~events-clear-oob-entry ()
(defcomp ~layouts/clear-oob-entry ()
"Clear OOB divs for entry level (public, no admin rows)."
(<>
(~clear-oob-div :id "entry-admin-row")
(~clear-oob-div :id "entry-admin-header-child")
(~clear-oob-div :id "day-admin-row")
(~clear-oob-div :id "day-admin-header-child")
(~clear-oob-div :id "calendar-admin-row")
(~clear-oob-div :id "calendar-admin-header-child")
(~clear-oob-div :id "calendars-row")
(~clear-oob-div :id "calendars-header-child")
(~clear-oob-div :id "post-admin-row")
(~clear-oob-div :id "post-admin-header-child")))
(~shared:layout/clear-oob-div :id "entry-admin-row")
(~shared:layout/clear-oob-div :id "entry-admin-header-child")
(~shared:layout/clear-oob-div :id "day-admin-row")
(~shared:layout/clear-oob-div :id "day-admin-header-child")
(~shared:layout/clear-oob-div :id "calendar-admin-row")
(~shared:layout/clear-oob-div :id "calendar-admin-header-child")
(~shared:layout/clear-oob-div :id "calendars-row")
(~shared:layout/clear-oob-div :id "calendars-header-child")
(~shared:layout/clear-oob-div :id "post-admin-row")
(~shared:layout/clear-oob-div :id "post-admin-header-child")))
(defcomp ~events-clear-oob-entry-admin ()
(defcomp ~layouts/clear-oob-entry-admin ()
"Clear OOB divs for entry-admin level."
(<>
(~clear-oob-div :id "calendars-row")
(~clear-oob-div :id "calendars-header-child")))
(~shared:layout/clear-oob-div :id "calendars-row")
(~shared:layout/clear-oob-div :id "calendars-header-child")))
;; ---------------------------------------------------------------------------
;; OOB clear helpers for renders.py — clear all deeper IDs except kept ones
;; ---------------------------------------------------------------------------
(defcomp ~events-clear-deeper-post ()
(defcomp ~layouts/clear-deeper-post ()
"Clear all events IDs deeper than post level."
(<>
(~clear-oob-div :id "entry-admin-row") (~clear-oob-div :id "entry-admin-header-child")
(~clear-oob-div :id "entry-row") (~clear-oob-div :id "entry-header-child")
(~clear-oob-div :id "day-admin-row") (~clear-oob-div :id "day-admin-header-child")
(~clear-oob-div :id "day-row") (~clear-oob-div :id "day-header-child")
(~clear-oob-div :id "calendar-admin-row") (~clear-oob-div :id "calendar-admin-header-child")
(~clear-oob-div :id "calendar-row") (~clear-oob-div :id "calendar-header-child")
(~clear-oob-div :id "calendars-row") (~clear-oob-div :id "calendars-header-child")
(~clear-oob-div :id "post-admin-row") (~clear-oob-div :id "post-admin-header-child")))
(~shared:layout/clear-oob-div :id "entry-admin-row") (~shared:layout/clear-oob-div :id "entry-admin-header-child")
(~shared:layout/clear-oob-div :id "entry-row") (~shared:layout/clear-oob-div :id "entry-header-child")
(~shared:layout/clear-oob-div :id "day-admin-row") (~shared:layout/clear-oob-div :id "day-admin-header-child")
(~shared:layout/clear-oob-div :id "day-row") (~shared:layout/clear-oob-div :id "day-header-child")
(~shared:layout/clear-oob-div :id "calendar-admin-row") (~shared:layout/clear-oob-div :id "calendar-admin-header-child")
(~shared:layout/clear-oob-div :id "calendar-row") (~shared:layout/clear-oob-div :id "calendar-header-child")
(~shared:layout/clear-oob-div :id "calendars-row") (~shared:layout/clear-oob-div :id "calendars-header-child")
(~shared:layout/clear-oob-div :id "post-admin-row") (~shared:layout/clear-oob-div :id "post-admin-header-child")))
(defcomp ~events-clear-deeper-post-admin ()
(defcomp ~layouts/clear-deeper-post-admin ()
"Clear all events IDs deeper than post-admin level."
(<>
(~clear-oob-div :id "entry-admin-row") (~clear-oob-div :id "entry-admin-header-child")
(~clear-oob-div :id "entry-row") (~clear-oob-div :id "entry-header-child")
(~clear-oob-div :id "day-admin-row") (~clear-oob-div :id "day-admin-header-child")
(~clear-oob-div :id "day-row") (~clear-oob-div :id "day-header-child")
(~clear-oob-div :id "calendar-admin-row") (~clear-oob-div :id "calendar-admin-header-child")
(~clear-oob-div :id "calendar-row") (~clear-oob-div :id "calendar-header-child")
(~clear-oob-div :id "calendars-row") (~clear-oob-div :id "calendars-header-child")))
(~shared:layout/clear-oob-div :id "entry-admin-row") (~shared:layout/clear-oob-div :id "entry-admin-header-child")
(~shared:layout/clear-oob-div :id "entry-row") (~shared:layout/clear-oob-div :id "entry-header-child")
(~shared:layout/clear-oob-div :id "day-admin-row") (~shared:layout/clear-oob-div :id "day-admin-header-child")
(~shared:layout/clear-oob-div :id "day-row") (~shared:layout/clear-oob-div :id "day-header-child")
(~shared:layout/clear-oob-div :id "calendar-admin-row") (~shared:layout/clear-oob-div :id "calendar-admin-header-child")
(~shared:layout/clear-oob-div :id "calendar-row") (~shared:layout/clear-oob-div :id "calendar-header-child")
(~shared:layout/clear-oob-div :id "calendars-row") (~shared:layout/clear-oob-div :id "calendars-header-child")))
(defcomp ~events-clear-deeper-calendar ()
(defcomp ~layouts/clear-deeper-calendar ()
"Clear all events IDs deeper than calendar level."
(<>
(~clear-oob-div :id "entry-admin-row") (~clear-oob-div :id "entry-admin-header-child")
(~clear-oob-div :id "entry-row") (~clear-oob-div :id "entry-header-child")
(~clear-oob-div :id "day-admin-row") (~clear-oob-div :id "day-admin-header-child")
(~clear-oob-div :id "day-row") (~clear-oob-div :id "day-header-child")
(~clear-oob-div :id "calendar-admin-row") (~clear-oob-div :id "calendar-admin-header-child")
(~clear-oob-div :id "calendars-row") (~clear-oob-div :id "calendars-header-child")
(~clear-oob-div :id "post-admin-row") (~clear-oob-div :id "post-admin-header-child")))
(~shared:layout/clear-oob-div :id "entry-admin-row") (~shared:layout/clear-oob-div :id "entry-admin-header-child")
(~shared:layout/clear-oob-div :id "entry-row") (~shared:layout/clear-oob-div :id "entry-header-child")
(~shared:layout/clear-oob-div :id "day-admin-row") (~shared:layout/clear-oob-div :id "day-admin-header-child")
(~shared:layout/clear-oob-div :id "day-row") (~shared:layout/clear-oob-div :id "day-header-child")
(~shared:layout/clear-oob-div :id "calendar-admin-row") (~shared:layout/clear-oob-div :id "calendar-admin-header-child")
(~shared:layout/clear-oob-div :id "calendars-row") (~shared:layout/clear-oob-div :id "calendars-header-child")
(~shared:layout/clear-oob-div :id "post-admin-row") (~shared:layout/clear-oob-div :id "post-admin-header-child")))
(defcomp ~events-clear-deeper-day ()
(defcomp ~layouts/clear-deeper-day ()
"Clear all events IDs deeper than day level."
(<>
(~clear-oob-div :id "entry-admin-row") (~clear-oob-div :id "entry-admin-header-child")
(~clear-oob-div :id "entry-row") (~clear-oob-div :id "entry-header-child")
(~clear-oob-div :id "day-admin-row") (~clear-oob-div :id "day-admin-header-child")
(~clear-oob-div :id "calendar-admin-row") (~clear-oob-div :id "calendar-admin-header-child")
(~clear-oob-div :id "calendars-row") (~clear-oob-div :id "calendars-header-child")
(~clear-oob-div :id "post-admin-row") (~clear-oob-div :id "post-admin-header-child")))
(~shared:layout/clear-oob-div :id "entry-admin-row") (~shared:layout/clear-oob-div :id "entry-admin-header-child")
(~shared:layout/clear-oob-div :id "entry-row") (~shared:layout/clear-oob-div :id "entry-header-child")
(~shared:layout/clear-oob-div :id "day-admin-row") (~shared:layout/clear-oob-div :id "day-admin-header-child")
(~shared:layout/clear-oob-div :id "calendar-admin-row") (~shared:layout/clear-oob-div :id "calendar-admin-header-child")
(~shared:layout/clear-oob-div :id "calendars-row") (~shared:layout/clear-oob-div :id "calendars-header-child")
(~shared:layout/clear-oob-div :id "post-admin-row") (~shared:layout/clear-oob-div :id "post-admin-header-child")))
;; ---------------------------------------------------------------------------
;; Calendar admin layout: root + post + child(post-admin + cal + cal-admin)
;; ---------------------------------------------------------------------------
(defcomp ~events-cal-admin-layout-full ()
(defcomp ~layouts/cal-admin-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (<> (~post-header-auto nil)
(~post-admin-header-auto nil "calendars")
(~events-calendar-header-auto nil)
(~events-calendar-admin-header-auto nil)))))
(defcomp ~events-cal-admin-layout-oob ()
(defcomp ~layouts/cal-admin-layout-oob ()
(<> (~post-admin-header-auto true "calendars")
(~events-calendar-header-auto true)
(~oob-header-sx :parent-id "calendar-header-child"
(~shared:layout/oob-header-sx :parent-id "calendar-header-child"
:row (~events-calendar-admin-header-auto nil))
(~events-clear-oob-cal-admin)
(~layouts/clear-oob-cal-admin)
(~root-header-auto true)))
;; ---------------------------------------------------------------------------
;; Slots layout: same full as cal-admin
;; ---------------------------------------------------------------------------
(defcomp ~events-slots-layout-full ()
(defcomp ~layouts/slots-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (<> (~post-header-auto nil)
(~post-admin-header-auto nil "calendars")
(~events-calendar-header-auto nil)
(~events-calendar-admin-header-auto nil)))))
(defcomp ~events-slots-layout-oob ()
(defcomp ~layouts/slots-layout-oob ()
(<> (~post-admin-header-auto true "calendars")
(~events-calendar-admin-header-auto true)
(~events-clear-oob-cal-admin)
(~layouts/clear-oob-cal-admin)
(~root-header-auto true)))
;; ---------------------------------------------------------------------------
;; Slot detail layout: root + post + child(admin + cal + cal-admin + slot)
;; ---------------------------------------------------------------------------
(defcomp ~events-slot-layout-full ()
(defcomp ~layouts/slot-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (<> (~post-header-auto nil)
(~post-admin-header-auto nil "calendars")
(~events-calendar-header-auto nil)
(~events-calendar-admin-header-auto nil)
(~events-slot-header-auto nil)))))
(defcomp ~events-slot-layout-oob ()
(defcomp ~layouts/slot-layout-oob ()
(<> (~post-admin-header-auto true "calendars")
(~events-calendar-admin-header-auto true)
(~oob-header-sx :parent-id "calendar-admin-header-child"
(~shared:layout/oob-header-sx :parent-id "calendar-admin-header-child"
:row (~events-slot-header-auto nil))
(~events-clear-oob-slot)
(~layouts/clear-oob-slot)
(~root-header-auto true)))
;; ---------------------------------------------------------------------------
;; Day admin layout: root + post + child(admin + cal + day + day-admin)
;; ---------------------------------------------------------------------------
(defcomp ~events-day-admin-layout-full ()
(defcomp ~layouts/day-admin-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (<> (~post-header-auto nil)
(~post-admin-header-auto nil "calendars")
(~events-calendar-header-auto nil)
(~events-day-header-auto nil)
(~events-day-admin-header-auto nil)))))
(defcomp ~events-day-admin-layout-oob ()
(defcomp ~layouts/day-admin-layout-oob ()
(<> (~post-admin-header-auto true "calendars")
(~events-calendar-header-auto true)
(~oob-header-sx :parent-id "day-header-child"
(~shared:layout/oob-header-sx :parent-id "day-header-child"
:row (~events-day-admin-header-auto nil))
(~events-clear-oob-day-admin)
(~layouts/clear-oob-day-admin)
(~root-header-auto true)))
;; ---------------------------------------------------------------------------
;; Entry layout: root + child(post + cal + day + entry) — public, no admin
;; ---------------------------------------------------------------------------
(defcomp ~events-entry-layout-full ()
(defcomp ~layouts/entry-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (<> (~post-header-auto nil)
(~events-calendar-header-auto nil)
(~events-day-header-auto nil)
(~events-entry-header-auto nil)))))
(defcomp ~events-entry-layout-oob ()
(defcomp ~layouts/entry-layout-oob ()
(<> (~events-day-header-auto true)
(~oob-header-sx :parent-id "day-header-child"
(~shared:layout/oob-header-sx :parent-id "day-header-child"
:row (~events-entry-header-auto nil))
(~events-clear-oob-entry)
(~layouts/clear-oob-entry)
(~root-header-auto true)))
;; ---------------------------------------------------------------------------
;; Entry admin layout: root + post + child(admin + cal + day + entry + entry-admin)
;; ---------------------------------------------------------------------------
(defcomp ~events-entry-admin-layout-full ()
(defcomp ~layouts/entry-admin-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (<> (~post-header-auto nil)
(~post-admin-header-auto nil "calendars")
(~events-calendar-header-auto nil)
@@ -387,21 +387,21 @@
(~events-entry-header-auto nil)
(~events-entry-admin-header-auto nil)))))
(defcomp ~events-entry-admin-layout-oob ()
(defcomp ~layouts/entry-admin-layout-oob ()
(<> (~post-admin-header-auto true "calendars")
(~events-entry-header-auto true)
(~oob-header-sx :parent-id "entry-header-child"
(~shared:layout/oob-header-sx :parent-id "entry-header-child"
:row (~events-entry-admin-header-auto nil))
(~events-clear-oob-entry-admin)
(~layouts/clear-oob-entry-admin)
(~root-header-auto true)))
;; ---------------------------------------------------------------------------
;; Ticket types layout: root + child(post + cal + day + entry + entry-admin + ticket-types)
;; ---------------------------------------------------------------------------
(defcomp ~events-ticket-types-layout-full ()
(defcomp ~layouts/ticket-types-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (<> (~post-header-auto nil)
(~events-calendar-header-auto nil)
(~events-day-header-auto nil)
@@ -409,9 +409,9 @@
(~events-entry-admin-header-auto nil)
(~events-ticket-types-header-auto nil)))))
(defcomp ~events-ticket-types-layout-oob ()
(defcomp ~layouts/ticket-types-layout-oob ()
(<> (~events-entry-admin-header-auto true)
(~oob-header-sx :parent-id "entry-admin-header-child"
(~shared:layout/oob-header-sx :parent-id "entry-admin-header-child"
:row (~events-ticket-types-header-auto nil))
(~root-header-auto true)))
@@ -419,9 +419,9 @@
;; Ticket type layout: all headers down to ticket-type
;; ---------------------------------------------------------------------------
(defcomp ~events-ticket-type-layout-full ()
(defcomp ~layouts/ticket-type-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (<> (~post-header-auto nil)
(~events-calendar-header-auto nil)
(~events-day-header-auto nil)
@@ -430,9 +430,9 @@
(~events-ticket-types-header-auto nil)
(~events-ticket-type-header-auto nil)))))
(defcomp ~events-ticket-type-layout-oob ()
(defcomp ~layouts/ticket-type-layout-oob ()
(<> (~events-ticket-types-header-auto true)
(~oob-header-sx :parent-id "ticket_types-header-child"
(~shared:layout/oob-header-sx :parent-id "ticket_types-header-child"
:row (~events-ticket-type-header-auto nil))
(~root-header-auto true)))
@@ -440,14 +440,14 @@
;; Markets layout: root + child(post + markets)
;; ---------------------------------------------------------------------------
(defcomp ~events-markets-layout-full ()
(defcomp ~layouts/markets-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (<> (~post-header-auto nil)
(~events-markets-header-auto nil)))))
(defcomp ~events-markets-layout-oob ()
(defcomp ~layouts/markets-layout-oob ()
(<> (~post-header-auto true)
(~oob-header-sx :parent-id "post-header-child"
(~shared:layout/oob-header-sx :parent-id "post-header-child"
:row (~events-markets-header-auto nil))
(~root-header-auto true)))

View File

@@ -1,15 +1,15 @@
;; Events page-level components (slots, ticket types, buy form, cart, posts nav)
(defcomp ~events-slot-days-pills (&key days-inner)
(defcomp ~page/slot-days-pills (&key days-inner)
(div :class "flex flex-wrap gap-1" days-inner))
(defcomp ~events-slot-day-pill (&key day)
(defcomp ~page/slot-day-pill (&key day)
(span :class "px-2 py-0.5 rounded-full text-xs bg-slate-200" day))
(defcomp ~events-slot-no-days ()
(defcomp ~page/slot-no-days ()
(span :class "text-xs text-slate-400" "No days"))
(defcomp ~events-slot-panel (&key slot-id list-container days flexible time-str cost-str pre-action edit-url)
(defcomp ~page/slot-panel (&key slot-id list-container days flexible time-str cost-str pre-action edit-url)
(section :id (str "slot-" slot-id) :class list-container
(div :class "flex flex-col"
(div :class "text-xs font-semibold uppercase tracking-wide text-stone-500" "Days")
@@ -27,15 +27,15 @@
(button :type "button" :class pre-action :sx-get edit-url
:sx-target (str "#slot-" slot-id) :sx-swap "outerHTML" "Edit")))
(defcomp ~events-slot-description-oob (&key description)
(defcomp ~page/slot-description-oob (&key description)
(div :id "slot-description-title" :sx-swap-oob "outerHTML"
:class "text-base font-normal break-words whitespace-normal min-w-0 break-all w-full text-center block"
description))
(defcomp ~events-slots-empty-row ()
(defcomp ~page/slots-empty-row ()
(tr (td :colspan "5" :class "p-3 text-stone-500" "No slots yet.")))
(defcomp ~events-slots-row (&key tr-cls slot-href pill-cls hx-select slot-name description
(defcomp ~page/slots-row (&key tr-cls slot-href pill-cls hx-select slot-name description
flexible days time-str cost-str action-btn del-url csrf-hdr)
(tr :class tr-cls
(td :class "p-2 align-top w-1/6"
@@ -57,7 +57,7 @@
:sx-swap "outerHTML" :sx-headers csrf-hdr :sx-trigger "confirmed"
(i :class "fa-solid fa-trash")))))
(defcomp ~events-slots-table (&key list-container rows pre-action add-url)
(defcomp ~page/slots-table (&key list-container rows pre-action add-url)
(section :id "slots-table" :class list-container
(table :class "w-full text-sm border table-fixed"
(thead :class "bg-stone-100"
@@ -78,61 +78,61 @@
;; ---------------------------------------------------------------------------
;; Days pills from data — replaces Python loop
(defcomp ~events-days-pills-from-data (&key days)
(defcomp ~page/days-pills-from-data (&key days)
(if (empty? (or days (list)))
(~events-slot-no-days)
(~events-slot-days-pills
:days-inner (<> (map (lambda (d) (~events-slot-day-pill :day d)) days)))))
(~page/slot-no-days)
(~page/slot-days-pills
:days-inner (<> (map (lambda (d) (~page/slot-day-pill :day d)) days)))))
;; Slot panel from data
(defcomp ~events-slot-panel-from-data (&key slot-id list-container days
(defcomp ~page/slot-panel-from-data (&key slot-id list-container days
flexible time-str cost-str
pre-action edit-url description oob)
(<>
(~events-slot-panel
(~page/slot-panel
:slot-id slot-id :list-container list-container
:days (~events-days-pills-from-data :days days)
:days (~page/days-pills-from-data :days days)
:flexible flexible :time-str time-str :cost-str cost-str
:pre-action pre-action :edit-url edit-url)
(when oob
(~events-slot-description-oob :description (or description "")))))
(~page/slot-description-oob :description (or description "")))))
;; Slots table from data
(defcomp ~events-slots-table-from-data (&key list-container slots pre-action add-url
(defcomp ~page/slots-table-from-data (&key list-container slots pre-action add-url
tr-cls pill-cls action-btn hx-select csrf-hdr)
(~events-slots-table
(~page/slots-table
:list-container list-container
:rows (if (empty? (or slots (list)))
(~events-slots-empty-row)
(~page/slots-empty-row)
(<> (map (lambda (s)
(~events-slots-row
(~page/slots-row
:tr-cls tr-cls :slot-href (get s "slot-href")
:pill-cls pill-cls :hx-select hx-select
:slot-name (get s "slot-name") :description (get s "description")
:flexible (get s "flexible")
:days (~events-days-pills-from-data :days (get s "days"))
:days (~page/days-pills-from-data :days (get s "days"))
:time-str (get s "time-str")
:cost-str (get s "cost-str") :action-btn action-btn
:del-url (get s "del-url") :csrf-hdr csrf-hdr))
(or slots (list)))))
:pre-action pre-action :add-url add-url))
(defcomp ~events-ticket-type-col (&key label value)
(defcomp ~page/ticket-type-col (&key label value)
(div :class "flex flex-col"
(div :class "text-xs font-semibold uppercase tracking-wide text-stone-500" label)
(div :class "mt-1" value)))
(defcomp ~events-ticket-type-panel (&key ticket-id list-container c1 c2 c3 pre-action edit-url)
(defcomp ~page/ticket-type-panel (&key ticket-id list-container c1 c2 c3 pre-action edit-url)
(section :id (str "ticket-" ticket-id) :class list-container
(div :class "grid grid-cols-1 sm:grid-cols-3 gap-4 text-sm"
c1 c2 c3)
(button :type "button" :class pre-action :sx-get edit-url
:sx-target (str "#ticket-" ticket-id) :sx-swap "outerHTML" "Edit")))
(defcomp ~events-ticket-types-empty-row ()
(defcomp ~page/ticket-types-empty-row ()
(tr (td :colspan "4" :class "p-3 text-stone-500" "No ticket types yet.")))
(defcomp ~events-ticket-types-row (&key tr-cls tt-href pill-cls hx-select tt-name cost-str count
(defcomp ~page/ticket-types-row (&key tr-cls tt-href pill-cls hx-select tt-name cost-str count
action-btn del-url csrf-hdr)
(tr :class tr-cls
(td :class "p-2 align-top w-1/3"
@@ -151,7 +151,7 @@
:sx-swap "outerHTML" :sx-headers csrf-hdr :sx-trigger "confirmed"
(i :class "fa-solid fa-trash")))))
(defcomp ~events-ticket-types-table (&key list-container rows action-btn add-url)
(defcomp ~page/ticket-types-table (&key list-container rows action-btn add-url)
(section :id "tickets-table" :class list-container
(table :class "w-full text-sm border table-fixed"
(thead :class "bg-stone-100"
@@ -164,7 +164,7 @@
(button :class action-btn :sx-get add-url :sx-target "#ticket-add-container" :sx-swap "innerHTML"
(i :class "fa fa-plus") " Add ticket type"))))
(defcomp ~events-ticket-config-display (&key price-str count-str show-js)
(defcomp ~page/ticket-config-display (&key price-str count-str show-js)
(div :class "space-y-2"
(div :class "flex items-center gap-2"
(span :class "text-sm font-medium text-stone-700" "Price:")
@@ -175,13 +175,13 @@
(button :type "button" :class "text-xs text-blue-600 hover:text-blue-800 underline"
:onclick show-js "Edit ticket config")))
(defcomp ~events-ticket-config-none (&key show-js)
(defcomp ~page/ticket-config-none (&key show-js)
(div :class "space-y-2"
(span :class "text-sm text-stone-400" "No tickets configured")
(button :type "button" :class "block text-xs text-blue-600 hover:text-blue-800 underline"
:onclick show-js "Configure tickets")))
(defcomp ~events-ticket-config-form (&key entry-id hidden-cls update-url csrf price-val count-val hide-js)
(defcomp ~page/ticket-config-form (&key entry-id hidden-cls update-url csrf price-val count-val hide-js)
(form :id (str "ticket-form-" entry-id) :class (str hidden-cls " space-y-3 mt-2 p-3 border rounded bg-stone-50")
:sx-post update-url :sx-target (str "#entry-tickets-" entry-id) :sx-swap "innerHTML"
(input :type "hidden" :name "csrf_token" :value csrf)
@@ -203,12 +203,12 @@
:onclick hide-js "Cancel"))))
;; Data-driven buy form — Python passes pre-resolved data, .sx does layout + iteration
(defcomp ~events-buy-form (&key entry-id info-sold info-remaining info-basket
(defcomp ~page/buy-form (&key entry-id info-sold info-remaining info-basket
ticket-types user-ticket-counts-by-type
user-ticket-count price-str adjust-url csrf state
my-tickets-href)
(if (!= state "confirmed")
(~events-buy-not-confirmed :entry-id (str entry-id))
(~page/buy-not-confirmed :entry-id (str entry-id))
(let ((eid-s (str entry-id))
(target (str "#ticket-buy-" entry-id)))
(div :id (str "ticket-buy-" entry-id) :class "rounded-xl border border-stone-200 bg-white p-4"
@@ -234,19 +234,19 @@
(div :class "flex items-center justify-between p-3 rounded-lg bg-stone-50 border border-stone-100"
(div (div :class "font-medium text-sm" (get tt "name"))
(div :class "text-xs text-stone-500" (get tt "cost_str")))
(~events-adjust-inline :csrf csrf :adjust-url adjust-url :target target
(~page/adjust-inline :csrf csrf :adjust-url adjust-url :target target
:entry-id eid-s :count tt-count :ticket-type-id tt-id
:my-tickets-href my-tickets-href))))
ticket-types))
(<> (div :class "flex items-center justify-between mb-4"
(div (span :class "font-medium text-green-600" price-str)
(span :class "text-sm text-stone-500 ml-2" "per ticket")))
(~events-adjust-inline :csrf csrf :adjust-url adjust-url :target target
(~page/adjust-inline :csrf csrf :adjust-url adjust-url :target target
:entry-id eid-s :count (if user-ticket-count user-ticket-count 0)
:ticket-type-id nil :my-tickets-href my-tickets-href)))))))
;; Inline +/- controls (used by both default and per-type)
(defcomp ~events-adjust-inline (&key csrf adjust-url target entry-id count ticket-type-id my-tickets-href)
(defcomp ~page/adjust-inline (&key csrf adjust-url target entry-id count ticket-type-id my-tickets-href)
(if (= count 0)
(form :sx-post adjust-url :sx-target target :sx-swap "outerHTML" :class "flex items-center"
(input :type "hidden" :name "csrf_token" :value csrf)
@@ -279,13 +279,13 @@
:class "inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
"+")))))
(defcomp ~events-buy-not-confirmed (&key entry-id)
(defcomp ~page/buy-not-confirmed (&key entry-id)
(div :id (str "ticket-buy-" entry-id) :class "rounded-xl border border-stone-200 bg-stone-50 p-4 text-sm text-stone-500"
(i :class "fa fa-ticket mr-1" :aria-hidden "true")
"Tickets available once this event is confirmed."))
(defcomp ~events-buy-result (&key entry-id tickets remaining my-tickets-href)
(defcomp ~page/buy-result (&key entry-id tickets remaining my-tickets-href)
(let ((count (len tickets))
(suffix (if (= count 1) "" "s")))
(div :id (str "ticket-buy-" entry-id) :class "rounded-xl border border-emerald-200 bg-emerald-50 p-4"
@@ -308,21 +308,21 @@
"View all my tickets")))))
;; Single response wrappers for POST routes (include OOB cart icon)
(defcomp ~events-buy-response (&key entry-id tickets remaining my-tickets-href
(defcomp ~page/buy-response (&key entry-id tickets remaining my-tickets-href
cart-count blog-href cart-href logo)
(<>
(~events-cart-icon :cart-count cart-count :blog-href blog-href :cart-href cart-href :logo logo)
(~events-buy-result :entry-id entry-id :tickets tickets :remaining remaining
(~page/cart-icon :cart-count cart-count :blog-href blog-href :cart-href cart-href :logo logo)
(~page/buy-result :entry-id entry-id :tickets tickets :remaining remaining
:my-tickets-href my-tickets-href)))
(defcomp ~events-adjust-response (&key cart-count blog-href cart-href logo
(defcomp ~page/adjust-response (&key cart-count blog-href cart-href logo
entry-id info-sold info-remaining info-basket
ticket-types user-ticket-counts-by-type
user-ticket-count price-str adjust-url csrf state
my-tickets-href)
(<>
(~events-cart-icon :cart-count cart-count :blog-href blog-href :cart-href cart-href :logo logo)
(~events-buy-form :entry-id entry-id :info-sold info-sold :info-remaining info-remaining
(~page/cart-icon :cart-count cart-count :blog-href blog-href :cart-href cart-href :logo logo)
(~page/buy-form :entry-id entry-id :info-sold info-sold :info-remaining info-remaining
:info-basket info-basket :ticket-types ticket-types
:user-ticket-counts-by-type user-ticket-counts-by-type
:user-ticket-count user-ticket-count :price-str price-str
@@ -330,18 +330,18 @@
:my-tickets-href my-tickets-href)))
;; Unified OOB cart icon — picks logo or badge based on count
(defcomp ~events-cart-icon (&key cart-count blog-href cart-href logo)
(defcomp ~page/cart-icon (&key cart-count blog-href cart-href logo)
(if (= cart-count 0)
(~events-cart-icon-logo :blog-href blog-href :logo logo)
(~events-cart-icon-badge :cart-href cart-href :count (str cart-count))))
(~page/cart-icon-logo :blog-href blog-href :logo logo)
(~page/cart-icon-badge :cart-href cart-href :count (str cart-count))))
(defcomp ~events-cart-icon-logo (&key blog-href logo)
(defcomp ~page/cart-icon-logo (&key blog-href logo)
(div :id "cart-mini" :sx-swap-oob "true"
(div :class "h-12 w-12 rounded-full overflow-hidden border border-stone-300 flex-shrink-0"
(a :href blog-href :class "h-full w-full font-bold text-5xl flex-shrink-0 flex flex-row items-center gap-1"
(img :src logo :class "h-full w-full rounded-full object-cover border border-stone-300 flex-shrink-0")))))
(defcomp ~events-cart-icon-badge (&key cart-href count)
(defcomp ~page/cart-icon-badge (&key cart-href count)
(div :id "cart-mini" :sx-swap-oob "true"
(a :href cart-href :class "relative inline-flex items-center justify-center text-stone-700 hover:text-emerald-700"
(i :class "fa fa-shopping-cart text-5xl" :aria-hidden "true")
@@ -349,37 +349,37 @@
count))))
;; Inline ticket widget (for all-events/page-summary cards)
(defcomp ~events-tw-form (&key ticket-url target csrf entry-id count-val btn)
(defcomp ~page/tw-form (&key ticket-url target csrf entry-id count-val btn)
(form :action ticket-url :method "post" :sx-post ticket-url :sx-target target :sx-swap "outerHTML"
(input :type "hidden" :name "csrf_token" :value csrf)
(input :type "hidden" :name "entry_id" :value entry-id)
(input :type "hidden" :name "count" :value count-val)
btn))
(defcomp ~events-tw-cart-plus ()
(defcomp ~page/tw-cart-plus ()
(button :type "submit" :class "relative inline-flex items-center justify-center text-stone-500 hover:bg-emerald-50 rounded p-1"
(i :class "fa fa-cart-plus text-2xl" :aria-hidden "true")))
(defcomp ~events-tw-minus ()
(defcomp ~page/tw-minus ()
(button :type "submit" :class "inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl" "-"))
(defcomp ~events-tw-plus ()
(defcomp ~page/tw-plus ()
(button :type "submit" :class "inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl" "+"))
(defcomp ~events-tw-cart-icon (&key qty)
(defcomp ~page/tw-cart-icon (&key qty)
(span :class "relative inline-flex items-center justify-center text-emerald-700"
(span :class "relative inline-flex items-center justify-center"
(i :class "fa-solid fa-shopping-cart text-xl" :aria-hidden "true")
(span :class "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none"
(span :class "flex items-center justify-center bg-black text-white rounded-full w-4 h-4 text-xs font-bold" qty)))))
(defcomp ~events-tw-widget (&key entry-id price inner)
(defcomp ~page/tw-widget (&key entry-id price inner)
(div :id (str "page-ticket-" entry-id) :class "flex items-center gap-2"
(span :class "text-green-600 font-medium text-sm" price)
inner))
;; Entry posts panel
(defcomp ~events-entry-posts-panel (&key posts search-url entry-id)
(defcomp ~page/entry-posts-panel (&key posts search-url entry-id)
(div :class "space-y-2"
posts
(div :class "mt-3 pt-3 border-t"
@@ -390,13 +390,13 @@
:sx-target (str "#post-search-results-" entry-id) :sx-swap "innerHTML" :name "q")
(div :id (str "post-search-results-" entry-id) :class "mt-2 max-h-96 overflow-y-auto border rounded"))))
(defcomp ~events-entry-posts-list (&key items)
(defcomp ~page/entry-posts-list (&key items)
(div :class "space-y-2" items))
(defcomp ~events-entry-posts-none ()
(defcomp ~page/entry-posts-none ()
(p :class "text-sm text-stone-400" "No posts associated"))
(defcomp ~events-entry-post-item (&key img title del-url entry-id csrf-hdr)
(defcomp ~page/entry-post-item (&key img title del-url entry-id csrf-hdr)
(div :class "flex items-center justify-between gap-3 p-2 bg-stone-50 rounded border"
img (span :class "text-sm flex-1" title)
(button :type "button" :class "text-xs text-red-600 hover:text-red-800 flex-shrink-0"
@@ -409,41 +409,41 @@
:sx-headers csrf-hdr
(i :class "fa fa-times") " Remove")))
(defcomp ~events-post-img (&key src alt)
(defcomp ~page/post-img (&key src alt)
(img :src src :alt alt :class "w-8 h-8 rounded-full object-cover flex-shrink-0"))
(defcomp ~events-post-img-placeholder ()
(defcomp ~page/post-img-placeholder ()
(div :class "w-8 h-8 rounded-full bg-stone-200 flex-shrink-0"))
;; Entry posts nav OOB
(defcomp ~events-entry-posts-nav-oob-empty ()
(defcomp ~page/entry-posts-nav-oob-empty ()
(div :id "entry-posts-nav-wrapper" :sx-swap-oob "true"))
(defcomp ~events-entry-posts-nav-oob (&key items)
(defcomp ~page/entry-posts-nav-oob (&key items)
(div :class "flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
:id "entry-posts-nav-wrapper" :sx-swap-oob "true"
(div :class "flex overflow-x-auto gap-1 scrollbar-thin" items)))
(defcomp ~events-entry-nav-post (&key href nav-btn img title)
(defcomp ~page/entry-nav-post (&key href nav-btn img title)
(a :href href :class nav-btn img (div :class "flex-1 min-w-0" (div :class "font-medium truncate" title))))
;; Post nav entries OOB
(defcomp ~events-post-nav-oob-empty ()
(defcomp ~page/post-nav-oob-empty ()
(div :id "entries-calendars-nav-wrapper" :sx-swap-oob "true"))
(defcomp ~events-post-nav-entry (&key href nav-btn name time-str)
(defcomp ~page/post-nav-entry (&key href nav-btn name time-str)
(a :href href :class nav-btn
(div :class "w-8 h-8 rounded bg-stone-200 flex-shrink-0")
(div :class "flex-1 min-w-0"
(div :class "font-medium truncate" name)
(div :class "text-xs text-stone-600 truncate" time-str))))
(defcomp ~events-post-nav-calendar (&key href nav-btn name)
(defcomp ~page/post-nav-calendar (&key href nav-btn name)
(a :href href :class nav-btn
(i :class "fa fa-calendar" :aria-hidden "true")
(div name)))
(defcomp ~events-post-nav-wrapper (&key items hyperscript)
(defcomp ~page/post-nav-wrapper (&key items hyperscript)
(div :class "flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
:id "entries-calendars-nav-wrapper" :sx-swap-oob "true"
(button :class "entries-nav-arrow hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
@@ -461,7 +461,7 @@
(i :class "fa fa-chevron-right"))))
;; Entry nav post link (with image)
(defcomp ~events-entry-nav-post-link (&key href img title)
(defcomp ~page/entry-nav-post-link (&key href img title)
(a :href href :class "flex items-center gap-2 px-3 py-2 hover:bg-stone-100 rounded transition text-sm border sm:whitespace-nowrap sm:flex-shrink-0"
img (div :class "flex-1 min-w-0" (div :class "font-medium truncate" title))))
@@ -471,60 +471,60 @@
;; ---------------------------------------------------------------------------
;; Post image helper from data
(defcomp ~events-post-img-from-data (&key src alt)
(defcomp ~page/post-img-from-data (&key src alt)
(if src
(~events-post-img :src src :alt alt)
(~events-post-img-placeholder)))
(~page/post-img :src src :alt alt)
(~page/post-img-placeholder)))
;; Entry posts nav OOB from data
(defcomp ~events-entry-posts-nav-oob-from-data (&key nav-btn posts)
(defcomp ~page/entry-posts-nav-oob-from-data (&key nav-btn posts)
(if (empty? (or posts (list)))
(~events-entry-posts-nav-oob-empty)
(~events-entry-posts-nav-oob
(~page/entry-posts-nav-oob-empty)
(~page/entry-posts-nav-oob
:items (<> (map (lambda (p)
(~events-entry-nav-post
(~page/entry-nav-post
:href (get p "href") :nav-btn nav-btn
:img (~events-post-img-from-data :src (get p "img") :alt (get p "title"))
:img (~page/post-img-from-data :src (get p "img") :alt (get p "title"))
:title (get p "title")))
posts)))))
;; Entry posts nav (non-OOB) from data — for desktop nav embedding
(defcomp ~events-entry-posts-nav-inner-from-data (&key posts)
(defcomp ~page/entry-posts-nav-inner-from-data (&key posts)
(when (not (empty? (or posts (list))))
(~events-entry-posts-nav-oob
(~page/entry-posts-nav-oob
:items (<> (map (lambda (p)
(~events-entry-nav-post-link
(~page/entry-nav-post-link
:href (get p "href")
:img (~events-post-img-from-data :src (get p "img") :alt (get p "title"))
:img (~page/post-img-from-data :src (get p "img") :alt (get p "title"))
:title (get p "title")))
posts)))))
;; Post nav entries+calendars OOB from data
(defcomp ~events-post-nav-wrapper-from-data (&key nav-btn entries calendars hyperscript)
(defcomp ~page/post-nav-wrapper-from-data (&key nav-btn entries calendars hyperscript)
(if (and (empty? (or entries (list))) (empty? (or calendars (list))))
(~events-post-nav-oob-empty)
(~events-post-nav-wrapper
(~page/post-nav-oob-empty)
(~page/post-nav-wrapper
:items (<>
(map (lambda (e)
(~events-post-nav-entry
(~page/post-nav-entry
:href (get e "href") :nav-btn nav-btn
:name (get e "name") :time-str (get e "time-str")))
(or entries (list)))
(map (lambda (c)
(~events-post-nav-calendar
(~page/post-nav-calendar
:href (get c "href") :nav-btn nav-btn :name (get c "name")))
(or calendars (list))))
:hyperscript hyperscript)))
;; Entry posts panel from data
(defcomp ~events-entry-posts-panel-from-data (&key entry-id posts search-url)
(~events-entry-posts-panel
(defcomp ~page/entry-posts-panel-from-data (&key entry-id posts search-url)
(~page/entry-posts-panel
:posts (if (empty? (or posts (list)))
(~events-entry-posts-none)
(~events-entry-posts-list
(~page/entry-posts-none)
(~page/entry-posts-list
:items (<> (map (lambda (p)
(~events-entry-post-item
:img (~events-post-img-from-data :src (get p "img") :alt (get p "title"))
(~page/entry-post-item
:img (~page/post-img-from-data :src (get p "img") :alt (get p "title"))
:title (get p "title")
:del-url (get p "del-url") :entry-id entry-id
:csrf-hdr (get p "csrf-hdr")))
@@ -532,11 +532,11 @@
:search-url search-url :entry-id entry-id))
;; CRUD list/panel from data — shared by calendars + markets
(defcomp ~events-crud-list-from-data (&key items empty-msg list-id)
(defcomp ~page/crud-list-from-data (&key items empty-msg list-id)
(if (empty? (or items (list)))
(~empty-state :message empty-msg :cls "text-gray-500 mt-4")
(~shared:misc/empty-state :message empty-msg :cls "text-gray-500 mt-4")
(<> (map (lambda (item)
(~crud-item
(~shared:misc/crud-item
:href (get item "href") :name (get item "name") :slug (get item "slug")
:del-url (get item "del-url") :csrf-hdr (get item "csrf-hdr")
:list-id list-id
@@ -544,84 +544,84 @@
:confirm-text (get item "confirm-text")))
items))))
(defcomp ~events-crud-panel-from-data (&key can-create create-url csrf errors-id list-id
(defcomp ~page/crud-panel-from-data (&key can-create create-url csrf errors-id list-id
placeholder btn-label items empty-msg)
(~crud-panel
(~shared:misc/crud-panel
:form (when can-create
(~crud-create-form
(~shared:misc/crud-create-form
:create-url create-url :csrf csrf :errors-id errors-id
:list-id list-id :placeholder placeholder :btn-label btn-label))
:list (~events-crud-list-from-data :items items :empty-msg empty-msg :list-id list-id)
:list (~page/crud-list-from-data :items items :empty-msg empty-msg :list-id list-id)
:list-id list-id))
;; Post nav admin cog
(defcomp ~events-post-nav-admin-cog (&key href aclass)
(defcomp ~page/post-nav-admin-cog (&key href aclass)
(div :class "relative nav-group"
(a :href href :class aclass
(i :class "fa fa-cog" :aria-hidden "true"))))
;; Post nav from data — calendar links + container nav + admin
(defcomp ~events-post-nav-from-data (&key calendars container-nav select-colours
(defcomp ~page/post-nav-from-data (&key calendars container-nav select-colours
has-admin admin-href aclass)
(<>
(map (lambda (c)
(~nav-link :href (get c "href") :icon "fa fa-calendar"
(~shared:layout/nav-link :href (get c "href") :icon "fa fa-calendar"
:label (get c "name") :select-colours select-colours
:is-selected (get c "is-selected")))
(or calendars (list)))
(when container-nav container-nav)
(when has-admin
(~events-post-nav-admin-cog :href admin-href :aclass aclass))))
(~page/post-nav-admin-cog :href admin-href :aclass aclass))))
;; Calendar nav from data — slots + admin link
(defcomp ~events-calendar-nav-from-data (&key slots-href admin-href select-colours is-admin)
(defcomp ~page/calendar-nav-from-data (&key slots-href admin-href select-colours is-admin)
(<>
(~nav-link :href slots-href :icon "fa fa-clock"
(~shared:layout/nav-link :href slots-href :icon "fa fa-clock"
:label "Slots" :select-colours select-colours)
(when is-admin
(~nav-link :href admin-href :icon "fa fa-cog"
(~shared:layout/nav-link :href admin-href :icon "fa fa-cog"
:select-colours select-colours))))
;; Calendar admin nav from data
(defcomp ~events-calendar-admin-nav-from-data (&key links select-colours)
(defcomp ~page/calendar-admin-nav-from-data (&key links select-colours)
(<> (map (lambda (l)
(~nav-link :href (get l "href") :label (get l "label")
(~shared:layout/nav-link :href (get l "href") :label (get l "label")
:select-colours select-colours))
(or links (list)))))
;; Day nav from data — confirmed entries + admin link
(defcomp ~events-day-nav-from-data (&key entries is-admin admin-href)
(defcomp ~page/day-nav-from-data (&key entries is-admin admin-href)
(<>
(when (not (empty? (or entries (list))))
(~events-day-entries-nav
(~day/entries-nav
:inner (<> (map (lambda (e)
(~events-day-entry-link
(~day/entry-link
:href (get e "href") :name (get e "name") :time-str (get e "time-str")))
entries))))
(when is-admin
(~nav-link :href admin-href :icon "fa fa-cog"))))
(~shared:layout/nav-link :href admin-href :icon "fa fa-cog"))))
;; Post search results from data
(defcomp ~events-post-search-results-from-data (&key items page next-url has-more)
(defcomp ~page/post-search-results-from-data (&key items page next-url has-more)
(<>
(map (lambda (item)
(~events-post-search-item
(~forms/post-search-item
:post-url (get item "post-url") :entry-id (get item "entry-id")
:csrf (get item "csrf") :post-id (get item "post-id")
:img (~events-post-img-from-data :src (get item "img") :alt (get item "title"))
:img (~page/post-img-from-data :src (get item "img") :alt (get item "title"))
:title (get item "title")))
(or items (list)))
(cond
(has-more (~events-post-search-sentinel :page page :next-url next-url))
((not (empty? (or items (list)))) (~events-post-search-end))
(has-more (~forms/post-search-sentinel :page page :next-url next-url))
((not (empty? (or items (list)))) (~forms/post-search-end))
(true ""))))
;; Entry options from data — state-driven button composition
(defcomp ~events-entry-options-from-data (&key entry-id state buttons)
(~events-entry-options
(defcomp ~page/entry-options-from-data (&key entry-id state buttons)
(~admin/entry-options
:entry-id entry-id
:buttons (<> (map (lambda (b)
(~events-entry-option-button
(~admin/entry-option-button
:url (get b "url") :target (str "#calendar_entry_options_" entry-id)
:csrf (get b "csrf") :btn-type (get b "btn-type")
:action-btn (get b "action-btn")

View File

@@ -1,12 +1,12 @@
;; Events payments components
(defcomp ~events-payments-panel (&key update-url csrf merchant-code placeholder input-cls sumup-configured checkout-prefix)
(defcomp ~payments/panel (&key update-url csrf merchant-code placeholder input-cls sumup-configured checkout-prefix)
(section :class "p-4 max-w-lg mx-auto"
(~sumup-settings-form :update-url update-url :csrf csrf :merchant-code merchant-code
(~shared:misc/sumup-settings-form :update-url update-url :csrf csrf :merchant-code merchant-code
:placeholder placeholder :input-cls input-cls :sumup-configured sumup-configured
:checkout-prefix checkout-prefix :sx-select "#payments-panel")))
(defcomp ~events-markets-create-form (&key create-url csrf)
(defcomp ~payments/markets-create-form (&key create-url csrf)
(<>
(div :id "market-create-errors" :class "mt-2 text-sm text-red-600")
(form :class "mt-4 flex gap-2 items-end" :sx-post create-url
@@ -20,15 +20,15 @@
:placeholder "e.g. Farm Shop, Bakery"))
(button :type "submit" :class "border rounded px-3 py-2" "Add market"))))
(defcomp ~events-markets-panel (&key form list)
(defcomp ~payments/markets-panel (&key form list)
(section :class "p-4"
form
(div :id "markets-list" :class "mt-6" list)))
(defcomp ~events-markets-empty ()
(defcomp ~payments/markets-empty ()
(p :class "text-gray-500 mt-4" "No markets yet. Create one above."))
(defcomp ~events-markets-item (&key href market-name market-slug del-url csrf-hdr)
(defcomp ~payments/markets-item (&key href market-name market-slug del-url csrf-hdr)
(div :class "mt-6 border rounded-lg p-4"
(div :class "flex items-center justify-between gap-3"
(a :class "flex items-baseline gap-3" :href href

View File

@@ -1,6 +1,6 @@
;; Events ticket components
(defcomp ~events-ticket-card (&key (href :as string) (entry-name :as string) (type-name :as string?) (time-str :as string?) (cal-name :as string?) badge (code-prefix :as string))
(defcomp ~tickets/card (&key (href :as string) (entry-name :as string) (type-name :as string?) (time-str :as string?) (cal-name :as string?) badge (code-prefix :as string))
(a :href href :class "block rounded-xl border border-stone-200 bg-white p-4 hover:shadow-md transition"
(div :class "flex items-start justify-between gap-4"
(div :class "flex-1 min-w-0"
@@ -12,7 +12,7 @@
badge
(span :class "text-xs text-stone-400 font-mono" (str code-prefix "..."))))))
(defcomp ~events-tickets-panel (&key (list-container :as string) (has-tickets :as boolean) cards)
(defcomp ~tickets/panel (&key (list-container :as string) (has-tickets :as boolean) cards)
(section :id "tickets-list" :class list-container
(h1 :class "text-2xl font-bold mb-6" "My Tickets")
(if has-tickets
@@ -22,7 +22,7 @@
(p :class "text-lg" "No tickets yet")
(p :class "text-sm mt-1" "Tickets will appear here after you purchase them.")))))
(defcomp ~events-ticket-detail (&key (list-container :as string) (back-href :as string) (header-bg :as string) (entry-name :as string) badge
(defcomp ~tickets/detail (&key (list-container :as string) (back-href :as string) (header-bg :as string) (entry-name :as string) badge
(type-name :as string?) (code :as string) (time-date :as string?) (time-range :as string?) (cal-name :as string?)
(type-desc :as string?) (checkin-str :as string?) (qr-script :as string))
(section :id "ticket-detail" :class (str list-container " max-w-lg mx-auto")
@@ -54,25 +54,25 @@
(script :src "https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js")
(script qr-script)))
(defcomp ~events-ticket-admin-stat (&key (border :as string) (bg :as string) (text-cls :as string) (label-cls :as string) (value :as string) (label :as string))
(defcomp ~tickets/admin-stat (&key (border :as string) (bg :as string) (text-cls :as string) (label-cls :as string) (value :as string) (label :as string))
(div :class (str "rounded-xl border " border " " bg " p-4 text-center")
(div :class (str "text-2xl font-bold " text-cls) value)
(div :class (str "text-xs " label-cls " uppercase tracking-wide") label)))
(defcomp ~events-ticket-admin-date (&key (date-str :as string))
(defcomp ~tickets/admin-date (&key (date-str :as string))
(div :class "text-xs text-stone-500" date-str))
(defcomp ~events-ticket-admin-checkin-form (&key (checkin-url :as string) (code :as string) (csrf :as string))
(defcomp ~tickets/admin-checkin-form (&key (checkin-url :as string) (code :as string) (csrf :as string))
(form :sx-post checkin-url :sx-target (str "#ticket-row-" code) :sx-swap "outerHTML"
(input :type "hidden" :name "csrf_token" :value csrf)
(button :type "submit" :class "px-3 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 transition"
(i :class "fa fa-check mr-1" :aria-hidden "true") "Check in")))
(defcomp ~events-ticket-admin-checked-in (&key (time-str :as string))
(defcomp ~tickets/admin-checked-in (&key (time-str :as string))
(span :class "text-xs text-blue-600"
(i :class "fa fa-check-circle" :aria-hidden "true") (str " " time-str)))
(defcomp ~events-ticket-admin-row (&key (code :as string) (code-short :as string) (entry-name :as string) date (type-name :as string) badge action)
(defcomp ~tickets/admin-row (&key (code :as string) (code-short :as string) (entry-name :as string) date (type-name :as string) badge action)
(tr :class "hover:bg-stone-50 transition" :id (str "ticket-row-" code)
(td :class "px-4 py-3" (span :class "font-mono text-xs" code-short))
(td :class "px-4 py-3" (div :class "font-medium" entry-name) date)
@@ -80,7 +80,7 @@
(td :class "px-4 py-3" badge)
(td :class "px-4 py-3" action)))
(defcomp ~events-ticket-admin-panel (&key (list-container :as string) stats (lookup-url :as string) (has-tickets :as boolean) rows)
(defcomp ~tickets/admin-panel (&key (list-container :as string) stats (lookup-url :as string) (has-tickets :as boolean) rows)
(section :id "ticket-admin" :class list-container
(h1 :class "text-2xl font-bold mb-6" "Ticket Admin")
(div :class "grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8" stats)
@@ -113,11 +113,11 @@
(tbody :class "divide-y divide-stone-100" rows))
(div :class "px-6 py-8 text-center text-stone-500" "No tickets yet"))))))
(defcomp ~events-checkin-error (&key (message :as string))
(defcomp ~tickets/checkin-error (&key (message :as string))
(div :class "rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-800"
(i :class "fa fa-exclamation-circle mr-2" :aria-hidden "true") message))
(defcomp ~events-checkin-success-row (&key (code :as string) (code-short :as string) (entry-name :as string) date (type-name :as string) badge (time-str :as string))
(defcomp ~tickets/checkin-success-row (&key (code :as string) (code-short :as string) (entry-name :as string) date (type-name :as string) badge (time-str :as string))
(tr :class "bg-blue-50" :id (str "ticket-row-" code)
(td :class "px-4 py-3" (span :class "font-mono text-xs" code-short))
(td :class "px-4 py-3" (div :class "font-medium" entry-name) date)
@@ -127,65 +127,65 @@
(span :class "text-xs text-blue-600"
(i :class "fa fa-check-circle" :aria-hidden "true") (str " " time-str)))))
(defcomp ~events-lookup-error (&key (message :as string))
(defcomp ~tickets/lookup-error (&key (message :as string))
(div :class "rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-800"
(i :class "fa fa-exclamation-circle mr-2" :aria-hidden "true") message))
(defcomp ~events-lookup-info (&key (entry-name :as string))
(defcomp ~tickets/lookup-info (&key (entry-name :as string))
(div :class "font-semibold text-lg" entry-name))
(defcomp ~events-lookup-type (&key (type-name :as string))
(defcomp ~tickets/lookup-type (&key (type-name :as string))
(div :class "text-sm text-stone-600" type-name))
(defcomp ~events-lookup-date (&key (date-str :as string))
(defcomp ~tickets/lookup-date (&key (date-str :as string))
(div :class "text-sm text-stone-500 mt-1" date-str))
(defcomp ~events-lookup-cal (&key (cal-name :as string))
(defcomp ~tickets/lookup-cal (&key (cal-name :as string))
(div :class "text-xs text-stone-400 mt-0.5" cal-name))
(defcomp ~events-lookup-status (&key badge (code :as string))
(defcomp ~tickets/lookup-status (&key badge (code :as string))
(div :class "mt-2" badge (span :class "text-xs text-stone-400 ml-2 font-mono" code)))
(defcomp ~events-lookup-checkin-time (&key (date-str :as string))
(defcomp ~tickets/lookup-checkin-time (&key (date-str :as string))
(div :class "text-xs text-blue-600 mt-1" (str "Checked in: " date-str)))
(defcomp ~events-lookup-checkin-btn (&key (checkin-url :as string) (code :as string) (csrf :as string))
(defcomp ~tickets/lookup-checkin-btn (&key (checkin-url :as string) (code :as string) (csrf :as string))
(form :sx-post checkin-url :sx-target (str "#checkin-action-" code) :sx-swap "innerHTML"
(input :type "hidden" :name "csrf_token" :value csrf)
(button :type "submit"
:class "px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition font-semibold text-lg"
(i :class "fa fa-check mr-2" :aria-hidden "true") "Check In")))
(defcomp ~events-lookup-checked-in ()
(defcomp ~tickets/lookup-checked-in ()
(div :class "text-blue-600 text-center"
(i :class "fa fa-check-circle text-3xl" :aria-hidden "true")
(div :class "text-sm font-medium mt-1" "Checked In")))
(defcomp ~events-lookup-cancelled ()
(defcomp ~tickets/lookup-cancelled ()
(div :class "text-red-600 text-center"
(i :class "fa fa-times-circle text-3xl" :aria-hidden "true")
(div :class "text-sm font-medium mt-1" "Cancelled")))
(defcomp ~events-lookup-card (&key info (code :as string) action)
(defcomp ~tickets/lookup-card (&key info (code :as string) action)
(div :class "rounded-lg border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-start justify-between gap-4"
(div :class "flex-1" info)
(div :id (str "checkin-action-" code) action))))
(defcomp ~events-entry-tickets-admin-row (&key (code :as string) (code-short :as string) (type-name :as string) badge action)
(defcomp ~tickets/entry-tickets-admin-row (&key (code :as string) (code-short :as string) (type-name :as string) badge action)
(tr :class "hover:bg-stone-50" :id (str "entry-ticket-row-" code)
(td :class "px-4 py-2 font-mono text-xs" code-short)
(td :class "px-4 py-2" type-name)
(td :class "px-4 py-2" badge)
(td :class "px-4 py-2" action)))
(defcomp ~events-entry-tickets-admin-checkin (&key (checkin-url :as string) (code :as string) (csrf :as string))
(defcomp ~tickets/entry-tickets-admin-checkin (&key (checkin-url :as string) (code :as string) (csrf :as string))
(form :sx-post checkin-url :sx-target (str "#entry-ticket-row-" code) :sx-swap "outerHTML"
(input :type "hidden" :name "csrf_token" :value csrf)
(button :type "submit" :class "px-3 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700"
"Check in")))
(defcomp ~events-entry-tickets-admin-table (&key rows)
(defcomp ~tickets/entry-tickets-admin-table (&key rows)
(div :class "overflow-x-auto rounded-xl border border-stone-200"
(table :class "w-full text-sm"
(thead :class "bg-stone-50"
@@ -195,10 +195,10 @@
(th :class "px-4 py-2 text-left font-medium text-stone-600" "Actions")))
(tbody :class "divide-y divide-stone-100" rows))))
(defcomp ~events-entry-tickets-admin-empty ()
(defcomp ~tickets/entry-tickets-admin-empty ()
(div :class "text-center py-6 text-stone-500 text-sm" "No tickets for this entry"))
(defcomp ~events-entry-tickets-admin-panel (&key (entry-name :as string) (count-label :as string) body)
(defcomp ~tickets/entry-tickets-admin-panel (&key (entry-name :as string) (count-label :as string) body)
(div :class "space-y-4"
(div :class "flex items-center justify-between"
(h3 :class "text-lg font-semibold" (str "Tickets for: " entry-name))
@@ -211,72 +211,72 @@
;; ---------------------------------------------------------------------------
;; My tickets panel from data
(defcomp ~events-tickets-panel-from-data (&key (list-container :as string) (tickets :as list?))
(~events-tickets-panel
(defcomp ~tickets/panel-from-data (&key (list-container :as string) (tickets :as list?))
(~tickets/panel
:list-container list-container
:has-tickets (not (empty? (or tickets (list))))
:cards (<> (map (lambda (t)
(~events-ticket-card
(~tickets/card
:href (get t "href") :entry-name (get t "entry-name")
:type-name (get t "type-name") :time-str (get t "time-str")
:cal-name (get t "cal-name")
:badge (~ticket-state-badge :state (get t "state"))
:badge (~entries/ticket-state-badge :state (get t "state"))
:code-prefix (get t "code-prefix")))
(or tickets (list))))))
;; Ticket detail from data — uses lg badge variant
(defcomp ~events-ticket-detail-from-data (&key (list-container :as string) (back-href :as string) (header-bg :as string) (entry-name :as string)
(defcomp ~tickets/detail-from-data (&key (list-container :as string) (back-href :as string) (header-bg :as string) (entry-name :as string)
(state :as string) (type-name :as string?) (code :as string) (time-date :as string?) (time-range :as string?)
(cal-name :as string?) (type-desc :as string?) (checkin-str :as string?) (qr-script :as string))
(~events-ticket-detail
(~tickets/detail
:list-container list-container :back-href back-href
:header-bg header-bg :entry-name entry-name
:badge (~ticket-state-badge-lg :state state)
:badge (~entries/ticket-state-badge-lg :state state)
:type-name type-name :code code
:time-date time-date :time-range time-range
:cal-name cal-name :type-desc type-desc
:checkin-str checkin-str :qr-script qr-script))
;; Ticket admin row from data — conditional action column
(defcomp ~events-ticket-admin-row-from-data (&key (code :as string) (code-short :as string) (entry-name :as string) (date-str :as string?)
(defcomp ~tickets/admin-row-from-data (&key (code :as string) (code-short :as string) (entry-name :as string) (date-str :as string?)
(type-name :as string) (state :as string) (checkin-url :as string) (csrf :as string)
(checked-in-time :as string?))
(~events-ticket-admin-row
(~tickets/admin-row
:code code :code-short code-short
:entry-name entry-name
:date (when date-str (~events-ticket-admin-date :date-str date-str))
:date (when date-str (~tickets/admin-date :date-str date-str))
:type-name type-name
:badge (~ticket-state-badge :state state)
:badge (~entries/ticket-state-badge :state state)
:action (cond
((or (= state "confirmed") (= state "reserved"))
(~events-ticket-admin-checkin-form
(~tickets/admin-checkin-form
:checkin-url checkin-url :code code :csrf csrf))
((= state "checked_in")
(~events-ticket-admin-checked-in :time-str (or checked-in-time "")))
(~tickets/admin-checked-in :time-str (or checked-in-time "")))
(true nil))))
;; Ticket admin panel from data
(defcomp ~events-ticket-admin-panel-from-data (&key (list-container :as string) (lookup-url :as string) (tickets :as list?)
(defcomp ~tickets/admin-panel-from-data (&key (list-container :as string) (lookup-url :as string) (tickets :as list?)
(total :as number?) (confirmed :as number?) (checked-in :as number?) (reserved :as number?))
(~events-ticket-admin-panel
(~tickets/admin-panel
:list-container list-container
:stats (<>
(~events-ticket-admin-stat :border "border-stone-200" :bg ""
(~tickets/admin-stat :border "border-stone-200" :bg ""
:text-cls "text-stone-900" :label-cls "text-stone-500"
:value (str (or total 0)) :label "Total")
(~events-ticket-admin-stat :border "border-emerald-200" :bg "bg-emerald-50"
(~tickets/admin-stat :border "border-emerald-200" :bg "bg-emerald-50"
:text-cls "text-emerald-700" :label-cls "text-emerald-600"
:value (str (or confirmed 0)) :label "Confirmed")
(~events-ticket-admin-stat :border "border-blue-200" :bg "bg-blue-50"
(~tickets/admin-stat :border "border-blue-200" :bg "bg-blue-50"
:text-cls "text-blue-700" :label-cls "text-blue-600"
:value (str (or checked-in 0)) :label "Checked In")
(~events-ticket-admin-stat :border "border-amber-200" :bg "bg-amber-50"
(~tickets/admin-stat :border "border-amber-200" :bg "bg-amber-50"
:text-cls "text-amber-700" :label-cls "text-amber-600"
:value (str (or reserved 0)) :label "Reserved"))
:lookup-url lookup-url
:has-tickets (not (empty? (or tickets (list))))
:rows (<> (map (lambda (t)
(~events-ticket-admin-row-from-data
(~tickets/admin-row-from-data
:code (get t "code") :code-short (get t "code-short")
:entry-name (get t "entry-name") :date-str (get t "date-str")
:type-name (get t "type-name") :state (get t "state")
@@ -285,45 +285,45 @@
(or tickets (list))))))
;; Entry tickets admin from data
(defcomp ~events-entry-tickets-admin-from-data (&key (entry-name :as string) (count-label :as string) (tickets :as list?) (csrf :as string))
(~events-entry-tickets-admin-panel
(defcomp ~tickets/entry-tickets-admin-from-data (&key (entry-name :as string) (count-label :as string) (tickets :as list?) (csrf :as string))
(~tickets/entry-tickets-admin-panel
:entry-name entry-name :count-label count-label
:body (if (empty? (or tickets (list)))
(~events-entry-tickets-admin-empty)
(~events-entry-tickets-admin-table
(~tickets/entry-tickets-admin-empty)
(~tickets/entry-tickets-admin-table
:rows (<> (map (lambda (t)
(~events-entry-tickets-admin-row
(~tickets/entry-tickets-admin-row
:code (get t "code") :code-short (get t "code-short")
:type-name (get t "type-name")
:badge (~ticket-state-badge :state (get t "state"))
:badge (~entries/ticket-state-badge :state (get t "state"))
:action (cond
((or (= (get t "state") "confirmed") (= (get t "state") "reserved"))
(~events-entry-tickets-admin-checkin
(~tickets/entry-tickets-admin-checkin
:checkin-url (get t "checkin-url") :code (get t "code") :csrf csrf))
((= (get t "state") "checked_in")
(~events-ticket-admin-checked-in :time-str (or (get t "checked-in-time") "")))
(~tickets/admin-checked-in :time-str (or (get t "checked-in-time") "")))
(true nil))))
(or tickets (list))))))))
;; Checkin success row from data
(defcomp ~events-checkin-success-row-from-data (&key (code :as string) (code-short :as string) (entry-name :as string) (date-str :as string?) (type-name :as string) (time-str :as string))
(~events-checkin-success-row
(defcomp ~tickets/checkin-success-row-from-data (&key (code :as string) (code-short :as string) (entry-name :as string) (date-str :as string?) (type-name :as string) (time-str :as string))
(~tickets/checkin-success-row
:code code :code-short code-short
:entry-name entry-name
:date (when date-str (~events-ticket-admin-date :date-str date-str))
:date (when date-str (~tickets/admin-date :date-str date-str))
:type-name type-name
:badge (~ticket-state-badge :state "checked_in")
:badge (~entries/ticket-state-badge :state "checked_in")
:time-str time-str))
;; Ticket types table from data
(defcomp ~events-ticket-types-table-from-data (&key (list-container :as string) (ticket-types :as list?) (action-btn :as string) (add-url :as string)
(defcomp ~tickets/types-table-from-data (&key (list-container :as string) (ticket-types :as list?) (action-btn :as string) (add-url :as string)
(tr-cls :as string) (pill-cls :as string) (hx-select :as string) (csrf-hdr :as string))
(~events-ticket-types-table
(~page/ticket-types-table
:list-container list-container
:rows (if (empty? (or ticket-types (list)))
(~events-ticket-types-empty-row)
(~page/ticket-types-empty-row)
(<> (map (lambda (tt)
(~events-ticket-types-row
(~page/ticket-types-row
:tr-cls tr-cls :tt-href (get tt "tt-href")
:pill-cls pill-cls :hx-select hx-select
:tt-name (get tt "tt-name") :cost-str (get tt "cost-str")
@@ -333,23 +333,23 @@
:action-btn action-btn :add-url add-url))
;; Lookup result from data
(defcomp ~events-lookup-result-from-data (&key (entry-name :as string) (type-name :as string?) (date-str :as string?) (cal-name :as string?)
(defcomp ~tickets/lookup-result-from-data (&key (entry-name :as string) (type-name :as string?) (date-str :as string?) (cal-name :as string?)
(state :as string) (code :as string) (checked-in-str :as string?)
(checkin-url :as string) (csrf :as string))
(~events-lookup-card
(~tickets/lookup-card
:info (<>
(~events-lookup-info :entry-name entry-name)
(when type-name (~events-lookup-type :type-name type-name))
(when date-str (~events-lookup-date :date-str date-str))
(when cal-name (~events-lookup-cal :cal-name cal-name))
(~events-lookup-status
:badge (~ticket-state-badge :state state) :code code)
(~tickets/lookup-info :entry-name entry-name)
(when type-name (~tickets/lookup-type :type-name type-name))
(when date-str (~tickets/lookup-date :date-str date-str))
(when cal-name (~tickets/lookup-cal :cal-name cal-name))
(~tickets/lookup-status
:badge (~entries/ticket-state-badge :state state) :code code)
(when checked-in-str
(~events-lookup-checkin-time :date-str checked-in-str)))
(~tickets/lookup-checkin-time :date-str checked-in-str)))
:code code
:action (cond
((or (= state "confirmed") (= state "reserved"))
(~events-lookup-checkin-btn :checkin-url checkin-url :code code :csrf csrf))
((= state "checked_in") (~events-lookup-checked-in))
((= state "cancelled") (~events-lookup-cancelled))
(~tickets/lookup-checkin-btn :checkin-url checkin-url :code code :csrf csrf))
((= state "checked_in") (~tickets/lookup-checked-in))
((= state "cancelled") (~tickets/lookup-cancelled))
(true nil))))

View File

@@ -7,8 +7,8 @@
:auth :admin
:layout :events-calendar-admin
:data (calendar-admin-data calendar-slug)
:content (~events-calendar-admin-panel
:description-content (~events-calendar-description-display
:content (~admin/calendar-admin-panel
:description-content (~calendar/description-display
:description cal-description :edit-url desc-edit-url)
:csrf csrf :description cal-description))
@@ -18,7 +18,7 @@
:auth :admin
:layout :events-day-admin
:data (day-admin-data calendar-slug year month day)
:content (~events-day-admin-panel))
:content (~day/admin-panel))
;; Slots listing
(defpage slots-listing
@@ -26,25 +26,25 @@
:auth :public
:layout :events-slots
:data (slots-data calendar-slug)
:content (~events-slots-table
:content (~page/slots-table
:list-container list-container
:rows (if has-slots
(<> (map (fn (s)
(~events-slots-row
(~page/slots-row
:tr-cls tr-cls :slot-href (get s "slot-href")
:pill-cls pill-cls :hx-select hx-select
:slot-name (get s "name") :description (get s "description")
:flexible (get s "flexible")
:days (if (get s "has-days")
(~events-slot-days-pills :days-inner
(<> (map (fn (d) (~events-slot-day-pill :day d)) (get s "day-list"))))
(~events-slot-no-days))
(~page/slot-days-pills :days-inner
(<> (map (fn (d) (~page/slot-day-pill :day d)) (get s "day-list"))))
(~page/slot-no-days))
:time-str (get s "time-str")
:cost-str (get s "cost-str") :action-btn action-btn
:del-url (get s "del-url")
:csrf-hdr csrf-hdr))
slots-list))
(~events-slots-empty-row))
(~page/slots-empty-row))
:pre-action pre-action :add-url add-url))
;; Slot detail
@@ -53,13 +53,13 @@
:auth :admin
:layout :events-slot
:data (slot-data calendar-slug slot-id)
:content (~events-slot-panel
:content (~page/slot-panel
:slot-id slot-id-str
:list-container list-container
:days (if has-days
(~events-slot-days-pills :days-inner
(<> (map (fn (d) (~events-slot-day-pill :day d)) day-list)))
(~events-slot-no-days))
(~page/slot-days-pills :days-inner
(<> (map (fn (d) (~page/slot-day-pill :day d)) day-list)))
(~page/slot-no-days))
:flexible flexible
:time-str time-str :cost-str cost-str
:pre-action pre-action :edit-url edit-url))
@@ -70,29 +70,29 @@
:auth :admin
:layout :events-entry
:data (entry-data calendar-slug entry-id)
:content (~events-entry-panel
:content (~admin/entry-panel
:entry-id entry-id-str :list-container list-container
:name (~events-entry-field :label "Name"
:content (~events-entry-name-field :name entry-name))
:slot (~events-entry-field :label "Slot"
:name (~admin/entry-field :label "Name"
:content (~admin/entry-name-field :name entry-name))
:slot (~admin/entry-field :label "Slot"
:content (if has-slot
(~events-entry-slot-assigned :slot-name slot-name :flex-label flex-label)
(~events-entry-slot-none)))
:time (~events-entry-field :label "Time Period"
:content (~events-entry-time-field :time-str time-str))
:state (~events-entry-field :label "State"
:content (~events-entry-state-field :entry-id entry-id-str
:badge (~badge :cls state-badge-cls :label state-badge-label)))
:cost (~events-entry-field :label "Cost"
:content (~events-entry-cost-field :cost cost-str))
:tickets (~events-entry-field :label "Tickets"
:content (~events-entry-tickets-field :entry-id entry-id-str
(~admin/entry-slot-assigned :slot-name slot-name :flex-label flex-label)
(~admin/entry-slot-none)))
:time (~admin/entry-field :label "Time Period"
:content (~admin/entry-time-field :time-str time-str))
:state (~admin/entry-field :label "State"
:content (~admin/entry-state-field :entry-id entry-id-str
:badge (~shared:misc/badge :cls state-badge-cls :label state-badge-label)))
:cost (~admin/entry-field :label "Cost"
:content (~admin/entry-cost-field :cost cost-str))
:tickets (~admin/entry-field :label "Tickets"
:content (~admin/entry-tickets-field :entry-id entry-id-str
:tickets-config tickets-config))
:buy buy-form
:date (~events-entry-field :label "Date"
:content (~events-entry-date-field :date-str date-str))
:posts (~events-entry-field :label "Associated Posts"
:content (~events-entry-posts-field :entry-id entry-id-str
:date (~admin/entry-field :label "Date"
:content (~admin/entry-date-field :date-str date-str))
:posts (~admin/entry-field :label "Associated Posts"
:content (~admin/entry-posts-field :entry-id entry-id-str
:posts-panel posts-panel))
:options options-html
:pre-action pre-action :edit-url edit-url)
@@ -104,9 +104,9 @@
:auth :admin
:layout :events-entry-admin
:data (entry-admin-data calendar-slug entry-id year month day)
:content (~nav-link :href ticket-types-href :label "ticket_types"
:content (~shared:layout/nav-link :href ticket-types-href :label "ticket_types"
:select-colours select-colours :aclass nav-btn :is-selected false)
:menu (~events-admin-placeholder-nav))
:menu (~forms/admin-placeholder-nav))
;; Ticket types listing
(defpage ticket-types-listing
@@ -114,11 +114,11 @@
:auth :public
:layout :events-ticket-types
:data (ticket-types-data calendar-slug entry-id year month day)
:content (~events-ticket-types-table
:content (~page/ticket-types-table
:list-container list-container
:rows (if has-types
(<> (map (fn (tt)
(~events-ticket-types-row
(~page/ticket-types-row
:tr-cls tr-cls :tt-href (get tt "tt-href")
:pill-cls pill-cls :hx-select hx-select
:tt-name (get tt "tt-name") :cost-str (get tt "cost-str")
@@ -126,9 +126,9 @@
:del-url (get tt "del-url")
:csrf-hdr csrf-hdr))
types-list))
(~events-ticket-types-empty-row))
(~page/ticket-types-empty-row))
:action-btn action-btn :add-url add-url)
:menu (~events-admin-placeholder-nav))
:menu (~forms/admin-placeholder-nav))
;; Ticket type detail
(defpage ticket-type-detail
@@ -136,13 +136,13 @@
:auth :admin
:layout :events-ticket-type
:data (ticket-type-data calendar-slug entry-id ticket-type-id year month day)
:content (~events-ticket-type-panel
:content (~page/ticket-type-panel
:ticket-id ticket-id :list-container list-container
:c1 (~events-ticket-type-col :label "Name" :value tt-name)
:c2 (~events-ticket-type-col :label "Cost" :value cost-str)
:c3 (~events-ticket-type-col :label "Count" :value count-str)
:c1 (~page/ticket-type-col :label "Name" :value tt-name)
:c2 (~page/ticket-type-col :label "Cost" :value cost-str)
:c3 (~page/ticket-type-col :label "Count" :value count-str)
:pre-action pre-action :edit-url edit-url)
:menu (~events-admin-placeholder-nav))
:menu (~forms/admin-placeholder-nav))
;; My tickets
(defpage my-tickets
@@ -150,16 +150,16 @@
:auth :public
:layout :root
:data (tickets-data)
:content (~events-tickets-panel
:content (~tickets/panel
:list-container list-container
:has-tickets has-tickets
:cards (when has-tickets
(<> (map (fn (t)
(~events-ticket-card
(~tickets/card
:href (get t "href") :entry-name (get t "entry-name")
:type-name (get t "type-name") :time-str (get t "time-str")
:cal-name (get t "cal-name")
:badge (~badge :cls (get t "badge-cls") :label (get t "badge-label"))
:badge (~shared:misc/badge :cls (get t "badge-cls") :label (get t "badge-label"))
:code-prefix (get t "code-prefix")))
tickets-list)))))
@@ -169,7 +169,7 @@
:auth :public
:layout :root
:data (ticket-detail-data code)
:content (~events-ticket-detail
:content (~tickets/detail
:list-container list-container :back-href back-href
:header-bg header-bg :entry-name entry-name
:badge (span :class (str "inline-flex items-center rounded-full px-3 py-1 text-sm font-medium " badge-cls)
@@ -185,10 +185,10 @@
:auth :admin
:layout :root
:data (ticket-admin-data)
:content (~events-ticket-admin-panel
:content (~tickets/admin-panel
:list-container list-container
:stats (<> (map (fn (s)
(~events-ticket-admin-stat
(~tickets/admin-stat
:border (get s "border") :bg (get s "bg")
:text-cls (get s "text-cls") :label-cls (get s "label-cls")
:value (get s "value") :label (get s "label")))
@@ -196,18 +196,18 @@
:lookup-url lookup-url :has-tickets has-tickets
:rows (when has-tickets
(<> (map (fn (t)
(~events-ticket-admin-row
(~tickets/admin-row
:code (get t "code") :code-short (get t "code-short")
:entry-name (get t "entry-name")
:date (when (get t "date-str")
(~events-ticket-admin-date :date-str (get t "date-str")))
(~tickets/admin-date :date-str (get t "date-str")))
:type-name (get t "type-name")
:badge (~badge :cls (get t "badge-cls") :label (get t "badge-label"))
:badge (~shared:misc/badge :cls (get t "badge-cls") :label (get t "badge-label"))
:action (if (get t "can-checkin")
(~events-ticket-admin-checkin-form
(~tickets/admin-checkin-form
:checkin-url (get t "checkin-url") :code (get t "code") :csrf csrf)
(when (get t "is-checked-in")
(~events-ticket-admin-checked-in :time-str (get t "checkin-time"))))))
(~tickets/admin-checked-in :time-str (get t "checkin-time"))))))
admin-tickets)))))
;; Markets
@@ -216,20 +216,20 @@
:auth :public
:layout :events-markets
:data (markets-data)
:content (~crud-panel
:content (~shared:misc/crud-panel
:list-id "markets-list"
:form (when can-create
(~crud-create-form :create-url create-url :csrf csrf
(~shared:misc/crud-create-form :create-url create-url :csrf csrf
:errors-id "market-create-errors" :list-id "markets-list"
:placeholder "e.g. Farm Shop, Bakery" :btn-label "Add market"))
:list (if markets-list
(<> (map (fn (m)
(~crud-item :href (get m "href") :name (get m "name")
(~shared:misc/crud-item :href (get m "href") :name (get m "name")
:slug (get m "slug") :del-url (get m "del-url")
:csrf-hdr (get m "csrf-hdr")
:list-id "markets-list"
:confirm-title "Delete market?"
:confirm-text "Products will be hidden (soft delete)"))
markets-list))
(~empty-state :message "No markets yet. Create one above."
(~shared:misc/empty-state :message "No markets yet. Create one above."
:cls "text-gray-500 mt-4"))))

View File

@@ -117,7 +117,7 @@ def _cart_icon_oob(count: int) -> str:
def _cart_icon_ctx(count: int) -> dict:
"""Return data dict for the ~events-cart-icon component."""
"""Return data dict for the ~page/cart-icon component."""
from quart import g
blog_url_fn = getattr(g, "blog_url", None)