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

@@ -6,26 +6,26 @@
;; ---------------------------------------------------------------------------
;; Auth section nav items (newsletters link + account_nav slot)
(defcomp ~auth-nav-items (&key (account-url :as string?) (select-colours :as string?) account-nav)
(defcomp ~shared:auth/nav-items (&key (account-url :as string?) (select-colours :as string?) account-nav)
(<>
(~nav-link :href (str (or account-url "") "/newsletters/")
(~shared:layout/nav-link :href (str (or account-url "") "/newsletters/")
:label "newsletters"
:select-colours (or select-colours ""))
(when account-nav account-nav)))
;; Auth header row — wraps ~menu-row-sx for account section
(defcomp ~auth-header-row (&key (account-url :as string?) (select-colours :as string?) account-nav (oob :as boolean?))
(~menu-row-sx :id "auth-row" :level 1 :colour "sky"
;; Auth header row — wraps ~shared:layout/menu-row-sx for account section
(defcomp ~shared:auth/header-row (&key (account-url :as string?) (select-colours :as string?) account-nav (oob :as boolean?))
(~shared:layout/menu-row-sx :id "auth-row" :level 1 :colour "sky"
:link-href (str (or account-url "") "/")
:link-label "account" :icon "fa-solid fa-user"
:nav (~auth-nav-items :account-url account-url
:nav (~shared:auth/nav-items :account-url account-url
:select-colours select-colours
:account-nav account-nav)
:child-id "auth-header-child" :oob oob))
;; Auth header row without nav (for cart service)
(defcomp ~auth-header-row-simple (&key (account-url :as string?) (oob :as boolean?))
(~menu-row-sx :id "auth-row" :level 1 :colour "sky"
(defcomp ~shared:auth/header-row-simple (&key (account-url :as string?) (oob :as boolean?))
(~shared:layout/menu-row-sx :id "auth-row" :level 1 :colour "sky"
:link-href (str (or account-url "") "/")
:link-label "account" :icon "fa-solid fa-user"
:child-id "auth-header-child" :oob oob))
@@ -34,26 +34,26 @@
;; Expands inline (defmacro) so IO calls resolve in _aser mode.
(defmacro ~auth-header-row-auto (oob)
(quasiquote
(~auth-header-row :account-url (app-url "account" "")
(~shared:auth/header-row :account-url (app-url "account" "")
:select-colours (select-colours)
:account-nav (account-nav-ctx)
:oob (unquote oob))))
(defmacro ~auth-header-row-simple-auto (oob)
(quasiquote
(~auth-header-row-simple :account-url (app-url "account" "")
(~shared:auth/header-row-simple :account-url (app-url "account" "")
:oob (unquote oob))))
;; Auto-fetching auth nav items — for mobile menus
(defmacro ~auth-nav-items-auto ()
(quasiquote
(~auth-nav-items :account-url (app-url "account" "")
(~shared:auth/nav-items :account-url (app-url "account" "")
:select-colours (select-colours)
:account-nav (account-nav-ctx))))
;; Orders header row
(defcomp ~orders-header-row (&key (list-url :as string))
(~menu-row-sx :id "orders-row" :level 2 :colour "sky"
(defcomp ~shared:auth/orders-header-row (&key (list-url :as string))
(~shared:layout/menu-row-sx :id "orders-row" :level 2 :colour "sky"
:link-href list-url :link-label "Orders" :icon "fa fa-gbp"
:child-id "orders-header-child"))
@@ -61,12 +61,12 @@
;; Auth forms — login flow, check email
;; ---------------------------------------------------------------------------
(defcomp ~auth-error-banner (&key (error :as string?))
(defcomp ~shared:auth/error-banner (&key (error :as string?))
(when error
(div :class "bg-red-50 border border-red-200 text-red-700 p-3 rounded mb-4"
error)))
(defcomp ~auth-login-form (&key error (action :as string) (csrf-token :as string) (email :as string?))
(defcomp ~shared:auth/login-form (&key error (action :as string) (csrf-token :as string) (email :as string?))
(div :class "py-8 max-w-md mx-auto"
(h1 :class "text-2xl font-bold mb-6" "Sign in")
error
@@ -80,12 +80,12 @@
:class "w-full bg-stone-800 text-white py-2 px-4 rounded hover:bg-stone-700 transition"
"Send magic link"))))
(defcomp ~auth-check-email-error (&key (error :as string?))
(defcomp ~shared:auth/check-email-error (&key (error :as string?))
(when error
(div :class "bg-yellow-50 border border-yellow-200 text-yellow-700 p-3 rounded mt-4"
error)))
(defcomp ~auth-check-email (&key (email :as string) error)
(defcomp ~shared:auth/check-email (&key (email :as string) error)
(div :class "py-8 max-w-md mx-auto text-center"
(h1 :class "text-2xl font-bold mb-4" "Check your email")
(p :class "text-stone-600 mb-2" "We sent a sign-in link to " (strong email) ".")

View File

@@ -1,4 +1,4 @@
(defcomp ~post-card (&key (title :as string) (slug :as string) (href :as string) (feature-image :as string?)
(defcomp ~shared:cards/post-card (&key (title :as string) (slug :as string) (href :as string) (feature-image :as string?)
(excerpt :as string?) (status :as string?) (published-at :as string?) (updated-at :as string?)
(publish-requested :as boolean?) (hx-select :as string?) like widgets at-bar)
(article :class "border-b pb-6 last:border-b-0 relative"
@@ -31,13 +31,13 @@
(when widgets widgets)
(when at-bar at-bar)))
(defcomp ~order-summary-card (&key (order-id :as string) (created-at :as string?) (description :as string?)
(defcomp ~shared:cards/order-summary-card (&key (order-id :as string) (created-at :as string?) (description :as string?)
(status :as string?) (currency :as string?) (total-amount :as string?))
(div :class "rounded-2xl border border-stone-200 bg-white/80 p-4 sm:p-6 space-y-2 text-xs sm:text-sm text-stone-800"
(p (span :class "font-medium" "Order ID:") " " (span :class "font-mono" (str "#" order-id)))
(p (span :class "font-medium" "Created:") " " (or created-at "\u2014"))
(p (span :class "font-medium" "Description:") " " (or description "\u2013"))
(p (span :class "font-medium" "Status:") " " (~status-pill :status (or status "pending") :size "[11px]"))
(p (span :class "font-medium" "Status:") " " (~shared:controls/status-pill :status (or status "pending") :size "[11px]"))
(p (span :class "font-medium" "Currency:") " " (or currency "GBP"))
(p (span :class "font-medium" "Total:") " "
(if total-amount

View File

@@ -1,4 +1,4 @@
(defcomp ~search-mobile (&key (current-local-href :as string) (search :as string?) (search-count :as number?)
(defcomp ~shared:controls/search-mobile (&key (current-local-href :as string) (search :as string?) (search-count :as number?)
(hx-select :as string?) (search-headers-mobile :as string?))
(div :id "search-mobile-wrapper"
:class "flex flex-row gap-2 items-center flex-1 min-w-0 pr-2"
@@ -21,7 +21,7 @@
:class (if (not search-count) "text-xl text-red-500" "")
(when search (str search-count)))))
(defcomp ~search-desktop (&key (current-local-href :as string) (search :as string?) (search-count :as number?)
(defcomp ~shared:controls/search-desktop (&key (current-local-href :as string) (search :as string?) (search-count :as number?)
(hx-select :as string?) (search-headers-desktop :as string?))
(div :id "search-desktop-wrapper"
:class "flex flex-row gap-2 items-center"
@@ -44,7 +44,7 @@
:class (if (not search-count) "text-xl text-red-500" "")
(when search (str search-count)))))
(defcomp ~mobile-filter (&key filter-summary action-buttons filter-details)
(defcomp ~shared:controls/mobile-filter (&key filter-summary action-buttons filter-details)
(details :class "group/filter p-2 md:hidden" :data-toggle-group "mobile-panels"
(summary :class "bg-white/90"
(div :class "flex flex-row items-start"
@@ -64,7 +64,7 @@
(div :id "filter-details-mobile" :style "display:contents"
(when filter-details filter-details))))
(defcomp ~infinite-scroll (&key (url :as string) (page :as number) (total-pages :as number)
(defcomp ~shared:controls/infinite-scroll (&key (url :as string) (page :as number) (total-pages :as number)
(id-prefix :as string) (colspan :as number))
(if (< page total-pages)
(tr :id (str id-prefix "-sentinel-" page)
@@ -85,7 +85,7 @@
(tr (td :colspan colspan :class "px-3 py-4 text-center text-xs text-stone-400"
"End of results"))))
(defcomp ~status-pill (&key (status :as string?) (size :as string?))
(defcomp ~shared:controls/status-pill (&key (status :as string?) (size :as string?))
(let* ((s (or status "pending"))
(lower (lower s))
(sz (or size "xs"))

View File

@@ -1,4 +1,4 @@
(defcomp ~link-card (&key (link :as string) (title :as string) (image :as string?) (icon :as string?)
(defcomp ~shared:fragments/link-card (&key (link :as string) (title :as string) (image :as string?) (icon :as string?)
(subtitle :as string?) (detail :as string?) (data-app :as string?))
(a :href link
:class "block rounded border border-stone-200 bg-white hover:bg-stone-50 transition-colors no-underline"
@@ -17,7 +17,7 @@
(when detail
(div :class "text-xs text-stone-400 mt-1" detail))))))
(defcomp ~cart-mini (&key (cart-count :as number) (blog-url :as string) (cart-url :as string) (oob :as string?))
(defcomp ~shared:fragments/cart-mini (&key (cart-count :as number) (blog-url :as string) (cart-url :as string) (oob :as string?))
(div :id "cart-mini"
:sx-swap-oob oob
(if (= cart-count 0)
@@ -34,7 +34,7 @@
(span :class "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 inline-flex items-center justify-center rounded-full bg-emerald-600 text-white text-sm w-5 h-5"
cart-count)))))
(defcomp ~auth-menu (&key (user-email :as string?) (account-url :as string))
(defcomp ~shared:fragments/auth-menu (&key (user-email :as string?) (account-url :as string))
(<>
(span :id "auth-menu-desktop" :class "hidden md:inline-flex"
(if user-email
@@ -66,7 +66,7 @@
(i :class "fa-solid fa-key")
(span "sign in or register"))))))
(defcomp ~account-nav-item (&key (href :as string) (label :as string))
(defcomp ~shared:fragments/account-nav-item (&key (href :as string) (label :as string))
(div :class "relative nav-group"
(a :href href
:class "justify-center cursor-pointer flex flex-row items-center gap-2 rounded bg-stone-200 text-black p-3"

View File

@@ -1,4 +1,4 @@
(defcomp ~app-body (&key header-rows filter aside menu content)
(defcomp ~shared:layout/app-body (&key header-rows filter aside menu content)
(div :class "max-w-screen-2xl mx-auto py-1 px-1"
(when header-rows
(div :class "w-full"
@@ -24,7 +24,7 @@
(when content content)
(div :class "pb-8")))))))
(defcomp ~oob-sx (&key oobs filter aside menu content)
(defcomp ~shared:layout/oob-sx (&key oobs filter aside menu content)
(<>
(when oobs oobs)
(div :id "filter" :sx-swap-oob "outerHTML"
@@ -38,7 +38,7 @@
:class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"
(when content content))))
(defcomp ~hamburger ()
(defcomp ~shared:layout/hamburger ()
(div :class "md:hidden bg-stone-200 rounded"
(svg :class "h-12 w-12 transition-transform group-open/root:hidden block self-start"
:viewBox "0 0 24 24" :fill "none" :stroke "currentColor"
@@ -48,17 +48,17 @@
:class "w-12 h-12 rotate-180 transition-transform group-open/root:block hidden self-start"
(path :d "M6 9l6 6 6-6" :fill "currentColor"))))
(defcomp ~post-label (&key (feature-image :as string?) (title :as string))
(defcomp ~shared:layout/post-label (&key (feature-image :as string?) (title :as string))
(<> (when feature-image
(img :src feature-image :class "h-8 w-8 rounded-full object-cover border border-stone-300 flex-shrink-0"))
(span title)))
(defcomp ~page-cart-badge (&key (href :as string) (count :as string))
(defcomp ~shared:layout/page-cart-badge (&key (href :as string) (count :as string))
(a :href href :class "relative inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-full border border-emerald-300 bg-emerald-50 text-emerald-800 hover:bg-emerald-100 transition"
(i :class "fa fa-shopping-cart" :aria-hidden "true")
(span count)))
(defcomp ~header-row-sx (&key cart-mini (blog-url :as string?) (site-title :as string?)
(defcomp ~shared:layout/header-row-sx (&key cart-mini (blog-url :as string?) (site-title :as string?)
(app-label :as string?) nav-tree auth-menu nav-panel
(settings-url :as string?) (is-admin :as boolean?) (oob :as boolean?))
(<>
@@ -79,13 +79,13 @@
(when (and is-admin settings-url)
(a :href settings-url :class "justify-center cursor-pointer flex flex-row items-center gap-2 rounded bg-stone-200 text-black p-3"
(i :class "fa fa-cog" :aria-hidden "true"))))
(~hamburger)))
(~shared:layout/hamburger)))
(div :class "block md:hidden text-md font-bold"
(when auth-menu auth-menu))))
; @css bg-sky-400 bg-sky-300 bg-sky-200 bg-sky-100 bg-violet-400 bg-violet-300 bg-violet-200 bg-violet-100
; @css aria-selected:bg-violet-200 aria-selected:text-violet-900 aria-selected:bg-stone-500 aria-selected:text-white
(defcomp ~menu-row-sx (&key (id :as string) (level :as number?) (colour :as string?)
(defcomp ~shared:layout/menu-row-sx (&key (id :as string) (level :as number?) (colour :as string?)
(link-href :as string) (link-label :as string?) link-label-content
(icon :as string?) (selected :as string?) (hx-select :as string?)
nav (child-id :as string?) child (oob :as boolean?) (external :as boolean?))
@@ -117,11 +117,11 @@
(div :id child-id :class "flex flex-col w-full items-center"
(when child child))))))
(defcomp ~oob-header-sx (&key (parent-id :as string) row)
(defcomp ~shared:layout/oob-header-sx (&key (parent-id :as string) row)
(div :id parent-id :sx-swap-oob "outerHTML" :class "flex flex-col w-full items-center"
row))
(defcomp ~header-child-sx (&key (id :as string?) inner)
(defcomp ~shared:layout/header-child-sx (&key (id :as string?) inner)
(div :id (or id "root-header-child") :class "flex flex-col w-full items-center" inner))
;; ---------------------------------------------------------------------------
@@ -129,7 +129,7 @@
;; ---------------------------------------------------------------------------
;; Labelled section: colour bar header + vertical nav items
(defcomp ~mobile-menu-section (&key (label :as string) (href :as string?) (colour :as string?)
(defcomp ~shared:layout/mobile-menu-section (&key (label :as string) (href :as string?) (colour :as string?)
(level :as number?) items)
(let* ((c (or colour "sky"))
(lv (or level 1))
@@ -143,7 +143,7 @@
items))))
;; Root-level mobile nav: site nav items + auth links
(defcomp ~mobile-root-nav (&key nav-tree auth-menu)
(defcomp ~shared:layout/mobile-root-nav (&key nav-tree auth-menu)
(<>
(when nav-tree
(div :class "flex flex-col gap-2 p-3 text-sm" nav-tree))
@@ -156,27 +156,27 @@
;; nested component calls in _aser are serialized without expansion.
;; ---------------------------------------------------------------------------
(defcomp ~root-header (&key cart-mini (blog-url :as string?) (site-title :as string?)
(defcomp ~shared:layout/root-header (&key cart-mini (blog-url :as string?) (site-title :as string?)
(app-label :as string?) nav-tree auth-menu nav-panel
(settings-url :as string?) (is-admin :as boolean?) (oob :as boolean?))
(~header-row-sx :cart-mini cart-mini :blog-url blog-url :site-title site-title
(~shared:layout/header-row-sx :cart-mini cart-mini :blog-url blog-url :site-title site-title
:app-label app-label :nav-tree nav-tree :auth-menu auth-menu
:nav-panel nav-panel :settings-url settings-url :is-admin is-admin
:oob oob))
(defcomp ~root-mobile (&key nav-tree auth-menu)
(~mobile-root-nav :nav-tree nav-tree :auth-menu auth-menu))
(defcomp ~shared:layout/root-mobile (&key nav-tree auth-menu)
(~shared:layout/mobile-root-nav :nav-tree nav-tree :auth-menu auth-menu))
;; ---------------------------------------------------------------------------
;; Auto-fetching header/mobile macros — use IO primitives to self-populate.
;; These expand inline so IO calls resolve in _aser mode within layout bodies.
;; Replaces the 10-parameter ~root-header boilerplate in layout defcomps.
;; Replaces the 10-parameter ~shared:layout/root-header boilerplate in layout defcomps.
;; ---------------------------------------------------------------------------
(defmacro ~root-header-auto (oob)
(quasiquote
(let ((__rhctx (root-header-ctx)))
(~header-row-sx :cart-mini (get __rhctx "cart-mini")
(~shared:layout/header-row-sx :cart-mini (get __rhctx "cart-mini")
:blog-url (get __rhctx "blog-url")
:site-title (get __rhctx "site-title")
:app-label (get __rhctx "app-label")
@@ -190,7 +190,7 @@
(defmacro ~root-mobile-auto ()
(quasiquote
(let ((__rhctx (root-header-ctx)))
(~mobile-root-nav :nav-tree (get __rhctx "nav-tree")
(~shared:layout/mobile-root-nav :nav-tree (get __rhctx "nav-tree")
:auth-menu (get __rhctx "auth-menu")))))
;; ---------------------------------------------------------------------------
@@ -198,31 +198,31 @@
;; These use ~root-header-auto / ~root-mobile-auto macros (IO primitives).
;; ---------------------------------------------------------------------------
(defcomp ~layout-root-full ()
(defcomp ~shared:layout/root-full ()
(~root-header-auto))
(defcomp ~layout-root-oob ()
(~oob-header-sx :parent-id "root-header-child"
(defcomp ~shared:layout/root-oob ()
(~shared:layout/oob-header-sx :parent-id "root-header-child"
:row (~root-header-auto true)))
(defcomp ~layout-root-mobile ()
(defcomp ~shared:layout/root-mobile ()
(~root-mobile-auto))
;; Post layout — root + post header
(defcomp ~layout-post-full ()
(defcomp ~shared:layout/post-full ()
(<> (~root-header-auto)
(~header-child-sx :inner (~post-header-auto))))
(~shared:layout/header-child-sx :inner (~post-header-auto))))
(defcomp ~layout-post-oob ()
(defcomp ~shared:layout/post-oob ()
(<> (~post-header-auto true)
(~oob-header-sx :parent-id "post-header-child" :row "")))
(~shared:layout/oob-header-sx :parent-id "post-header-child" :row "")))
(defcomp ~layout-post-mobile ()
(defcomp ~shared:layout/post-mobile ()
(let ((__phctx (post-header-ctx))
(__rhctx (root-header-ctx)))
(<>
(when (get __phctx "slug")
(~mobile-menu-section
(~shared:layout/mobile-menu-section
:label (slice (get __phctx "title") 0 40)
:href (get __phctx "link-href")
:level 1
@@ -230,35 +230,35 @@
(~root-mobile-auto))))
;; Post-admin layout — root + post header with nested admin row
(defcomp ~layout-post-admin-full (&key (selected :as string?))
(defcomp ~shared:layout/post-admin-full (&key (selected :as string?))
(let ((__admin-hdr (~post-admin-header-auto nil selected)))
(<> (~root-header-auto)
(~header-child-sx
(~shared:layout/header-child-sx
:inner (~post-header-auto nil)))))
(defcomp ~layout-post-admin-oob (&key (selected :as string?))
(defcomp ~shared:layout/post-admin-oob (&key (selected :as string?))
(<> (~post-header-auto true)
(~oob-header-sx :parent-id "post-header-child"
(~shared:layout/oob-header-sx :parent-id "post-header-child"
:row (~post-admin-header-auto nil selected))))
(defcomp ~layout-post-admin-mobile (&key (selected :as string?))
(defcomp ~shared:layout/post-admin-mobile (&key (selected :as string?))
(let ((__phctx (post-header-ctx)))
(<>
(when (get __phctx "slug")
(~mobile-menu-section
(~shared:layout/mobile-menu-section
:label "admin"
:href (get __phctx "admin-href")
:level 2
:items (~post-admin-nav-auto selected)))
(when (get __phctx "slug")
(~mobile-menu-section
(~shared:layout/mobile-menu-section
:label (slice (get __phctx "title") 0 40)
:href (get __phctx "link-href")
:level 1
:items (~post-nav-auto)))
(~root-mobile-auto))))
(defcomp ~error-content (&key (errnum :as string) (message :as string) (image :as string?))
(defcomp ~shared:layout/error-content (&key (errnum :as string) (message :as string) (image :as string?))
(div :class "text-center p-8 max-w-lg mx-auto"
(div :class "font-bold text-2xl md:text-4xl text-red-500 mb-4" errnum)
(div :class "text-stone-600 mb-4" message)
@@ -266,7 +266,7 @@
(div :class "flex justify-center"
(img :src image :width "300" :height "300")))))
(defcomp ~clear-oob-div (&key (id :as string))
(defcomp ~shared:layout/clear-oob-div (&key (id :as string))
(div :id id :sx-swap-oob "outerHTML"))
;; ---------------------------------------------------------------------------
@@ -280,12 +280,12 @@
(when (get __phctx "slug")
(<>
(when (> (get __phctx "page-cart-count") 0)
(~page-cart-badge :href (get __phctx "cart-href")
(~shared:layout/page-cart-badge :href (get __phctx "cart-href")
:count (str (get __phctx "page-cart-count"))))
(when (get __phctx "container-nav")
(~container-nav-wrapper :content (get __phctx "container-nav")))
(~shared:layout/container-nav-wrapper :content (get __phctx "container-nav")))
(when (get __phctx "is-admin")
(~admin-cog-button :href (get __phctx "admin-href")
(~shared:layout/admin-cog-button :href (get __phctx "admin-href")
:is-admin-page (get __phctx "is-admin-page"))))))))
(defmacro ~post-header-auto (oob)
@@ -293,9 +293,9 @@
(quasiquote
(let ((__phctx (post-header-ctx)))
(when (get __phctx "slug")
(~menu-row-sx :id "post-row" :level 1
(~shared:layout/menu-row-sx :id "post-row" :level 1
:link-href (get __phctx "link-href")
:link-label-content (~post-label
:link-label-content (~shared:layout/post-label
:feature-image (get __phctx "feature-image")
:title (get __phctx "title"))
:nav (~post-nav-auto)
@@ -310,28 +310,28 @@
(let ((__slug (get __phctx "slug"))
(__sc (get __phctx "select-colours")))
(<>
(~nav-link :href (app-url "events" (str "/" __slug "/admin/"))
(~shared:layout/nav-link :href (app-url "events" (str "/" __slug "/admin/"))
:label "calendars" :select-colours __sc
:is-selected (when (= (unquote selected) "calendars") "true"))
(~nav-link :href (app-url "market" (str "/" __slug "/admin/"))
(~shared:layout/nav-link :href (app-url "market" (str "/" __slug "/admin/"))
:label "markets" :select-colours __sc
:is-selected (when (= (unquote selected) "markets") "true"))
(~nav-link :href (app-url "cart" (str "/" __slug "/admin/payments/"))
(~shared:layout/nav-link :href (app-url "cart" (str "/" __slug "/admin/payments/"))
:label "payments" :select-colours __sc
:is-selected (when (= (unquote selected) "payments") "true"))
(~nav-link :href (app-url "blog" (str "/" __slug "/admin/entries/"))
(~shared:layout/nav-link :href (app-url "blog" (str "/" __slug "/admin/entries/"))
:label "entries" :select-colours __sc
:is-selected (when (= (unquote selected) "entries") "true"))
(~nav-link :href (app-url "blog" (str "/" __slug "/admin/data/"))
(~shared:layout/nav-link :href (app-url "blog" (str "/" __slug "/admin/data/"))
:label "data" :select-colours __sc
:is-selected (when (= (unquote selected) "data") "true"))
(~nav-link :href (app-url "blog" (str "/" __slug "/admin/preview/"))
(~shared:layout/nav-link :href (app-url "blog" (str "/" __slug "/admin/preview/"))
:label "preview" :select-colours __sc
:is-selected (when (= (unquote selected) "preview") "true"))
(~nav-link :href (app-url "blog" (str "/" __slug "/admin/edit/"))
(~shared:layout/nav-link :href (app-url "blog" (str "/" __slug "/admin/edit/"))
:label "edit" :select-colours __sc
:is-selected (when (= (unquote selected) "edit") "true"))
(~nav-link :href (app-url "blog" (str "/" __slug "/admin/settings/"))
(~shared:layout/nav-link :href (app-url "blog" (str "/" __slug "/admin/settings/"))
:label "settings" :select-colours __sc
:is-selected (when (= (unquote selected) "settings") "true"))))))))
@@ -340,9 +340,9 @@
(quasiquote
(let ((__phctx (post-header-ctx)))
(when (get __phctx "slug")
(~menu-row-sx :id "post-admin-row" :level 2
(~shared:layout/menu-row-sx :id "post-admin-row" :level 2
:link-href (get __phctx "admin-href")
:link-label-content (~post-admin-label
:link-label-content (~shared:layout/post-admin-label
:selected (unquote selected))
:nav (~post-admin-nav-auto (unquote selected))
:child-id "post-admin-header-child"
@@ -352,27 +352,27 @@
;; Shared nav helpers — used by post_header_sx / post_admin_header_sx
;; ---------------------------------------------------------------------------
(defcomp ~container-nav-wrapper (&key content)
(defcomp ~shared:layout/container-nav-wrapper (&key content)
(div :id "entries-calendars-nav-wrapper"
:class "flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
content))
; @css justify-center cursor-pointer flex flex-row items-center gap-2 rounded bg-stone-200 text-black p-3 !bg-stone-500 !text-white
(defcomp ~admin-cog-button (&key (href :as string) (is-admin-page :as boolean?))
(defcomp ~shared:layout/admin-cog-button (&key (href :as string) (is-admin-page :as boolean?))
(div :class "relative nav-group"
(a :href href
:class (str "justify-center cursor-pointer flex flex-row items-center gap-2 rounded bg-stone-200 text-black p-3 "
(if is-admin-page "!bg-stone-500 !text-white" ""))
(i :class "fa fa-cog" :aria-hidden "true"))))
(defcomp ~post-admin-label (&key (selected :as string?))
(defcomp ~shared:layout/post-admin-label (&key (selected :as string?))
(<>
(i :class "fa fa-shield-halved" :aria-hidden "true")
" admin"
(when selected
(span :class "text-white" selected))))
(defcomp ~nav-link (&key (href :as string) (hx-select :as string?) (label :as string?) (icon :as string?)
(defcomp ~shared:layout/nav-link (&key (href :as string) (hx-select :as string?) (label :as string?) (icon :as string?)
(aclass :as string?) (select-colours :as string?) (is-selected :as string?))
(div :class "relative nav-group"
(a :href href

View File

@@ -2,33 +2,33 @@
;; The single place where raw! lives — for CMS content (Ghost post body,
;; product descriptions, etc.) that arrives as pre-rendered HTML.
(defcomp ~rich-text (&key (html :as string))
(defcomp ~shared:misc/rich-text (&key (html :as string))
(raw! html))
(defcomp ~error-inline (&key (message :as string))
(defcomp ~shared:misc/error-inline (&key (message :as string))
(div :class "text-red-600 text-sm" message))
(defcomp ~notification-badge (&key (count :as number))
(defcomp ~shared:misc/notification-badge (&key (count :as number))
(span :class "bg-red-500 text-white text-xs rounded-full px-1.5 py-0.5" count))
(defcomp ~cache-cleared (&key (time-str :as string))
(defcomp ~shared:misc/cache-cleared (&key (time-str :as string))
(span :class "text-green-600 font-bold" "Cache cleared at " time-str))
(defcomp ~error-list (&key (items :as list?))
(defcomp ~shared:misc/error-list (&key (items :as list?))
(ul :class "list-disc pl-5 space-y-1 text-sm text-red-600"
(when items items)))
(defcomp ~error-list-item (&key (message :as string))
(defcomp ~shared:misc/error-list-item (&key (message :as string))
(li message))
(defcomp ~fragment-error (&key (service :as string))
(defcomp ~shared:misc/fragment-error (&key (service :as string))
(p :class "text-sm text-red-600" "Service " (b service) " is unavailable."))
(defcomp ~htmx-sentinel (&key (id :as string) (hx-get :as string) (hx-trigger :as string)
(defcomp ~shared:misc/htmx-sentinel (&key (id :as string) (hx-get :as string) (hx-trigger :as string)
(hx-swap :as string) (class :as string?) extra-attrs)
(div :id id :sx-get hx-get :sx-trigger hx-trigger :sx-swap hx-swap :class class))
(defcomp ~nav-group-link (&key (href :as string) (hx-select :as string?) (nav-class :as string?) (label :as string))
(defcomp ~shared:misc/nav-group-link (&key (href :as string) (hx-select :as string?) (nav-class :as string?) (label :as string))
(div :class "relative nav-group"
(a :href href :sx-get href :sx-target "#main-panel"
:sx-select hx-select :sx-swap "outerHTML"
@@ -39,7 +39,7 @@
;; Shared sentinel components — infinite scroll triggers
;; ---------------------------------------------------------------------------
(defcomp ~sentinel-mobile (&key (id :as string) (next-url :as string) (hyperscript :as string?))
(defcomp ~shared:misc/sentinel-mobile (&key (id :as string) (next-url :as string) (hyperscript :as string?))
(div :id id :class "block md:hidden h-[60vh] opacity-0 pointer-events-none js-mobile-sentinel"
:sx-get next-url :sx-trigger "intersect once delay:250ms, sentinelmobile:retry"
:sx-swap "outerHTML" :_ hyperscript
@@ -50,7 +50,7 @@
(i :class "fa fa-exclamation-triangle text-2xl")
(p :class "mt-2" "Loading failed \u2014 retrying\u2026"))))
(defcomp ~sentinel-desktop (&key (id :as string) (next-url :as string) (hyperscript :as string?))
(defcomp ~shared:misc/sentinel-desktop (&key (id :as string) (next-url :as string) (hyperscript :as string?))
(div :id id :class "hidden md:block h-4 opacity-0 pointer-events-none"
:sx-get next-url :sx-trigger "intersect once delay:250ms, sentinel:retry"
:sx-swap "outerHTML" :_ hyperscript
@@ -59,20 +59,20 @@
(div :class "animate-spin h-6 w-6 border-2 border-stone-300 border-t-stone-600 rounded-full"))
(div :class "js-neterr hidden text-center py-2 text-stone-400 text-sm" "Retry\u2026")))
(defcomp ~sentinel-simple (&key (id :as string) (next-url :as string))
(defcomp ~shared:misc/sentinel-simple (&key (id :as string) (next-url :as string))
(div :id id :class "h-4 opacity-0 pointer-events-none"
:sx-get next-url :sx-trigger "intersect once delay:250ms" :sx-swap "outerHTML"
:role "status" :aria-hidden "true"
(div :class "text-center text-xs text-stone-400" "loading...")))
(defcomp ~end-of-results (&key (cls :as string?))
(defcomp ~shared:misc/end-of-results (&key (cls :as string?))
(div :class (or cls "col-span-full mt-4 text-center text-xs text-stone-400") "End of results"))
;; ---------------------------------------------------------------------------
;; Shared empty state — icon + message + optional action
;; ---------------------------------------------------------------------------
(defcomp ~empty-state (&key (icon :as string?) (message :as string) (cls :as string?) &rest children)
(defcomp ~shared:misc/empty-state (&key (icon :as string?) (message :as string) (cls :as string?) &rest children)
(div :class (or cls "p-8 text-center text-stone-400")
(when icon (div (i :class (str icon " text-4xl mb-2") :aria-hidden "true")))
(p message)
@@ -82,7 +82,7 @@
;; Shared badge — inline pill with configurable colours
;; ---------------------------------------------------------------------------
(defcomp ~badge (&key (label :as string) (cls :as string?))
(defcomp ~shared:misc/badge (&key (label :as string) (cls :as string?))
(span :class (str "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium " (or cls "bg-stone-100 text-stone-700"))
label))
@@ -90,7 +90,7 @@
;; Shared delete button with confirm dialog
;; ---------------------------------------------------------------------------
(defcomp ~delete-btn (&key (url :as string) (trigger-target :as string) (title :as string?)
(defcomp ~shared:misc/delete-btn (&key (url :as string) (trigger-target :as string) (title :as string?)
(text :as string?) (confirm-text :as string?) (cancel-text :as string?)
(sx-headers :as string?) (cls :as string?))
(button :type "button"
@@ -110,7 +110,7 @@
;; Shared price display — special + regular with strikethrough
;; ---------------------------------------------------------------------------
(defcomp ~price (&key (special-price :as string?) (regular-price :as string?))
(defcomp ~shared:misc/price (&key (special-price :as string?) (regular-price :as string?))
(div :class "mt-1 flex items-baseline gap-2 justify-center"
(when special-price (div :class "text-lg font-semibold text-emerald-700" special-price))
(when (and special-price regular-price) (div :class "text-sm line-through text-stone-500" regular-price))
@@ -120,7 +120,7 @@
;; Shared image-or-placeholder
;; ---------------------------------------------------------------------------
(defcomp ~img-or-placeholder (&key (src :as string?) (alt :as string?) (size-cls :as string?)
(defcomp ~shared:misc/img-or-placeholder (&key (src :as string?) (alt :as string?) (size-cls :as string?)
(placeholder-icon :as string?) (placeholder-text :as string?))
(if src
(img :src src :alt (or alt "") :class (or size-cls "w-12 h-12 rounded-full object-cover flex-shrink-0"))
@@ -133,35 +133,35 @@
;; Shared view toggle — list/tile view switcher
;; ---------------------------------------------------------------------------
(defcomp ~list-svg ()
(defcomp ~shared:misc/list-svg ()
(svg :xmlns "http://www.w3.org/2000/svg" :class "h-5 w-5" :fill "none" :viewBox "0 0 24 24"
:stroke "currentColor" :stroke-width "2"
(path :stroke-linecap "round" :stroke-linejoin "round" :d "M4 6h16M4 12h16M4 18h16")))
(defcomp ~tile-svg ()
(defcomp ~shared:misc/tile-svg ()
(svg :xmlns "http://www.w3.org/2000/svg" :class "h-5 w-5" :fill "none" :viewBox "0 0 24 24"
:stroke "currentColor" :stroke-width "2"
(path :stroke-linecap "round" :stroke-linejoin "round"
:d "M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zM14 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z")))
(defcomp ~view-toggle (&key (list-href :as string) (tile-href :as string) (hx-select :as string?)
(defcomp ~shared:misc/view-toggle (&key (list-href :as string) (tile-href :as string) (hx-select :as string?)
(list-cls :as string?) (tile-cls :as string?) (storage-key :as string?)
list-svg tile-svg)
(div :class "hidden md:flex justify-end px-3 pt-3 gap-1"
(a :href list-href :sx-get list-href :sx-target "#main-panel" :sx-select hx-select
:sx-swap "outerHTML" :sx-push-url "true" :class (str "p-1.5 rounded " list-cls) :title "List view"
:_ (str "on click js localStorage.removeItem('" (or storage-key "view") "') end")
(or list-svg (~list-svg)))
(or list-svg (~shared:misc/list-svg)))
(a :href tile-href :sx-get tile-href :sx-target "#main-panel" :sx-select hx-select
:sx-swap "outerHTML" :sx-push-url "true" :class (str "p-1.5 rounded " tile-cls) :title "Tile view"
:_ (str "on click js localStorage.setItem('" (or storage-key "view") "','tile') end")
(or tile-svg (~tile-svg)))))
(or tile-svg (~shared:misc/tile-svg)))))
;; ---------------------------------------------------------------------------
;; Shared CRUD admin panel — for calendars, markets, etc.
;; ---------------------------------------------------------------------------
(defcomp ~crud-create-form (&key (create-url :as string) (csrf :as string) (errors-id :as string?)
(defcomp ~shared:misc/crud-create-form (&key (create-url :as string) (csrf :as string) (errors-id :as string?)
(list-id :as string?) (placeholder :as string?) (label :as string?)
(btn-label :as string?))
(<>
@@ -177,12 +177,12 @@
:placeholder (or placeholder "Name")))
(button :type "submit" :class "border rounded px-3 py-2" (or btn-label "Add")))))
(defcomp ~crud-panel (&key form list (list-id :as string?))
(defcomp ~shared:misc/crud-panel (&key form list (list-id :as string?))
(section :class "p-4"
form
(div :id (or list-id "crud-list") :class "mt-6" list)))
(defcomp ~crud-item (&key (href :as string) (name :as string) (slug :as string) (del-url :as string)
(defcomp ~shared:misc/crud-item (&key (href :as string) (name :as string) (slug :as string) (del-url :as string)
(csrf-hdr :as string) (list-id :as string?) (confirm-title :as string?)
(confirm-text :as string?))
(div :class "mt-6 border rounded-lg p-4"
@@ -206,7 +206,7 @@
;; checkout prefix) used by blog, events, and cart admin panels.
;; ---------------------------------------------------------------------------
(defcomp ~sumup-settings-form (&key (update-url :as string) (csrf :as string?) (merchant-code :as string?)
(defcomp ~shared:misc/sumup-settings-form (&key (update-url :as string) (csrf :as string?) (merchant-code :as string?)
(placeholder :as string?) (input-cls :as string?)
(sumup-configured :as boolean?) (checkout-prefix :as string?)
(panel-id :as string?) (sx-select :as string?))
@@ -241,7 +241,7 @@
;; Shared avatar — image or initial-letter placeholder
;; ---------------------------------------------------------------------------
(defcomp ~avatar (&key (src :as string?) (cls :as string?) (initial :as string?))
(defcomp ~shared:misc/avatar (&key (src :as string?) (cls :as string?) (initial :as string?))
(if src
(img :src src :alt "" :class cls)
(div :class cls initial)))
@@ -250,7 +250,7 @@
;; Shared scroll-nav wrapper — horizontal scrollable nav with arrows
;; ---------------------------------------------------------------------------
(defcomp ~scroll-nav-wrapper (&key (wrapper-id :as string) (container-id :as string) (arrow-cls :as string?)
(defcomp ~shared:misc/scroll-nav-wrapper (&key (wrapper-id :as string) (container-id :as string) (arrow-cls :as string?)
(left-hs :as string?) (scroll-hs :as string?) (right-hs :as string?)
items (oob :as boolean?))
(div :class "flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"

View File

@@ -1,24 +1,24 @@
;; Blog navigation components
(defcomp ~blog-nav-empty (&key wrapper-id)
(defcomp ~shared:nav/blog-nav-empty (&key wrapper-id)
(div :id wrapper-id :sx-swap-oob "outerHTML"))
(defcomp ~blog-nav-item-link (&key href hx-get selected nav-cls img label)
(defcomp ~shared:nav/blog-nav-item-link (&key href hx-get selected nav-cls img label)
(div (a :href href :sx-get hx-get :sx-target "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:aria-selected selected :class nav-cls
img (span label))))
(defcomp ~blog-nav-item-plain (&key href selected nav-cls img label)
(defcomp ~shared:nav/blog-nav-item-plain (&key href selected nav-cls img label)
(div (a :href href :aria-selected selected :class nav-cls
img (span label))))
;; Nav entries
(defcomp ~blog-nav-entries-empty ()
(defcomp ~shared:nav/blog-nav-entries-empty ()
(div :id "entries-calendars-nav-wrapper" :sx-swap-oob "true"))
(defcomp ~blog-nav-calendar-item (&key href nav-cls name)
(defcomp ~shared:nav/blog-nav-calendar-item (&key href nav-cls name)
(a :href href :class nav-cls
(i :class "fa fa-calendar" :aria-hidden "true")
(div name)))

View File

@@ -1,11 +1,11 @@
(defcomp ~calendar-entry-nav (&key (href :as string) (name :as string) (date-str :as string) (nav-class :as string?))
(defcomp ~shared:navigation/calendar-entry-nav (&key (href :as string) (name :as string) (date-str :as string) (nav-class :as string?))
(a :href href :class nav-class
(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" date-str))))
(defcomp ~calendar-link-nav (&key (href :as string) (name :as string) (nav-class :as string?)
(defcomp ~shared:navigation/calendar-link-nav (&key (href :as string) (name :as string) (nav-class :as string?)
(is-selected :as string?) (select-colours :as string?))
(a :href href
:sx-get href
@@ -18,13 +18,13 @@
(i :class "fa fa-calendar" :aria-hidden "true")
(span name)))
(defcomp ~market-link-nav (&key (href :as string) (name :as string) (nav-class :as string?)
(defcomp ~shared:navigation/market-link-nav (&key (href :as string) (name :as string) (nav-class :as string?)
(select-colours :as string?))
(a :href href :class (str (or nav-class "") " " (or select-colours ""))
(i :class "fa fa-shopping-bag" :aria-hidden "true")
(span name)))
(defcomp ~relation-nav (&key (href :as string) (name :as string) (icon :as string?)
(defcomp ~shared:navigation/relation-nav (&key (href :as string) (name :as string) (icon :as string?)
(nav-class :as string?) (relation-type :as string?))
(a :href href :class (or nav-class "flex items-center gap-3 rounded-lg py-2 px-3 text-sm text-stone-700 hover:bg-stone-100 transition-colors")
(when icon

View File

@@ -6,7 +6,7 @@
;; Order table rows
;; ---------------------------------------------------------------------------
(defcomp ~order-row-desktop (&key (oid :as string) (created :as string) (desc :as string) (total :as string)
(defcomp ~shared:orders/row-desktop (&key (oid :as string) (created :as string) (desc :as string) (total :as string)
(pill :as string) (status :as string) (url :as string))
(tr :class "hidden sm:table-row border-t border-stone-100 hover:bg-stone-50/60"
(td :class "px-3 py-2 align-top" (span :class "font-mono text-[11px] sm:text-xs" oid))
@@ -17,7 +17,7 @@
(td :class "px-3 py-0.5 align-top text-right"
(a :href url :class "inline-flex items-center px-3 py-1.5 text-xs sm:text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition" "View"))))
(defcomp ~order-row-mobile (&key (oid :as string) (created :as string) (total :as string)
(defcomp ~shared:orders/row-mobile (&key (oid :as string) (created :as string) (total :as string)
(pill :as string) (status :as string) (url :as string))
(tr :class "sm:hidden border-t border-stone-100"
(td :colspan "5" :class "px-3 py-3"
@@ -30,16 +30,16 @@
(div :class "font-medium text-stone-800" total)
(a :href url :class "inline-flex items-center px-2 py-1 text-[11px] rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition shrink-0" "View"))))))
(defcomp ~order-end-row ()
(defcomp ~shared:orders/end-row ()
(tr (td :colspan "5" :class "px-3 py-4 text-center text-xs text-stone-400"
(~end-of-results :cls "text-center text-xs text-stone-400"))))
(~shared:misc/end-of-results :cls "text-center text-xs text-stone-400"))))
(defcomp ~order-empty-state ()
(defcomp ~shared:orders/empty-state ()
(div :class "max-w-full px-3 py-3 space-y-3"
(div :class "rounded-2xl border border-dashed border-stone-300 bg-white/80 p-4 sm:p-6 text-sm text-stone-700"
"No orders yet.")))
(defcomp ~order-table (&key rows)
(defcomp ~shared:orders/table (&key rows)
(div :class "max-w-full px-3 py-3 space-y-3"
(div :class "overflow-x-auto rounded-2xl border border-stone-200 bg-white/80"
(table :class "min-w-full text-xs sm:text-sm"
@@ -53,7 +53,7 @@
(th :class "px-3 py-2 text-left font-medium" "")))
(tbody rows)))))
(defcomp ~order-list-header (&key search-mobile)
(defcomp ~shared:orders/list-header (&key search-mobile)
(header :class "mb-6 sm:mb-8 flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4"
(div :class "space-y-1"
(p :class "text-xs sm:text-sm text-stone-600" "Recent orders placed via the checkout."))
@@ -63,13 +63,13 @@
;; Order detail panels
;; ---------------------------------------------------------------------------
(defcomp ~order-item-image (&key (src :as string) (alt :as string))
(defcomp ~shared:orders/item-image (&key (src :as string) (alt :as string))
(img :src src :alt alt :class "w-full h-full object-contain object-center" :loading "lazy" :decoding "async"))
(defcomp ~order-item-no-image ()
(defcomp ~shared:orders/item-no-image ()
(div :class "w-full h-full flex items-center justify-center text-[9px] text-stone-400" "No image"))
(defcomp ~order-item-row (&key (href :as string) img (title :as string) (pid :as string)
(defcomp ~shared:orders/item-row (&key (href :as string) img (title :as string) (pid :as string)
(qty :as string) (price :as string))
(li (a :class "w-full py-2 flex gap-3" :href href
(div :class "w-12 h-12 sm:w-14 sm:h-14 rounded-md bg-stone-100 flex-shrink-0 overflow-hidden" img)
@@ -81,12 +81,12 @@
(p qty)
(p price))))))
(defcomp ~order-items-panel (&key items)
(defcomp ~shared:orders/items-panel (&key items)
(div :class "rounded-2xl border border-stone-200 bg-white/80 p-4 sm:p-6"
(h2 :class "text-sm sm:text-base font-semibold mb-3" "Items")
(ul :class "divide-y divide-stone-100 text-xs sm:text-sm" items)))
(defcomp ~order-calendar-entry (&key (name :as string) (pill :as string) (status :as string)
(defcomp ~shared:orders/calendar-entry (&key (name :as string) (pill :as string) (status :as string)
(date-str :as string) (cost :as string))
(li :class "px-4 py-3 flex items-start justify-between text-sm"
(div (div :class "font-medium flex items-center gap-2"
@@ -94,19 +94,19 @@
(div :class "text-xs text-stone-500" date-str))
(div :class "ml-4 font-medium" cost)))
(defcomp ~order-calendar-section (&key items)
(defcomp ~shared:orders/calendar-section (&key items)
(section :class "mt-6 space-y-3"
(h2 :class "text-base sm:text-lg font-semibold" "Calendar bookings in this order")
(ul :class "divide-y divide-stone-200 rounded-2xl border border-stone-200 bg-white/80" items)))
(defcomp ~order-detail-panel (&key summary items calendar)
(defcomp ~shared:orders/detail-panel (&key summary items calendar)
(div :class "max-w-full px-3 py-3 space-y-4" summary items calendar))
(defcomp ~order-pay-btn (&key (url :as string))
(defcomp ~shared:orders/pay-btn (&key (url :as string))
(a :href url :class "inline-flex items-center px-3 py-2 text-xs sm:text-sm rounded-full border border-emerald-600 bg-emerald-600 text-white hover:bg-emerald-700 transition"
(i :class "fa fa-credit-card mr-2" :aria-hidden "true") "Open payment page"))
(defcomp ~order-detail-filter (&key (info :as string) (list-url :as string) (recheck-url :as string)
(defcomp ~shared:orders/detail-filter (&key (info :as string) (list-url :as string) (recheck-url :as string)
(csrf :as string) pay)
(header :class "mb-6 sm:mb-8 flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4"
(div :class "space-y-1"
@@ -120,43 +120,43 @@
(i :class "fa-solid fa-rotate mr-2" :aria-hidden "true") "Re-check status"))
pay)))
(defcomp ~order-detail-header-stack (&key auth orders order)
(~header-child-sx :inner
(<> auth (~header-child-sx :id "auth-header-child" :inner
(<> orders (~header-child-sx :id "orders-header-child" :inner order))))))
(defcomp ~shared:orders/detail-header-stack (&key auth orders order)
(~shared:layout/header-child-sx :inner
(<> auth (~shared:layout/header-child-sx :id "auth-header-child" :inner
(<> orders (~shared:layout/header-child-sx :id "orders-header-child" :inner order))))))
;; ---------------------------------------------------------------------------
;; Data-driven order rows (replaces Python loop)
;; ---------------------------------------------------------------------------
(defcomp ~order-rows-from-data (&key (orders :as list?) (page :as number) (total-pages :as number)
(defcomp ~shared:orders/rows-from-data (&key (orders :as list?) (page :as number) (total-pages :as number)
(next-url :as string?))
(<>
(map (lambda (o)
(<>
(~order-row-desktop :oid (get o "oid") :created (get o "created")
(~shared:orders/row-desktop :oid (get o "oid") :created (get o "created")
:desc (get o "desc") :total (get o "total")
:pill (get o "pill_desktop") :status (get o "status") :url (get o "url"))
(~order-row-mobile :oid (get o "oid") :created (get o "created")
(~shared:orders/row-mobile :oid (get o "oid") :created (get o "created")
:total (get o "total") :pill (get o "pill_mobile")
:status (get o "status") :url (get o "url"))))
(or orders (list)))
(if next-url
(~infinite-scroll :url next-url :page page :total-pages total-pages
(~shared:controls/infinite-scroll :url next-url :page page :total-pages total-pages
:id-prefix "orders" :colspan 5)
(~order-end-row))))
(~shared:orders/end-row))))
;; ---------------------------------------------------------------------------
;; Data-driven order items (replaces Python loop)
;; ---------------------------------------------------------------------------
(defcomp ~order-items-from-data (&key (items :as list?))
(~order-items-panel
(defcomp ~shared:orders/items-from-data (&key (items :as list?))
(~shared:orders/items-panel
:items (<> (map (lambda (item)
(let* ((img (if (get item "product_image")
(~order-item-image :src (get item "product_image") :alt (or (get item "product_title") "Product image"))
(~order-item-no-image))))
(~order-item-row
(~shared:orders/item-image :src (get item "product_image") :alt (or (get item "product_title") "Product image"))
(~shared:orders/item-no-image))))
(~shared:orders/item-row
:href (get item "href") :img img
:title (or (get item "product_title") "Unknown product")
:pid (str "Product ID: " (get item "product_id"))
@@ -168,10 +168,10 @@
;; Data-driven calendar entries (replaces Python loop)
;; ---------------------------------------------------------------------------
(defcomp ~order-calendar-from-data (&key (entries :as list?))
(~order-calendar-section
(defcomp ~shared:orders/calendar-from-data (&key (entries :as list?))
(~shared:orders/calendar-section
:items (<> (map (lambda (e)
(~order-calendar-entry
(~shared:orders/calendar-entry
:name (get e "name") :pill (get e "pill")
:status (get e "status") :date-str (get e "date_str")
:cost (get e "cost")))
@@ -186,7 +186,7 @@
;; ---------------------------------------------------------------------------
;; Status pill class mapping
(defcomp ~order-status-pill-cls (&key (status :as string?))
(defcomp ~shared:orders/status-pill-cls (&key (status :as string?))
(let* ((sl (lower (or status ""))))
(cond
((= sl "paid") "border-emerald-300 bg-emerald-50 text-emerald-700")
@@ -194,46 +194,46 @@
(true "border-stone-300 bg-stone-50 text-stone-700"))))
;; Single order row pair (desktop + mobile) — takes serialized order data dict
(defcomp ~order-row-pair (&key (order :as dict) (detail-url-prefix :as string))
(defcomp ~shared:orders/row-pair (&key (order :as dict) (detail-url-prefix :as string))
(let* ((status (or (get order "status") "pending"))
(pill-base (~order-status-pill-cls :status status))
(pill-base (~shared:orders/status-pill-cls :status status))
(oid (str "#" (get order "id")))
(created (or (get order "created_at_formatted") "\u2014"))
(desc (or (get order "description") ""))
(total (str (or (get order "currency") "GBP") " " (or (get order "total_formatted") "0.00")))
(url (str detail-url-prefix (get order "id") "/")))
(<>
(~order-row-desktop
(~shared:orders/row-desktop
:oid oid :created created :desc desc :total total
:pill (str "inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] sm:text-xs " pill-base)
:status status :url url)
(~order-row-mobile
(~shared:orders/row-mobile
:oid oid :created created :total total
:pill (str "inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] " pill-base)
:status status :url url))))
;; Assembled orders list content
(defcomp ~orders-list-content (&key (orders :as list) (page :as number) (total-pages :as number)
(defcomp ~shared:orders/list-content (&key (orders :as list) (page :as number) (total-pages :as number)
(rows-url :as string) (detail-url-prefix :as string))
(if (empty? orders)
(~order-empty-state)
(~order-table
(~shared:orders/empty-state)
(~shared:orders/table
:rows (<>
(map (lambda (order)
(~order-row-pair :order order :detail-url-prefix detail-url-prefix))
(~shared:orders/row-pair :order order :detail-url-prefix detail-url-prefix))
orders)
(if (< page total-pages)
(~infinite-scroll
(~shared:controls/infinite-scroll
:url (str rows-url "?page=" (inc page))
:page page :total-pages total-pages
:id-prefix "orders" :colspan 5)
(~order-end-row))))))
(~shared:orders/end-row))))))
;; Assembled order detail content — replaces Python _order_main_sx
(defcomp ~order-detail-content (&key (order :as dict) (calendar-entries :as list?))
(defcomp ~shared:orders/detail-content (&key (order :as dict) (calendar-entries :as list?))
(let* ((items (get order "items")))
(~order-detail-panel
:summary (~order-summary-card
(~shared:orders/detail-panel
:summary (~shared:cards/order-summary-card
:order-id (get order "id")
:created-at (get order "created_at_formatted")
:description (get order "description")
@@ -241,21 +241,21 @@
:currency (get order "currency")
:total-amount (get order "total_formatted"))
:items (when (not (empty? (or items (list))))
(~order-items-panel
(~shared:orders/items-panel
:items (map (lambda (item)
(~order-item-row
(~shared:orders/item-row
:href (get item "product_url")
:img (if (get item "product_image")
(~order-item-image :src (get item "product_image")
(~shared:orders/item-image :src (get item "product_image")
:alt (or (get item "product_title") "Product image"))
(~order-item-no-image))
(~shared:orders/item-no-image))
:title (or (get item "product_title") "Unknown product")
:pid (str "Product ID: " (get item "product_id"))
:qty (str "Qty: " (get item "quantity"))
:price (str (or (get item "currency") (get order "currency") "GBP") " " (or (get item "unit_price_formatted") "0.00"))))
items)))
:calendar (when (not (empty? (or calendar-entries (list))))
(~order-calendar-section
(~shared:orders/calendar-section
:items (map (lambda (e)
(let* ((st (or (get e "state") ""))
(pill (cond
@@ -263,7 +263,7 @@
((= st "provisional") "bg-amber-100 text-amber-800")
((= st "ordered") "bg-blue-100 text-blue-800")
(true "bg-stone-100 text-stone-700"))))
(~order-calendar-entry
(~shared:orders/calendar-entry
:name (get e "name")
:pill (str "inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium " pill)
:status (upper (slice st 0 1))
@@ -272,33 +272,33 @@
calendar-entries))))))
;; Assembled order detail filter — replaces Python _order_filter_sx
(defcomp ~order-detail-filter-content (&key (order :as dict) (list-url :as string) (recheck-url :as string)
(defcomp ~shared:orders/detail-filter-content (&key (order :as dict) (list-url :as string) (recheck-url :as string)
(pay-url :as string) (csrf :as string))
(let* ((status (or (get order "status") "pending"))
(created (or (get order "created_at_formatted") "\u2014")))
(~order-detail-filter
(~shared:orders/detail-filter
:info (str "Placed " created " \u00b7 Status: " status)
:list-url list-url
:recheck-url recheck-url
:csrf csrf
:pay (when (!= status "paid")
(~order-pay-btn :url pay-url)))))
(~shared:orders/pay-btn :url pay-url)))))
;; ---------------------------------------------------------------------------
;; Checkout return components
;; ---------------------------------------------------------------------------
(defcomp ~checkout-return-header (&key (status :as string))
(defcomp ~shared:orders/checkout-return-header (&key (status :as string))
(header :class "mb-6 sm:mb-8"
(h1 :class "text-xl sm:text-2xl md:text-3xl font-semibold tracking-tight" "Payment complete")
(p :class "text-xs sm:text-sm text-stone-600"
(str "Your checkout session is " status "."))))
(defcomp ~checkout-return-missing ()
(defcomp ~shared:orders/checkout-return-missing ()
(div :class "max-w-full px-3 py-3 space-y-4"
(p :class "text-sm text-stone-600" "Order not found.")))
(defcomp ~checkout-return-ticket (&key (name :as string) (pill :as string) (state :as string)
(defcomp ~shared:orders/checkout-return-ticket (&key (name :as string) (pill :as string) (state :as string)
(type-name :as string?) (date-str :as string) (code :as string?)
(price :as string))
(li :class "px-4 py-3 flex items-start justify-between text-sm"
@@ -310,23 +310,23 @@
(when code (div :class "font-mono text-xs text-stone-400" code)))
(div :class "ml-4 font-medium" price)))
(defcomp ~checkout-return-tickets (&key items)
(defcomp ~shared:orders/checkout-return-tickets (&key items)
(section :class "mt-6 space-y-3"
(h2 :class "text-base sm:text-lg font-semibold" "Tickets")
(ul :class "divide-y divide-stone-200 rounded-2xl border border-stone-200 bg-white/80" items)))
(defcomp ~checkout-return-failed (&key (order-id :as string?))
(defcomp ~shared:orders/checkout-return-failed (&key (order-id :as string?))
(div :class "rounded-lg border border-rose-200 bg-rose-50 p-4 text-sm text-rose-900"
(p :class "font-medium" "Payment failed")
(p "Please try again or contact support."
(when order-id (span " Order #" (str order-id))))))
(defcomp ~checkout-return-paid ()
(defcomp ~shared:orders/checkout-return-paid ()
(div :class "rounded-lg border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-900"
(p :class "font-medium" "Payment successful!")
(p "Your order has been confirmed.")))
(defcomp ~checkout-return-content (&key summary items calendar tickets status-message)
(defcomp ~shared:orders/checkout-return-content (&key summary items calendar tickets status-message)
(div :class "max-w-full px-3 py-3 space-y-4"
status-message summary items calendar tickets))
@@ -334,15 +334,15 @@
;; Checkout error screens
;; ---------------------------------------------------------------------------
(defcomp ~checkout-error-header ()
(defcomp ~shared:orders/checkout-error-header ()
(header :class "mb-6 sm:mb-8"
(h1 :class "text-xl sm:text-2xl md:text-3xl font-semibold tracking-tight" "Checkout error")
(p :class "text-xs sm:text-sm text-stone-600" "We tried to start your payment with SumUp but hit a problem.")))
(defcomp ~checkout-error-order-id (&key (oid :as string))
(defcomp ~shared:orders/checkout-error-order-id (&key (oid :as string))
(p :class "text-xs text-rose-800/80" "Order ID: " (span :class "font-mono" oid)))
(defcomp ~checkout-error-content (&key (msg :as string) order (back-url :as string))
(defcomp ~shared:orders/checkout-error-content (&key (msg :as string) order (back-url :as string))
(div :class "max-w-full px-3 py-3 space-y-4"
(div :class "rounded-2xl border border-rose-200 bg-rose-50/80 p-4 sm:p-6 text-sm text-rose-900 space-y-2"
(p :class "font-medium" "Something went wrong.")

View File

@@ -1,4 +1,4 @@
(defcomp ~base-shell (&key (title :as string) (asset-url :as string) &rest children)
(defcomp ~shared:pages/base-shell (&key (title :as string) (asset-url :as string) &rest children)
(<>
(raw! "<!doctype html>")
(html :lang "en"
@@ -23,14 +23,14 @@
;; <script>__sxResolve("id", "(resolved sx ...)")</script>
;; ---------------------------------------------------------------------------
(defcomp ~suspense (&key (id :as string) fallback &rest children)
(defcomp ~shared:pages/suspense (&key (id :as string) fallback &rest children)
(div :id (str "sx-suspense-" id)
:data-suspense id
:style "display:contents"
(if (not (empty? children)) children fallback)))
(defcomp ~error-page (&key (title :as string) (message :as string) (image :as string?) (asset-url :as string))
(~base-shell :title title :asset-url asset-url
(defcomp ~shared:pages/error-page (&key (title :as string) (message :as string) (image :as string?) (asset-url :as string))
(~shared:pages/base-shell :title title :asset-url asset-url
(div :class "text-center p-8 max-w-lg mx-auto"
(div :class "font-bold text-2xl md:text-4xl text-red-500 mb-4"
(div message))

View File

@@ -1,4 +1,4 @@
(defcomp ~relation-attach (&key (create-url :as string) (label :as string?) (icon :as string?))
(defcomp ~shared:relations/attach (&key (create-url :as string) (label :as string?) (icon :as string?))
(a :href create-url
:sx-get create-url
:sx-target "#main-panel"
@@ -8,7 +8,7 @@
(when icon (i :class icon))
(span (or label "Add"))))
(defcomp ~relation-detach (&key (detach-url :as string) (name :as string?))
(defcomp ~shared:relations/detach (&key (detach-url :as string) (name :as string?))
(button :sx-delete detach-url
:sx-confirm (str "Remove " (or name "this item") "?")
:class "text-red-500 hover:text-red-700 text-sm p-1 rounded hover:bg-red-50 transition-colors"

View File

@@ -11,7 +11,7 @@
;; - Pre-rendered meta HTML from callers
;; ---------------------------------------------------------------------------
(defcomp ~sx-page-shell (&key (title :as string) (meta-html :as string?) (csrf :as string)
(defcomp ~shared:shell/sx-page-shell (&key (title :as string) (meta-html :as string?) (csrf :as string)
(sx-css :as string?) (sx-css-classes :as string?)
(component-hash :as string?) (component-defs :as string?)
(pages-sx :as string?) (page-sx :as string?)