aboutsummaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authorAkshay <[email protected]>2020-12-26 05:21:46 +0000
committerAkshay <[email protected]>2020-12-26 05:21:46 +0000
commit8014def1a8da3397d78d1162f9e1b8c3f22d0322 (patch)
tree346e1de0ac6aa4ca973c1b3e5897c2c44948e5a8 /frontend
parent7c6006e1abc6094b5922ab69ccfa5449b8dbbc99 (diff)
add transactions and quantities
- backend exposes endpoints to perform transactions - frontend introduces a new page to "checkout" cart
Diffstat (limited to 'frontend')
-rw-r--r--frontend/src/Cart.elm76
-rw-r--r--frontend/src/Catalog.elm16
-rw-r--r--frontend/src/Checkout.elm126
-rw-r--r--frontend/src/Main.elm39
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
24type alias CartListing =
25 { productItem : Product
26 , quantity : Int
27 }
28
29
24type alias Model = 30type 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
36type Msg 42type 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
43init : Model 51init : 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
73decodeProduct : D.Decoder Product 87decodeProduct : D.Decoder Product
74decodeProduct = 88decodeProduct =
@@ -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
83decodeResponse : D.Decoder (List Product) 97decodeResponse : D.Decoder (List CartListing)
84decodeResponse = 98decodeResponse =
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
88removeProduct : Int -> Cmd Msg 106removeProduct : Int -> Cmd Msg
@@ -132,15 +150,45 @@ viewStatus s =
132 "Not loaded ..." 150 "Not loaded ..."
133 151
134 152
135viewProduct : Product -> Html Msg 153addToCart : Int -> Cmd Msg
136viewProduct p = 154addToCart 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
170calculateTotal : Model -> Float
171calculateTotal 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
181viewCartItemListing : CartListing -> Html Msg
182viewCartItemListing 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
188viewFilters model = 188viewFilters 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 @@
1module Checkout exposing (..)
2
3import Browser
4import Browser.Navigation as Nav
5import Html exposing (..)
6import Html.Attributes exposing (..)
7import Html.Events exposing (..)
8import Http
9import Json.Decode as D
10import Json.Encode as Encode
11import Tuple exposing (..)
12import Url
13import Url.Parser as P exposing ((</>), Parser, int, oneOf, s, string)
14import Utils exposing (..)
15
16
17type alias Model =
18 { pageStatus : Status
19 , paymentMode : String
20 , cartTotal : Float
21 }
22
23
24type Status
25 = Loading
26 | Loaded
27 | NotLoaded
28
29
30type Msg
31 = CheckoutPressed
32 | CheckoutSuccessful (Result Http.Error ())
33 | AmountLoaded (Result Http.Error Float)
34 | FetchAmount
35 | PaymentModeSelected String
36
37
38init : Model
39init =
40 Model NotLoaded "Cash" 0
41
42
43update : Msg -> Model -> ( Model, Cmd Msg )
44update 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
71fetchAmount : Cmd Msg
72fetchAmount =
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
84tryCheckout : String -> Cmd Msg
85tryCheckout 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
97viewStatus : Status -> String
98viewStatus s =
99 case s of
100 Loading ->
101 "Loading"
102
103 Loaded ->
104 "Ready!"
105
106 NotLoaded ->
107 "Not loaded ..."
108
109
110view : Model -> Html Msg
111view 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
4import Browser.Navigation as Nav 4import Browser.Navigation as Nav
5import Cart 5import Cart
6import Catalog 6import Catalog
7import Checkout
7import Html exposing (..) 8import Html exposing (..)
8import Html.Attributes exposing (..) 9import Html.Attributes exposing (..)
9import Html.Events exposing (..) 10import 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))