From 66c51b0e714fa8a1c80784108191270babc8525e Mon Sep 17 00:00:00 2001 From: unwox Date: Sun, 31 Aug 2025 17:51:57 +0600 Subject: implement shop --- pages/shop/index.fnl | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 pages/shop/index.fnl (limited to 'pages/shop/index.fnl') diff --git a/pages/shop/index.fnl b/pages/shop/index.fnl new file mode 100644 index 0000000..5a96ab2 --- /dev/null +++ b/pages/shop/index.fnl @@ -0,0 +1,155 @@ +(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} -- cgit v1.2.3