From 192a5c36c46b50167461c3cf1d7afa069e816e01 Mon Sep 17 00:00:00 2001 From: Akshay Date: Sun, 27 Dec 2020 12:31:43 +0530 Subject: add more styles, profile page --- frontend/src/Cart.elm | 83 ++++++++++++------ frontend/src/Catalog.elm | 2 +- frontend/src/Checkout.elm | 31 +++++-- frontend/src/Main.elm | 62 ++++++++++++-- frontend/src/Product.elm | 2 +- frontend/src/Profile.elm | 211 ++++++++++++++++++++++++++++++++++++++++++++++ frontend/src/Styles.elm | 14 +++ 7 files changed, 361 insertions(+), 44 deletions(-) create mode 100644 frontend/src/Profile.elm diff --git a/frontend/src/Cart.elm b/frontend/src/Cart.elm index 44d5a0d..008d0bc 100644 --- a/frontend/src/Cart.elm +++ b/frontend/src/Cart.elm @@ -2,6 +2,7 @@ module Cart exposing (..) import Browser import Browser.Navigation as Nav +import Css exposing (..) import Html import Html.Styled exposing (..) import Html.Styled.Attributes exposing (..) @@ -9,8 +10,8 @@ import Html.Styled.Events exposing (..) import Http import Json.Decode as D import Json.Encode as Encode -import Url -import Url.Parser as P exposing ((), Parser, int, oneOf, s, string) +import Styles exposing (..) +import Utils exposing (..) type alias Product = @@ -181,15 +182,24 @@ calculateTotal model = viewCartItemListing : CartListing -> Html Msg viewCartItemListing listing = - div [] - [ 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" ] ] + -- div [] + -- [ 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" ] ] + -- ] + tr [] + [ td [] [ a [ href ("/product/" ++ String.fromInt listing.productItem.id) ] [ text listing.productItem.name ] ] + , td [] [ text <| String.fromFloat listing.productItem.price ] + , td [] + [ furbyButton [ onClick (RemoveFromCart listing.productItem.id) ] [ div [ style "font-family" "monospace" ] [ text "-" ] ] + , text <| String.fromInt listing.quantity + , furbyButton [ onClick (AddToCartPressed listing.productItem.id) ] [ div [ style "font-family" "monospace" ] [ text "+" ] ] + ] ] @@ -200,16 +210,41 @@ view model = div [] [ text <| viewStatus Loading ] _ -> - div [] - [ let - cart = - 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" ] - ] + let + cart = + List.map viewCartItemListing model.products + + headings = + [ "Product Name", "Price (₹)", "Quantity" ] + |> List.map (th [] << List.singleton << text) + in + if List.isEmpty cart then + text "No items in cart" + + else + div + [ css + [ margin auto + , marginTop (pct 5) + , Css.width (pct 40) + ] + ] + [ div [ css [ bigHeading, marginBottom (px 20) ] ] [ text "Cart" ] + , Html.Styled.table + [ css + [ Css.width (pct 100) + , maxWidth (px 650) + , textAlign right + ] + ] + (tr [] headings + :: cart + ++ [ tr [ style "padding-top" "20px" ] + [ td [ style "border-top" "1px solid black" ] [] + , td [ style "border-top" "1px solid black" ] [ div [] [ text "Cart total: " ] ] + , td [ style "border-top" "1px solid black" ] [ calculateTotal model |> String.fromFloat |> text ] + ] + ] + ) + , a [ href "/checkout" ] [ text "Checkout" ] + ] diff --git a/frontend/src/Catalog.elm b/frontend/src/Catalog.elm index 7e9bde7..d4cbf96 100644 --- a/frontend/src/Catalog.elm +++ b/frontend/src/Catalog.elm @@ -210,7 +210,7 @@ viewProduct p = [ css [ cardSecondaryText , paddingBottom (px 3) - , fontVariant smallCaps + , textTransform uppercase ] ] [ text <| Maybe.withDefault "" p.kind ] diff --git a/frontend/src/Checkout.elm b/frontend/src/Checkout.elm index 4df20d8..216b90d 100644 --- a/frontend/src/Checkout.elm +++ b/frontend/src/Checkout.elm @@ -2,6 +2,7 @@ module Checkout exposing (..) import Browser import Browser.Navigation as Nav +import Css exposing (..) import Html import Html.Styled exposing (..) import Html.Styled.Attributes exposing (..) @@ -9,9 +10,8 @@ import Html.Styled.Events exposing (..) import Http import Json.Decode as D import Json.Encode as Encode +import Styles exposing (..) import Tuple exposing (..) -import Url -import Url.Parser as P exposing ((), Parser, int, oneOf, s, string) import Utils exposing (..) @@ -26,6 +26,7 @@ type Status = Loading | Loaded | NotLoaded + | CheckedOut type Msg @@ -48,7 +49,7 @@ update msg model = ( model, tryCheckout model.paymentMode ) CheckoutSuccessful _ -> - ( model, Cmd.none ) + ( { model | pageStatus = CheckedOut }, Cmd.none ) AmountLoaded res -> case res of @@ -107,6 +108,9 @@ viewStatus s = NotLoaded -> "Not loaded ..." + CheckedOut -> + "Checked out!" + view : Model -> Html Msg view model = @@ -115,13 +119,22 @@ view model = 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 + [ css + [ margin auto + , marginTop (pct 5) + , Css.width (pct 40) ] + ] + [ div [ css [ bigHeading, marginBottom (px 20) ] ] [ text "Checkout" ] + , div [ css [ cardSupportingText ] ] [ text "Your total is" ] + , div + [ css [ bigHeading, fontWeight bold, marginBottom (px 20) ] ] + [ text <| (++) "₹ " <| String.fromFloat <| model.cartTotal ] + , div [ css [ cardSupportingText ] ] [ text "Select a payment mode" ] + , div [] [ furbyRadio "Cash" (PaymentModeSelected "Cash") ] + , div [] [ furbyRadio "Debit Card" (PaymentModeSelected "Debit Card") ] + , div [] [ furbyRadio "Credit Card" (PaymentModeSelected "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 c1489bf..09a851d 100644 --- a/frontend/src/Main.elm +++ b/frontend/src/Main.elm @@ -14,6 +14,7 @@ import Http import Json.Encode as Encode import Login import Product +import Profile import Signup import Styles exposing (..) import Url @@ -48,6 +49,7 @@ type Route | CartPage | ProductPage Int | CheckoutPage + | ProfilePage | NotFoundPage @@ -61,6 +63,7 @@ parseRoute = , P.map SignupPage (P.s "signup") , P.map CheckoutPage (P.s "checkout") , P.map ProductPage (P.s "product" P.int) + , P.map ProfilePage (P.s "profile") ] @@ -74,6 +77,7 @@ type alias Model = , signupModel : Signup.Model , cartModel : Cart.Model , checkoutModel : Checkout.Model + , profileModel : Profile.Model } @@ -100,8 +104,11 @@ init flags url key = checkout = Checkout.init + + profile = + Profile.init in - ( Model key url start login catalog product signup cart checkout, Cmd.none ) + ( Model key url start login catalog product signup cart checkout profile, Cmd.none ) @@ -117,6 +124,7 @@ type Msg | SignupMessage Signup.Msg | CartMessage Cart.Msg | CheckoutMessage Checkout.Msg + | ProfileMessage Profile.Msg | LogoutPressed | LogoutSuccess (Result Http.Error ()) @@ -174,6 +182,13 @@ update msg model = in ( { model | location = CheckoutPage }, cmd ) + Just ProfilePage -> + let + cmd = + Cmd.map ProfileMessage Profile.tryFetchProfile + in + ( { model | location = ProfilePage }, cmd ) + Just p -> ( { model | location = p }, Cmd.none ) @@ -229,10 +244,28 @@ update msg model = ( cmn, cmd ) = Checkout.update cm model.checkoutModel + redir = + case cmn.pageStatus of + Checkout.CheckedOut -> + Nav.replaceUrl model.key "/profile" + + _ -> + Cmd.none + _ = Debug.log "err" "received checkout message ..." in - ( { model | checkoutModel = cmn }, Cmd.map CheckoutMessage cmd ) + ( { model | checkoutModel = cmn }, Cmd.batch [ Cmd.map CheckoutMessage cmd, redir ] ) + + ProfileMessage pm -> + let + ( pmn, cmd ) = + Profile.update pm model.profileModel + + _ = + Debug.log "err" "recieved profile message" + in + ( { model | profileModel = pmn }, Cmd.map ProfileMessage cmd ) ProductMessage pm -> let @@ -359,6 +392,15 @@ view model = |> pageWrap model } + ProfilePage -> + { title = "Profile" + , body = + model.profileModel + |> Profile.view + |> Html.Styled.map ProfileMessage + |> pageWrap model + } + ProductPage item -> { title = "Product " ++ String.fromInt item , body = @@ -379,7 +421,7 @@ viewHeader model = in div [ css - [ padding (px 40) + [ padding (px 30) , paddingTop (px 3) , paddingBottom (px 3) , textAlign left @@ -393,17 +435,19 @@ viewHeader model = ] ) links - ++ [ if model.loginModel.loginStatus /= Login.LoggedIn then - li [ css [ display inline ] ] [ headerLink [ href "/login" ] [ text "Login" ] ] + ++ (if model.loginModel.loginStatus /= Login.LoggedIn then + [ furbyButton [] [ headerLink [ href "/login" ] [ text "Login" ] ] ] - else - furbyButton [ onClick LogoutPressed ] [ text "Logout" ] - ] + else + [ headerLink [ href "/profile" ] [ text "Profile" ] + , furbyButton [ onClick LogoutPressed ] [ text "Logout" ] + ] + ) |> ul [ css [ listStyle Css.none , padding (px 0) - , margin (px 24) + , margin (px 12) ] ] ] diff --git a/frontend/src/Product.elm b/frontend/src/Product.elm index 79256cc..0975c8d 100644 --- a/frontend/src/Product.elm +++ b/frontend/src/Product.elm @@ -287,7 +287,7 @@ viewProduct p = [ css [ cardSecondaryText , paddingBottom (px 3) - , fontVariant smallCaps + , textTransform uppercase ] ] [ text <| Maybe.withDefault "" p.kind ] diff --git a/frontend/src/Profile.elm b/frontend/src/Profile.elm new file mode 100644 index 0000000..6575d41 --- /dev/null +++ b/frontend/src/Profile.elm @@ -0,0 +1,211 @@ +module Profile exposing (..) + +import Browser +import Browser.Navigation as Nav +import Css exposing (..) +import Html +import Html.Styled exposing (..) +import Html.Styled.Attributes exposing (..) +import Html.Styled.Events exposing (..) +import Http +import Icons exposing (..) +import Json.Decode as D +import Json.Encode as Encode +import Styles exposing (..) +import Utils exposing (..) + + +emptyProfile = + UserProfile "" "" "" Nothing 0 [] + + +type alias Transaction = + { amount : Float + , transactionId : Int + , orderDate : String + , paymentMode : String + } + + +type alias UserProfile = + { username : String + , phoneNumber : String + , emailId : String + , address : Maybe String + , ratingsGiven : Int + , transactions : List Transaction + } + + +type alias Model = + { profile : UserProfile + , status : Status + } + + +type Status + = Loading + | Loaded + | NotLoaded + + +type Msg + = ProfileLoaded (Result Http.Error UserProfile) + | FetchProfile + + +init : Model +init = + Model emptyProfile NotLoaded + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + ProfileLoaded res -> + case res of + Ok p -> + ( { model | profile = p }, Cmd.none ) + + Err _ -> + ( { model | status = NotLoaded }, Cmd.none ) + + FetchProfile -> + ( { model | status = Loading }, tryFetchProfile ) + + +decodeProfile : D.Decoder UserProfile +decodeProfile = + D.map6 UserProfile + (D.field "username" D.string) + (D.field "phone_number" D.string) + (D.field "email_id" D.string) + (D.field "address" (D.nullable D.string)) + (D.field "ratings_given" D.int) + (D.field "transactions" (D.list decodeTransaction)) + + +decodeTransaction : D.Decoder Transaction +decodeTransaction = + D.map4 Transaction + (D.field "amount" D.float) + (D.field "id" D.int) + (D.field "order_date" D.string) + (D.field "payment_type" D.string) + + +tryFetchProfile : Cmd Msg +tryFetchProfile = + let + _ = + Debug.log "err" <| "fetching user profile" + in + Http.riskyRequest + { method = "GET" + , headers = [] + , url = "http://127.0.0.1:7878/user/profile" + , body = Http.emptyBody + , expect = Http.expectJson ProfileLoaded decodeProfile + , timeout = Nothing + , tracker = Nothing + } + + +viewStatus : Status -> String +viewStatus s = + case s of + Loading -> + "Loading" + + Loaded -> + "Ready!" + + NotLoaded -> + "Not loaded ..." + + +viewTransactions : List Transaction -> Html Msg +viewTransactions ts = + let + headings = + [ "Order ID", "Date", "Amount (₹)", "Payment Mode" ] + |> List.map (th [] << List.singleton << text) + + transactionRow t = + List.map (td [] << List.singleton) + [ text <| String.fromInt t.transactionId + , text t.orderDate + , text <| String.fromFloat t.amount + , text t.paymentMode + ] + in + div [] + [ div + [ css [ bigHeading, marginTop (px 20), marginBottom (px 12) ] ] + [ text "Transactions" ] + , Html.Styled.table + [ css + [ Css.width (pct 100) + , maxWidth (px 650) + ] + ] + ([ tr [ style "text-align" "right" ] headings + ] + ++ List.map (tr [ style "text-align" "right" ] << transactionRow) ts + ) + ] + + +profileField : String -> String -> Html Msg +profileField fieldName entry = + div [] + [ div + [ css + [ cardSecondaryText + , textTransform uppercase + , paddingBottom (px 3) + ] + ] + [ text fieldName ] + , div + [ css + [ cardPrimaryText + , paddingBottom (px 12) + ] + ] + [ text entry ] + ] + + +viewProfile : UserProfile -> Html Msg +viewProfile u = + div + [] + [ div + [ css [ bigHeading, marginTop (px 20), marginBottom (px 12) ] ] + [ text "Profile" ] + , profileField "name" u.username + , profileField "email" u.emailId + , profileField "contact number" u.phoneNumber + , profileField "address" <| Maybe.withDefault "No address provided" u.address + , profileField "Total Reviews" <| String.fromInt u.ratingsGiven + , hr [] [] + , viewTransactions u.transactions + ] + + +view : Model -> Html Msg +view model = + case model.status of + Loading -> + div [] [ text <| viewStatus Loading ] + + _ -> + div + [ css + [ margin auto + , marginTop (pct 5) + , Css.width (pct 40) + ] + ] + [ viewProfile model.profile ] diff --git a/frontend/src/Styles.elm b/frontend/src/Styles.elm index fbef6e1..4222024 100644 --- a/frontend/src/Styles.elm +++ b/frontend/src/Styles.elm @@ -71,6 +71,20 @@ furbyButton = ] +furbyRadio : String -> msg -> Html msg +furbyRadio value msg = + label + [] + [ input + [ type_ "radio" + , onClick msg + , name "radio" + ] + [] + , text value + ] + + furbySelect : List (Attribute msg) -> List (Html msg) -> Html msg furbySelect = styled select -- cgit v1.2.3