all repos

onasty @ 9c8b9eae5400bed303e3892d640786b0cb0b3554

a one-time notes service
3 files changed, 89 insertions(+), 30 deletions(-)
web: prompt user to check email to verifiy after signing up (#139)

* fix(web): dont error when api does not provide body

* web: first naive implementation

* web: unite banner messages

* web: resending verification email does not sent the form

* web: some styling

* fixup! fix(web): dont error when api does not provide body
Author: Smirnov Oleksandr ss2316544@gmail.com
Committed by: GitHub noreply@github.com
Committed at: 2025-06-21 14:53:11 +0300
Parent: dfbb8b4
M web/src/Api/Auth.elm
···
        1
        
        -module Api.Auth exposing (refreshToken, signin, signup)

      
        
        1
        +module Api.Auth exposing (refreshToken, resendVerificationEmail, signin, signup)

      
        2
        2
         

      
        3
        3
         import Api

      
        4
        4
         import Data.Credentials as Credentials exposing (Credentials)

      ···
        75
        75
                 , onResponse = options.onResponse

      
        76
        76
                 , decoder = Credentials.decode

      
        77
        77
                 }

      
        
        78
        +

      
        
        79
        +

      
        
        80
        +resendVerificationEmail :

      
        
        81
        +    { onResponse : Result Api.Error () -> msg

      
        
        82
        +    , email : String

      
        
        83
        +    , password : String

      
        
        84
        +    }

      
        
        85
        +    -> Effect msg

      
        
        86
        +resendVerificationEmail options =

      
        
        87
        +    let

      
        
        88
        +        body : Encode.Value

      
        
        89
        +        body =

      
        
        90
        +            Encode.object

      
        
        91
        +                [ ( "email", Encode.string options.email )

      
        
        92
        +                , ( "password", Encode.string options.password )

      
        
        93
        +                ]

      
        
        94
        +    in

      
        
        95
        +    Effect.sendApiRequest

      
        
        96
        +        { endpoint = "/api/v1/auth/resend-verification-email"

      
        
        97
        +        , method = "POST"

      
        
        98
        +        , body = Http.jsonBody body

      
        
        99
        +        , onResponse = options.onResponse

      
        
        100
        +        , decoder = Decode.succeed ()

      
        
        101
        +        }

      
M web/src/Effect.elm
···
        176
        176
             -> Effect msg

      
        177
        177
         sendApiRequest opts =

      
        178
        178
             let

      
        179
        
        -        onSuccess : value -> msg

      
        180
        
        -        onSuccess value =

      
        181
        
        -            opts.onResponse (Ok value)

      
        182
        
        -

      
        183
        179
                 onHttpError : Api.Error -> msg

      
        184
        180
                 onHttpError err =

      
        185
        181
                     opts.onResponse (Err err)

      
        
        182
        +

      
        
        183
        +        decoder : Json.Decode.Decoder msg

      
        
        184
        +        decoder =

      
        
        185
        +            opts.decoder

      
        
        186
        +                |> Json.Decode.map Ok

      
        
        187
        +                |> Json.Decode.map opts.onResponse

      
        186
        188
             in

      
        187
        189
             SendApiRequest

      
        188
        190
                 { endpoint = opts.endpoint

      
        189
        191
                 , method = opts.method

      
        190
        192
                 , body = opts.body

      
        191
        193
                 , onHttpError = onHttpError

      
        192
        
        -        , decoder = Json.Decode.map onSuccess opts.decoder

      
        
        194
        +        , decoder = decoder

      
        193
        195
                 }

      
        194
        196
         

      
        195
        197
         

      ···
        353
        355
         fromHttpResponseToCustomError decoder response =

      
        354
        356
             case response of

      
        355
        357
                 Http.GoodStatus_ _ body ->

      
        356
        
        -            case Json.Decode.decodeString decoder body of

      
        357
        
        -                Ok data ->

      
        358
        
        -                    Ok data

      
        
        358
        +            case

      
        
        359
        +                Json.Decode.decodeString decoder

      
        
        360
        +                    (if String.isEmpty body then

      
        
        361
        +                        "\"\""

      
        
        362
        +

      
        
        363
        +                     else

      
        
        364
        +                        body

      
        
        365
        +                    )

      
        
        366
        +            of

      
        
        367
        +                Ok value ->

      
        
        368
        +                    Ok value

      
        359
        369
         

      
        360
        370
                         Err err ->

      
        361
        
        -                    Err (Api.JsonDecodeError { message = "Something unexpected happened", reason = err })

      
        
        371
        +                    Err (Api.JsonDecodeError { message = "Failed to decode JSON response", reason = err })

      
        362
        372
         

      
        363
        373
                 Http.BadStatus_ { statusCode } body ->

      
        364
        374
                     case Json.Decode.decodeString Data.Error.decode body of

      
M web/src/Pages/Auth.elm
···
        37
        37
             , passwordAgain : String

      
        38
        38
             , isSubmittingForm : Bool

      
        39
        39
             , formVariant : Variant

      
        40
        
        -    , error : Maybe Api.Error

      
        
        40
        +    , gotSignedUp : Bool

      
        
        41
        +    , apiError : Maybe Api.Error

      
        41
        42
             }

      
        42
        43
         

      
        43
        44
         

      ···
        48
        49
               , password = ""

      
        49
        50
               , passwordAgain = ""

      
        50
        51
               , formVariant = SignIn

      
        51
        
        -      , error = Nothing

      
        
        52
        +      , apiError = Nothing

      
        
        53
        +      , gotSignedUp = False

      
        52
        54
               }

      
        53
        55
             , case shared.user of

      
        54
        56
                 Auth.User.SignedIn _ ->

      
        55
        57
                     Effect.pushRoutePath Route.Path.Home_

      
        56
        58
         

      
        57
        
        -        Auth.User.NotSignedIn ->

      
        58
        
        -            Effect.none

      
        59
        
        -

      
        60
        
        -        Auth.User.RefreshingTokens ->

      
        
        59
        +        _ ->

      
        61
        60
                     Effect.none

      
        62
        61
             )

      
        63
        62
         

      ···
        70
        69
             = UserUpdatedInput Field String

      
        71
        70
             | UserChangedFormVariant Variant

      
        72
        71
             | UserClickedSubmit

      
        
        72
        +    | UserClickedResendActivationEmail

      
        73
        73
             | ApiSignInResponded (Result Api.Error Credentials)

      
        74
        74
             | ApiSignUpResponded (Result Api.Error ())

      
        
        75
        +    | ApiResendVerificationEmail (Result Api.Error ())

      
        75
        76
         

      
        76
        77
         

      
        77
        78
         type Field

      ···
        89
        90
         update msg model =

      
        90
        91
             case msg of

      
        91
        92
                 UserClickedSubmit ->

      
        92
        
        -            ( { model | isSubmittingForm = True }

      
        
        93
        +            ( { model | isSubmittingForm = True, apiError = Nothing }

      
        93
        94
                     , case model.formVariant of

      
        94
        95
                         SignIn ->

      
        95
        96
                             Api.Auth.signin

      ···
        106
        107
                                 }

      
        107
        108
                     )

      
        108
        109
         

      
        
        110
        +        UserClickedResendActivationEmail ->

      
        
        111
        +            ( model

      
        
        112
        +            , Api.Auth.resendVerificationEmail

      
        
        113
        +                { onResponse = ApiResendVerificationEmail

      
        
        114
        +                , email = model.email

      
        
        115
        +                , password = model.password

      
        
        116
        +                }

      
        
        117
        +            )

      
        
        118
        +

      
        109
        119
                 UserChangedFormVariant variant ->

      
        110
        120
                     ( { model | formVariant = variant }, Effect.none )

      
        111
        121
         

      ···
        119
        129
                     ( { model | passwordAgain = passwordAgain }, Effect.none )

      
        120
        130
         

      
        121
        131
                 ApiSignInResponded (Ok credentials) ->

      
        122
        
        -            ( { model | isSubmittingForm = False }

      
        123
        
        -            , Effect.signin credentials

      
        124
        
        -            )

      
        
        132
        +            ( { model | isSubmittingForm = False }, Effect.signin credentials )

      
        125
        133
         

      
        126
        134
                 ApiSignInResponded (Err error) ->

      
        127
        
        -            ( { model | isSubmittingForm = False, error = Just error }, Effect.none )

      
        
        135
        +            ( { model | isSubmittingForm = False, apiError = Just error }, Effect.none )

      
        128
        136
         

      
        129
        137
                 ApiSignUpResponded (Ok ()) ->

      
        130
        
        -            -- TODO: show banner with that they have to activate account

      
        131
        
        -            ( { model | isSubmittingForm = False }, Effect.none )

      
        
        138
        +            ( { model | isSubmittingForm = False, gotSignedUp = True }, Effect.none )

      
        132
        139
         

      
        133
        140
                 ApiSignUpResponded (Err error) ->

      
        134
        
        -            ( { model | isSubmittingForm = False, error = Just error }, Effect.none )

      
        
        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 )

      
        135
        148
         

      
        136
        149
         

      
        137
        150
         

      ···
        153
        166
             , body =

      
        154
        167
                 [ Html.div [ Attr.class "center" ]

      
        155
        168
                     -- TODO: add oauth buttons

      
        156
        
        -            [ viewError model.error

      
        
        169
        +            [ viewBanner model.apiError model.gotSignedUp

      
        157
        170
                     , viewChangeVariant model.formVariant

      
        158
        171
                     , viewForm model

      
        159
        172
                     , viewForgotPassword

      ···
        197
        210
                 )

      
        198
        211
         

      
        199
        212
         

      
        200
        
        -viewError : Maybe Api.Error -> Html Msg

      
        201
        
        -viewError maybeError =

      
        202
        
        -    case maybeError of

      
        203
        
        -        Just error ->

      
        
        213
        +viewBanner : Maybe Api.Error -> Bool -> Html Msg

      
        
        214
        +viewBanner maybeError gotSignedUp =

      
        
        215
        +    case ( maybeError, gotSignedUp ) of

      
        
        216
        +        ( Just error, _ ) ->

      
        204
        217
                     Html.div [ Attr.class "box bad" ]

      
        205
        218
                         [ Html.strong [ Attr.class "block titlebar" ] [ Html.text "Error" ]

      
        206
        219
                         , Html.text (Api.errorMessage error)

      
        207
        220
                         ]

      
        208
        221
         

      
        209
        
        -        Nothing ->

      
        
        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 ) ->

      
        210
        235
                     Html.text ""

      
        211
        236
         

      
        212
        237