From d2b794bfa5ec30003837897c996ae4d4721b0469 Mon Sep 17 00:00:00 2001 From: Dillon Kearns Date: Mon, 13 Jun 2022 12:47:18 -0700 Subject: [PATCH] Add edit screen. --- examples/smoothies/app/Route/Index.elm | 1 + .../smoothies/app/Route/SmoothieId_/Edit.elm | 262 ++++++++++++++++++ examples/smoothies/src/Data/Smoothies.elm | 30 +- 3 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 examples/smoothies/app/Route/SmoothieId_/Edit.elm diff --git a/examples/smoothies/app/Route/Index.elm b/examples/smoothies/app/Route/Index.elm index 90c56420..ed81811b 100644 --- a/examples/smoothies/app/Route/Index.elm +++ b/examples/smoothies/app/Route/Index.elm @@ -263,6 +263,7 @@ productView cart item = Html.li [ Attr.class "item" ] [ Html.div [] [ Html.h3 [] [ Html.text item.name ] + , Route.SmoothieId___Edit { smoothieId = uuidToString item.id } |> Route.link [] [ Html.text "Edit" ] , Html.p [] [ Html.text item.description ] , Html.p [] [ "$" ++ String.fromInt item.price |> Html.text ] ] diff --git a/examples/smoothies/app/Route/SmoothieId_/Edit.elm b/examples/smoothies/app/Route/SmoothieId_/Edit.elm new file mode 100644 index 00000000..130d5667 --- /dev/null +++ b/examples/smoothies/app/Route/SmoothieId_/Edit.elm @@ -0,0 +1,262 @@ +module Route.SmoothieId_.Edit exposing (ActionData, Data, Model, Msg, route) + +import Api.Scalar exposing (Uuid(..)) +import Data.Smoothies as Smoothies exposing (Smoothie) +import DataSource exposing (DataSource) +import Dict +import Dict.Extra +import Effect exposing (Effect) +import ErrorPage exposing (ErrorPage) +import Form.Value +import Graphql.SelectionSet as SelectionSet +import Head +import Head.Seo as Seo +import Html exposing (Html) +import Html.Attributes as Attr +import MySession +import Pages.Field as Field +import Pages.Form +import Pages.FormParser as FormParser +import Pages.Msg +import Pages.PageUrl exposing (PageUrl) +import Pages.Url +import Path exposing (Path) +import Request.Hasura +import Route +import RouteBuilder exposing (StatefulRoute, StatelessRoute, StaticPayload) +import Server.Request as Request +import Server.Response as Response exposing (Response) +import Server.Session as Session +import Shared +import View exposing (View) + + +type alias Model = + {} + + +type Msg + = NoOp + + +type alias RouteParams = + { smoothieId : String } + + +type alias NewItem = + { name : String + , description : String + , price : Int + , imageUrl : String + } + + +route : StatefulRoute RouteParams Data ActionData Model Msg +route = + RouteBuilder.serverRender + { head = head + , data = data + , action = action + } + |> RouteBuilder.buildWithLocalState + { view = view + , update = update + , subscriptions = subscriptions + , init = init + } + + +init : + Maybe PageUrl + -> Shared.Model + -> StaticPayload Data ActionData RouteParams + -> ( Model, Effect Msg ) +init maybePageUrl sharedModel static = + ( {}, Effect.none ) + + +update : + PageUrl + -> Shared.Model + -> StaticPayload Data ActionData RouteParams + -> Msg + -> Model + -> ( Model, Effect Msg ) +update pageUrl sharedModel static msg model = + case msg of + NoOp -> + ( model, Effect.none ) + + +subscriptions : Maybe PageUrl -> RouteParams -> Path -> Shared.Model -> Model -> Sub Msg +subscriptions maybePageUrl routeParams path sharedModel model = + Sub.none + + +pages : DataSource (List RouteParams) +pages = + DataSource.succeed [] + + +type alias Data = + { smoothie : Smoothie + } + + +type alias ActionData = + {} + + +data : RouteParams -> Request.Parser (DataSource (Response Data ErrorPage)) +data routeParams = + Request.requestTime + |> MySession.expectSessionDataOrRedirect (Session.get "userId") + (\userId requestTime session -> + ((Smoothies.find (Uuid routeParams.smoothieId) + |> Request.Hasura.dataSource requestTime + ) + |> DataSource.map + (\maybeSmoothie -> + maybeSmoothie + |> Maybe.map (Data >> Response.render) + |> Maybe.withDefault (Response.errorPage ErrorPage.NotFound) + ) + ) + |> DataSource.map (Tuple.pair session) + ) + + +action : RouteParams -> Request.Parser (DataSource (Response ActionData ErrorPage)) +action routeParams = + Request.skip "No action." + + +head : StaticPayload Data ActionData RouteParams -> List Head.Tag +head static = + [] + + +form : FormParser.HtmlForm String { name : String, description : String, price : Int, imageUrl : String } Data Msg +form = + FormParser.andThenNew + (\name description price imageUrl -> + FormParser.ok + { name = name.value + , description = description.value + , price = price.value + , imageUrl = imageUrl.value + } + ) + (\info name description price imageUrl -> + let + errors field = + info.errors + |> Dict.get field.name + |> Maybe.withDefault [] + + errorsView field = + (if field.status == Pages.Form.Blurred || True then + field + |> errors + |> List.map (\error -> Html.li [] [ Html.text error ]) + + else + [] + ) + |> Html.ul [ Attr.style "color" "red" ] + + fieldView label field = + Html.div [] + [ Html.label [] + [ Html.text (label ++ " ") + , field |> FormParser.input [] + ] + , errorsView field + ] + in + ( [ Attr.style "display" "flex" + , Attr.style "flex-direction" "column" + , Attr.style "gap" "20px" + ] + , [ fieldView "Name" name + , fieldView "Description" description + , fieldView "Price" price + , fieldView "Image" imageUrl + , Html.button [] [ Html.text "Update" ] + ] + ) + ) + |> FormParser.field "name" + (Field.text + |> Field.required "Required" + |> Field.withInitialValue (\{ smoothie } -> Form.Value.string smoothie.name) + ) + |> FormParser.field "description" + (Field.text + |> Field.required "Required" + |> Field.withInitialValue (\{ smoothie } -> Form.Value.string smoothie.description) + ) + |> FormParser.field "price" + (Field.int { invalid = \_ -> "Invalid int" } + |> Field.required "Required" + |> Field.withInitialValue (\{ smoothie } -> Form.Value.int smoothie.price) + ) + |> FormParser.field "imageUrl" + (Field.text + |> Field.required "Required" + |> Field.withInitialValue (\{ smoothie } -> Form.Value.string smoothie.unsplashImage) + ) + + +parseIgnoreErrors : ( Maybe parsed, FormParser.FieldErrors error ) -> Result (FormParser.FieldErrors error) parsed +parseIgnoreErrors ( maybeParsed, fieldErrors ) = + case maybeParsed of + Just parsed -> + Ok parsed + + _ -> + Err fieldErrors + + +view : + Maybe PageUrl + -> Shared.Model + -> Model + -> StaticPayload Data ActionData RouteParams + -> View (Pages.Msg.Msg Msg) +view maybeUrl sharedModel model app = + let + pendingCreation : Result (FormParser.FieldErrors String) NewItem + pendingCreation = + form + |> FormParser.runNew + (app.pageFormState |> Dict.get "test" |> Maybe.withDefault Dict.empty) + |> .result + |> parseIgnoreErrors + in + { title = "Update Item" + , body = + [ Html.h2 [] [ Html.text "Update item" ] + , FormParser.renderHtml app form + , pendingCreation + |> Result.toMaybe + |> Maybe.map pendingView + |> Maybe.withDefault (Html.div [] []) + ] + } + + +pendingView : NewItem -> Html (Pages.Msg.Msg Msg) +pendingView item = + Html.div [ Attr.class "item" ] + [ Html.div [] + [ Html.h3 [] [ Html.text item.name ] + , Html.p [] [ Html.text item.description ] + , Html.p [] [ "$" ++ String.fromInt item.price |> Html.text ] + ] + , Html.div [] + [ Html.img + [ Attr.src (item.imageUrl ++ "?ixlib=rb-1.2.1&raw_url=true&q=80&fm=jpg&crop=entropy&cs=tinysrgb&auto=format&fit=crop&w=600&h=903") ] + [] + ] + ] diff --git a/examples/smoothies/src/Data/Smoothies.elm b/examples/smoothies/src/Data/Smoothies.elm index cbb9b426..7309d378 100644 --- a/examples/smoothies/src/Data/Smoothies.elm +++ b/examples/smoothies/src/Data/Smoothies.elm @@ -1,7 +1,8 @@ -module Data.Smoothies exposing (Smoothie, create, selection) +module Data.Smoothies exposing (Smoothie, create, find, selection) import Api.InputObject import Api.Mutation +import Api.Object import Api.Object.Products import Api.Query import Api.Scalar exposing (Uuid(..)) @@ -21,14 +22,25 @@ type alias Smoothie = selection : SelectionSet (List Smoothie) RootQuery selection = - Api.Query.products identity - (SelectionSet.map5 Smoothie - Api.Object.Products.name - Api.Object.Products.id - Api.Object.Products.description - Api.Object.Products.price - Api.Object.Products.unsplash_image_id - ) + Api.Query.products identity singleSelection + + +singleSelection : SelectionSet Smoothie Api.Object.Products +singleSelection = + SelectionSet.map5 Smoothie + Api.Object.Products.name + Api.Object.Products.id + Api.Object.Products.description + Api.Object.Products.price + Api.Object.Products.unsplash_image_id + + +find : Uuid -> SelectionSet (Maybe Smoothie) RootQuery +find id = + Api.Query.products_by_pk + { id = id + } + singleSelection create :