mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-12-22 11:21:31 +03:00
1153 lines
35 KiB
Elm
1153 lines
35 KiB
Elm
module Route.TailwindForm exposing (ActionData, Data, Model, Msg, route)
|
|
|
|
import Browser.Dom
|
|
import Css exposing (Color)
|
|
import Css.Global
|
|
import DataSource exposing (DataSource)
|
|
import Date exposing (Date)
|
|
import Dict exposing (Dict)
|
|
import Effect exposing (Effect)
|
|
import ErrorPage exposing (ErrorPage)
|
|
import Form
|
|
import Form.Field as Field
|
|
import Form.FieldView
|
|
import Form.Validation as Validation exposing (Validation)
|
|
import Form.Value
|
|
import Head
|
|
import Head.Seo as Seo
|
|
import Html.Styled as Html exposing (Html)
|
|
import Html.Styled.Attributes as Attr exposing (css)
|
|
import Http
|
|
import Icon
|
|
import Pages.FormState
|
|
import Pages.Msg
|
|
import Pages.PageUrl exposing (PageUrl)
|
|
import Pages.Url
|
|
import RouteBuilder exposing (StatefulRoute, StatelessRoute, StaticPayload)
|
|
import Server.Request as Request exposing (Parser)
|
|
import Server.Response as Response exposing (Response)
|
|
import Shared
|
|
import Tailwind.Breakpoints as Bp
|
|
import Tailwind.Utilities as Tw
|
|
import Task
|
|
import Time
|
|
import Url exposing (Url)
|
|
import View exposing (View)
|
|
|
|
|
|
type alias Model =
|
|
{}
|
|
|
|
|
|
type Msg
|
|
= MovedToTop
|
|
|
|
|
|
type alias RouteParams =
|
|
{}
|
|
|
|
|
|
type alias User =
|
|
{ first : String
|
|
, last : String
|
|
, username : String
|
|
, email : String
|
|
, birthDay : Date
|
|
, checkIn : Date
|
|
, checkOut : Date
|
|
, rating : Int
|
|
, password : ( String, String )
|
|
, notificationPreferences : NotificationPreferences
|
|
}
|
|
|
|
|
|
type alias NotificationPreferences =
|
|
{ comments : Bool
|
|
, candidates : Bool
|
|
, offers : Bool
|
|
, pushNotificationsSetting : PushNotificationsSetting
|
|
}
|
|
|
|
|
|
defaultUser : User
|
|
defaultUser =
|
|
{ first = "jane"
|
|
, last = "Doe"
|
|
, username = "janedoe"
|
|
, email = "janedoe@example.com"
|
|
, birthDay = Date.fromCalendarDate 1969 Time.Jul 20
|
|
, checkIn = Date.fromCalendarDate 2022 Time.Jan 11
|
|
, checkOut = Date.fromCalendarDate 2022 Time.Jan 12
|
|
, rating = 5
|
|
, password = ( "", "" )
|
|
, notificationPreferences =
|
|
{ comments = False
|
|
, candidates = False
|
|
, offers = False
|
|
, pushNotificationsSetting = PushNone
|
|
}
|
|
}
|
|
|
|
|
|
styleAttrs attrs =
|
|
List.map Attr.fromUnstyled attrs
|
|
|
|
|
|
usernameInput formState field =
|
|
Html.div []
|
|
[ Html.div
|
|
[ css
|
|
[ Bp.sm
|
|
[ Tw.grid
|
|
, Tw.grid_cols_3
|
|
, Tw.gap_4
|
|
, Tw.items_start
|
|
, Tw.border_t
|
|
, Tw.border_gray_200
|
|
, Tw.pt_5
|
|
]
|
|
]
|
|
]
|
|
[ Html.label
|
|
[ Attr.for "username"
|
|
, css
|
|
[ Tw.block
|
|
, Tw.text_sm
|
|
, Tw.font_medium
|
|
, Tw.text_gray_700
|
|
, Bp.sm
|
|
[ Tw.mt_px
|
|
, Tw.pt_2
|
|
]
|
|
]
|
|
]
|
|
[ Html.text "Username" ]
|
|
, Html.div
|
|
[ css
|
|
[ Tw.mt_1
|
|
, Bp.sm
|
|
[ Tw.mt_0
|
|
, Tw.col_span_2
|
|
]
|
|
]
|
|
]
|
|
[ Html.div
|
|
[ css
|
|
[ Tw.max_w_lg
|
|
, Tw.flex
|
|
, Tw.rounded_md
|
|
, Tw.shadow_sm
|
|
, Tw.relative
|
|
]
|
|
]
|
|
[ Html.span
|
|
[ css
|
|
[ Tw.inline_flex
|
|
, Tw.items_center
|
|
, Tw.px_3
|
|
, Tw.rounded_l_md
|
|
, Tw.border
|
|
, Tw.border_r_0
|
|
, Tw.border_gray_300
|
|
, Tw.bg_gray_50
|
|
, Tw.text_gray_500
|
|
, Bp.sm
|
|
[ Tw.text_sm
|
|
]
|
|
]
|
|
]
|
|
[ Html.text "workcation.com/" ]
|
|
, Form.FieldView.inputStyled
|
|
[ Attr.type_ "text"
|
|
, Attr.name "username"
|
|
, Attr.id "username"
|
|
, Attr.attribute "autocomplete" "username"
|
|
, css
|
|
[ Tw.flex_1
|
|
, Tw.block
|
|
, Tw.w_full
|
|
, Tw.min_w_0
|
|
, Tw.rounded_none
|
|
, Tw.rounded_r_md
|
|
, Tw.border_gray_300
|
|
, Css.focus
|
|
[ Tw.ring_indigo_500
|
|
, Tw.border_indigo_500
|
|
]
|
|
, Bp.sm
|
|
[ Tw.text_sm
|
|
]
|
|
]
|
|
]
|
|
field
|
|
, Html.div
|
|
[ css
|
|
[ Tw.absolute
|
|
, Tw.inset_y_0
|
|
, Tw.right_0
|
|
, Tw.pr_3
|
|
, Tw.flex
|
|
, Tw.items_center
|
|
, Tw.pointer_events_none
|
|
]
|
|
]
|
|
[ if formState.errors |> Form.errorsForField field |> List.isEmpty then
|
|
Html.text ""
|
|
|
|
else
|
|
Icon.error
|
|
]
|
|
]
|
|
]
|
|
]
|
|
, errorsView formState field
|
|
]
|
|
|
|
|
|
validateCapitalized : String -> ( Maybe String, List String )
|
|
validateCapitalized string =
|
|
if string |> String.toList |> List.head |> Maybe.withDefault 'a' |> Char.isUpper then
|
|
( Just string, [] )
|
|
|
|
else
|
|
( Nothing, [ "Needs to be capitalized" ] )
|
|
|
|
|
|
form : Form.StyledHtmlForm String User data msg
|
|
form =
|
|
Form.init
|
|
(\first last username email dob checkin checkout rating password passwordConfirmation comments candidates offers pushNotifications acceptTerms ->
|
|
{ combine =
|
|
Validation.succeed User
|
|
|> Validation.andMap first
|
|
|> Validation.andMap last
|
|
|> Validation.andMap username
|
|
|> Validation.andMap email
|
|
|> Validation.andMap dob
|
|
|> Validation.andMap checkin
|
|
|> Validation.andMap checkout
|
|
|> Validation.andMap rating
|
|
|> Validation.andMap
|
|
(Validation.map2
|
|
(\passwordValue passwordConfirmationValue ->
|
|
if passwordValue == passwordConfirmationValue then
|
|
Validation.succeed ( passwordValue, passwordConfirmationValue )
|
|
|
|
else
|
|
Validation.fail passwordConfirmation "Must match password"
|
|
)
|
|
password
|
|
passwordConfirmation
|
|
|> Validation.andThen identity
|
|
)
|
|
|> Validation.andMap
|
|
(Validation.succeed NotificationPreferences
|
|
|> Validation.andMap comments
|
|
|> Validation.andMap candidates
|
|
|> Validation.andMap offers
|
|
|> Validation.andMap pushNotifications
|
|
)
|
|
|> Validation.andThen
|
|
(\validated ->
|
|
if Date.toRataDie validated.checkIn >= Date.toRataDie validated.checkOut then
|
|
Validation.succeed validated |> Validation.withError checkin "Must be before checkout"
|
|
|
|
else
|
|
Validation.succeed validated
|
|
)
|
|
, view =
|
|
\formState ->
|
|
let
|
|
fieldView labelText field =
|
|
textInput formState labelText field
|
|
in
|
|
[ wrapSection
|
|
[ fieldView "First name" first
|
|
, fieldView "Last name" last
|
|
, usernameInput formState username
|
|
, fieldView "Email" email
|
|
, fieldView "Date of Birth" dob
|
|
, fieldView "Check-in" checkin
|
|
, fieldView "Check-out" checkout
|
|
, fieldView "Rating" rating
|
|
]
|
|
, fieldView "Password" password
|
|
, fieldView "Password Confirmation" passwordConfirmation
|
|
, wrapEmailSection
|
|
[ checkboxInput { name = "Comments", description = "Get notified when someones posts a comment on a posting." } formState comments
|
|
, checkboxInput { name = "Candidates", description = "Get notified when a candidate applies for a job." } formState candidates
|
|
, checkboxInput { name = "Offers", description = "Get notified when a candidate accepts or rejects an offer." } formState offers
|
|
]
|
|
, wrapNotificationsSections
|
|
[ wrapPushNotificationsSection formState
|
|
pushNotifications
|
|
[ Form.FieldView.radioStyled
|
|
[ css
|
|
[ Tw.mt_4
|
|
, Tw.space_y_4
|
|
]
|
|
]
|
|
(radioInput [])
|
|
pushNotifications
|
|
]
|
|
]
|
|
, checkboxInput { name = "Accept terms", description = "Please read the terms before proceeding." } formState acceptTerms
|
|
, Html.div
|
|
[ css
|
|
[ Tw.pt_5
|
|
]
|
|
]
|
|
[ Html.div
|
|
[ css
|
|
[ Tw.flex
|
|
, Tw.justify_end
|
|
]
|
|
]
|
|
[ cancelButton
|
|
, saveButton False []
|
|
]
|
|
]
|
|
]
|
|
}
|
|
)
|
|
|> Form.field "first"
|
|
(Field.text
|
|
|> Field.required "Required"
|
|
|> Field.withInitialValue (always defaultUser.first >> Form.Value.string)
|
|
|> Field.withClientValidation validateCapitalized
|
|
)
|
|
|> Form.field "last"
|
|
(Field.text
|
|
|> Field.required "Required"
|
|
|> Field.withInitialValue (always defaultUser.last >> Form.Value.string)
|
|
|> Field.withClientValidation validateCapitalized
|
|
)
|
|
|> Form.field "username"
|
|
(Field.text
|
|
|> Field.withInitialValue (always defaultUser.username >> Form.Value.string)
|
|
|> Field.required "Required"
|
|
|> Field.withClientValidation
|
|
(\username ->
|
|
( Just username
|
|
, if username |> String.contains "@" then
|
|
[ "Cannot contain @ symbol" ]
|
|
|
|
else
|
|
[]
|
|
)
|
|
)
|
|
|> Field.withClientValidation
|
|
(\username ->
|
|
( Just username
|
|
, if username |> String.contains "#" then
|
|
[ "Cannot contain # symbol" ]
|
|
|
|
else
|
|
[]
|
|
)
|
|
)
|
|
|> Field.withClientValidation
|
|
(\username ->
|
|
( Just username
|
|
, if (username |> String.length) < 3 then
|
|
[ "Must be at least 3 characters long" ]
|
|
|
|
else
|
|
[]
|
|
)
|
|
)
|
|
|> Field.withServerValidation
|
|
(\username ->
|
|
if username == "asdf" then
|
|
DataSource.succeed [ "username is taken" ]
|
|
|
|
else
|
|
DataSource.succeed []
|
|
)
|
|
)
|
|
|> Form.field "email"
|
|
(Field.text
|
|
|> Field.withInitialValue (always defaultUser.email >> Form.Value.string)
|
|
|> Field.email
|
|
|> Field.required "Required"
|
|
)
|
|
|> Form.field "dob"
|
|
(Field.date
|
|
{ invalid = \_ -> "Invalid date" }
|
|
|> Field.required "Required"
|
|
|> Field.withMin (Date.fromCalendarDate 1900 Time.Jan 1 |> Form.Value.date) "Choose a later date"
|
|
|> Field.withMax (Date.fromCalendarDate 2022 Time.Jan 1 |> Form.Value.date) "Choose an earlier date"
|
|
|> Field.withInitialValue (always defaultUser.birthDay >> Form.Value.date)
|
|
|> Field.withServerValidation
|
|
(\birthDate ->
|
|
if birthDate == Date.fromCalendarDate 1969 Time.Jul 20 then
|
|
DataSource.succeed [ "No way, that's when the moon landing happened!" ]
|
|
|
|
else
|
|
DataSource.succeed []
|
|
)
|
|
)
|
|
|> Form.field "checkin"
|
|
(Field.date
|
|
{ invalid = \_ -> "Invalid date" }
|
|
|> Field.required "Required"
|
|
|> Field.withInitialValue (always defaultUser.checkIn >> Form.Value.date)
|
|
)
|
|
|> Form.field "checkout"
|
|
(Field.date
|
|
{ invalid = \_ -> "Invalid date" }
|
|
|> Field.required "Required"
|
|
|> Field.withInitialValue (always defaultUser.checkOut >> Form.Value.date)
|
|
)
|
|
|> Form.field "rating"
|
|
(Field.int { invalid = \_ -> "Invalid number" }
|
|
|> Field.range
|
|
{ missing = "Required"
|
|
, invalid = \_ -> "Outside range"
|
|
, initial = \_ -> Form.Value.int 3
|
|
, min = Form.Value.int 1
|
|
, max = Form.Value.int 5
|
|
}
|
|
)
|
|
|> Form.field "password"
|
|
(Field.text |> Field.password |> Field.required "Required")
|
|
|> Form.field "password-confirmation"
|
|
(Field.text |> Field.password |> Field.required "Required")
|
|
|> Form.field "comments"
|
|
Field.checkbox
|
|
|> Form.field "candidates"
|
|
Field.checkbox
|
|
|> Form.field "offers"
|
|
Field.checkbox
|
|
|> Form.field
|
|
"push-notifications"
|
|
(Field.select
|
|
[ ( "PushAll", PushAll )
|
|
, ( "PushEmail", PushEmail )
|
|
, ( "PushNone", PushNone )
|
|
]
|
|
(\_ -> "Invalid option")
|
|
|> Field.required "Please select your notification preference."
|
|
)
|
|
|> Form.field "acceptTerms"
|
|
(Field.checkbox
|
|
|> Field.withClientValidation
|
|
(\checked ->
|
|
( Just ()
|
|
, if checked then
|
|
[]
|
|
|
|
else
|
|
[ "Please agree to terms to proceed." ]
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
type PushNotificationsSetting
|
|
= PushAll
|
|
| PushEmail
|
|
| PushNone
|
|
|
|
|
|
saveButton formHasErrors formAttrs =
|
|
Html.button
|
|
(styleAttrs formAttrs
|
|
++ [ css
|
|
[ Tw.ml_3
|
|
, Tw.inline_flex
|
|
, Tw.justify_center
|
|
, Tw.py_2
|
|
, Tw.px_4
|
|
, Tw.border
|
|
, Tw.border_transparent
|
|
, Tw.shadow_sm
|
|
, Tw.text_sm
|
|
, Tw.font_medium
|
|
, Tw.rounded_md
|
|
, Tw.text_white
|
|
, Tw.bg_indigo_600
|
|
, Css.focus
|
|
[ Tw.outline_none
|
|
, Tw.ring_2
|
|
, Tw.ring_offset_2
|
|
, Tw.ring_indigo_500
|
|
]
|
|
, --if formHasErrors then
|
|
-- Css.batch
|
|
-- [ Tw.text_gray_200
|
|
-- , Tw.bg_indigo_500
|
|
-- , Tw.cursor_default
|
|
-- ]
|
|
--
|
|
-- else
|
|
Css.hover
|
|
[ Tw.bg_indigo_700
|
|
]
|
|
]
|
|
]
|
|
)
|
|
[ Html.text "Save" ]
|
|
|
|
|
|
cancelButton : Html msg
|
|
cancelButton =
|
|
Html.button
|
|
[ Attr.type_ "button"
|
|
, css
|
|
[ Tw.bg_white
|
|
, Tw.py_2
|
|
, Tw.px_4
|
|
, Tw.border
|
|
, Tw.border_gray_300
|
|
, Tw.rounded_md
|
|
, Tw.shadow_sm
|
|
, Tw.text_sm
|
|
, Tw.font_medium
|
|
, Tw.text_gray_700
|
|
, Css.focus
|
|
[ Tw.outline_none
|
|
, Tw.ring_2
|
|
, Tw.ring_offset_2
|
|
, Tw.ring_indigo_500
|
|
]
|
|
, Css.hover
|
|
[ Tw.bg_gray_50
|
|
]
|
|
]
|
|
]
|
|
[ Html.text "Cancel" ]
|
|
|
|
|
|
route : StatefulRoute RouteParams Data ActionData Model Msg
|
|
route =
|
|
RouteBuilder.serverRender
|
|
{ head = head
|
|
, data = data
|
|
, action = action
|
|
}
|
|
|> RouteBuilder.buildWithLocalState
|
|
{ view = view
|
|
, update = update
|
|
, init = init
|
|
, subscriptions = \_ _ _ _ _ -> Sub.none
|
|
}
|
|
|
|
|
|
action : RouteParams -> Parser (DataSource (Response ActionData ErrorPage))
|
|
action routeParams =
|
|
Request.formData [ form ]
|
|
|> Request.map
|
|
(\toDataSource ->
|
|
toDataSource
|
|
|> DataSource.andThen
|
|
(\result ->
|
|
case result of
|
|
Ok user ->
|
|
DataSource.succeed
|
|
{ user = user
|
|
, flashMessage =
|
|
Ok ("Successfully updated profile for user " ++ user.first ++ " " ++ user.last)
|
|
, formResponse = Nothing
|
|
}
|
|
|> DataSource.map Response.render
|
|
|
|
Err error ->
|
|
DataSource.succeed
|
|
{ flashMessage = Err "Got errors"
|
|
, user = defaultUser
|
|
, formResponse = Just error
|
|
}
|
|
|> DataSource.map Response.render
|
|
)
|
|
)
|
|
|
|
|
|
update : a -> b -> c -> Msg -> Model -> ( Model, Effect Msg )
|
|
update _ _ _ msg model =
|
|
case msg of
|
|
MovedToTop ->
|
|
( model, Effect.none )
|
|
|
|
|
|
init _ _ static =
|
|
( {}, Effect.none )
|
|
|
|
|
|
type alias Data =
|
|
{}
|
|
|
|
|
|
type alias ActionData =
|
|
{ user : User
|
|
, flashMessage : Result String String
|
|
, formResponse : Maybe { fields : List ( String, String ), errors : Dict String (List String) }
|
|
}
|
|
|
|
|
|
data : RouteParams -> Parser (DataSource (Response Data ErrorPage))
|
|
data routeParams =
|
|
Request.oneOf
|
|
[ {}
|
|
|> Response.render
|
|
|> DataSource.succeed
|
|
|> Request.succeed
|
|
]
|
|
|
|
|
|
head :
|
|
StaticPayload Data ActionData RouteParams
|
|
-> List Head.Tag
|
|
head static =
|
|
Seo.summary
|
|
{ canonicalUrlOverride = Nothing
|
|
, siteName = "elm-pages"
|
|
, image =
|
|
{ url = Pages.Url.external "TODO"
|
|
, alt = "elm-pages logo"
|
|
, dimensions = Nothing
|
|
, mimeType = Nothing
|
|
}
|
|
, description = "TODO"
|
|
, locale = Nothing
|
|
, title = "TODO title" -- metadata.title -- TODO
|
|
}
|
|
|> Seo.website
|
|
|
|
|
|
wrapSection : List (Html msg) -> Html msg
|
|
wrapSection children =
|
|
Html.div []
|
|
[ Html.div []
|
|
[ Html.h3
|
|
[ css
|
|
[ Tw.text_lg
|
|
, Tw.leading_6
|
|
, Tw.font_medium
|
|
, Tw.text_gray_900
|
|
]
|
|
]
|
|
[ Html.text "Profile" ]
|
|
, Html.p
|
|
[ css
|
|
[ Tw.mt_1
|
|
, Tw.max_w_2xl
|
|
, Tw.text_sm
|
|
, Tw.text_gray_500
|
|
]
|
|
]
|
|
[ Html.text "This information will be displayed publicly so be careful what you share." ]
|
|
]
|
|
, Html.div
|
|
[ css
|
|
[ Tw.mt_6
|
|
, Tw.space_y_6
|
|
, Bp.sm
|
|
[ Tw.mt_5
|
|
, Tw.space_y_5
|
|
]
|
|
]
|
|
]
|
|
children
|
|
]
|
|
|
|
|
|
|
|
--formModelView formModel =
|
|
-- formModel
|
|
-- |> Debug.toString
|
|
-- |> Html.text
|
|
-- |> List.singleton
|
|
-- |> Html.pre
|
|
-- [ Attr.style "white-space" "break-spaces"
|
|
-- ]
|
|
|
|
|
|
view :
|
|
Maybe PageUrl
|
|
-> Shared.Model
|
|
-> Model
|
|
-> StaticPayload Data ActionData RouteParams
|
|
-> View (Pages.Msg.Msg Msg)
|
|
view maybeUrl sharedModel model static =
|
|
let
|
|
user : User
|
|
user =
|
|
static.action
|
|
|> Maybe.map .user
|
|
|> Maybe.withDefault defaultUser
|
|
in
|
|
{ title = "Form Example"
|
|
, body =
|
|
[ Html.div []
|
|
[ Css.Global.global Tw.globalStyles
|
|
, static.action
|
|
|> Maybe.map .flashMessage
|
|
|> Maybe.map flashView
|
|
|> Maybe.withDefault (Html.p [] [])
|
|
, Html.p []
|
|
[ -- TODO should this be calling a function in Form and passing in the form, like `Form.isSubmitting form`?
|
|
if static.transition /= Nothing then
|
|
Html.text "Submitting..."
|
|
|
|
else
|
|
Html.text ""
|
|
]
|
|
, Html.div
|
|
[ css
|
|
[ Tw.flex
|
|
, Tw.flex_col
|
|
, Tw.items_center
|
|
, Tw.mt_8
|
|
, Tw.border_gray_700
|
|
, Tw.rounded_lg
|
|
]
|
|
]
|
|
[ Html.text
|
|
(static.action
|
|
|> Maybe.andThen .formResponse
|
|
|> Debug.toString
|
|
)
|
|
, form
|
|
|> Form.toDynamicTransition "test"
|
|
|> Form.renderStyledHtml []
|
|
(static.action
|
|
|> Maybe.andThen .formResponse
|
|
)
|
|
static
|
|
()
|
|
]
|
|
]
|
|
|> Html.toUnstyled
|
|
]
|
|
}
|
|
|
|
|
|
successColor : Color
|
|
successColor =
|
|
Css.rgb 163 251 163
|
|
|
|
|
|
errorColor : Color
|
|
errorColor =
|
|
Css.rgb 251 163 163
|
|
|
|
|
|
flashView : Result String String -> Html msg
|
|
flashView message =
|
|
Html.p
|
|
[ css
|
|
[ Css.backgroundColor
|
|
(case message of
|
|
Ok _ ->
|
|
successColor
|
|
|
|
Err _ ->
|
|
errorColor
|
|
)
|
|
, Tw.p_4
|
|
]
|
|
]
|
|
[ Html.text <|
|
|
case message of
|
|
Ok okMessage ->
|
|
okMessage
|
|
|
|
Err error ->
|
|
"Something went wrong: " ++ error
|
|
]
|
|
|
|
|
|
textInput info labelText field =
|
|
Html.div
|
|
[ css
|
|
[ Bp.sm
|
|
[ Tw.grid
|
|
, Tw.grid_cols_3
|
|
, Tw.gap_4
|
|
, Tw.items_start
|
|
, Tw.border_t
|
|
, Tw.border_gray_200
|
|
, Tw.pt_5
|
|
]
|
|
]
|
|
]
|
|
[ --Html.text (Debug.toString submitStatus),
|
|
Html.span
|
|
[ css
|
|
[ Tw.font_bold
|
|
]
|
|
]
|
|
[-- @@@ TODO WIP need to add a way to get status from a Validation type field
|
|
--Html.text (Pages.FormState.fieldStatusToString field.status)
|
|
]
|
|
, Html.label
|
|
([ css
|
|
[ Tw.block
|
|
, Tw.text_sm
|
|
, Tw.font_medium
|
|
, Tw.text_gray_700
|
|
, Bp.sm
|
|
[ Tw.mt_px
|
|
, Tw.pt_2
|
|
]
|
|
]
|
|
]
|
|
-- TODO need for="..." attribute on label
|
|
--++ styleAttrs toLabel
|
|
)
|
|
[ Html.text labelText ]
|
|
, Html.div
|
|
[ css
|
|
[ Tw.mt_1
|
|
, Bp.sm
|
|
[ Tw.mt_0
|
|
, Tw.col_span_2
|
|
]
|
|
]
|
|
]
|
|
[ field
|
|
|> Form.FieldView.inputStyled
|
|
[ --Attr.attribute "autocomplete" "given-name",
|
|
css
|
|
[ Tw.max_w_lg
|
|
, Tw.block
|
|
, Tw.w_full
|
|
, Tw.shadow_sm
|
|
, Tw.border_gray_300
|
|
, Tw.rounded_md
|
|
, Css.focus
|
|
[ Tw.ring_indigo_500
|
|
, Tw.border_indigo_500
|
|
]
|
|
, Bp.sm
|
|
[ Tw.max_w_xs
|
|
, Tw.text_sm
|
|
]
|
|
]
|
|
]
|
|
]
|
|
, errorsView info field
|
|
]
|
|
|
|
|
|
errorsView : Form.Context String data -> Validation String parsed kind -> Html msg
|
|
errorsView formState field =
|
|
let
|
|
showErrors : Bool
|
|
showErrors =
|
|
--formState.submitAttempted
|
|
True
|
|
in
|
|
Html.ul
|
|
[ css
|
|
[ Tw.mt_2
|
|
, Tw.text_sm
|
|
, Tw.text_red_600
|
|
]
|
|
]
|
|
(if showErrors then
|
|
formState.errors
|
|
|> Form.errorsForField field
|
|
|> List.map
|
|
(\error ->
|
|
Html.li
|
|
[ css [ Tw.list_disc ]
|
|
]
|
|
[ Html.text error ]
|
|
)
|
|
|
|
else
|
|
[]
|
|
)
|
|
|
|
|
|
checkboxInput { name, description } info field =
|
|
Html.div
|
|
[ css
|
|
[ Tw.max_w_lg
|
|
, Tw.space_y_4
|
|
]
|
|
]
|
|
[ Html.label
|
|
[ css
|
|
[ Tw.relative
|
|
, Tw.flex
|
|
, Tw.items_start
|
|
]
|
|
]
|
|
[ Html.div
|
|
[ css
|
|
[ Tw.flex
|
|
, Tw.items_center
|
|
, Tw.h_5
|
|
]
|
|
]
|
|
[ field
|
|
|> Form.FieldView.inputStyled
|
|
[ css
|
|
[ Tw.h_4
|
|
, Tw.w_4
|
|
, Tw.text_indigo_600
|
|
, Tw.border_gray_300
|
|
, Tw.rounded
|
|
, Css.focus
|
|
[ Tw.ring_indigo_500
|
|
]
|
|
]
|
|
]
|
|
]
|
|
, Html.div
|
|
[ css
|
|
[ Tw.ml_3
|
|
, Tw.text_sm
|
|
]
|
|
]
|
|
[ Html.div
|
|
[ css
|
|
[ Tw.font_medium
|
|
, Tw.text_gray_700
|
|
]
|
|
]
|
|
[ Html.text name ]
|
|
, Html.p
|
|
[ css
|
|
[ Tw.text_gray_500
|
|
]
|
|
]
|
|
[ Html.text description ]
|
|
]
|
|
]
|
|
, errorsView info field
|
|
]
|
|
|
|
|
|
wrapNotificationsSections children =
|
|
Html.div
|
|
[ css
|
|
[ Tw.divide_y
|
|
, Tw.divide_gray_200
|
|
, Tw.pt_8
|
|
, Tw.space_y_6
|
|
, Bp.sm
|
|
[ Tw.pt_10
|
|
, Tw.space_y_5
|
|
]
|
|
]
|
|
]
|
|
[ Html.div []
|
|
[ Html.h3
|
|
[ css
|
|
[ Tw.text_lg
|
|
, Tw.leading_6
|
|
, Tw.font_medium
|
|
, Tw.text_gray_900
|
|
]
|
|
]
|
|
[ Html.text "Notifications" ]
|
|
, Html.p
|
|
[ css
|
|
[ Tw.mt_1
|
|
, Tw.max_w_2xl
|
|
, Tw.text_sm
|
|
, Tw.text_gray_500
|
|
]
|
|
]
|
|
[ Html.text "We'll always let you know about important changes, but you pick what else you want to hear about." ]
|
|
]
|
|
, Html.div
|
|
[ css
|
|
[ Tw.space_y_6
|
|
, Tw.divide_y
|
|
, Tw.divide_gray_200
|
|
, Bp.sm
|
|
[ Tw.space_y_5
|
|
]
|
|
]
|
|
]
|
|
children
|
|
]
|
|
|
|
|
|
wrapEmailSection children =
|
|
Html.div
|
|
[ css
|
|
[ Tw.pt_6
|
|
, Bp.sm
|
|
[ Tw.pt_5
|
|
]
|
|
]
|
|
]
|
|
[ Html.div
|
|
[ Attr.attribute "role" "group"
|
|
, Attr.attribute "aria-labelledby" "label-email"
|
|
]
|
|
[ Html.div
|
|
[ css
|
|
[ Bp.sm
|
|
[ Tw.grid
|
|
, Tw.grid_cols_3
|
|
, Tw.gap_4
|
|
, Tw.items_baseline
|
|
]
|
|
]
|
|
]
|
|
[ Html.div []
|
|
[ Html.div
|
|
[ css
|
|
[ Tw.text_base
|
|
, Tw.font_medium
|
|
, Tw.text_gray_900
|
|
, Bp.sm
|
|
[ Tw.text_sm
|
|
, Tw.text_gray_700
|
|
]
|
|
]
|
|
, Attr.id "label-email"
|
|
]
|
|
[ Html.text "By Email" ]
|
|
]
|
|
, Html.div
|
|
[ css
|
|
[ Tw.mt_4
|
|
, Bp.sm
|
|
[ Tw.mt_0
|
|
, Tw.col_span_2
|
|
]
|
|
]
|
|
]
|
|
[ Html.div
|
|
[ css
|
|
[ Tw.max_w_lg
|
|
, Tw.space_y_4
|
|
]
|
|
]
|
|
children
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
|
|
radioInput errors item toRadio =
|
|
Html.label
|
|
[ css
|
|
[ Tw.ml_3
|
|
, Tw.block
|
|
, Tw.text_sm
|
|
, Tw.font_medium
|
|
, Tw.text_gray_700
|
|
]
|
|
]
|
|
[ Html.div
|
|
[ css
|
|
[ Tw.flex
|
|
, Tw.items_center
|
|
]
|
|
]
|
|
[ toRadio
|
|
[ css
|
|
[ Tw.h_4
|
|
, Tw.w_4
|
|
, Tw.text_indigo_600
|
|
, Tw.border_gray_300
|
|
, Tw.mr_2
|
|
, Css.focus
|
|
[ Tw.ring_indigo_500
|
|
]
|
|
]
|
|
]
|
|
, (case item of
|
|
PushAll ->
|
|
"Everything"
|
|
|
|
PushEmail ->
|
|
"Same as email"
|
|
|
|
PushNone ->
|
|
"No push notifications"
|
|
)
|
|
|> Html.text
|
|
]
|
|
]
|
|
|
|
|
|
wrapPushNotificationsSection formState field children =
|
|
Html.div
|
|
[ css
|
|
[ Tw.pt_6
|
|
, Bp.sm
|
|
[ Tw.pt_5
|
|
]
|
|
]
|
|
]
|
|
[ Html.div
|
|
[ Attr.attribute "role" "group"
|
|
, Attr.attribute "aria-labelledby" "label-notifications"
|
|
]
|
|
[ Html.span
|
|
[ css
|
|
[ Tw.font_bold
|
|
]
|
|
]
|
|
[-- @@@ TODO WIP need to add a way to get status from a Validation type field
|
|
--Html.text (Pages.FormState.fieldStatusToString field.status)
|
|
]
|
|
, Html.div
|
|
[ css
|
|
[ Bp.sm
|
|
[ Tw.grid
|
|
, Tw.grid_cols_3
|
|
, Tw.gap_4
|
|
, Tw.items_baseline
|
|
]
|
|
]
|
|
]
|
|
[ Html.div []
|
|
[ Html.div
|
|
[ css
|
|
[ Tw.text_base
|
|
, Tw.font_medium
|
|
, Tw.text_gray_900
|
|
, Bp.sm
|
|
[ Tw.text_sm
|
|
, Tw.text_gray_700
|
|
]
|
|
]
|
|
, Attr.id "label-notifications"
|
|
]
|
|
[ Html.text "Push Notifications" ]
|
|
]
|
|
, Html.div
|
|
[ css
|
|
[ Bp.sm
|
|
[ Tw.col_span_2
|
|
]
|
|
]
|
|
]
|
|
[ Html.div
|
|
[ css
|
|
[ Tw.max_w_lg
|
|
]
|
|
]
|
|
[ Html.p
|
|
[ css
|
|
[ Tw.text_sm
|
|
, Tw.text_gray_500
|
|
]
|
|
]
|
|
[ Html.text "These are delivered via SMS to your mobile phone." ]
|
|
, Html.div
|
|
[ css
|
|
[ Tw.mt_4
|
|
, Tw.space_y_4
|
|
]
|
|
]
|
|
children
|
|
]
|
|
]
|
|
]
|
|
]
|
|
, errorsView formState field
|
|
]
|