Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 2m33s
Continues the pattern of eliminating Python sx_call tree-building in favour of data-driven .sx defcomps. POST/PUT/DELETE routes now pass plain data (dicts, lists, scalars) and let .sx handle iteration, conditionals, and layout via map/let/when/if. Single response components wrap OOB swaps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
263 lines
11 KiB
Plaintext
263 lines
11 KiB
Plaintext
;; Events entry card components (all events / page summary)
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; State badges — cond maps state string to class + label
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~entry-state-badge (&key state)
|
|
(~badge
|
|
:cls (cond
|
|
((= state "confirmed") "bg-emerald-100 text-emerald-800")
|
|
((= state "provisional") "bg-amber-100 text-amber-800")
|
|
((= state "ordered") "bg-sky-100 text-sky-800")
|
|
((= state "pending") "bg-stone-100 text-stone-700")
|
|
((= state "declined") "bg-red-100 text-red-800")
|
|
(true "bg-stone-100 text-stone-700"))
|
|
:label (cond
|
|
((= state "confirmed") "Confirmed")
|
|
((= state "provisional") "Provisional")
|
|
((= state "ordered") "Ordered")
|
|
((= state "pending") "Pending")
|
|
((= state "declined") "Declined")
|
|
(true (or state "Unknown")))))
|
|
|
|
(defcomp ~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")
|
|
((= state "provisional") "bg-amber-100 text-amber-800")
|
|
((= state "ordered") "bg-sky-100 text-sky-800")
|
|
((= state "pending") "bg-stone-100 text-stone-700")
|
|
((= state "declined") "bg-red-100 text-red-800")
|
|
(true "bg-stone-100 text-stone-700")))
|
|
(cond
|
|
((= state "confirmed") "Confirmed")
|
|
((= state "provisional") "Provisional")
|
|
((= state "ordered") "Ordered")
|
|
((= state "pending") "Pending")
|
|
((= state "declined") "Declined")
|
|
(true (or state "Unknown")))))
|
|
|
|
(defcomp ~ticket-state-badge (&key state)
|
|
(~badge
|
|
:cls (cond
|
|
((= state "confirmed") "bg-emerald-100 text-emerald-800")
|
|
((= state "checked_in") "bg-blue-100 text-blue-800")
|
|
((= state "reserved") "bg-amber-100 text-amber-800")
|
|
((= state "cancelled") "bg-red-100 text-red-800")
|
|
(true "bg-stone-100 text-stone-700"))
|
|
:label (cond
|
|
((= state "confirmed") "Confirmed")
|
|
((= state "checked_in") "Checked in")
|
|
((= state "reserved") "Reserved")
|
|
((= state "cancelled") "Cancelled")
|
|
(true (or state "Unknown")))))
|
|
|
|
(defcomp ~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")
|
|
((= state "checked_in") "bg-blue-100 text-blue-800")
|
|
((= state "reserved") "bg-amber-100 text-amber-800")
|
|
((= state "cancelled") "bg-red-100 text-red-800")
|
|
(true "bg-stone-100 text-stone-700")))
|
|
(cond
|
|
((= state "confirmed") "Confirmed")
|
|
((= state "checked_in") "Checked in")
|
|
((= state "reserved") "Reserved")
|
|
((= state "cancelled") "Cancelled")
|
|
(true (or state "Unknown")))))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Entry card components
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~events-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)
|
|
(h2 :class "text-lg font-semibold text-stone-900" name))
|
|
|
|
(defcomp ~events-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)
|
|
(h2 :class "text-base font-semibold text-stone-900 line-clamp-2" name))
|
|
|
|
(defcomp ~events-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)
|
|
(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)
|
|
(<> (a :href href :class "hover:text-stone-700" date-str) " · "))
|
|
|
|
(defcomp ~events-entry-time-plain (&key date-str)
|
|
(<> (span date-str) " · "))
|
|
|
|
(defcomp ~events-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)
|
|
(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"
|
|
title
|
|
(div :class "flex flex-wrap items-center gap-1.5 mt-1" badges)
|
|
(div :class "mt-1 text-sm text-stone-500" time-parts)
|
|
cost)
|
|
widget)))
|
|
|
|
(defcomp ~events-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
|
|
(div :class "flex flex-wrap items-center gap-1 mt-1" badges)
|
|
(div :class "mt-1 text-xs text-stone-500" time)
|
|
cost)
|
|
widget))
|
|
|
|
(defcomp ~events-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)
|
|
(div :class "shrink-0" widget))
|
|
|
|
(defcomp ~events-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)
|
|
(div :class grid-cls cards))
|
|
|
|
(defcomp ~events-main-panel-body (&key toggle body)
|
|
(<> toggle body (div :class "pb-8")))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Composition defcomps — receive data, compose entry card trees
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
;; 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
|
|
:inner (if (= (or qty 0) 0)
|
|
(~events-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))
|
|
(<>
|
|
(~events-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)
|
|
:csrf csrf :entry-id (str entry-id) :count-val (str (+ qty 1))
|
|
:btn (~events-tw-plus))))))
|
|
|
|
;; Entry card (list view) from data
|
|
(defcomp ~events-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
|
|
:title (if entry-href
|
|
(~events-entry-title-linked :href entry-href :name name)
|
|
(~events-entry-title-plain :name name))
|
|
:badges (<>
|
|
(when page-badge-title
|
|
(~events-entry-page-badge :href page-badge-href :title page-badge-title))
|
|
(when cal-name
|
|
(~events-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))
|
|
(when (and (not day-href) (not is-page-scoped) date-str)
|
|
(~events-entry-time-plain :date-str date-str))
|
|
start-time
|
|
(when end-time (str " \u2013 " end-time)))
|
|
:cost (when cost (~events-entry-cost :cost cost))
|
|
:widget (when has-ticket
|
|
(~events-entry-widget-wrapper
|
|
:widget (~events-tw-widget-from-data
|
|
:entry-id (get ticket-data "entry-id")
|
|
:price (get ticket-data "price")
|
|
:qty (get ticket-data "qty")
|
|
:ticket-url (get ticket-data "ticket-url")
|
|
:csrf (get ticket-data "csrf"))))))
|
|
|
|
;; Entry card (tile view) from data
|
|
(defcomp ~events-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
|
|
:title (if entry-href
|
|
(~events-entry-title-tile-linked :href entry-href :name name)
|
|
(~events-entry-title-tile-plain :name name))
|
|
:badges (<>
|
|
(when page-badge-title
|
|
(~events-entry-page-badge :href page-badge-href :title page-badge-title))
|
|
(when cal-name
|
|
(~events-entry-cal-badge :name cal-name)))
|
|
:time time-str
|
|
:cost (when cost (~events-entry-cost :cost cost))
|
|
:widget (when has-ticket
|
|
(~events-entry-tile-widget-wrapper
|
|
:widget (~events-tw-widget-from-data
|
|
:entry-id (get ticket-data "entry-id")
|
|
:price (get ticket-data "price")
|
|
:qty (get ticket-data "qty")
|
|
:ticket-url (get ticket-data "ticket-url")
|
|
: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)
|
|
(<>
|
|
(map (lambda (item)
|
|
(if (get item "is-separator")
|
|
(~events-date-separator :date-str (get item "date-str"))
|
|
(if (= view "tile")
|
|
(~events-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")
|
|
:page-badge-title (get item "page-badge-title")
|
|
:cal-name (get item "cal-name")
|
|
: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
|
|
:entry-href (get item "entry-href") :name (get item "name")
|
|
:day-href (get item "day-href")
|
|
:page-badge-href (get item "page-badge-href")
|
|
:page-badge-title (get item "page-badge-title")
|
|
:cal-name (get item "cal-name")
|
|
:date-str (get item "date-str")
|
|
:start-time (get item "start-time") :end-time (get item "end-time")
|
|
:is-page-scoped (get item "is-page-scoped")
|
|
:cost (get item "cost") :has-ticket (get item "has-ticket")
|
|
:ticket-data (get item "ticket-data")))))
|
|
(or items (list)))
|
|
(when has-more
|
|
(~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
|
|
:toggle toggle
|
|
:body (if items
|
|
(~events-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
|
|
:items items :view view :page page
|
|
:has-more has-more :next-url next-url))
|
|
(~empty-state :icon "fa fa-calendar-xmark"
|
|
:message "No upcoming events"
|
|
:cls "px-3 py-12 text-center text-stone-400"))))
|