all repos

onasty @ 9c8b9eae5400bed303e3892d640786b0cb0b3554

a one-time notes service

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

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
web: prompt user to check email to verifiy after signing up (#139)..., 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
    , gotSignedUp : Bool
41
    , apiError : Maybe Api.Error
42
    }
43
44
45
init : Shared.Model -> () -> ( Model, Effect Msg )
46
init shared _ =
47
    ( { isSubmittingForm = False
48
      , email = ""
49
      , password = ""
50
      , passwordAgain = ""
51
      , formVariant = SignIn
52
      , apiError = Nothing
53
      , gotSignedUp = False
54
      }
55
    , case shared.user of
56
        Auth.User.SignedIn _ ->
57
            Effect.pushRoutePath Route.Path.Home_
58
59
        _ ->
60
            Effect.none
61
    )
62
63
64
65
-- UPDATE
66
67
68
type Msg
69
    = UserUpdatedInput Field String
70
    | UserChangedFormVariant Variant
71
    | UserClickedSubmit
72
    | UserClickedResendActivationEmail
73
    | ApiSignInResponded (Result Api.Error Credentials)
74
    | ApiSignUpResponded (Result Api.Error ())
75
    | ApiResendVerificationEmail (Result Api.Error ())
76
77
78
type Field
79
    = Email
80
    | Password
81
    | PasswordAgain
82
83
84
type Variant
85
    = SignIn
86
    | SignUp
87
88
89
update : Msg -> Model -> ( Model, Effect Msg )
90
update msg model =
91
    case msg of
92
        UserClickedSubmit ->
93
            ( { model | isSubmittingForm = True, apiError = Nothing }
94
            , case model.formVariant of
95
                SignIn ->
96
                    Api.Auth.signin
97
                        { onResponse = ApiSignInResponded
98
                        , email = model.email
99
                        , password = model.password
100
                        }
101
102
                SignUp ->
103
                    Api.Auth.signup
104
                        { onResponse = ApiSignUpResponded
105
                        , email = model.email
106
                        , password = model.password
107
                        }
108
            )
109
110
        UserClickedResendActivationEmail ->
111
            ( model
112
            , Api.Auth.resendVerificationEmail
113
                { onResponse = ApiResendVerificationEmail
114
                , email = model.email
115
                , password = model.password
116
                }
117
            )
118
119
        UserChangedFormVariant variant ->
120
            ( { model | formVariant = variant }, Effect.none )
121
122
        UserUpdatedInput Email email ->
123
            ( { model | email = email }, Effect.none )
124
125
        UserUpdatedInput Password password ->
126
            ( { model | password = password }, Effect.none )
127
128
        UserUpdatedInput PasswordAgain passwordAgain ->
129
            ( { model | passwordAgain = passwordAgain }, Effect.none )
130
131
        ApiSignInResponded (Ok credentials) ->
132
            ( { model | isSubmittingForm = False }, Effect.signin credentials )
133
134
        ApiSignInResponded (Err error) ->
135
            ( { model | isSubmittingForm = False, apiError = Just error }, Effect.none )
136
137
        ApiSignUpResponded (Ok ()) ->
138
            ( { model | isSubmittingForm = False, gotSignedUp = True }, Effect.none )
139
140
        ApiSignUpResponded (Err error) ->
141
            ( { model | isSubmittingForm = False, apiError = Just error }, Effect.none )
142
143
        ApiResendVerificationEmail (Ok ()) ->
144
            ( { model | apiError = Nothing }, Effect.none )
145
146
        ApiResendVerificationEmail (Err err) ->
147
            ( { model | apiError = Just err }, Effect.none )
148
149
150
151
-- SUBSCRIPTIONS
152
153
154
subscriptions : Model -> Sub Msg
155
subscriptions _ =
156
    Sub.none
157
158
159
160
-- VIEW
161
162
163
view : Model -> View Msg
164
view model =
165
    { title = "Authentication"
166
    , body =
167
        [ Html.div [ Attr.class "center" ]
168
            -- TODO: add oauth buttons
169
            [ viewBanner model.apiError model.gotSignedUp
170
            , viewChangeVariant model.formVariant
171
            , viewForm model
172
            , viewForgotPassword
173
            ]
174
        ]
175
    }
176
177
178
viewChangeVariant : Variant -> Html Msg
179
viewChangeVariant variant =
180
    Html.div [ Attr.class "mb1" ]
181
        [ Html.button
182
            [ Attr.disabled (variant == SignIn)
183
            , Html.Events.onClick (UserChangedFormVariant SignIn)
184
            ]
185
            [ Html.text "Sign In" ]
186
        , Html.button
187
            [ Attr.disabled (variant == SignUp)
188
            , Html.Events.onClick (UserChangedFormVariant SignUp)
189
            ]
190
            [ Html.text "Sign Up" ]
191
        ]
192
193
194
viewForm : Model -> Html Msg
195
viewForm model =
196
    Html.form [ Html.Events.onSubmit UserClickedSubmit ]
197
        (case model.formVariant of
198
            SignIn ->
199
                [ viewFormInput { field = Email, value = model.email }
200
                , viewFormInput { field = Password, value = model.password }
201
                , viewSubmitButton model
202
                ]
203
204
            SignUp ->
205
                [ viewFormInput { field = Email, value = model.email }
206
                , viewFormInput { field = Password, value = model.password }
207
                , viewFormInput { field = PasswordAgain, value = model.passwordAgain }
208
                , viewSubmitButton model
209
                ]
210
        )
211
212
213
viewBanner : Maybe Api.Error -> Bool -> Html Msg
214
viewBanner maybeError gotSignedUp =
215
    case ( maybeError, gotSignedUp ) of
216
        ( Just error, _ ) ->
217
            Html.div [ Attr.class "box bad" ]
218
                [ Html.strong [ Attr.class "block titlebar" ] [ Html.text "Error" ]
219
                , Html.text (Api.errorMessage error)
220
                ]
221
222
        ( Nothing, True ) ->
223
            Html.div [ Attr.class "box ok" ]
224
                [ Html.strong [ Attr.class "block titlebar" ] [ Html.text "Successfully signed up!" ]
225
                , Html.p []
226
                    [ Html.text "Please check your email to activate your account."
227
                    , Html.text " If you don't see the email, please check your spam folder."
228
                    , Html.button [ Html.Events.onClick UserClickedResendActivationEmail ]
229
                        [ Html.text "Resend activation email"
230
                        ]
231
                    ]
232
                ]
233
234
        ( Nothing, False ) ->
235
            Html.text ""
236
237
238
viewFormInput : { field : Field, value : String } -> Html Msg
239
viewFormInput opts =
240
    Html.div [ Attr.class "mb1" ]
241
        [ Html.label [] [ Html.text (fromFieldToLabel opts.field) ]
242
        , Html.div []
243
            [ Html.input
244
                [ Attr.type_ (fromFieldToInputType opts.field)
245
                , Attr.value opts.value
246
                , Html.Events.onInput (UserUpdatedInput opts.field)
247
                ]
248
                []
249
            ]
250
        ]
251
252
253
viewForgotPassword : Html Msg
254
viewForgotPassword =
255
    Html.div []
256
        [ Html.a
257
            [ Attr.href "/forgot-password" ]
258
            [ Html.text "Forgot password?" ]
259
        ]
260
261
262
viewSubmitButton : Model -> Html Msg
263
viewSubmitButton model =
264
    Html.div [ Attr.class "mb1" ]
265
        [ Html.button
266
            [ Attr.disabled (isFormDisabled model) ]
267
            [ Html.text (fromVariantToLabel model.formVariant) ]
268
        ]
269
270
271
isFormDisabled : Model -> Bool
272
isFormDisabled model =
273
    case model.formVariant of
274
        SignIn ->
275
            model.isSubmittingForm
276
                || String.isEmpty model.email
277
                || String.isEmpty model.password
278
279
        SignUp ->
280
            model.isSubmittingForm
281
                || String.isEmpty model.email
282
                || String.isEmpty model.password
283
                || String.isEmpty model.passwordAgain
284
                || (model.password /= model.passwordAgain)
285
286
287
fromVariantToLabel : Variant -> String
288
fromVariantToLabel variant =
289
    case variant of
290
        SignIn ->
291
            "Sign In"
292
293
        SignUp ->
294
            "Sign Up"
295
296
297
fromFieldToLabel : Field -> String
298
fromFieldToLabel field =
299
    case field of
300
        Email ->
301
            "Email address"
302
303
        Password ->
304
            "Password"
305
306
        PasswordAgain ->
307
            "Password again"
308
309
310
fromFieldToInputType : Field -> String
311
fromFieldToInputType field =
312
    case field of
313
        Email ->
314
            "email"
315
316
        Password ->
317
            "password"
318
319
        PasswordAgain ->
320
            "password"