Files
rose-ash/events/sx/entries.sx
giles c0665ba58e Adopt Step 7 language features across SX codebase
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>
2026-04-04 20:49:02 +00:00

319 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
;; 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"))))