(import-macros {:compile-html HTML} :macros) (local lib (require :lib)) (local dicts (require :dicts)) (fn read-file [file] (with-open [f (io.open file "r")] (f:read :*all))) (fn base [content] (HTML [:html {:lang "ru-RU"} [:head {} [:title {} "«Белая жаба» — маленькая уютная чайная в Омске"] [:meta {:charset "utf-8"}] [:meta {:name "viewport" :content (.. "width=device-width,initial-scale=1," "minimum-scale=1.0,maximum-scale=5.0")}] [:meta {:name "description" :content (.. "Маленькая уютная чайная: " "Омск, ул. Пушкина, д. 133/9, этаж 2. " "Посещение по предварительной договоренности, " "стоимость 500 рублей с человека.")}] [:style {} [:NO-ESCAPE (read-file "static/style.css")]] [:link {:rel "icon" :href "/static/favicon.svg" :type "image/svg+xml"}]] [:body {} [:main {:class "container"} (table.concat content)]]])) (fn header [current-path authenticated?] (local logo (HTML [:img {:class "logo-img" :src "/static/logo.svg" :alt "Белая жаба в мультяшном стиле с чайником на голове"}])) (HTML [:section {} [:div {:class "logo"} (if authenticated? (HTML [:img {:class "logo-glasses" :src "/static/glasses.png" :alt "Солнцезащитные очки"}]) "") (if (~= current-path "") (HTML [:a {:href "/" :class "d-inline-block"} logo]) logo) [:h1 {} [:NO-ESCAPE "Чайный клуб
«Белая жаба»"]]] [:nav {} [:a {:href "/shop" :class (if (lib.starts-with? current-path "/shop") "active" "")} "магазин"] [:span {} "~"] [:a {:href "https://t.me/whitetoadtea"} "телеграм"] [:span {} "~"] [:a {:href "https://vk.com/whitetoadtea"} "вконтакте"]]])) (fn basket-item [item redirect-url] (local item-link (.. "/shop/" item.name)) (HTML [:div {:class "basket-item"} [:div {:class "basket-item-image"} [:a {:href item-link :style "font-size: 0;"} [:img {:src (.. "/static/files/" item.image1) :alt item.title}]]] [:div {} [:a {:href item-link :class "basket-item-title"} item.title] [:div {:class "basket-item-price"} (if (= item.packaging :piece) (.. (lib.format-price (* item.price-per item.quantity)) "₽ за " item.quantity " шт.") (.. (lib.format-price (* item.price-per item.quantity)) "₽ за " item.quantity " гр.")) [:form {:class "basket-item-remove" :method "POST" :action "/shop/cart/remove"} [:input {:type "hidden" :name "redirect-url" :value redirect-url}] [:input {:type "hidden" :name "id" :value (tostring item.id)}] [:button {:type "submit"} "⨯ убрать"]]]]])) (fn basket [basket redirect-url] (let [total (accumulate [sum 0 _ v (ipairs basket)] (+ sum (* v.quantity v.price-per)))] (HTML [:section {} [:h2 {} "Корзина"] [:div {:class "mb-0-25"} (table.concat (icollect [_ item (ipairs basket)] (basket-item item redirect-url)))] [:div {:class "mt-0-25"} "—"] [:div {} [:strong {} (.. "Итого: " (lib.format-price total) "₽")]] [:a {:href "/shop/order"} "Оформить заказ ⟶"]]))) (fn product-overview [product classes] (local classes (or classes "")) (HTML [:div {:class classes :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 {} (lib.format-price product.price-per) "₽"]) (HTML [:span {} [:strong {} [:NO-ESCAPE (lib.format-price (* 50 product.price-per)) "₽ за 50 грамм "]] [:NO-ESCAPE "(" (lib.format-price product.price-per) "₽ за 1 грамм)"]]))])) (fn add-to-basket-form [product basket classes redirect-url] (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) (var in-basket-quantity nil) (each [_ basket-item (pairs basket) &until in-basket-quantity] (when (= product.name basket-item.name) (set in-basket-quantity basket-item.quantity))) (var quantity-options []) (var out-of-stock? false) (let [piece? (= product.packaging :piece)] (if (< 0 product.stock) (each [_ q (ipairs (quantity-steps product.stock (if piece? 1 50)))] (table.insert quantity-options (HTML [:option (fn [] {:value q :selected (= in-basket-quantity q)}) (.. (lib.format-price (* product.price-per q)) "₽ за " q (if piece? " шт." " гр."))]))) (do (table.insert quantity-options (HTML [:option {:value "0"} "Товар закончился"])) (set out-of-stock? true)))) (local disabled (or out-of-stock? in-basket-quantity)) (HTML [:form {:method "POST" :action "/shop/cart/add" :class classes} [:input {:type "hidden" :name "name" :value product.name}] [:input {:type "hidden" :name "redirect-url" :value redirect-url}] [:select (fn [] {:name "quantity" :disabled disabled}) (table.concat quantity-options)] [:button (fn [] {:type "submit" :disabled disabled}) (if in-basket-quantity "В корзине" "Добавить")]])) (fn order-lines [order-lines] (HTML [:div {:class "order mb-0-5"} (table.concat (icollect [_ item (ipairs order-lines)] (let [item-link (.. "/shop/" item.name)] (HTML [:div {:class "order-line"} [:div {:class "order-line-image"} [:a {:href item-link :style "font-size: 0;"} [:img {:src (.. "/static/files/" item.image1) :alt item.title}]]] [:div {} [:a {:href item-link :class "order-line-title"} item.title] [:div {:class "order-line-price"} (if (= item.packaging :piece) (.. (lib.format-price (* item.price-per item.quantity)) "₽ за " item.quantity " шт.") (.. (lib.format-price (* item.price-per item.quantity)) "₽ за " item.quantity " гр."))] [:em {:class "font-size-0-875"} (dicts.label dicts.product-type item.type)]]]))))])) (fn order-state [state] (HTML [:strong {:class (.. "order-state order-state-" state)} (dicts.label dicts.order-state state)])) {: base : header : basket-item : basket : product-overview : add-to-basket-form : order-lines : order-state}