diff options
Diffstat (limited to 'frontend/src')
-rw-r--r-- | frontend/src/Catalog.elm | 100 | ||||
-rw-r--r-- | frontend/src/Icons.elm | 8 | ||||
-rw-r--r-- | frontend/src/Main.elm | 1 | ||||
-rw-r--r-- | frontend/src/Product.elm | 144 | ||||
-rw-r--r-- | frontend/src/Styles.elm | 21 |
5 files changed, 240 insertions, 34 deletions
diff --git a/frontend/src/Catalog.elm b/frontend/src/Catalog.elm index 5b233f2..7e9bde7 100644 --- a/frontend/src/Catalog.elm +++ b/frontend/src/Catalog.elm | |||
@@ -27,6 +27,8 @@ type alias Product = | |||
27 | , price : Float | 27 | , price : Float |
28 | , description : Maybe String | 28 | , description : Maybe String |
29 | , averageRating : Maybe Float | 29 | , averageRating : Maybe Float |
30 | , src : String | ||
31 | , iosSrc : String | ||
30 | } | 32 | } |
31 | 33 | ||
32 | 34 | ||
@@ -129,13 +131,15 @@ update msg model = | |||
129 | 131 | ||
130 | decodeProduct : D.Decoder Product | 132 | decodeProduct : D.Decoder Product |
131 | decodeProduct = | 133 | decodeProduct = |
132 | D.map6 Product | 134 | D.map8 Product |
133 | (D.field "id" D.int) | 135 | (D.field "id" D.int) |
134 | (D.field "name" D.string) | 136 | (D.field "name" D.string) |
135 | (D.field "kind" (D.nullable D.string)) | 137 | (D.field "kind" (D.nullable D.string)) |
136 | (D.field "price" D.float) | 138 | (D.field "price" D.float) |
137 | (D.field "description" (D.nullable D.string)) | 139 | (D.field "description" (D.nullable D.string)) |
138 | (D.field "average_rating" (D.nullable D.float)) | 140 | (D.field "average_rating" (D.nullable D.float)) |
141 | (D.field "src" D.string) | ||
142 | (D.field "ios_src" D.string) | ||
139 | 143 | ||
140 | 144 | ||
141 | decodeResponse : D.Decoder (List Product) | 145 | decodeResponse : D.Decoder (List Product) |
@@ -170,18 +174,83 @@ viewStatus s = | |||
170 | 174 | ||
171 | viewProduct : Product -> Html Msg | 175 | viewProduct : Product -> Html Msg |
172 | viewProduct p = | 176 | viewProduct p = |
173 | div [] | 177 | div |
174 | [ div [] [ text p.name ] | 178 | [ css |
175 | , div [] [ text <| Maybe.withDefault "" p.kind ] | 179 | [ marginBottom (px 20) |
176 | , div [] [ text <| Maybe.withDefault "" p.description ] | 180 | , border3 (px 1) solid theme.primary |
177 | , div [] [ text <| String.fromFloat p.price ] | 181 | , borderRadius (px 4) |
178 | , case p.averageRating of | 182 | , padding (px 20) |
179 | Just v -> | 183 | , Css.width (pct 100) |
180 | text <| "Avg Rating: " ++ String.fromFloat v | 184 | , maxWidth (px 650) |
181 | 185 | ] | |
182 | Nothing -> | 186 | ] |
183 | text "No Ratings" | 187 | [ div |
184 | , div [] [ a [ href ("/product/" ++ String.fromInt p.id) ] [ text "View Product" ] ] | 188 | [ css |
189 | [ float left | ||
190 | , Css.width (pct 50) | ||
191 | ] | ||
192 | ] | ||
193 | [ modelViewer | ||
194 | [ cameraControls | ||
195 | , autoRotate | ||
196 | , arSrc p.src | ||
197 | , arIosSrc p.iosSrc | ||
198 | , loading "eager" | ||
199 | , arModes "webxr" | ||
200 | ] | ||
201 | [] | ||
202 | ] | ||
203 | , div | ||
204 | [ css | ||
205 | [ float left | ||
206 | , Css.width (pct 50) | ||
207 | ] | ||
208 | ] | ||
209 | [ div | ||
210 | [ css | ||
211 | [ cardSecondaryText | ||
212 | , paddingBottom (px 3) | ||
213 | , fontVariant smallCaps | ||
214 | ] | ||
215 | ] | ||
216 | [ text <| Maybe.withDefault "" p.kind ] | ||
217 | , div | ||
218 | [ css | ||
219 | [ cardPrimaryText | ||
220 | , paddingBottom (px 3) | ||
221 | ] | ||
222 | ] | ||
223 | [ a [ href ("/product/" ++ String.fromInt p.id) ] [ text p.name ] ] | ||
224 | , div | ||
225 | [ css | ||
226 | [ cardSecondaryText | ||
227 | , paddingBottom (px 12) | ||
228 | ] | ||
229 | ] | ||
230 | [ case p.averageRating of | ||
231 | Just v -> | ||
232 | text <| "Avg Rating: " ++ String.fromFloat v | ||
233 | |||
234 | Nothing -> | ||
235 | text "No Ratings" | ||
236 | ] | ||
237 | , div | ||
238 | [ css | ||
239 | [ cardSupportingText | ||
240 | , paddingBottom (px 6) | ||
241 | ] | ||
242 | ] | ||
243 | [ text <| Maybe.withDefault "No description provided" p.description ] | ||
244 | , div | ||
245 | [ css | ||
246 | [ fontWeight bold | ||
247 | , fontSize (px 14) | ||
248 | , money | ||
249 | ] | ||
250 | ] | ||
251 | [ text <| String.fromFloat p.price ] | ||
252 | ] | ||
253 | , div [ style "clear" "both" ] [] | ||
185 | ] | 254 | ] |
186 | 255 | ||
187 | 256 | ||
@@ -260,7 +329,10 @@ view model = | |||
260 | ] | 329 | ] |
261 | [ div [ css [ bigHeading ] ] [ text "Products" ] | 330 | [ div [ css [ bigHeading ] ] [ text "Products" ] |
262 | , ul | 331 | , ul |
263 | [ css [ padding (px 0) ] | 332 | [ css |
333 | [ padding (px 0) | ||
334 | , listStyle Css.none | ||
335 | ] | ||
264 | ] | 336 | ] |
265 | (filterProducts model |> List.map viewProduct) | 337 | (filterProducts model |> List.map viewProduct) |
266 | ] | 338 | ] |
diff --git a/frontend/src/Icons.elm b/frontend/src/Icons.elm index d3b862f..96fa6ff 100644 --- a/frontend/src/Icons.elm +++ b/frontend/src/Icons.elm | |||
@@ -1,6 +1,6 @@ | |||
1 | module Icons exposing (..) | 1 | module Icons exposing (..) |
2 | 2 | ||
3 | import FeatherIcons exposing (toHtml) | 3 | import FeatherIcons exposing (toHtml, withSize) |
4 | import Html | 4 | import Html |
5 | import Html.Styled exposing (..) | 5 | import Html.Styled exposing (..) |
6 | import Html.Styled.Attributes exposing (..) | 6 | import Html.Styled.Attributes exposing (..) |
@@ -8,8 +8,12 @@ import Html.Styled.Events exposing (..) | |||
8 | 8 | ||
9 | 9 | ||
10 | convert = | 10 | convert = |
11 | Html.Styled.fromUnstyled << toHtml [] | 11 | Html.Styled.fromUnstyled << toHtml [] << withSize 14 |
12 | 12 | ||
13 | 13 | ||
14 | loginIcon = | 14 | loginIcon = |
15 | convert FeatherIcons.logIn | 15 | convert FeatherIcons.logIn |
16 | |||
17 | |||
18 | starIcon = | ||
19 | convert FeatherIcons.star | ||
diff --git a/frontend/src/Main.elm b/frontend/src/Main.elm index ea80921..c1489bf 100644 --- a/frontend/src/Main.elm +++ b/frontend/src/Main.elm | |||
@@ -403,6 +403,7 @@ viewHeader model = | |||
403 | [ css | 403 | [ css |
404 | [ listStyle Css.none | 404 | [ listStyle Css.none |
405 | , padding (px 0) | 405 | , padding (px 0) |
406 | , margin (px 24) | ||
406 | ] | 407 | ] |
407 | ] | 408 | ] |
408 | ] | 409 | ] |
diff --git a/frontend/src/Product.elm b/frontend/src/Product.elm index b97a847..79256cc 100644 --- a/frontend/src/Product.elm +++ b/frontend/src/Product.elm | |||
@@ -2,13 +2,16 @@ module Product exposing (..) | |||
2 | 2 | ||
3 | import Browser | 3 | import Browser |
4 | import Browser.Navigation as Nav | 4 | import Browser.Navigation as Nav |
5 | import Css exposing (..) | ||
5 | import Html | 6 | import Html |
6 | import Html.Styled exposing (..) | 7 | import Html.Styled exposing (..) |
7 | import Html.Styled.Attributes exposing (..) | 8 | import Html.Styled.Attributes exposing (..) |
8 | import Html.Styled.Events exposing (..) | 9 | import Html.Styled.Events exposing (..) |
9 | import Http | 10 | import Http |
11 | import Icons exposing (..) | ||
10 | import Json.Decode as D | 12 | import Json.Decode as D |
11 | import Json.Encode as Encode | 13 | import Json.Encode as Encode |
14 | import Styles exposing (..) | ||
12 | import Url | 15 | import Url |
13 | import Url.Parser as P exposing ((</>), Parser, int, oneOf, s, string) | 16 | import Url.Parser as P exposing ((</>), Parser, int, oneOf, s, string) |
14 | import Utils exposing (..) | 17 | import Utils exposing (..) |
@@ -77,7 +80,7 @@ type Msg | |||
77 | 80 | ||
78 | init : Model | 81 | init : Model |
79 | init = | 82 | init = |
80 | Model NotLoaded emptyProduct [] 0 "" NotSubmitted | 83 | Model NotLoaded emptyProduct [] 5 "" NotSubmitted |
81 | 84 | ||
82 | 85 | ||
83 | update : Msg -> Model -> ( Model, Cmd Msg ) | 86 | update : Msg -> Model -> ( Model, Cmd Msg ) |
@@ -251,12 +254,19 @@ viewStatus s = | |||
251 | 254 | ||
252 | viewProduct : Product -> Html Msg | 255 | viewProduct : Product -> Html Msg |
253 | viewProduct p = | 256 | viewProduct p = |
254 | div [] | 257 | div |
255 | [ div [] [ text p.name ] | 258 | [ css |
256 | , div [] [ text <| Maybe.withDefault "" p.kind ] | 259 | [ marginBottom (px 20) |
257 | , div [] [ text <| Maybe.withDefault "" p.description ] | 260 | , padding (px 20) |
258 | , div [] [ text <| String.fromFloat p.price ] | 261 | , Css.width (pct 100) |
259 | , div [] | 262 | ] |
263 | ] | ||
264 | [ div | ||
265 | [ css | ||
266 | [ float left | ||
267 | , Css.width (pct 50) | ||
268 | ] | ||
269 | ] | ||
260 | [ modelViewer | 270 | [ modelViewer |
261 | [ cameraControls | 271 | [ cameraControls |
262 | , autoRotate | 272 | , autoRotate |
@@ -267,16 +277,96 @@ viewProduct p = | |||
267 | ] | 277 | ] |
268 | [] | 278 | [] |
269 | ] | 279 | ] |
280 | , div | ||
281 | [ css | ||
282 | [ float left | ||
283 | , Css.width (pct 50) | ||
284 | ] | ||
285 | ] | ||
286 | [ div | ||
287 | [ css | ||
288 | [ cardSecondaryText | ||
289 | , paddingBottom (px 3) | ||
290 | , fontVariant smallCaps | ||
291 | ] | ||
292 | ] | ||
293 | [ text <| Maybe.withDefault "" p.kind ] | ||
294 | , div | ||
295 | [ css | ||
296 | [ cardPrimaryText | ||
297 | , paddingBottom (px 12) | ||
298 | ] | ||
299 | ] | ||
300 | [ text p.name ] | ||
301 | , div | ||
302 | [ css | ||
303 | [ cardSupportingText | ||
304 | , paddingBottom (px 6) | ||
305 | ] | ||
306 | ] | ||
307 | [ text <| Maybe.withDefault "No description provided" p.description ] | ||
308 | , div | ||
309 | [ css | ||
310 | [ fontWeight bold | ||
311 | , fontSize (px 14) | ||
312 | , money | ||
313 | ] | ||
314 | ] | ||
315 | [ text <| String.fromFloat p.price | ||
316 | , div [] | ||
317 | [ furbyButton [ onClick AddToCartPressed ] [ text "Add To Cart" ] | ||
318 | ] | ||
319 | ] | ||
320 | ] | ||
321 | , div [ style "clear" "both" ] [] | ||
270 | ] | 322 | ] |
271 | 323 | ||
272 | 324 | ||
325 | viewStarRating : Int -> Html Msg | ||
326 | viewStarRating i = | ||
327 | div [] | ||
328 | (List.repeat i starIcon) | ||
329 | |||
330 | |||
273 | viewRating : Rating -> Html Msg | 331 | viewRating : Rating -> Html Msg |
274 | viewRating r = | 332 | viewRating r = |
275 | div [] | 333 | -- div [] |
276 | [ text <| r.customerName ++ " posted on " | 334 | -- [ text <| r.customerName ++ " posted on " |
277 | , text <| r.commentDate ++ " " | 335 | -- , text <| r.commentDate ++ " " |
278 | , text <| Maybe.withDefault "" r.commentText | 336 | -- , text <| Maybe.withDefault "" r.commentText |
279 | , text <| " Stars: " ++ String.fromInt r.stars | 337 | -- , text <| " Stars: " ++ String.fromInt r.stars |
338 | -- ] | ||
339 | div | ||
340 | [ css | ||
341 | [ border3 (px 1) solid theme.primary | ||
342 | , borderRadius (px 4) | ||
343 | , marginBottom (px 20) | ||
344 | , padding (px 20) | ||
345 | ] | ||
346 | ] | ||
347 | [ div | ||
348 | [ css | ||
349 | [ fontSize (px 16) | ||
350 | , fontWeight bold | ||
351 | , paddingBottom (px 3) | ||
352 | ] | ||
353 | ] | ||
354 | [ text r.customerName ] | ||
355 | , viewStarRating r.stars | ||
356 | , div | ||
357 | [ css | ||
358 | [ cardSecondaryText | ||
359 | , paddingBottom (px 12) | ||
360 | ] | ||
361 | ] | ||
362 | [ text <| "Reviewed on " ++ r.commentDate ] | ||
363 | , if r.commentText /= Nothing then | ||
364 | div | ||
365 | [ css [ cardSupportingText ] ] | ||
366 | [ text <| Maybe.withDefault "" <| r.commentText ] | ||
367 | |||
368 | else | ||
369 | text "" | ||
280 | ] | 370 | ] |
281 | 371 | ||
282 | 372 | ||
@@ -290,7 +380,7 @@ viewStars = | |||
290 | ul [] | 380 | ul [] |
291 | (List.map | 381 | (List.map |
292 | (\i -> button [ onClick (AddRatingStars i) ] [ text <| String.fromInt i ]) | 382 | (\i -> button [ onClick (AddRatingStars i) ] [ text <| String.fromInt i ]) |
293 | [ 0, 1, 2, 3, 4, 5 ] | 383 | [ 1, 2, 3, 4, 5 ] |
294 | ) | 384 | ) |
295 | 385 | ||
296 | 386 | ||
@@ -301,9 +391,30 @@ view model = | |||
301 | div [] [ text <| viewStatus Loading ] | 391 | div [] [ text <| viewStatus Loading ] |
302 | 392 | ||
303 | _ -> | 393 | _ -> |
304 | div [] | 394 | div |
395 | [ css | ||
396 | [ Css.width (pct 60) | ||
397 | , margin auto | ||
398 | ] | ||
399 | ] | ||
305 | [ div [] [ viewProduct model.listing ] | 400 | [ div [] [ viewProduct model.listing ] |
306 | , ul [] (List.map viewRating model.ratings) | 401 | , div |
402 | [ css | ||
403 | [ cardPrimaryText | ||
404 | ] | ||
405 | ] | ||
406 | [ text "User Reviews" ] | ||
407 | , if model.ratings == [] then | ||
408 | text "Be the first to add a review." | ||
409 | |||
410 | else | ||
411 | ul | ||
412 | [ css | ||
413 | [ padding (px 0) | ||
414 | , listStyle Css.none | ||
415 | ] | ||
416 | ] | ||
417 | (List.map viewRating model.ratings) | ||
307 | , div [] [ text "Add Rating: " ] | 418 | , div [] [ text "Add Rating: " ] |
308 | , div [] | 419 | , div [] |
309 | [ viewStars | 420 | [ viewStars |
@@ -311,9 +422,6 @@ view model = | |||
311 | , button [ onClick AddRatingPressed ] [ text "Submit Rating" ] | 422 | , button [ onClick AddRatingPressed ] [ text "Submit Rating" ] |
312 | ] | 423 | ] |
313 | , div [] | 424 | , div [] |
314 | [ button [ onClick AddToCartPressed ] [ text "Add To Cart" ] | ||
315 | ] | ||
316 | , div [] | ||
317 | [ a [ href "/catalog" ] [ text "Back to catalog" ] | 425 | [ a [ href "/catalog" ] [ text "Back to catalog" ] |
318 | ] | 426 | ] |
319 | ] | 427 | ] |
diff --git a/frontend/src/Styles.elm b/frontend/src/Styles.elm index 36f2a81..fbef6e1 100644 --- a/frontend/src/Styles.elm +++ b/frontend/src/Styles.elm | |||
@@ -60,6 +60,7 @@ furbyButton = | |||
60 | , color theme.fg | 60 | , color theme.fg |
61 | , Css.height (px 40) | 61 | , Css.height (px 40) |
62 | , border (px 0) | 62 | , border (px 0) |
63 | , borderRadius (px 2) | ||
63 | , padding2 (px 6) (px 12) | 64 | , padding2 (px 6) (px 12) |
64 | , backgroundColor theme.primary | 65 | , backgroundColor theme.primary |
65 | , hover | 66 | , hover |
@@ -102,3 +103,23 @@ loginInputField = | |||
102 | bigHeading : Style | 103 | bigHeading : Style |
103 | bigHeading = | 104 | bigHeading = |
104 | fontSize (px 24) | 105 | fontSize (px 24) |
106 | |||
107 | |||
108 | |||
109 | -- card styles | ||
110 | |||
111 | |||
112 | cardPrimaryText = | ||
113 | fontSize (px 18) | ||
114 | |||
115 | |||
116 | cardSecondaryText = | ||
117 | Css.batch [ color theme.fgLight, fontSize (px 12) ] | ||
118 | |||
119 | |||
120 | cardSupportingText = | ||
121 | fontSize (px 16) | ||
122 | |||
123 | |||
124 | money = | ||
125 | before [ Css.property "content" "\"₹ \"" ] | ||