diff options
Diffstat (limited to 'frontend/src')
-rw-r--r-- | frontend/src/Cart.elm | 76 | ||||
-rw-r--r-- | frontend/src/Catalog.elm | 16 | ||||
-rw-r--r-- | frontend/src/Checkout.elm | 126 | ||||
-rw-r--r-- | frontend/src/Main.elm | 39 |
4 files changed, 232 insertions, 25 deletions
diff --git a/frontend/src/Cart.elm b/frontend/src/Cart.elm index a1750f6..58fb72e 100644 --- a/frontend/src/Cart.elm +++ b/frontend/src/Cart.elm | |||
@@ -21,9 +21,15 @@ type alias Product = | |||
21 | } | 21 | } |
22 | 22 | ||
23 | 23 | ||
24 | type alias CartListing = | ||
25 | { productItem : Product | ||
26 | , quantity : Int | ||
27 | } | ||
28 | |||
29 | |||
24 | type alias Model = | 30 | type alias Model = |
25 | { pageStatus : Status | 31 | { pageStatus : Status |
26 | , products : List Product | 32 | , products : List CartListing |
27 | } | 33 | } |
28 | 34 | ||
29 | 35 | ||
@@ -34,10 +40,12 @@ type Status | |||
34 | 40 | ||
35 | 41 | ||
36 | type Msg | 42 | type Msg |
37 | = CartLoaded (Result Http.Error (List Product)) | 43 | = CartLoaded (Result Http.Error (List CartListing)) |
38 | | FetchCartItems | 44 | | FetchCartItems |
39 | | RemoveFromCart Int | 45 | | RemoveFromCart Int |
40 | | CartItemRemoved (Result Http.Error ()) | 46 | | CartItemRemoved (Result Http.Error ()) |
47 | | AddToCartSuccess (Result Http.Error ()) | ||
48 | | AddToCartPressed Int | ||
41 | 49 | ||
42 | 50 | ||
43 | init : Model | 51 | init : Model |
@@ -69,6 +77,12 @@ update msg model = | |||
69 | FetchCartItems -> | 77 | FetchCartItems -> |
70 | ( { model | pageStatus = Loading }, fetchCartItems ) | 78 | ( { model | pageStatus = Loading }, fetchCartItems ) |
71 | 79 | ||
80 | AddToCartPressed id -> | ||
81 | ( model, addToCart id ) | ||
82 | |||
83 | AddToCartSuccess _ -> | ||
84 | ( { model | pageStatus = Loading }, fetchCartItems ) | ||
85 | |||
72 | 86 | ||
73 | decodeProduct : D.Decoder Product | 87 | decodeProduct : D.Decoder Product |
74 | decodeProduct = | 88 | decodeProduct = |
@@ -80,9 +94,13 @@ decodeProduct = | |||
80 | (D.field "description" (D.nullable D.string)) | 94 | (D.field "description" (D.nullable D.string)) |
81 | 95 | ||
82 | 96 | ||
83 | decodeResponse : D.Decoder (List Product) | 97 | decodeResponse : D.Decoder (List CartListing) |
84 | decodeResponse = | 98 | decodeResponse = |
85 | D.list decodeProduct | 99 | D.list |
100 | (D.map2 CartListing | ||
101 | (D.field "product_item" decodeProduct) | ||
102 | (D.field "quantity" D.int) | ||
103 | ) | ||
86 | 104 | ||
87 | 105 | ||
88 | removeProduct : Int -> Cmd Msg | 106 | removeProduct : Int -> Cmd Msg |
@@ -132,15 +150,45 @@ viewStatus s = | |||
132 | "Not loaded ..." | 150 | "Not loaded ..." |
133 | 151 | ||
134 | 152 | ||
135 | viewProduct : Product -> Html Msg | 153 | addToCart : Int -> Cmd Msg |
136 | viewProduct p = | 154 | addToCart id = |
155 | let | ||
156 | _ = | ||
157 | Debug.log "err" <| "adding to cart: " ++ String.fromInt id | ||
158 | in | ||
159 | Http.riskyRequest | ||
160 | { method = "POST" | ||
161 | , headers = [] | ||
162 | , url = "http://127.0.0.1:7878/cart/add" | ||
163 | , body = Http.stringBody "applcation/json" <| String.fromInt <| id | ||
164 | , expect = Http.expectWhatever AddToCartSuccess | ||
165 | , timeout = Nothing | ||
166 | , tracker = Nothing | ||
167 | } | ||
168 | |||
169 | |||
170 | calculateTotal : Model -> Float | ||
171 | calculateTotal model = | ||
172 | let | ||
173 | items = | ||
174 | model.products | ||
175 | in | ||
176 | items | ||
177 | |> List.map (\i -> toFloat i.quantity * i.productItem.price) | ||
178 | |> List.foldl (+) 0 | ||
179 | |||
180 | |||
181 | viewCartItemListing : CartListing -> Html Msg | ||
182 | viewCartItemListing listing = | ||
137 | div [] | 183 | div [] |
138 | [ text p.name | 184 | [ text listing.productItem.name |
139 | , div [] [ text <| Maybe.withDefault "" p.kind ] | 185 | , div [] [ text <| Maybe.withDefault "" listing.productItem.kind ] |
140 | , div [] [ text <| Maybe.withDefault "" p.description ] | 186 | , div [] [ text <| Maybe.withDefault "" listing.productItem.description ] |
141 | , div [] [ text <| String.fromFloat p.price ] | 187 | , div [] [ text <| String.fromFloat listing.productItem.price ] |
142 | , div [] [ button [ onClick (RemoveFromCart p.id) ] [ text "Remove" ] ] | 188 | , div [] [ text <| String.fromInt listing.quantity ] |
143 | , div [] [ a [ href ("/product/" ++ String.fromInt p.id) ] [ text "View Product" ] ] | 189 | , div [] [ button [ onClick (AddToCartPressed listing.productItem.id) ] [ text "Add" ] ] |
190 | , div [] [ button [ onClick (RemoveFromCart listing.productItem.id) ] [ text "Remove" ] ] | ||
191 | , div [] [ a [ href ("/product/" ++ String.fromInt listing.productItem.id) ] [ text "View Product" ] ] | ||
144 | ] | 192 | ] |
145 | 193 | ||
146 | 194 | ||
@@ -154,11 +202,13 @@ view model = | |||
154 | div [] | 202 | div [] |
155 | [ let | 203 | [ let |
156 | cart = | 204 | cart = |
157 | List.map viewProduct model.products | 205 | List.map viewCartItemListing model.products |
158 | in | 206 | in |
159 | if List.isEmpty cart then | 207 | if List.isEmpty cart then |
160 | text "No items in cart" | 208 | text "No items in cart" |
161 | 209 | ||
162 | else | 210 | else |
163 | ul [] cart | 211 | ul [] cart |
212 | , calculateTotal model |> String.fromFloat |> text | ||
213 | , a [ href "/checkout" ] [ text "Checkout" ] | ||
164 | ] | 214 | ] |
diff --git a/frontend/src/Catalog.elm b/frontend/src/Catalog.elm index 6882c73..d00cb92 100644 --- a/frontend/src/Catalog.elm +++ b/frontend/src/Catalog.elm | |||
@@ -188,13 +188,13 @@ viewFilters : Model -> Html Msg | |||
188 | viewFilters model = | 188 | viewFilters model = |
189 | let | 189 | let |
190 | priceRange = | 190 | priceRange = |
191 | range 0 50000 5000 | 191 | range 0 55000 5000 |
192 | 192 | ||
193 | ratingRange = | 193 | ratingRange = |
194 | List.range 1 5 | 194 | range 1 6 1 |
195 | 195 | ||
196 | viewRange = | 196 | viewRange default scale = |
197 | List.map (\i -> option [] [ text <| String.fromInt i ]) | 197 | List.map (\i -> option [ selected (i == default) ] [ text <| String.fromInt i ]) scale |
198 | 198 | ||
199 | inp = | 199 | inp = |
200 | Maybe.withDefault 0 << String.toFloat | 200 | Maybe.withDefault 0 << String.toFloat |
@@ -202,15 +202,15 @@ viewFilters model = | |||
202 | div [] | 202 | div [] |
203 | [ div [] | 203 | [ div [] |
204 | [ text "Price" | 204 | [ text "Price" |
205 | , select [ onInput (ChangePriceLower << inp) ] (viewRange priceRange) | 205 | , select [ onInput (ChangePriceLower << inp) ] (viewRange 0 priceRange) |
206 | , text "to" | 206 | , text "to" |
207 | , select [ onInput (ChangePriceUpper << inp) ] (viewRange priceRange) | 207 | , select [ onInput (ChangePriceUpper << inp) ] (viewRange 50000 priceRange) |
208 | ] | 208 | ] |
209 | , div [] | 209 | , div [] |
210 | [ text "Rating" | 210 | [ text "Rating" |
211 | , select [ onInput (ChangeRatingLower << inp) ] (viewRange ratingRange) | 211 | , select [ onInput (ChangeRatingLower << inp) ] (viewRange 1 ratingRange) |
212 | , text "to" | 212 | , text "to" |
213 | , select [ onInput (ChangeRatingUpper << inp) ] (viewRange ratingRange) | 213 | , select [ onInput (ChangeRatingUpper << inp) ] (viewRange 5 ratingRange) |
214 | ] | 214 | ] |
215 | ] | 215 | ] |
216 | 216 | ||
diff --git a/frontend/src/Checkout.elm b/frontend/src/Checkout.elm new file mode 100644 index 0000000..c60da0d --- /dev/null +++ b/frontend/src/Checkout.elm | |||
@@ -0,0 +1,126 @@ | |||
1 | module Checkout exposing (..) | ||
2 | |||
3 | import Browser | ||
4 | import Browser.Navigation as Nav | ||
5 | import Html exposing (..) | ||
6 | import Html.Attributes exposing (..) | ||
7 | import Html.Events exposing (..) | ||
8 | import Http | ||
9 | import Json.Decode as D | ||
10 | import Json.Encode as Encode | ||
11 | import Tuple exposing (..) | ||
12 | import Url | ||
13 | import Url.Parser as P exposing ((</>), Parser, int, oneOf, s, string) | ||
14 | import Utils exposing (..) | ||
15 | |||
16 | |||
17 | type alias Model = | ||
18 | { pageStatus : Status | ||
19 | , paymentMode : String | ||
20 | , cartTotal : Float | ||
21 | } | ||
22 | |||
23 | |||
24 | type Status | ||
25 | = Loading | ||
26 | | Loaded | ||
27 | | NotLoaded | ||
28 | |||
29 | |||
30 | type Msg | ||
31 | = CheckoutPressed | ||
32 | | CheckoutSuccessful (Result Http.Error ()) | ||
33 | | AmountLoaded (Result Http.Error Float) | ||
34 | | FetchAmount | ||
35 | | PaymentModeSelected String | ||
36 | |||
37 | |||
38 | init : Model | ||
39 | init = | ||
40 | Model NotLoaded "Cash" 0 | ||
41 | |||
42 | |||
43 | update : Msg -> Model -> ( Model, Cmd Msg ) | ||
44 | update msg model = | ||
45 | case msg of | ||
46 | CheckoutPressed -> | ||
47 | ( model, tryCheckout model.paymentMode ) | ||
48 | |||
49 | CheckoutSuccessful _ -> | ||
50 | ( model, Cmd.none ) | ||
51 | |||
52 | AmountLoaded res -> | ||
53 | case res of | ||
54 | Ok v -> | ||
55 | ( { model | cartTotal = v }, Cmd.none ) | ||
56 | |||
57 | Err _ -> | ||
58 | ( { model | pageStatus = NotLoaded }, Cmd.none ) | ||
59 | |||
60 | FetchAmount -> | ||
61 | let | ||
62 | _ = | ||
63 | Debug.log "err" "fetching checkout amount" | ||
64 | in | ||
65 | ( { model | pageStatus = Loading }, fetchAmount ) | ||
66 | |||
67 | PaymentModeSelected s -> | ||
68 | ( { model | paymentMode = s }, Cmd.none ) | ||
69 | |||
70 | |||
71 | fetchAmount : Cmd Msg | ||
72 | fetchAmount = | ||
73 | Http.riskyRequest | ||
74 | { method = "GET" | ||
75 | , headers = [] | ||
76 | , url = "http://127.0.0.1:7878/cart/total" | ||
77 | , body = Http.emptyBody | ||
78 | , expect = Http.expectJson AmountLoaded D.float | ||
79 | , timeout = Nothing | ||
80 | , tracker = Nothing | ||
81 | } | ||
82 | |||
83 | |||
84 | tryCheckout : String -> Cmd Msg | ||
85 | tryCheckout pm = | ||
86 | Http.riskyRequest | ||
87 | { method = "POST" | ||
88 | , headers = [] | ||
89 | , url = "http://127.0.0.1:7878/transaction/checkout" | ||
90 | , body = Http.stringBody "application/json" pm | ||
91 | , expect = Http.expectWhatever CheckoutSuccessful | ||
92 | , timeout = Nothing | ||
93 | , tracker = Nothing | ||
94 | } | ||
95 | |||
96 | |||
97 | viewStatus : Status -> String | ||
98 | viewStatus s = | ||
99 | case s of | ||
100 | Loading -> | ||
101 | "Loading" | ||
102 | |||
103 | Loaded -> | ||
104 | "Ready!" | ||
105 | |||
106 | NotLoaded -> | ||
107 | "Not loaded ..." | ||
108 | |||
109 | |||
110 | view : Model -> Html Msg | ||
111 | view model = | ||
112 | case model.pageStatus of | ||
113 | Loading -> | ||
114 | div [] [ text <| viewStatus Loading ] | ||
115 | |||
116 | _ -> | ||
117 | div [] | ||
118 | [ div [] [ text <| String.fromFloat <| model.cartTotal ] | ||
119 | , select [] | ||
120 | [ option [ onInput PaymentModeSelected ] [ text "Cash" ] | ||
121 | , option [ onInput PaymentModeSelected ] [ text "Debit Card" ] | ||
122 | , option [ onInput PaymentModeSelected ] [ text "Credit Card" ] | ||
123 | ] | ||
124 | , div [] [ a [ href "/cart" ] [ text "Cancel" ] ] | ||
125 | , div [] [ button [ onClick CheckoutPressed ] [ text "Confirm and Pay" ] ] | ||
126 | ] | ||
diff --git a/frontend/src/Main.elm b/frontend/src/Main.elm index bf1583c..f1883a1 100644 --- a/frontend/src/Main.elm +++ b/frontend/src/Main.elm | |||
@@ -4,6 +4,7 @@ import Browser | |||
4 | import Browser.Navigation as Nav | 4 | import Browser.Navigation as Nav |
5 | import Cart | 5 | import Cart |
6 | import Catalog | 6 | import Catalog |
7 | import Checkout | ||
7 | import Html exposing (..) | 8 | import Html exposing (..) |
8 | import Html.Attributes exposing (..) | 9 | import Html.Attributes exposing (..) |
9 | import Html.Events exposing (..) | 10 | import Html.Events exposing (..) |
@@ -43,6 +44,7 @@ type Route | |||
43 | | CatalogPage | 44 | | CatalogPage |
44 | | CartPage | 45 | | CartPage |
45 | | ProductPage Int | 46 | | ProductPage Int |
47 | | CheckoutPage | ||
46 | | NotFoundPage | 48 | | NotFoundPage |
47 | 49 | ||
48 | 50 | ||
@@ -54,9 +56,8 @@ parseRoute = | |||
54 | , P.map CatalogPage (P.s "catalog") | 56 | , P.map CatalogPage (P.s "catalog") |
55 | , P.map CartPage (P.s "cart") | 57 | , P.map CartPage (P.s "cart") |
56 | , P.map SignupPage (P.s "signup") | 58 | , P.map SignupPage (P.s "signup") |
59 | , P.map CheckoutPage (P.s "checkout") | ||
57 | , P.map ProductPage (P.s "product" </> P.int) | 60 | , P.map ProductPage (P.s "product" </> P.int) |
58 | |||
59 | --, P.map ProductPage (P.s "product" </> int) | ||
60 | ] | 61 | ] |
61 | 62 | ||
62 | 63 | ||
@@ -69,6 +70,7 @@ type alias Model = | |||
69 | , productModel : Product.Model | 70 | , productModel : Product.Model |
70 | , signupModel : Signup.Model | 71 | , signupModel : Signup.Model |
71 | , cartModel : Cart.Model | 72 | , cartModel : Cart.Model |
73 | , checkoutModel : Checkout.Model | ||
72 | } | 74 | } |
73 | 75 | ||
74 | 76 | ||
@@ -92,8 +94,11 @@ init flags url key = | |||
92 | 94 | ||
93 | cart = | 95 | cart = |
94 | Cart.init | 96 | Cart.init |
97 | |||
98 | checkout = | ||
99 | Checkout.init | ||
95 | in | 100 | in |
96 | ( Model key url start login catalog product signup cart, Cmd.none ) | 101 | ( Model key url start login catalog product signup cart checkout, Cmd.none ) |
97 | 102 | ||
98 | 103 | ||
99 | 104 | ||
@@ -108,6 +113,7 @@ type Msg | |||
108 | | ProductMessage Product.Msg | 113 | | ProductMessage Product.Msg |
109 | | SignupMessage Signup.Msg | 114 | | SignupMessage Signup.Msg |
110 | | CartMessage Cart.Msg | 115 | | CartMessage Cart.Msg |
116 | | CheckoutMessage Checkout.Msg | ||
111 | | LogoutPressed | 117 | | LogoutPressed |
112 | | LogoutSuccess (Result Http.Error ()) | 118 | | LogoutSuccess (Result Http.Error ()) |
113 | 119 | ||
@@ -127,7 +133,7 @@ update msg model = | |||
127 | ( model, tryLogout ) | 133 | ( model, tryLogout ) |
128 | 134 | ||
129 | LogoutSuccess _ -> | 135 | LogoutSuccess _ -> |
130 | ( model, Nav.replaceUrl model.key "/login" ) | 136 | ( { model | loginModel = Login.init }, Nav.replaceUrl model.key "/login" ) |
131 | 137 | ||
132 | UrlChanged url -> | 138 | UrlChanged url -> |
133 | let | 139 | let |
@@ -155,6 +161,16 @@ update msg model = | |||
155 | in | 161 | in |
156 | ( { model | location = CartPage }, cmd ) | 162 | ( { model | location = CartPage }, cmd ) |
157 | 163 | ||
164 | Just CheckoutPage -> | ||
165 | let | ||
166 | _ = | ||
167 | Debug.log "err" "loading checkout page ..." | ||
168 | |||
169 | cmd = | ||
170 | Cmd.map CheckoutMessage Checkout.fetchAmount | ||
171 | in | ||
172 | ( { model | location = CheckoutPage }, cmd ) | ||
173 | |||
158 | Just p -> | 174 | Just p -> |
159 | ( { model | location = p }, Cmd.none ) | 175 | ( { model | location = p }, Cmd.none ) |
160 | 176 | ||
@@ -205,6 +221,16 @@ update msg model = | |||
205 | in | 221 | in |
206 | ( { model | cartModel = cmn }, Cmd.map CartMessage cmd ) | 222 | ( { model | cartModel = cmn }, Cmd.map CartMessage cmd ) |
207 | 223 | ||
224 | CheckoutMessage cm -> | ||
225 | let | ||
226 | ( cmn, cmd ) = | ||
227 | Checkout.update cm model.checkoutModel | ||
228 | |||
229 | _ = | ||
230 | Debug.log "err" "received checkout message ..." | ||
231 | in | ||
232 | ( { model | checkoutModel = cmn }, Cmd.map CheckoutMessage cmd ) | ||
233 | |||
208 | ProductMessage pm -> | 234 | ProductMessage pm -> |
209 | let | 235 | let |
210 | ( pmn, cmd ) = | 236 | ( pmn, cmd ) = |
@@ -292,6 +318,11 @@ view model = | |||
292 | , body = pageWrap model (Html.map CartMessage (Cart.view model.cartModel)) | 318 | , body = pageWrap model (Html.map CartMessage (Cart.view model.cartModel)) |
293 | } | 319 | } |
294 | 320 | ||
321 | CheckoutPage -> | ||
322 | { title = "Checkout" | ||
323 | , body = pageWrap model (Html.map CheckoutMessage (Checkout.view model.checkoutModel)) | ||
324 | } | ||
325 | |||
295 | ProductPage item -> | 326 | ProductPage item -> |
296 | { title = "Product " ++ String.fromInt item | 327 | { title = "Product " ++ String.fromInt item |
297 | , body = pageWrap model (Html.map ProductMessage (Product.view model.productModel)) | 328 | , body = pageWrap model (Html.map ProductMessage (Product.view model.productModel)) |