Update smoothies app, and remove requestTime cache-busting URL string in preparation for disabling caching for SSR DataSources.

This commit is contained in:
Dillon Kearns 2022-07-09 11:57:16 -07:00
parent 65bf2113ba
commit 5e89b67b0b
10 changed files with 232 additions and 237 deletions

View File

@ -125,7 +125,9 @@ view :
view maybeUrl sharedModel model app =
{ title = "Create Group"
, body =
[ Form.renderHtml { method = Form.Post, submitStrategy = Form.TransitionStrategy } app () postForm
[ postForm
|> Form.toDynamicTransition "create-group"
|> Form.renderHtml [] app ()
]
}
@ -152,43 +154,41 @@ postForm =
|> Validation.andMap visibility
)
(\formState name description visibility ->
( []
, [ Html.h2 [] [ Html.text "Create a group" ]
, fieldView formState "What's the name of your group?" name
, fieldView formState "Describe what your group is about (you can fill out this later)" description
, Html.div []
[ Form.FieldView.radio []
(\enum toRadio ->
Html.div []
[ Html.label []
[ toRadio []
, Html.text
(case enum of
UnlistedGroup ->
"I want this group to be unlisted (people can only find it if you link it to them)"
[ Html.h2 [] [ Html.text "Create a group" ]
, fieldView formState "What's the name of your group?" name
, fieldView formState "Describe what your group is about (you can fill out this later)" description
, Html.div []
[ Form.FieldView.radio []
(\enum toRadio ->
Html.div []
[ Html.label []
[ toRadio []
, Html.text
(case enum of
UnlistedGroup ->
"I want this group to be unlisted (people can only find it if you link it to them)"
PublicGroup ->
"I want this group to be publicly visible"
)
]
PublicGroup ->
"I want this group to be publicly visible"
)
]
)
visibility
, errorsForField formState visibility
]
, Html.button
[ Attr.disabled formState.isTransitioning
]
[ Html.text
(if formState.isTransitioning then
"Submitting..."
]
)
visibility
, errorsForField formState visibility
]
, Html.button
[ Attr.disabled formState.isTransitioning
]
[ Html.text
(if formState.isTransitioning then
"Submitting..."
else
"Submit"
)
]
]
)
else
"Submit"
)
]
]
)
|> Form.field "name"
(Field.text

View File

@ -128,7 +128,9 @@ view maybeUrl sharedModel model app =
{ title = "Dependent Form Example"
, body =
[ Html.h2 [] [ Html.text "Example" ]
, Form.renderHtml { method = Form.Post, submitStrategy = Form.TransitionStrategy } app () dependentParser
, dependentParser
|> Form.toDynamicTransition "dependent-example"
|> Form.renderHtml [] app ()
]
}
@ -184,37 +186,35 @@ dependentParser : Form.HtmlForm String PostAction data Msg
dependentParser =
Form.init
(\kind postForm_ ->
kind.value
kind
|> Validation.andThen postForm_
)
(\formState kind postForm_ ->
( []
, [ Form.FieldView.radio []
(\enum toRadio ->
Html.label []
[ toRadio []
, Html.text
(case enum of
Link ->
"Link"
[ Form.FieldView.radio []
(\enum toRadio ->
Html.label []
[ toRadio []
, Html.text
(case enum of
Link ->
"Link"
Post ->
"Post"
)
]
)
kind
, Html.div []
(case kind.parsed of
Just justKind ->
postForm_ justKind
Post ->
"Post"
)
]
)
kind
, Html.div []
(case kind.parsed of
Just justKind ->
postForm_ justKind
Nothing ->
[ Html.text "Please select a post kind" ]
)
, Html.button [] [ Html.text "Submit" ]
]
)
Nothing ->
[ Html.text "Please select a post kind" ]
)
, Html.button [] [ Html.text "Submit" ]
]
)
|> Form.field "kind"
(Field.select

View File

@ -106,14 +106,14 @@ head static =
data : RouteParams -> Request.Parser (DataSource (Response Data ErrorPage))
data routeParams =
Request.requestTime
Request.succeed ()
|> MySession.expectSessionDataOrRedirect (Session.get "userId")
(\userId requestTime session ->
(\userId () session ->
SelectionSet.map3 Data
Smoothie.selection
(User.selection userId)
(Cart.selection userId)
|> Request.Hasura.dataSource requestTime
|> Request.Hasura.dataSource
|> DataSource.map Response.render
|> DataSource.map (Tuple.pair session)
)
@ -129,10 +129,8 @@ signoutForm =
Form.init
(Validation.succeed Signout)
(\formState ->
( []
, [ Html.button [] [ Html.text "Sign out" ]
]
)
[ Html.button [] [ Html.text "Sign out" ]
]
)
|> Form.hiddenKind ( "kind", "signout" ) "Expected signout"
@ -142,22 +140,20 @@ setQuantityForm =
Form.init
(\uuid quantity ->
Validation.succeed SetQuantity
|> Validation.andMap (uuid.value |> Validation.map Uuid)
|> Validation.andMap quantity.value
|> Validation.andMap (uuid |> Validation.map Uuid)
|> Validation.andMap quantity
)
(\formState ->
( []
, [ Html.button []
[ Html.text <|
case formState.data of
( _, Decrement, _ ) ->
"-"
[ Html.button []
[ Html.text <|
case formState.data of
( _, Decrement, _ ) ->
"-"
( _, Increment, _ ) ->
"+"
]
]
)
( _, Increment, _ ) ->
"+"
]
]
)
|> Form.hiddenKind ( "kind", "setQuantity" ) "Expected setQuantity"
|> Form.hiddenField "itemId"
@ -193,11 +189,9 @@ oneOfParsers =
action : RouteParams -> Request.Parser (DataSource (Response ActionData ErrorPage))
action routeParams =
Request.map2 Tuple.pair
(Request.formDataWithoutServerValidation oneOfParsers)
Request.requestTime
Request.formDataWithoutServerValidation oneOfParsers
|> MySession.expectSessionDataOrRedirect (Session.get "userId" >> Maybe.map Uuid)
(\userId ( parsedAction, requestTime ) session ->
(\userId parsedAction session ->
case parsedAction of
Ok Signout ->
DataSource.succeed (Route.redirectTo Route.Login)
@ -205,7 +199,7 @@ action routeParams =
Ok (SetQuantity itemId quantity) ->
(Cart.addItemToCart quantity userId itemId
|> Request.Hasura.mutationDataSource requestTime
|> Request.Hasura.mutationDataSource
|> DataSource.map
(\_ -> Response.render {})
)
@ -273,7 +267,9 @@ view maybeUrl sharedModel model app =
]
, Html.p []
[ Html.text <| "Welcome " ++ app.data.user.name ++ "!"
, Form.renderHtml { method = Form.Post, submitStrategy = Form.FetcherStrategy } app () signoutForm
, signoutForm
|> Form.toDynamicFetcher "signout"
|> Form.renderHtml [] app ()
]
, cartView totals
, app.data.smoothies
@ -323,9 +319,14 @@ productView app cart item =
]
, Html.div
[]
[ Form.renderHtml { method = Form.Post, submitStrategy = Form.FetcherStrategy } app ( quantityInCart, Decrement, item ) setQuantityForm
[ setQuantityForm
-- TODO should this be toStaticFetcher (don't need the formId here because there is no client-side state, only hidden form fields
|> Form.toDynamicFetcher "increment-quantity"
|> Form.renderHtml [] app ( quantityInCart, Decrement, item )
, Html.p [] [ quantityInCart |> String.fromInt |> Html.text ]
, Form.renderHtml { method = Form.Post, submitStrategy = Form.FetcherStrategy } app ( quantityInCart, Increment, item ) setQuantityForm
, setQuantityForm
|> Form.toDynamicFetcher "decrement-quantity"
|> Form.renderHtml [] app ( quantityInCart, Increment, item )
]
, Html.div []
[ Html.img

View File

@ -96,16 +96,14 @@ form =
|> Validation.andMap username
)
(\info username ->
( []
, [ Html.label []
[ username |> fieldView info "Username"
]
, Html.button
[ Attr.type_ "submit"
]
[ Html.text "Login" ]
]
)
[ Html.label []
[ username |> fieldView info "Username"
]
, Html.button
[ Attr.type_ "submit"
]
[ Html.text "Login" ]
]
)
|> Form.field "username" (Field.text |> Field.required "Required")
@ -214,6 +212,8 @@ view maybeUrl sharedModel app =
"You aren't logged in yet."
)
]
, Form.renderHtml { submitStrategy = Form.TransitionStrategy, method = Form.Post } app () form
, form
|> Form.toDynamicTransition "login"
|> Form.renderHtml [] app ()
]
}

View File

@ -98,15 +98,13 @@ data routeParams =
action : RouteParams -> Request.Parser (DataSource (Response ActionData ErrorPage))
action routeParams =
Request.map2 Tuple.pair
(Request.formDataWithoutServerValidation [ form ])
Request.requestTime
Request.formDataWithoutServerValidation [ form ]
|> MySession.expectSessionDataOrRedirect (Session.get "userId" >> Maybe.map Uuid)
(\userId ( parsed, requestTime ) session ->
(\userId parsed session ->
case parsed of
Ok okParsed ->
Smoothies.create okParsed
|> Request.Hasura.mutationDataSource requestTime
|> Request.Hasura.mutationDataSource
|> DataSource.map
(\_ ->
( session
@ -142,13 +140,11 @@ form =
let
errors field =
info.errors
|> Dict.get field.name
|> Maybe.withDefault []
|> Form.errorsForField field
errorsView field =
(if field.status == Pages.FormState.Blurred then
field
|> errors
errors field
|> List.map (\error -> Html.li [] [ Html.text error ])
else
@ -162,20 +158,17 @@ form =
[ Html.text (label ++ " ")
, field |> FieldView.input []
]
-- TODO @@@@@@@
, 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 "Create" ]
]
)
[ fieldView "Name" name
, fieldView "Description" description
, fieldView "Price" price
, fieldView "Image" imageUrl
, Html.button [] [ Html.text "Create" ]
]
)
|> Form.field "name" (Field.text |> Field.required "Required")
|> Form.field "description"
@ -214,7 +207,15 @@ view maybeUrl sharedModel model app =
{ title = "New Item"
, body =
[ Html.h2 [] [ Html.text "New item" ]
, Form.renderHtml { method = Form.Post, submitStrategy = Form.TransitionStrategy } app app.data form
, form
|> Form.toDynamicTransition "form"
|> Form.renderHtml
[ Attr.style "display" "flex"
, Attr.style "flex-direction" "column"
, Attr.style "gap" "20px"
]
app
app.data
, pendingCreation
|> Debug.log "pendingCreation"
|> Result.toMaybe

View File

@ -119,7 +119,9 @@ view maybeUrl sharedModel model app =
{ title = "Dependent Form Example"
, body =
[ Html.h2 [] [ Html.text "Example" ]
, Form.renderHtml { method = Form.Post, submitStrategy = Form.TransitionStrategy } app () dependentParser
, dependentParser
|> Form.toDynamicTransition "form"
|> Form.renderHtml [] app ()
]
}
@ -137,7 +139,7 @@ dependentParser : Form.HtmlForm String { username : String, password : String }
dependentParser =
Form.init
(\username password passwordConfirmation ->
username.value
username
|> Validation.map Validated
|> Validation.andMap
(Validation.map2
@ -146,20 +148,18 @@ dependentParser =
Validation.succeed passwordValue
else
Validation.fail passwordConfirmation.name "Must match password"
Validation.fail passwordConfirmation "Must match password"
)
password.value
passwordConfirmation.value
password
passwordConfirmation
|> Validation.andThen identity
)
)
(\formState username password passwordConfirmation ->
( []
, [ fieldView formState "Username" username
, fieldView formState "Password" password
, fieldView formState "Password Confirmation" passwordConfirmation
]
)
[ fieldView formState "Username" username
, fieldView formState "Password" password
, fieldView formState "Password Confirmation" passwordConfirmation
]
)
|> Form.field "username" (Field.text |> Field.required "Required")
|> Form.field "password" (Field.text |> Field.password |> Field.required "Required")
@ -177,8 +177,7 @@ fieldView formState label field =
errorsView =
(if formState.submitAttempted || True then
formState.errors
|> Dict.get field.name
|> Maybe.withDefault []
|> Form.errorsForField field
|> List.map (\error -> Html.li [] [ Html.text error ])
else

View File

@ -97,11 +97,11 @@ type alias ActionData =
data : RouteParams -> Request.Parser (DataSource (Response Data ErrorPage))
data routeParams =
Request.requestTime
Request.succeed ()
|> MySession.expectSessionDataOrRedirect (Session.get "userId")
(\userId requestTime session ->
(\userId () session ->
User.selection userId
|> Request.Hasura.dataSource requestTime
|> Request.Hasura.dataSource
|> DataSource.map
(\user ->
user

View File

@ -98,11 +98,11 @@ type alias ActionData =
data : RouteParams -> Request.Parser (DataSource (Response Data ErrorPage))
data routeParams =
Request.requestTime
Request.succeed ()
|> MySession.expectSessionDataOrRedirect (Session.get "userId")
(\userId requestTime session ->
(\userId () session ->
User.selection userId
|> Request.Hasura.dataSource requestTime
|> Request.Hasura.dataSource
|> DataSource.map
(\user ->
user
@ -131,8 +131,7 @@ formParser =
let
errors field =
info.errors
|> Dict.get field.name
|> Maybe.withDefault []
|> Form.errorsForField field
errorsView field =
(if field.status == Pages.FormState.Blurred then
@ -145,29 +144,24 @@ formParser =
)
|> Html.ul [ Attr.style "color" "red" ]
in
( [ Attr.style "display" "flex"
, Attr.style "flex-direction" "column"
, Attr.style "gap" "20px"
]
, [ Html.div
[]
[ Html.label [] [ Html.text "Username ", username |> FieldView.input [] ]
, errorsView username
]
, Html.div []
[ Html.label [] [ Html.text "Name ", name |> FieldView.input [] ]
, errorsView name
]
, Html.button []
[ Html.text <|
if info.isTransitioning then
"Updating..."
[ Html.div
[]
[ Html.label [] [ Html.text "Username ", username |> FieldView.input [] ]
, errorsView username
]
, Html.div []
[ Html.label [] [ Html.text "Name ", name |> FieldView.input [] ]
, errorsView name
]
, Html.button []
[ Html.text <|
if info.isTransitioning then
"Updating..."
else
"Update"
]
]
)
else
"Update"
]
]
)
|> Form.field "username"
(Field.text
@ -203,18 +197,16 @@ validateUsername rawUsername =
action : RouteParams -> Request.Parser (DataSource (Response ActionData ErrorPage))
action routeParams =
Request.map2 Tuple.pair
(Request.formData [ formParser ])
Request.requestTime
Request.formData [ formParser ]
|> MySession.expectSessionDataOrRedirect (Session.get "userId" >> Maybe.map Uuid)
(\userId ( parsedActionData, requestTime ) session ->
(\userId parsedActionData session ->
parsedActionData
|> DataSource.andThen
(\parsedAction ->
case parsedAction |> Debug.log "parsedAction" of
Ok { name } ->
User.updateUser { userId = userId, name = name |> Debug.log "Updating name mutation" }
|> Request.Hasura.mutationDataSource requestTime
|> Request.Hasura.mutationDataSource
|> DataSource.map
(\_ ->
Route.redirectTo Route.Profile
@ -272,6 +264,14 @@ view maybeUrl sharedModel model app =
_ ->
Html.text "No errors"
, Form.renderHtml { method = Form.Post, submitStrategy = Form.TransitionStrategy } app app.data formParser
, formParser
|> Form.toDynamicTransition "edit-form"
|> Form.renderHtml
[ Attr.style "display" "flex"
, Attr.style "flex-direction" "column"
, Attr.style "gap" "20px"
]
app
app.data
]
}

View File

@ -106,11 +106,11 @@ type alias ActionData =
data : RouteParams -> Request.Parser (DataSource (Response Data ErrorPage))
data routeParams =
Request.requestTime
Request.succeed ()
|> MySession.expectSessionDataOrRedirect (Session.get "userId")
(\userId requestTime session ->
(\userId () session ->
((Smoothies.find (Uuid routeParams.smoothieId)
|> Request.Hasura.dataSource requestTime
|> Request.Hasura.dataSource
)
|> DataSource.map
(\maybeSmoothie ->
@ -125,15 +125,13 @@ data routeParams =
action : RouteParams -> Request.Parser (DataSource (Response ActionData ErrorPage))
action routeParams =
Request.map2 Tuple.pair
(Request.formDataWithoutServerValidation [ form, deleteForm ])
Request.requestTime
Request.formDataWithoutServerValidation [ form, deleteForm ]
|> MySession.expectSessionDataOrRedirect (Session.get "userId" >> Maybe.map Uuid)
(\userId ( parsed, requestTime ) session ->
(\userId parsed session ->
case parsed of
Ok (Edit okParsed) ->
Smoothies.update (Uuid routeParams.smoothieId) okParsed
|> Request.Hasura.mutationDataSource requestTime
|> Request.Hasura.mutationDataSource
|> DataSource.map
(\_ ->
( session
@ -143,7 +141,7 @@ action routeParams =
Ok Delete ->
Smoothies.delete (Uuid routeParams.smoothieId)
|> Request.Hasura.mutationDataSource requestTime
|> Request.Hasura.mutationDataSource
|> DataSource.map
(\_ ->
( session
@ -181,13 +179,11 @@ deleteForm =
Form.init
(Validation.succeed Delete)
(\formState ->
( []
, [ Html.button
[ Attr.style "color" "red"
]
[ Html.text "Delete" ]
]
)
[ Html.button
[ Attr.style "color" "red"
]
[ Html.text "Delete" ]
]
)
|> Form.hiddenKind ( "kind", "delete" ) "Required"
@ -207,8 +203,7 @@ form =
let
errors field =
formState.errors
|> Dict.get field.name
|> Maybe.withDefault []
|> Form.errorsForField field
errorsView field =
(if formState.submitAttempted || True then
@ -230,43 +225,38 @@ form =
, 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
, Form.FieldView.radio []
(\enum toRadio ->
Html.label []
[ toRadio []
, Html.text
(case enum of
Article ->
"📄 Article"
[ fieldView "Name" name
, fieldView "Description" description
, fieldView "Price" price
, fieldView "Image" imageUrl
, Form.FieldView.radio []
(\enum toRadio ->
Html.label []
[ toRadio []
, Html.text
(case enum of
Article ->
"📄 Article"
Book ->
"📕 Book"
Book ->
"📕 Book"
Video ->
"📺 Video"
)
]
Video ->
"📺 Video"
)
]
)
media
, Html.button []
[ Html.text
(if formState.isTransitioning then
"Updating..."
else
"Update"
)
media
, Html.button []
[ Html.text
(if formState.isTransitioning then
"Updating..."
else
"Update"
)
]
]
)
]
]
)
|> Form.field "name"
(Field.text
@ -343,11 +333,21 @@ view maybeUrl sharedModel model app =
{ title = "Update Item"
, body =
[ Html.h2 [] [ Html.text "Update item" ]
, Form.renderHtml { method = Form.Post, submitStrategy = Form.TransitionStrategy } app app.data form
, form
|> Form.toDynamicTransition "form"
|> Form.renderHtml
[ Attr.style "display" "flex"
, Attr.style "flex-direction" "column"
, Attr.style "gap" "20px"
]
app
app.data
, pendingCreation
|> Maybe.map pendingView
|> Maybe.withDefault (Html.div [] [])
, Form.renderHtml { method = Form.Post, submitStrategy = Form.TransitionStrategy } app app.data deleteForm
, deleteForm
|> Form.toDynamicTransition "delete-form"
|> Form.renderHtml [] app app.data
]
}

View File

@ -10,19 +10,13 @@ import Json.Encode as Encode
import Time
dataSource : Time.Posix -> SelectionSet value RootQuery -> DataSource value
dataSource requestTime selectionSet =
dataSource : SelectionSet value RootQuery -> DataSource value
dataSource selectionSet =
DataSource.Env.expect "SMOOTHIES_HASURA_SECRET"
|> DataSource.andThen
(\hasuraSecret ->
DataSource.Http.request
{ url =
hasuraUrl
-- for now, this timestamp invalidates the dev server cache
-- it would be helpful to have a way to mark a DataSource as uncached. Maybe only allow
-- from server-rendered pages?
++ "?time="
++ (requestTime |> Time.posixToMillis |> String.fromInt)
{ url = hasuraUrl
, method = "POST"
, headers = [ ( "x-hasura-admin-secret", hasuraSecret ) ]
, body =
@ -43,13 +37,13 @@ dataSource requestTime selectionSet =
)
mutationDataSource : Time.Posix -> SelectionSet value RootMutation -> DataSource value
mutationDataSource requestTime selectionSet =
mutationDataSource : SelectionSet value RootMutation -> DataSource value
mutationDataSource selectionSet =
DataSource.Env.expect "SMOOTHIES_HASURA_SECRET"
|> DataSource.andThen
(\hasuraSecret ->
DataSource.Http.request
{ url = hasuraUrl ++ "?time=" ++ (requestTime |> Time.posixToMillis |> String.fromInt)
{ url = hasuraUrl
, method = "POST"
, headers = [ ( "x-hasura-admin-secret", hasuraSecret ) ]
, body =