From 3f5ade2e7a139bb4405437e8fc5546aafc7b05ef Mon Sep 17 00:00:00 2001 From: unwox Date: Fri, 3 Oct 2025 11:56:37 +0600 Subject: WIP shop --- pages/auth.fnl | 6 +-- pages/index.fnl | 21 ++++++-- pages/shop/_product/edit.fnl | 6 +-- pages/shop/_product/index.fnl | 42 ++++++--------- pages/shop/add.fnl | 7 ++- pages/shop/cart/add.fnl | 53 +++++++++---------- pages/shop/cart/remove.fnl | 9 ++-- pages/shop/index.fnl | 120 ++++++++++-------------------------------- pages/shop/order.fnl | 59 --------------------- pages/shop/order/_id.fnl | 35 ++++++++++++ pages/shop/order/index.fnl | 72 +++++++++++++++++++++++++ pages/shop/order/list.fnl | 72 +++++++++++++++++++++++++ pages/shop/order/state.fnl | 18 +++++++ pages/shop/success.fnl | 15 ------ 14 files changed, 292 insertions(+), 243 deletions(-) delete mode 100644 pages/shop/order.fnl create mode 100644 pages/shop/order/_id.fnl create mode 100644 pages/shop/order/index.fnl create mode 100644 pages/shop/order/list.fnl create mode 100644 pages/shop/order/state.fnl delete mode 100644 pages/shop/success.fnl (limited to 'pages') diff --git a/pages/auth.fnl b/pages/auth.fnl index 3afb517..1f40cab 100644 --- a/pages/auth.fnl +++ b/pages/auth.fnl @@ -13,11 +13,11 @@ (set data.password nil) [(HTML - [:div {:class "side"} + [:aside {} (templates.header "/auth")]) (HTML [:section {:class "content"} - [:div {:class "mb-1"} [:a {:href "/"} "⟵ Обратно на главную"]] + [:div {:class "back"} [:a {:href "/"} "⟵ Обратно на главную"]] [:h2 {} "Войти"] (forms.render-form auth-form data errors)])]) @@ -57,7 +57,7 @@ db name (os.date "%Y-%m-%d %H:%M:%S" next-week)) cookie-expires (os.date "%a, %d %b %Y %H:%M:%S GMT" next-week)] (values 302 {:Location "/shop" - :Set-Cookie (.. "auth= " session-id "; HttpOnly; SameSite=strict;" + :Set-Cookie (.. "auth=" session-id "; HttpOnly; SameSite=strict;" "Expires=" cookie-expires (if luna.debug? "" "; Secure"))} "")) (values 400 {} diff --git a/pages/index.fnl b/pages/index.fnl index 88cdca7..37ab914 100644 --- a/pages/index.fnl +++ b/pages/index.fnl @@ -19,7 +19,13 @@ "Еженедельное мероприятие: каждую субботу в 15:00 мы собираемся и пьем чай из нашей коллекции. Для посещения необходима запись в комментариях под соответствующим постом в нашей группе. Стоимость 500 рублей - с человека.")}) + с человека.") + + :everytea + (lib.improve-typography + "Мы разработали и поддерживаем сервис для поиска чая и чайной посуды на + популярных сайтах. Полезно, если не хотите искать на отдельных сайтах или + хотите быстро сравнить цены на конкретный чай.")}) (fn pick-gallery-photo [list] (let [chosen (. list (math.random (# list)))] @@ -30,21 +36,26 @@ (fn content [authenticated?] [(HTML - [:div {:class "side mb-2"} + [:aside {} (templates.header "" authenticated?) - [:section {:class "mb-2"} + [:section {} [:h2 {} "Адрес"] [:p {} [:NO-ESCAPE texts.address]]] [:section {} [:h2 {} "Форматы участия"] - [:div {:class "mb-2"} + [:div {:class "mb-1-5"} [:div {:class "mb-1"} [:NO-ESCAPE texts.individual-ceremony]] [:div {} [:a {:href "https://t.me/whitetoadvlad"} "Записаться ⟶"]]] [:div {} [:div {:class "mb-1"} [:NO-ESCAPE texts.weekly-meetings]] [:div {} - [:a {:href "https://t.me/whitetoadtea"} "Подписаться ⟶"]]]]]) + [:a {:href "https://t.me/whitetoadtea"} "Подписаться ⟶"]]]] + [:section {} + [:h2 {} "Агрегатор"] + [:p {} [:NO-ESCAPE texts.everytea]] + [:div {} + [:a {:href "https://everytea.ru"} "everytea.ru ⟶"]]]]) (HTML [:div {:class "content"} [:div {:class "gallery"} diff --git a/pages/shop/_product/edit.fnl b/pages/shop/_product/edit.fnl index a8fd445..3e4f2f0 100644 --- a/pages/shop/_product/edit.fnl +++ b/pages/shop/_product/edit.fnl @@ -48,11 +48,11 @@ (fn content [form data errors authenticated?] [(HTML - [:div {:class "side"} + [:aside {} (templates.header "/shop" authenticated?)]) (HTML [:div {:class "content"} - [:div {:class "mb-1"} + [:div {:class "back"} [:a {:href (.. "/shop/" data.name)} "⟵ Обратно к товару"]] [:h2 {} "Редактировать товар"] (forms.render-form form data errors)])]) @@ -61,7 +61,7 @@ (if (not authenticated?) (values 302 {:Location "/shop"} "") (if request.form - (let [data (forms.convert-values-from-html product-form request.form db) + (let [data (forms.html-form->data product-form request.form db) errors (forms.validate-form product-form data) has-errors? (not (lib.empty-table? errors))] (if has-errors? diff --git a/pages/shop/_product/index.fnl b/pages/shop/_product/index.fnl index cb451b7..f8b38f7 100644 --- a/pages/shop/_product/index.fnl +++ b/pages/shop/_product/index.fnl @@ -1,7 +1,7 @@ (import-macros {:compile-html HTML} :macros) (local templates (require :templates)) -(local dicts (require :dicts)) (local lib (require :lib)) +(local shop (require :shop)) (fn text->html [text] (assert (= (type text) "string")) @@ -41,44 +41,32 @@ [name])) 1)) -(fn content [product authenticated?] +(fn content [product basket authenticated?] + (local redirect-url (.. "/shop/" product.name)) (local images []) (for [i 1 5] (table.insert images (. product (.. "image" i)))) [(HTML - [:div {:class "side"} - (templates.header "/shop" authenticated?)]) + [:aside {} + (templates.header "/shop" authenticated?) + (if (< 0 (# basket)) (templates.basket basket redirect-url) "")]) (HTML [:div {:class "content"} - [:div {:class "mb-1"} [:a {:href "/shop"} "⟵ Обратно к списку"]] + [:div {:class "back"} [:a {:href "/shop"} "⟵ Обратно к списку"]] [:div {:class "product-page-layout"} - [:article {} + [:section {} [:h2 {:class "product-page-title"} product.title] (if authenticated? (HTML - [:div {:class "mb-1" :style "margin-top: -0.25rem;"} + [:div {:class "mb-1" :style "margin-top: -0.5rem;"} [:a {:href (.. "/shop/" product.name "/edit")} "% Редактировать"]]) "") - [:div {:class "mb-0-5" :style "font-style: italic;"} - (or (dicts.label dicts.product-type product.type) product.type) ", " - (if (not (lib.empty? product.year)) - (HTML [:span {} [:NO-ESCAPE (.. product.year " год, ")]]) - "") - (if (not (lib.empty? product.volume)) - (HTML [:span {} [:NO-ESCAPE (.. product.volume " мл., ")]]) - "") - (if (not (lib.empty? product.region)) (.. product.region ", ") "") - (if (= product.packaging "piece") - (HTML [:strong {} product.price-per "₽"]) - (HTML [:span {} - [:strong {} [:NO-ESCAPE - (* 50 product.price-per) - "₽ за 50 грамм "]] - [:NO-ESCAPE "(" product.price-per - "₽ за 1 грамм)"]]))] + (templates.add-to-basket-form product "mb-0-5" redirect-url) + (templates.product-overview product "mb-0-5") + [:div {:class "mb-1" :style "font-style: italic;"} product.short-description] (let [link (.. "/static/files/" product.image1)] @@ -105,9 +93,11 @@ (HTML [:img {:class "product-page-img" :src (.. link "-thumbnail.jpg")}]))]))))]]])]) (fn render [request db authenticated?] - (let [product (find-product db request.params._product)] + (let [product (find-product db request.params._product) + order-id (shop.order-id request) + basket (if order-id (shop.basket db order-id) [])] (if (and product (or product.published authenticated?)) - (values 200 {} (templates.base (content product authenticated?))) + (values 200 {} (templates.base (content product basket authenticated?))) (values 404 {} "not found")))) {: render} diff --git a/pages/shop/add.fnl b/pages/shop/add.fnl index b5fa8de..01b83d6 100644 --- a/pages/shop/add.fnl +++ b/pages/shop/add.fnl @@ -68,12 +68,11 @@ (fn content [form data errors authenticated?] [(HTML - [:div {:class "side"} + [:aside {} (templates.header "/shop" authenticated?)]) (HTML [:div {:class "content"} - [:div {:class "mb-1"} - [:a {:href "/shop"} "⟵ Обратно к списку"]] + [:div {:class "back"} [:a {:href "/shop"} "⟵ Обратно к списку"]] [:h2 {} "Добавить товар"] (forms.render-form form data errors)])]) @@ -81,7 +80,7 @@ (if (not authenticated?) (values 302 {:Location "/shop"} "") (if request.form - (let [data (forms.convert-values-from-html product-form request.form db) + (let [data (forms.html-form->data product-form request.form db) errors (forms.validate-form product-form request.form) has-errors? (not (lib.empty-table? errors))] (if has-errors? diff --git a/pages/shop/cart/add.fnl b/pages/shop/cart/add.fnl index 36e3e41..53366b4 100644 --- a/pages/shop/cart/add.fnl +++ b/pages/shop/cart/add.fnl @@ -1,38 +1,33 @@ (local lib (require :lib)) - -(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]))) +(local shop (require :shop)) (fn render [request db] (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"))}) - {})) + (var order-id (shop.order-id request)) + (var headers {}) + + (when (not order-id) + (local next-week + (os.date "%a, %d %b %Y %H:%M:%S GMT" (+ (os.time) (* 60 60 24 7)))) + (set order-id (shop.create-order db)) + (set headers + {:Set-Cookie (.. "order=" order-id "; Path=/; " + "Expires=" next-week "; " + "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"))) + (let [body-values (lib.parse-values request.body)] + (if (and order-id request.body + (< 0 (tonumber body-values.quantity))) + (do + (shop.create-order-line + db order-id body-values.name body-values.quantity) + (tset headers :Location (_G.must + (luna.http.decode-url + body-values.redirect-url))) + (values 302 headers "")) + (values 400 {} "bad body")))) (values 404 {} "not found"))) {: render} diff --git a/pages/shop/cart/remove.fnl b/pages/shop/cart/remove.fnl index d5e3531..0cdc417 100644 --- a/pages/shop/cart/remove.fnl +++ b/pages/shop/cart/remove.fnl @@ -1,16 +1,13 @@ (local lib (require :lib)) +(local shop (require :shop)) (fn render [request db] (if (= request.method "POST") - (let [order-id (lib.order-id request)] + (let [order-id (shop.order-id request)] (if (and order-id request.body) (do (local body-values (lib.parse-values request.body)) - (_G.must - (luna.db.exec - db - "DELETE FROM order_lines WHERE id = ? AND order_id = ?" - [body-values.id order-id])) + (shop.delete-order-line db body-values.id) (values 302 {:Location (_G.must diff --git a/pages/shop/index.fnl b/pages/shop/index.fnl index bd3e88b..dff486c 100644 --- a/pages/shop/index.fnl +++ b/pages/shop/index.fnl @@ -1,5 +1,6 @@ (import-macros {:compile-html HTML} :macros) (local lib (require :lib)) +(local shop (require :shop)) (local dicts (require :dicts)) (local templates (require :templates)) @@ -17,8 +18,10 @@ products.short_description as \"short-description\", products.price_per AS \"price-per\", products.volume, + products.stock, products.packaging, products.type, + products.region, products.image1, products.image2, products.image3, @@ -28,27 +31,8 @@ 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 - ;; (HTML - ;; [:option {:value (tostring q)} - ;; (.. q " грамм за " (* product.price-per q) "₽")]))) - ;; (table.insert quantity-options (HTML [:option {:value "0"} "Товар закончился"]))) - (local images []) (for [i 2 5] (table.insert images (. product (.. "image" i)))) @@ -74,47 +58,33 @@ :style (.. "z-index: " (+ idx 2) ";" "width: calc(100% / " (# without-videos) ");" "left: calc(100% / " (# without-videos) " * " (- 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.product-type product.type) product.type) ", " - (if (not (lib.empty? product.volume)) - (HTML [:span {} [:NO-ESCAPE (.. product.volume " мл., ")]]) - "") - (if (= product.packaging "piece") - (HTML [:strong {} product.price-per "₽"]) - (HTML [: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"} "Добавить"]]] + [:a {:href item-url} + [:h3 {:class "shop-item-title"} product.title]] + [:div {:class "shop-item-price"} + (templates.add-to-basket-form product "" "/shop")] + (templates.product-overview product "mb-0-25 font-size-0-875") [:div {} product.short-description]])) -(fn content [db basket basket-total authenticated?] +(fn content [db basket authenticated?] [(HTML - [:div {:class "side"} + [:aside {} (templates.header "/shop" authenticated?) - (if (< 0 (# basket)) - (HTML - [: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"} "Оформить заказ"]]) - "")]) + (if (< 0 (# basket)) (templates.basket basket "/shop") "") + [:section {} + [:h2 {} "Условия"] + [:p {} ""]]]) (HTML [:div {:class "content"} - [:div {:class "mb-1"} [:a {:href "/"} "⟵ Обратно на главную"]] - [:h2 {:class "product-page-title mb-1"} - "Магазин" - (if authenticated? - (HTML [:a {:style "font-size: 1rem; margin-left: 0.75rem;" - :href (.. "/shop/add")} "+ Добавить"]) - "")] + [:div {:class "back"} [:a {:href "/"} "⟵ Обратно на главную"]] + [:h2 {:class "product-page-title"} "Магазин"] + (if authenticated? + (HTML + [:div {:class "mb-1" :style "margin-top: -0.5rem"} + [:a {:style "white-space: nowrap" + :href (.. "/shop/add")} "+ Добавить"] + [:a {:style "white-space: nowrap; margin-left: 1rem;" + :href (.. "/shop/order/list")} "☰ Список заказов"]]) + "") [:div {:class "shop-items"} (let [products (all-products db authenticated?)] (if (< 0 (# products)) @@ -123,45 +93,9 @@ (item-template v))) (HTML [: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?)))))) + (let [order-id (shop.order-id request) + basket (if order-id (shop.basket db order-id) [])] + (values 200 {} (templates.base (content db basket authenticated?))))) {: render} diff --git a/pages/shop/order.fnl b/pages/shop/order.fnl deleted file mode 100644 index 50e12da..0000000 --- a/pages/shop/order.fnl +++ /dev/null @@ -1,59 +0,0 @@ -(import-macros {:compile-html HTML} :macros) -(local lib (require :lib)) -(local templates (require :templates)) - -(fn content-template [db basket basket-total] - [(HTML - [:div {:class "side"} - (templates.header "/shop/order")]) - (HTML - [:div {:class "content"} - (if (< 0 (# basket)) - [:section {} - [:h2 {} "Состав заказа"] - [:div {} - (table.unpack - (icollect [_ item (ipairs basket)] - (templates.basket-item item "/shop/order")))] - [:div {} "~~~"] - [:div {:class "basket-total"} (.. "Итого: " basket-total "₽")]] - "") - [:section {} - [:h2 {} "Данные для связи"] - [:form {:class "form" :method "POST"} - [:div {:class "form-row"} - [:label {:for "name"} "Имя"] - [:input {:type "text" :id "name" :name "name" :required "required"}]] - [:div {:class "form-row"} - [:label {:for "contact"} "Телеграм или Email для связи"] - [:input {:type "text" :id "contact" :name "contact" :required "required"}]] - [:div {:class "form-row"} - [:input {:type "checkbox" :id "everything-is-correct" - :name "everything-is-correct" :required "required"}] - [:label {:for "everything-is-correct"} "Данные заказа верны"]] - [:div {:class "form-row"} - [:input {:type "checkbox" :id "agree-to-conditions" - :name "agree-to-conditions" :required "required"}] - [:label {:for "agree-to-conditions"} "Согласен с условиями"]] - [:button {:type "submit"} "Оформить заказ"]]]])]) - -(fn place-order [db order-id form] - (_G.must - (luna.db.exec db - "UPDATE orders SET placement_time = ?, first_name = ?, contact = ?" - [(lib.now) form.name form.contact]))) - -(fn render [request db] - (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 - (place-order db order-id (lib.parse-values request.body)) - (values 302 {:Location "/shop/success"} "")) - (if (< 0 (# basket)) - (values 200 {} (templates.base (content-template db basket basket-total))) - (values 302 {:Location "/shop"} ""))))) - -{: render} diff --git a/pages/shop/order/_id.fnl b/pages/shop/order/_id.fnl new file mode 100644 index 0000000..5c929a3 --- /dev/null +++ b/pages/shop/order/_id.fnl @@ -0,0 +1,35 @@ +(import-macros {:compile-html HTML} :macros) +(local templates (require :templates)) +(local shop (require :shop)) +(local lib (require :lib)) +(local dicts (require :dicts)) + +(local texts + {:thanks + (lib.improve-typography + "Мы получили оповещение о заказе. В ближайшее время свяжемся с вами + для уточнения деталей.")}) + +(fn content [order-lines authenticated?] + (local total (accumulate [sum 0 _ v (ipairs order-lines)] + (+ sum (* v.quantity v.price-per)))) + + [(HTML [:aside {} + (templates.header "/shop/order" authenticated?)]) + (HTML + [:div {:class "content"} + [:div {:class "back"} [:a {:href "/shop"} "⟵ Обратно к списку"]] + [:section {} + [:h2 {} "Спасибо за заказ!"] + [:p {} texts.thanks] + (templates.order-lines order-lines) + [:div {} "—"] + [:div {} [:strong {} (.. "Итого: " (lib.format-price total) "₽")]]]])]) + +(fn render [request db authenticated?] + (let [order-lines (shop.basket db request.params._id)] + (if (< 0 (# order-lines)) + (values 200 {} (templates.base (content order-lines authenticated?))) + (values 302 {:Location "/shop"} "")))) + +{: render} diff --git a/pages/shop/order/index.fnl b/pages/shop/order/index.fnl new file mode 100644 index 0000000..7d25a40 --- /dev/null +++ b/pages/shop/order/index.fnl @@ -0,0 +1,72 @@ +(import-macros {:compile-html HTML} :macros) +(local forms (require :forms)) +(local lib (require :lib)) +(local shop (require :shop)) +(local templates (require :templates)) + +(local order-form + [{:title "" + :fields [ + (forms.text-input "name" "Как к вам обращаться?" true) + (forms.text-input "contact" "Телеграм или E-mail для связи" true) + (forms.checkbox-input "correct-order" "Данные заказа верны" true) + (forms.checkbox-input "consent" + (.. + "Я даю согласие ИП «Горенкин Владислав Константинович» (ИНН ...)" + " на хранение и обработку предоставленных персональных данных для уточнения деталей заказа.") + true)]}]) + +(fn content-template [db basket data errors] + [(HTML + [:aside {} + (templates.header "/shop/order") + (if (< 0 (# basket)) (templates.basket basket "/shop/order") "")]) + (HTML + [:div {:class "content"} + [:div {:class "back"} [:a {:href "/shop"} "⟵ Обратно к списку"]] + [:section {} + [:h2 {} "Оформление заказа"] + (forms.render-form order-form data errors)]])]) + +(fn check-stocks [db basket] + (var error nil) + + (each [_ line (ipairs basket) &until error] + (local product + (. (_G.must + (luna.db.query-assoc + db "SELECT title, stock FROM products WHERE name = ?" + [line.name])) + 1)) + (when (< (- product.stock line.quantity) 0) + (set error (.. "К сожалению, товар «" product.title "» закончился." + " Пожалуйста, уберите его из корзины и попробуйте оформить заказ снова.")))) + + error) + +(fn render [request db] + (let [order-id (shop.order-id request) + basket (if order-id (shop.basket db order-id) [])] + (if (= request.method "POST") + (let [data (forms.html-form->data order-form request.form) + stock-error (check-stocks db basket)] + (if (not stock-error) + (do + (shop.place-order db order-id data.name data.contact data.consent) + (lib.notify + (.. "Новый заказ " + "" + order-id + " от " data.name ".\n\n" + "Список заказов")) + ;; redirect and clear order cookie + (values 302 {:Location (.. "/shop/order/" order-id) + :Set-Cookie "order=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT"} + "")) + (values 400 {} + (templates.base (content-template db basket data {:consent stock-error}))))) + (if (< 0 (# basket)) + (values 200 {} (templates.base (content-template db basket {} {}))) + (values 302 {:Location "/shop"} ""))))) + +{: render} diff --git a/pages/shop/order/list.fnl b/pages/shop/order/list.fnl new file mode 100644 index 0000000..2f64f7b --- /dev/null +++ b/pages/shop/order/list.fnl @@ -0,0 +1,72 @@ +(import-macros {:compile-html HTML} :macros) +(local lib (require :lib)) +(local templates (require :templates)) +(local dicts (require :dicts)) + +(fn all-orders [db] + (lib.group-by + (_G.must + (luna.db.query-assoc db + "SELECT orders.id, + orders.placement_time AS \"placement-time\", + orders.name \"contact-name\", + orders.contact, + orders.state, + order_lines.id AS \"order-line-id\", + order_lines.quantity, + products.image1, + products.name, + products.type, + products.packaging, + products.title, + products.price_per \"price-per\" + FROM orders + INNER JOIN order_lines ON orders.id = order_lines.order_id + INNER JOIN products ON products.name = order_lines.product_name + WHERE orders.state != 'cart' + ORDER BY orders.placement_time DESC, orders.id" + {})) + [:id :placement-time :state :contact-name :contact])) + +(fn content [orders authenticated?] + [(HTML + [:aside {} + (templates.header "/shop/orders" authenticated?)]) + (HTML + [:section {:class "content"} + [:div {:class "mb-1"} [:a {:href "/"} "⟵ Обратно к списку"]] + [:h2 {:class "product-page-title"} "Список заказов"] + + (table.concat + (icollect [_ order (ipairs orders)] + (let [total (accumulate [sum 0 _ v (ipairs order.rest)] + (+ sum (* v.quantity v.price-per)))] + (HTML + [:section {:class "mb-2 font-size-0-875" } + [:h3 {:class "mb-0-25"} [:a {:href (.. "/shop/order/" order.id)} order.id]] + [:div {:class "mb-0-25 d-flex gap-0-25"} + (templates.order-state order.state) + (if (= "placed" order.state) + (HTML + [:form {:action "/shop/order/state" :method "POST"} + [:select {:name "state" :required true} + [:option {:value ""} "Новое состояние"] + [:option {:value "done"} "Выполнен"] + [:option {:value "canceled"} "Отменен"]] + [:input {:type "hidden" :name "id" :value order.id}] + [:button {:type "submit"} "Применить"]]) + "")] + [:em {:class "d-block mb-0-5"} + "Дата: " order.placement-time + " / " "контакт: " order.contact + " / " "как обращаться: " order.contact-name] + (templates.order-lines order.rest) + [:div {} "—"] + [:div {} [:strong {} (.. "Итого: " (lib.format-price total) "₽")]]]))))])]) + +(fn render [request db authenticated?] + (if authenticated? + (values 200 {} (templates.base (content (all-orders db) authenticated?))) + (values 302 {:Location "/"} ""))) + +{: render} diff --git a/pages/shop/order/state.fnl b/pages/shop/order/state.fnl new file mode 100644 index 0000000..1ff1ca9 --- /dev/null +++ b/pages/shop/order/state.fnl @@ -0,0 +1,18 @@ +(local lib (require :lib)) +(local shop (require :shop)) + +(fn render [request db authenticated?] + (if (and (= request.method "POST") authenticated?) + (let [vals (lib.parse-values request.body)] + (if (and vals vals.id + vals.state (or (= vals.state "done") + (= vals.state "canceled"))) + (do + (if (= "done" vals.state) + (shop.finish-order db vals.id) + (shop.cancel-order db vals.id)) + (values 302 {:Location "/shop/order/list"} "")) + (values 400 {} "bad body"))) + (values 404 {} "not found"))) + +{: render} diff --git a/pages/shop/success.fnl b/pages/shop/success.fnl deleted file mode 100644 index b28f84d..0000000 --- a/pages/shop/success.fnl +++ /dev/null @@ -1,15 +0,0 @@ -(import-macros {:compile-html HTML} :macros) -(local templates (require :templates)) - -(tset _G :package :loaded "pages.shop.success" nil) - -(fn content [] - [(HTML [:div {:class "side"} - (templates.header "/shop/order")]) - (HTML [:div {:class "content"} - "Спасибо за заказ!"])]) - -(fn render [] - (values 200 {} (templates.base (content)))) - -{: render} -- cgit v1.2.3