112 conversions across 19 .sx files using match, let-match, and pipe operators: match (17): type/value dispatch replacing cond/if chains - lib/vm.sx: HO form dispatch (for-each/map/filter/reduce/some/every?) - lib/tree-tools.sx: node-display, node-matches?, rename, count, replace, free-symbols - lib/types.sx: narrow-type, substitute-in-type, infer-type, resolve-type - web/engine.sx: default-trigger, resolve-target, classify-trigger - web/deps.sx: scan-refs-walk, scan-io-refs-walk let-match (89): dict destructuring replacing (get d "key") patterns - shared/page-functions.sx (20), blog/admin.sx (17), pub-api.sx (13) - events/ layouts/page/tickets/entries/forms (27 total) - specs-explorer.sx (7), federation/social.sx (3), lib/ small files (3) -> pipes (6): replacing triple-chained gets in lib/vm.sx - frame-closure → closure-code → code-bytecode chains Also: lib/vm.sx accessor upgrades (get vm "sp" → vm-sp vm throughout) 2650/2650 tests pass, zero regressions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
319 lines
12 KiB
Plaintext
319 lines
12 KiB
Plaintext
;; Events entry card components (all events / page summary)
|
||
|
||
;; ---------------------------------------------------------------------------
|
||
;; State badges — cond maps state string to class + label
|
||
;; ---------------------------------------------------------------------------
|
||
|
||
(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")
|
||
((= 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 ~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")
|
||
((= 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 ~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")
|
||
((= 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 ~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")
|
||
((= 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 ~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 ~entries/entry-title-plain (&key name)
|
||
(h2 :class "text-lg font-semibold text-stone-900" 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 ~entries/entry-title-tile-plain (&key name)
|
||
(h2 :class "text-base font-semibold text-stone-900 line-clamp-2" name))
|
||
|
||
(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 ~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 ~entries/entry-time-linked (&key href date-str)
|
||
(<> (a :href href :class "hover:text-stone-700" date-str) " · "))
|
||
|
||
(defcomp ~entries/entry-time-plain (&key date-str)
|
||
(<> (span date-str) " · "))
|
||
|
||
(defcomp ~entries/entry-cost (&key cost)
|
||
(div :class "mt-1 text-sm font-medium text-green-600" cost))
|
||
|
||
(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"
|
||
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 ~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
|
||
(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 ~entries/entry-tile-widget-wrapper (&key widget)
|
||
(div :class "border-t border-stone-100 px-3 py-2" widget))
|
||
|
||
(defcomp ~entries/entry-widget-wrapper (&key widget)
|
||
(div :class "shrink-0" widget))
|
||
|
||
(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 ~entries/grid (&key grid-cls cards)
|
||
(div :class grid-cls cards))
|
||
|
||
(defcomp ~entries/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 ~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)
|
||
(~page/tw-form :ticket-url ticket-url :target (str "#page-ticket-" entry-id)
|
||
:csrf csrf :entry-id (str entry-id) :count-val "1"
|
||
:btn (~page/tw-cart-plus))
|
||
(<>
|
||
(~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 (~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 (~page/tw-plus))))))
|
||
|
||
;; Entry card (list view) from data
|
||
(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)
|
||
(~entries/entry-card
|
||
:title (if
|
||
entry-href
|
||
(~entries/entry-title-linked :href entry-href :name name)
|
||
(~entries/entry-title-plain :name name))
|
||
:badges (<>
|
||
(when
|
||
page-badge-title
|
||
(~entries/entry-page-badge
|
||
:href page-badge-href
|
||
:title page-badge-title))
|
||
(when cal-name (~entries/entry-cal-badge :name cal-name)))
|
||
:time-parts (<>
|
||
(when
|
||
(and day-href (not is-page-scoped))
|
||
(~entries/entry-time-linked :href day-href :date-str date-str))
|
||
(when
|
||
(and (not day-href) (not is-page-scoped) date-str)
|
||
(~entries/entry-time-plain :date-str date-str))
|
||
start-time
|
||
(when end-time (str " – " end-time)))
|
||
:cost (when cost (~entries/entry-cost :cost cost))
|
||
:widget (when
|
||
has-ticket
|
||
(let-match
|
||
{:csrf csrf :entry-id entry-id :qty qty :price price :ticket-url ticket-url}
|
||
ticket-data
|
||
(~entries/entry-widget-wrapper
|
||
:widget (~entries/tw-widget-from-data
|
||
:entry-id entry-id
|
||
:price price
|
||
:qty qty
|
||
:ticket-url ticket-url
|
||
:csrf csrf))))))
|
||
|
||
;; Entry card (tile view) from data
|
||
(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)
|
||
(~entries/entry-card-tile
|
||
:title (if
|
||
entry-href
|
||
(~entries/entry-title-tile-linked :href entry-href :name name)
|
||
(~entries/entry-title-tile-plain :name name))
|
||
:badges (<>
|
||
(when
|
||
page-badge-title
|
||
(~entries/entry-page-badge
|
||
:href page-badge-href
|
||
:title page-badge-title))
|
||
(when cal-name (~entries/entry-cal-badge :name cal-name)))
|
||
:time time-str
|
||
:cost (when cost (~entries/entry-cost :cost cost))
|
||
:widget (when
|
||
has-ticket
|
||
(let-match
|
||
{:csrf csrf :entry-id entry-id :qty qty :price price :ticket-url ticket-url}
|
||
ticket-data
|
||
(~entries/entry-tile-widget-wrapper
|
||
:widget (~entries/tw-widget-from-data
|
||
:entry-id entry-id
|
||
:price price
|
||
:qty qty
|
||
:ticket-url ticket-url
|
||
:csrf csrf))))))
|
||
|
||
;; Entry cards list (with date separators + sentinel) from data
|
||
(defcomp
|
||
~entries/entry-cards-from-data
|
||
(&key items view page has-more next-url)
|
||
(<>
|
||
(map
|
||
(lambda
|
||
(item)
|
||
(let-match
|
||
{:date-str date-str :time-str time-str :has-ticket has-ticket :is-separator is-separator :ticket-data ticket-data :day-href day-href :page-badge-title page-badge-title :entry-href entry-href :start-time start-time :end-time end-time :is-page-scoped is-page-scoped :page-badge-href page-badge-href :cal-name cal-name :cost cost :name name}
|
||
item
|
||
(if
|
||
is-separator
|
||
(~entries/date-separator :date-str date-str)
|
||
(if
|
||
(= view "tile")
|
||
(~entries/entry-card-tile-from-data
|
||
:entry-href entry-href
|
||
:name name
|
||
:day-href day-href
|
||
:page-badge-href page-badge-href
|
||
:page-badge-title page-badge-title
|
||
:cal-name cal-name
|
||
:date-str date-str
|
||
:time-str time-str
|
||
:cost cost
|
||
:has-ticket has-ticket
|
||
:ticket-data ticket-data)
|
||
(~entries/entry-card-from-data
|
||
:entry-href entry-href
|
||
:name name
|
||
:day-href day-href
|
||
:page-badge-href page-badge-href
|
||
:page-badge-title page-badge-title
|
||
:cal-name cal-name
|
||
:date-str date-str
|
||
:start-time start-time
|
||
:end-time end-time
|
||
:is-page-scoped is-page-scoped
|
||
:cost cost
|
||
:has-ticket has-ticket
|
||
:ticket-data ticket-data)))))
|
||
(or items (list)))
|
||
(when
|
||
has-more
|
||
(~shared:misc/sentinel-simple
|
||
:id (str "sentinel-" page)
|
||
:next-url next-url))))
|
||
|
||
;; Events main panel (toggle + cards grid) from data
|
||
(defcomp ~entries/main-panel-from-data (&key toggle items view page has-more next-url)
|
||
(~entries/main-panel-body
|
||
:toggle toggle
|
||
:body (if items
|
||
(~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 (~entries/entry-cards-from-data
|
||
:items items :view view :page page
|
||
:has-more has-more :next-url next-url))
|
||
(~shared:misc/empty-state :icon "fa fa-calendar-xmark"
|
||
:message "No upcoming events"
|
||
:cls "px-3 py-12 text-center text-stone-400"))))
|