Move rendering logic from Python for-loops building sx_call strings into SX defcomp components that use map/lambda over data dicts. Python now serializes display data into plain dicts and passes them via a single sx_call; the SX layer handles iteration and conditional rendering. Covers orders (rows, items, calendar, tickets), federation (timeline, search, actors, profile activities), and blog (cards, pages, filters, snippets, menu items, tag groups, page search, nav OOB). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
194 lines
9.9 KiB
Plaintext
194 lines
9.9 KiB
Plaintext
;; Shared order components — used by both cart and orders services
|
|
;;
|
|
;; Order table (list view), order detail panels, and checkout error screens.
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Order table rows
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~order-row-desktop (&key oid created desc total pill status url)
|
|
(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))
|
|
(td :class "px-3 py-2 align-top text-stone-700 text-xs sm:text-sm" created)
|
|
(td :class "px-3 py-2 align-top text-stone-700 text-xs sm:text-sm" desc)
|
|
(td :class "px-3 py-2 align-top text-stone-700 text-xs sm:text-sm" total)
|
|
(td :class "px-3 py-2 align-top" (span :class pill status))
|
|
(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 created total pill status url)
|
|
(tr :class "sm:hidden border-t border-stone-100"
|
|
(td :colspan "5" :class "px-3 py-3"
|
|
(div :class "flex flex-col gap-2 text-xs"
|
|
(div :class "flex items-center justify-between gap-2"
|
|
(span :class "font-mono text-[11px] text-stone-700" oid)
|
|
(span :class pill status))
|
|
(div :class "text-[11px] text-stone-500 break-words" created)
|
|
(div :class "flex items-center justify-between gap-2"
|
|
(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 ()
|
|
(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"))))
|
|
|
|
(defcomp ~order-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)
|
|
(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"
|
|
(thead :class "bg-stone-50 border-b border-stone-200 text-stone-600"
|
|
(tr
|
|
(th :class "px-3 py-2 text-left font-medium" "Order")
|
|
(th :class "px-3 py-2 text-left font-medium" "Created")
|
|
(th :class "px-3 py-2 text-left font-medium" "Description")
|
|
(th :class "px-3 py-2 text-left font-medium" "Total")
|
|
(th :class "px-3 py-2 text-left font-medium" "Status")
|
|
(th :class "px-3 py-2 text-left font-medium" "")))
|
|
(tbody rows)))))
|
|
|
|
(defcomp ~order-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."))
|
|
(div :class "md:hidden" search-mobile)))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Order detail panels
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~order-item-image (&key src alt)
|
|
(img :src src :alt alt :class "w-full h-full object-contain object-center" :loading "lazy" :decoding "async"))
|
|
|
|
(defcomp ~order-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 img title pid qty price)
|
|
(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)
|
|
(div :class "flex-1 flex justify-between gap-3"
|
|
(div
|
|
(p :class "font-medium" title)
|
|
(p :class "text-[11px] text-stone-500" pid))
|
|
(div :class "text-right whitespace-nowrap"
|
|
(p qty)
|
|
(p price))))))
|
|
|
|
(defcomp ~order-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 pill status date-str cost)
|
|
(li :class "px-4 py-3 flex items-start justify-between text-sm"
|
|
(div (div :class "font-medium flex items-center gap-2"
|
|
name (span :class pill status))
|
|
(div :class "text-xs text-stone-500" date-str))
|
|
(div :class "ml-4 font-medium" cost)))
|
|
|
|
(defcomp ~order-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)
|
|
(div :class "max-w-full px-3 py-3 space-y-4" summary items calendar))
|
|
|
|
(defcomp ~order-pay-btn (&key url)
|
|
(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 list-url recheck-url csrf 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"
|
|
(p :class "text-xs sm:text-sm text-stone-600" info))
|
|
(div :class "flex w-full sm:w-auto justify-start sm:justify-end gap-2"
|
|
(a :href list-url :class "inline-flex items-center px-3 py-2 text-xs sm:text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition"
|
|
(i :class "fa-solid fa-list mr-2" :aria-hidden "true") "All orders")
|
|
(form :method "post" :action recheck-url :class "inline"
|
|
(input :type "hidden" :name "csrf_token" :value csrf)
|
|
(button :type "submit" :class "inline-flex items-center px-3 py-2 text-xs sm:text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition"
|
|
(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))))))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Data-driven order rows (replaces Python loop)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~order-rows-from-data (&key orders page total-pages next-url)
|
|
(<>
|
|
(map (lambda (o)
|
|
(<>
|
|
(~order-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")
|
|
: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
|
|
:id-prefix "orders" :colspan 5)
|
|
(~order-end-row))))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Data-driven order items (replaces Python loop)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~order-items-from-data (&key items)
|
|
(~order-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
|
|
:href (get item "href") :img img
|
|
:title (or (get item "product_title") "Unknown product")
|
|
:pid (str "Product ID: " (get item "product_id"))
|
|
:qty (str "Qty: " (get item "quantity"))
|
|
:price (get item "price"))))
|
|
(or items (list))))))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Data-driven calendar entries (replaces Python loop)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~order-calendar-from-data (&key entries)
|
|
(~order-calendar-section
|
|
:items (<> (map (lambda (e)
|
|
(~order-calendar-entry
|
|
:name (get e "name") :pill (get e "pill")
|
|
:status (get e "status") :date-str (get e "date_str")
|
|
:cost (get e "cost")))
|
|
(or entries (list))))))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Checkout error screens
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~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)
|
|
(p :class "text-xs text-rose-800/80" "Order ID: " (span :class "font-mono" oid)))
|
|
|
|
(defcomp ~checkout-error-content (&key msg order back-url)
|
|
(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.")
|
|
(p msg)
|
|
order)
|
|
(div (a :href back-url :class "inline-flex items-center px-3 py-2 text-xs sm:text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition"
|
|
(i :class "fa fa-shopping-cart mr-2" :aria-hidden "true") "Back to cart"))))
|