(import-macros {:compile-html <>} :macros)
(local lib (require :lib))
(local dicts (require :dicts))
(local templates (require :templates))
(fn all-products [db authenticated?]
(local where
(if (not authenticated?)
"WHERE products.published = true"
""))
(_G.must
(luna.db.query-assoc db
(..
"SELECT products.name,
products.title,
products.published,
products.short_description as \"short-description\",
products.price_per AS \"price-per\",
products.type,
products.image1,
products.image2,
products.image3,
products.image4,
products.image5
FROM products "
where
" ORDER BY products.position") [])))
(fn quantity-steps [stock step]
(assert (< 0 step) "step must be greater than 0")
(var result [])
(var first (math.min stock step))
(while (<= first stock)
(table.insert result first)
(set first (+ first step)))
result)
(fn item-template [product]
(local item-url (.. "/shop/" product.name))
;; (var quantity-options [])
;; (if (< 0 product.stock)
;; (each [_ q (ipairs (quantity-steps product.stock 50))]
;; (table.insert quantity-options
;; (<>
;; [:option {:value (tostring q)}
;; (.. q " грамм за " (* product.price-per q) "₽")])))
;; (table.insert quantity-options (<> [:option {:value "0"} "Товар закончился"])))
(local images [])
(for [i 2 5]
(table.insert images (. product (.. "image" i))))
(<>
[:section {:class (.. "shop-item"
(if (not product.published)
" shop-item-not-published" ""))}
[:a {:href item-url}
[:div {:class "shop-item-imgs"}
[:img {:class "shop-item-img"
:src (.. "/static/files/" (. product.image1) "-thumbnail.jpg")}]
(table.concat
(icollect [idx image (ipairs images)]
(<>
[:img {:class "shop-item-img" :src (.. "/static/files/" image "-thumbnail.jpg")
:loading "lazy"
:style (.. "z-index: " (+ idx 2) ";"
"width: calc(100% / " (# images) ");"
"left: calc(100% / " (# images) " * " (- idx 1) ")")}])))]]
[:a {:href item-url} [:h3 {:class "shop-item-title"} product.title]]
[:div {:style "font-style: italic; margin-bottom: 0.25rem;"}
(or (dicts.label dicts.tea-type product.type) product.type) ", "
[:strong {} (* 50 product.price-per) "₽ за 50 гр. "]]
;; [:div {:class "shop-item-price"}
;; [:form {:method "POST"}
;; [:input {:type "hidden" :name "name" :value product.name}]
;; [:select {:name "quantity"} (table.concat quantity-options)]
;; [:button {:type "submit"} "Добавить"]]]
[:div {} product.short-description]]))
(fn content [db basket basket-total authenticated?]
[(<>
[:div {:class "side"}
(templates.header "/shop" authenticated?)
(if (< 0 (# basket))
(<>
[:article {:class "article"}
[:h2 {} "Корзина"]
[:div {}
(table.concat
(icollect [_ item (ipairs basket)]
(templates.basket-item item "/shop")))]
[:div {} "~~~"]
[:div {:class "basket-total"} (.. "Итого: " basket-total "₽")]
[:a {:href "/shop/order"} "Оформить заказ"]])
"")])
(<>
[:div {:class "content"}
[:div {:class "mb-1"} [:a {:href "/"} "⟵ Обратно на главную"]]
[:h2 {:class "mb-1 product-page-title"}
"Магазин"
(if authenticated?
(<> [:a {:style "font-size: 1rem; margin-left: 0.75rem;"
:href (.. "/shop/add")} "+ Добавить"])
"")]
[:div {:class "shop-items"}
(let [products (all-products db authenticated?)]
(if (< 0 (# products))
(table.concat
(icollect [_ v (ipairs products)]
(item-template v)))
(<> [:em {} "Пока что здесь ничего нет!"])))]])])
(fn create-order [db]
(let [id (_G.must (luna.crypto.random-string 64))]
(_G.must
(luna.db.exec
db "INSERT INTO orders (id, creation_time) VALUES (?, ?)"
[id (lib.now)]))
id))
(fn create-order-line [db order-id name quantity]
(_G.must
(luna.db.exec
db
"INSERT INTO order_lines (order_id, product_name, quantity) VALUES (?, ?, ?)"
[order-id name quantity])))
(fn render [request db authenticated?]
(let [order-id (lib.order-id request)
basket (if order-id (lib.basket db order-id) [])
basket-total (accumulate [sum 0 _ v (ipairs basket)]
(+ sum (* v.quantity v.price-per)))]
(if (= request.method "POST")
(do
(var order-id (lib.order-id request))
(var headers
(if (not order-id)
(do
(set order-id (create-order db))
{:Set-Cookie (.. "order= " order-id "; HttpOnly; SameSite=strict"
(if luna.debug? "" "; Secure"))})
{}))
(if (and order-id request.body)
(let [body-values (lib.parse-values request.body)]
(create-order-line db order-id body-values.name body-values.quantity)
(tset headers :Location "/shop")
(values 302 headers ""))
(values 400 {} "bad body")))
(values
200 {}
(templates.base (content db basket basket-total authenticated?))))))
{: render}