all repos

onasty @ dfbb8b461a9edec6e94d3657fd006125040096c9

a one-time notes service

onasty/web/src/Pages/Auth.elm (view raw)

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
web: handle api errors (#138)..., 11 months ago
1
module Pages.Auth exposing (Model, Msg, Variant, page)
2
3
import Api
4
import Api.Auth
5
import Auth.User
6
import Data.Credentials exposing (Credentials)
7
import Effect exposing (Effect)
8
import Html exposing (Html)
9
import Html.Attributes as Attr
10
import Html.Events
11
import Layouts
12
import Page exposing (Page)
13
import Route exposing (Route)
14
import Route.Path
15
import Shared
16
import View exposing (View)
17
18
19
page : Shared.Model -> Route () -> Page Model Msg
20
page shared _ =
21
    Page.new
22
        { init = init shared
23
        , update = update
24
        , subscriptions = subscriptions
25
        , view = view
26
        }
27
        |> Page.withLayout (\_ -> Layouts.Header {})
28
29
30
31
-- INIT
32
33
34
type alias Model =
35
    { email : String
36
    , password : String
37
    , passwordAgain : String
38
    , isSubmittingForm : Bool
39
    , formVariant : Variant
40
    , error : Maybe Api.Error
41
    }
42
43
44
init : Shared.Model -> () -> ( Model, Effect Msg )
45
init shared _ =
46
    ( { isSubmittingForm = False
47
      , email = ""
48
      , password = ""
49
      , passwordAgain = ""
50
      , formVariant = SignIn
51
      , error = Nothing
52
      }
53
    , case shared.user of
54
        Auth.User.SignedIn _ ->
55
            Effect.pushRoutePath Route.Path.Home_
56
57
        Auth.User.NotSignedIn ->
58
            Effect.none
59
60
        Auth.User.RefreshingTokens ->
61
            Effect.none
62
    )
63
64
65
66
-- UPDATE
67
68
69
type Msg
70
    = UserUpdatedInput Field String
71
    | UserChangedFormVariant Variant
72
    | UserClickedSubmit
73
    | ApiSignInResponded (Result Api.Error Credentials)
74
    | ApiSignUpResponded (Result Api.Error ())
75
76
77
type Field
78
    = Email
79
    | Password
80
    | PasswordAgain
81
82
83
type Variant
84
    = SignIn
85
    | SignUp
86
87
88
update : Msg -> Model -> ( Model, Effect Msg )
89
update msg model =
90
    case msg of
91
        UserClickedSubmit ->
92
            ( { model | isSubmittingForm = True }
93
            , case model.formVariant of
94
                SignIn ->
95
                    Api.Auth.signin
96
                        { onResponse = ApiSignInResponded
97
                        , email = model.email
98
                        , password = model.password
99
                        }
100
101
                SignUp ->
102
                    Api.Auth.signup
103
                        { onResponse = ApiSignUpResponded
104
                        , email = model.email
105
                        , password = model.password
106
                        }
107
            )
108
109
        UserChangedFormVariant variant ->
110
            ( { model | formVariant = variant }, Effect.none )
111
112
        UserUpdatedInput Email email ->
113
            ( { model | email = email }, Effect.none )
114
115
        UserUpdatedInput Password password ->
116
            ( { model | password = password }, Effect.none )
117
118
        UserUpdatedInput PasswordAgain passwordAgain ->
119
            ( { model | passwordAgain = passwordAgain }, Effect.none )
120
121
        ApiSignInResponded (Ok credentials) ->
122
            ( { model | isSubmittingForm = False }
123
            , Effect.signin credentials
124
            )
125
126
        ApiSignInResponded (Err error) ->
127
            ( { model | isSubmittingForm = False, error = Just error }, Effect.none )
128
129
        ApiSignUpResponded (Ok ()) ->
130
            -- TODO: show banner with that they have to activate account
131
            ( { model | isSubmittingForm = False }, Effect.none )
132
133
        ApiSignUpResponded (Err error) ->
134
            ( { model | isSubmittingForm = False, error = Just error }, Effect.none )
135
136
137
138
-- SUBSCRIPTIONS
139
140
141
subscriptions : Model -> Sub Msg
142
subscriptions _ =
143
    Sub.none
144
145
146
147
-- VIEW
148
149
150
view : Model -> View Msg
151
view model =
152
    { title = "Authentication"
153
    , body =
154
        [ Html.div [ Attr.class "center" ]
155
            -- TODO: add oauth buttons
156
            [ viewError model.error
157
            , viewChangeVariant model.formVariant
158
            , viewForm model
159
            , viewForgotPassword
160
            ]
161
        ]
162
    }
163
164
165
viewChangeVariant : Variant -> Html Msg
166
viewChangeVariant variant =
167
    Html.div [ Attr.class "mb1" ]
168
        [ Html.button
169
            [ Attr.disabled (variant == SignIn)
170
            , Html.Events.onClick (UserChangedFormVariant SignIn)
171
            ]
172
            [ Html.text "Sign In" ]
173
        , Html.button
174
            [ Attr.disabled (variant == SignUp)
175
            , Html.Events.onClick (UserChangedFormVariant SignUp)
176
            ]
177
            [ Html.text "Sign Up" ]
178
        ]
179
180
181
viewForm : Model -> Html Msg
182
viewForm model =
183
    Html.form [ Html.Events.onSubmit UserClickedSubmit ]
184
        (case model.formVariant of
185
            SignIn ->
186
                [ viewFormInput { field = Email, value = model.email }
187
                , viewFormInput { field = Password, value = model.password }
188
                , viewSubmitButton model
189
                ]
190
191
            SignUp ->
192
                [ viewFormInput { field = Email, value = model.email }
193
                , viewFormInput { field = Password, value = model.password }
194
                , viewFormInput { field = PasswordAgain, value = model.passwordAgain }
195
                , viewSubmitButton model
196
                ]
197
        )
198
199
200
viewError : Maybe Api.Error -> Html Msg
201
viewError maybeError =
202
    case maybeError of
203
        Just error ->
204
            Html.div [ Attr.class "box bad" ]
205
                [ Html.strong [ Attr.class "block titlebar" ] [ Html.text "Error" ]
206
                , Html.text (Api.errorMessage error)
207
                ]
208
209
        Nothing ->
210
            Html.text ""
211
212
213
viewFormInput : { field : Field, value : String } -> Html Msg
214
viewFormInput opts =
215
    Html.div [ Attr.class "mb1" ]
216
        [ Html.label [] [ Html.text (fromFieldToLabel opts.field) ]
217
        , Html.div []
218
            [ Html.input
219
                [ Attr.type_ (fromFieldToInputType opts.field)
220
                , Attr.value opts.value
221
                , Html.Events.onInput (UserUpdatedInput opts.field)
222
                ]
223
                []
224
            ]
225
        ]
226
227
228
viewForgotPassword : Html Msg
229
viewForgotPassword =
230
    Html.div []
231
        [ Html.a
232
            [ Attr.href "/forgot-password" ]
233
            [ Html.text "Forgot password?" ]
234
        ]
235
236
237
viewSubmitButton : Model -> Html Msg
238
viewSubmitButton model =
239
    Html.div [ Attr.class "mb1" ]
240
        [ Html.button
241
            [ Attr.disabled (isFormDisabled model) ]
242
            [ Html.text (fromVariantToLabel model.formVariant) ]
243
        ]
244
245
246
isFormDisabled : Model -> Bool
247
isFormDisabled model =
248
    case model.formVariant of
249
        SignIn ->
250
            model.isSubmittingForm
251
                || String.isEmpty model.email
252
                || String.isEmpty model.password
253
254
        SignUp ->
255
            model.isSubmittingForm
256
                || String.isEmpty model.email
257
                || String.isEmpty model.password
258
                || String.isEmpty model.passwordAgain
259
                || (model.password /= model.passwordAgain)
260
261
262
fromVariantToLabel : Variant -> String
263
fromVariantToLabel variant =
264
    case variant of
265
        SignIn ->
266
            "Sign In"
267
268
        SignUp ->
269
            "Sign Up"
270
271
272
fromFieldToLabel : Field -> String
273
fromFieldToLabel field =
274
    case field of
275
        Email ->
276
            "Email address"
277
278
        Password ->
279
            "Password"
280
281
        PasswordAgain ->
282
            "Password again"
283
284
285
fromFieldToInputType : Field -> String
286
fromFieldToInputType field =
287
    case field of
288
        Email ->
289
            "email"
290
291
        Password ->
292
            "password"
293
294
        PasswordAgain ->
295
            "password"