From 8014def1a8da3397d78d1162f9e1b8c3f22d0322 Mon Sep 17 00:00:00 2001 From: Akshay Date: Sat, 26 Dec 2020 10:51:46 +0530 Subject: add transactions and quantities - backend exposes endpoints to perform transactions - frontend introduces a new page to "checkout" cart --- frontend/src/Cart.elm | 76 +++++++++++++++++++++++----- frontend/src/Catalog.elm | 16 +++--- frontend/src/Checkout.elm | 126 ++++++++++++++++++++++++++++++++++++++++++++++ frontend/src/Main.elm | 39 ++++++++++++-- 4 files changed, 232 insertions(+), 25 deletions(-) create mode 100644 frontend/src/Checkout.elm (limited to 'frontend') 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 = } +type alias CartListing = + { productItem : Product + , quantity : Int + } + + type alias Model = { pageStatus : Status - , products : List Product + , products : List CartListing } @@ -34,10 +40,12 @@ type Status type Msg - = CartLoaded (Result Http.Error (List Product)) + = CartLoaded (Result Http.Error (List CartListing)) | FetchCartItems | RemoveFromCart Int | CartItemRemoved (Result Http.Error ()) + | AddToCartSuccess (Result Http.Error ()) + | AddToCartPressed Int init : Model @@ -69,6 +77,12 @@ update msg model = FetchCartItems -> ( { model | pageStatus = Loading }, fetchCartItems ) + AddToCartPressed id -> + ( model, addToCart id ) + + AddToCartSuccess _ -> + ( { model | pageStatus = Loading }, fetchCartItems ) + decodeProduct : D.Decoder Product decodeProduct = @@ -80,9 +94,13 @@ decodeProduct = (D.field "description" (D.nullable D.string)) -decodeResponse : D.Decoder (List Product) +decodeResponse : D.Decoder (List CartListing) decodeResponse = - D.list decodeProduct + D.list + (D.map2 CartListing + (D.field "product_item" decodeProduct) + (D.field "quantity" D.int) + ) removeProduct : Int -> Cmd Msg @@ -132,15 +150,45 @@ viewStatus s = "Not loaded ..." -viewProduct : Product -> Html Msg -viewProduct p = +addToCart : Int -> Cmd Msg +addToCart id = + let + _ = + Debug.log "err" <| "adding to cart: " ++ String.fromInt id + in + Http.riskyRequest + { method = "POST" + , headers = [] + , url = "http://127.0.0.1:7878/cart/add" + , body = Http.stringBody "applcation/json" <| String.fromInt <| id + , expect = Http.expectWhatever AddToCartSuccess + , timeout = Nothing + , tracker = Nothing + } + + +calculateTotal : Model -> Float +calculateTotal model = + let + items = + model.products + in + items + |> List.map (\i -> toFloat i.quantity * i.productItem.price) + |> List.foldl (+) 0 + + +viewCartItemListing : CartListing -> Html Msg +viewCartItemListing listing = div [] - [ text p.name - , div [] [ text <| Maybe.withDefault "" p.kind ] - , div [] [ text <| Maybe.withDefault "" p.description ] - , div [] [ text <| String.fromFloat p.price ] - , div [] [ button [ onClick (RemoveFromCart p.id) ] [ text "Remove" ] ] - , div [] [ a [ href ("/product/" ++ String.fromInt p.id) ] [ text "View Product" ] ] + [ text listing.productItem.name + , div [] [ text <| Maybe.withDefault "" listing.productItem.kind ] + , div [] [ text <| Maybe.withDefault "" listing.productItem.description ] + , div [] [ text <| String.fromFloat listing.productItem.price ] + , div [] [ text <| String.fromInt listing.quantity ] + , div [] [ button [ onClick (AddToCartPressed listing.productItem.id) ] [ text "Add" ] ] + , div [] [ button [ onClick (RemoveFromCart listing.productItem.id) ] [ text "Remove" ] ] + , div [] [ a [ href ("/product/" ++ String.fromInt listing.productItem.id) ] [ text "View Product" ] ] ] @@ -154,11 +202,13 @@ view model = div [] [ let cart = - List.map viewProduct model.products + List.map viewCartItemListing model.products in if List.isEmpty cart then text "No items in cart" else ul [] cart + , calculateTotal model |> String.fromFloat |> text + , a [ href "/checkout" ] [ text "Checkout" ] ] 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 viewFilters model = let priceRange = - range 0 50000 5000 + range 0 55000 5000 ratingRange = - List.range 1 5 + range 1 6 1 - viewRange = - List.map (\i -> option [] [ text <| String.fromInt i ]) + viewRange default scale = + List.map (\i -> option [ selected (i == default) ] [ text <| String.fromInt i ]) scale inp = Maybe.withDefault 0 << String.toFloat @@ -202,15 +202,15 @@ viewFilters model = div [] [ div [] [ text "Price" - , select [ onInput (ChangePriceLower << inp) ] (viewRange priceRange) + , select [ onInput (ChangePriceLower << inp) ] (viewRange 0 priceRange) , text "to" - , select [ onInput (ChangePriceUpper << inp) ] (viewRange priceRange) + , select [ onInput (ChangePriceUpper << inp) ] (viewRange 50000 priceRange) ] , div [] [ text "Rating" - , select [ onInput (ChangeRatingLower << inp) ] (viewRange ratingRange) + , select [ onInput (ChangeRatingLower << inp) ] (viewRange 1 ratingRange) , text "to" - , select [ onInput (ChangeRatingUpper << inp) ] (viewRange ratingRange) + , select [ onInput (ChangeRatingUpper << inp) ] (viewRange 5 ratingRange) ] ] 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 @@ +module Checkout exposing (..) + +import Browser +import Browser.Navigation as Nav +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Http +import Json.Decode as D +import Json.Encode as Encode +import Tuple exposing (..) +import Url +import Url.Parser as P exposing ((), Parser, int, oneOf, s, string) +import Utils exposing (..) + + +type alias Model = + { pageStatus : Status + , paymentMode : String + , cartTotal : Float + } + + +type Status + = Loading + | Loaded + | NotLoaded + + +type Msg + = CheckoutPressed + | CheckoutSuccessful (Result Http.Error ()) + | AmountLoaded (Result Http.Error Float) + | FetchAmount + | PaymentModeSelected String + + +init : Model +init = + Model NotLoaded "Cash" 0 + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + CheckoutPressed -> + ( model, tryCheckout model.paymentMode ) + + CheckoutSuccessful _ -> + ( model, Cmd.none ) + + AmountLoaded res -> + case res of + Ok v -> + ( { model | cartTotal = v }, Cmd.none ) + + Err _ -> + ( { model | pageStatus = NotLoaded }, Cmd.none ) + + FetchAmount -> + let + _ = + Debug.log "err" "fetching checkout amount" + in + ( { model | pageStatus = Loading }, fetchAmount ) + + PaymentModeSelected s -> + ( { model | paymentMode = s }, Cmd.none ) + + +fetchAmount : Cmd Msg +fetchAmount = + Http.riskyRequest + { method = "GET" + , headers = [] + , url = "http://127.0.0.1:7878/cart/total" + , body = Http.emptyBody + , expect = Http.expectJson AmountLoaded D.float + , timeout = Nothing + , tracker = Nothing + } + + +tryCheckout : String -> Cmd Msg +tryCheckout pm = + Http.riskyRequest + { method = "POST" + , headers = [] + , url = "http://127.0.0.1:7878/transaction/checkout" + , body = Http.stringBody "application/json" pm + , expect = Http.expectWhatever CheckoutSuccessful + , timeout = Nothing + , tracker = Nothing + } + + +viewStatus : Status -> String +viewStatus s = + case s of + Loading -> + "Loading" + + Loaded -> + "Ready!" + + NotLoaded -> + "Not loaded ..." + + +view : Model -> Html Msg +view model = + case model.pageStatus of + Loading -> + div [] [ text <| viewStatus Loading ] + + _ -> + div [] + [ div [] [ text <| String.fromFloat <| model.cartTotal ] + , select [] + [ option [ onInput PaymentModeSelected ] [ text "Cash" ] + , option [ onInput PaymentModeSelected ] [ text "Debit Card" ] + , option [ onInput PaymentModeSelected ] [ text "Credit Card" ] + ] + , div [] [ a [ href "/cart" ] [ text "Cancel" ] ] + , div [] [ button [ onClick CheckoutPressed ] [ text "Confirm and Pay" ] ] + ] 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 import Browser.Navigation as Nav import Cart import Catalog +import Checkout import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (..) @@ -43,6 +44,7 @@ type Route | CatalogPage | CartPage | ProductPage Int + | CheckoutPage | NotFoundPage @@ -54,9 +56,8 @@ parseRoute = , P.map CatalogPage (P.s "catalog") , P.map CartPage (P.s "cart") , P.map SignupPage (P.s "signup") + , P.map CheckoutPage (P.s "checkout") , P.map ProductPage (P.s "product" P.int) - - --, P.map ProductPage (P.s "product" int) ] @@ -69,6 +70,7 @@ type alias Model = , productModel : Product.Model , signupModel : Signup.Model , cartModel : Cart.Model + , checkoutModel : Checkout.Model } @@ -92,8 +94,11 @@ init flags url key = cart = Cart.init + + checkout = + Checkout.init in - ( Model key url start login catalog product signup cart, Cmd.none ) + ( Model key url start login catalog product signup cart checkout, Cmd.none ) @@ -108,6 +113,7 @@ type Msg | ProductMessage Product.Msg | SignupMessage Signup.Msg | CartMessage Cart.Msg + | CheckoutMessage Checkout.Msg | LogoutPressed | LogoutSuccess (Result Http.Error ()) @@ -127,7 +133,7 @@ update msg model = ( model, tryLogout ) LogoutSuccess _ -> - ( model, Nav.replaceUrl model.key "/login" ) + ( { model | loginModel = Login.init }, Nav.replaceUrl model.key "/login" ) UrlChanged url -> let @@ -155,6 +161,16 @@ update msg model = in ( { model | location = CartPage }, cmd ) + Just CheckoutPage -> + let + _ = + Debug.log "err" "loading checkout page ..." + + cmd = + Cmd.map CheckoutMessage Checkout.fetchAmount + in + ( { model | location = CheckoutPage }, cmd ) + Just p -> ( { model | location = p }, Cmd.none ) @@ -205,6 +221,16 @@ update msg model = in ( { model | cartModel = cmn }, Cmd.map CartMessage cmd ) + CheckoutMessage cm -> + let + ( cmn, cmd ) = + Checkout.update cm model.checkoutModel + + _ = + Debug.log "err" "received checkout message ..." + in + ( { model | checkoutModel = cmn }, Cmd.map CheckoutMessage cmd ) + ProductMessage pm -> let ( pmn, cmd ) = @@ -292,6 +318,11 @@ view model = , body = pageWrap model (Html.map CartMessage (Cart.view model.cartModel)) } + CheckoutPage -> + { title = "Checkout" + , body = pageWrap model (Html.map CheckoutMessage (Checkout.view model.checkoutModel)) + } + ProductPage item -> { title = "Product " ++ String.fromInt item , body = pageWrap model (Html.map ProductMessage (Product.view model.productModel)) -- cgit v1.2.3