(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 title description] (HTML [:html {:lang "ru-RU"} [:head {} [:title {} (.. "«Белая жаба» / " (or 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 (or description (.. "Уютный чайный клуб в Омске: " "ул. Пушкина, д. 133/9, этаж 2. " "Посещение по предварительной договоренности."))}] [: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 "/information" :class (if (= current-path "/information") "active" "")} "информация"] [:span {} "–"] [:a {:href "https://t.me/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 "-thumbnail.jpg") :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) "₽")]] (if (~= redirect-url "/shop/order") (HTML [:a {:href "/shop/order#content"} "Оформить заказ ⟶"]) "")]))) (fn product-overview [product classes] (local classes (or classes "")) (HTML [:div {:class classes :style "font-style: italic"} [:span {:data-parser "type"} (or (dicts.label dicts.product-type product.type) product.type) ", "] (if (not (lib.empty? product.year)) (HTML [:span {:data-parser "year"} [:NO-ESCAPE (.. product.year " год, ")]]) "") (if (not (lib.empty? product.volume)) (HTML [:span {:data-parser "volume"} [:NO-ESCAPE (.. product.volume " мл., ")]]) "") (if (not (lib.empty? product.region)) (.. product.region ", ") "") (if (= product.packaging "piece") (HTML [:strong {:data-parser "price"} (lib.format-price product.price-per) "₽"]) (HTML [:span {} [:strong {:data-parser "price"} [: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 (.. "d-flex gap-0-5 " 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 contact-block [] (HTML [:section {} [:h2 {} "Как связаться"] [:p {} "Телеграм: " [:a {:href "https://t.me/whitetoadvlad"} "@whitetoadvlad"] [:br {}] "Почта: " [:a {:href "mailto:vlad@whitetoad.ru"} "vlad@whitetoad.ru"]]])) (fn address-block [] (HTML [:section {} [:h2 {} "Адрес"] [:p {} [:NO-ESCAPE ;; FIXME: lib.improve-typography is too slow for this text for ;; some reason "г. Омск, ул. Пушкина, д. 133/9, этаж 2. Вход с крыльца Магнита, дверь слева, домофон 4. Дверь в офисе узнаете по нашему логотипу."]]])) (fn conditions-block [] "") ;; (HTML ;; [:section {} ;; [:h2 {} "Условия"] ;; [:p {} ;; (lib.improve-typography ;; (.. "Доставка по Омску Яндекс-курьером. ;; Доставка по России обговаривается отдельно."))]])) (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 "-thumbnail.jpg") :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)])) (fn cookies-agreement [] (HTML [:div {:class "cookies-agreement"} [:span {:class "mr-0-5"} "Продолжая использовать сайт, вы даете согласие на использование куки для сохранения вашей корзины."] [:button {:type "button" :onclick (.. "document.cookie='agreed-to-cookies=true;path=/shop;Max-Age=34560000';" "this.parentElement.remove();")} "Хорошо"]])) {: base : header : basket-item : basket : product-overview : add-to-basket-form : order-lines : order-state : contact-block : address-block : conditions-block : cookies-agreement}