summaryrefslogtreecommitdiff
path: root/pages/shop/index.fnl
blob: 5c1d7341acdf4fe9f0f95bb205316f5368939504 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
(import-macros {:compile-html 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.packaging,
                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
  ;;                   (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))))

  (HTML
   [: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
        (let [without-videos
               (icollect [_ v (ipairs images)]
                 (if (lib.ends-with? (_G.must (luna.utf8.lower v)) ".webm")
                   nil
                   v))]
          (icollect [idx image (ipairs without-videos)]
            (HTML
              [:img {:class "shop-item-img" :src (.. "/static/files/" image "-thumbnail.jpg")
                     :loading "lazy"
                     :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 (= 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"} "Добавить"]]]
    [:div {} product.short-description]]))

(fn content [db basket basket-total authenticated?]
  [(HTML
    [:div {:class "side"}
     (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"} "Оформить заказ"]])
      "")])
   (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 "shop-items"}
      (let [products (all-products db authenticated?)]
        (if (< 0 (# products))
          (table.concat
            (icollect [_ v (ipairs products)]
              (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?))))))

{: render}