Skip to content

Commit

Permalink
Add custom page auth example, fix bug with asking for a view and not …
Browse files Browse the repository at this point in the history
…using it
  • Loading branch information
ryan-haskell committed Apr 13, 2024
1 parent 7e9819e commit a080254
Show file tree
Hide file tree
Showing 17 changed files with 361 additions and 34 deletions.
8 changes: 4 additions & 4 deletions docs/concepts/auth.md
Expand Up @@ -36,7 +36,7 @@ elm-land customize auth
By default, all auth-only pages redirect users to the `NotFound_` page when the application starts up. Let's edit our new `src/Auth.elm` file so it automatically passes the user to any pages that need it, but redirects to `/sign-in` if there's no user logged in.

```elm{7-8,14-28}
module Auth exposing (User, onPageLoad, viewLoadingPage)
module Auth exposing (User, onPageLoad, viewCustomPage)
-- ...
Expand Down Expand Up @@ -66,9 +66,9 @@ onPageLoad shared route =
}
{-| Used whenever `Auth.Action.showLoadingPage` is returned. -}
viewLoadingPage : Shared.Model -> Route () -> View Msg
viewLoadingPage shared route =
{-| Used whenever `Auth.Action.loadCustomPage` is returned. -}
viewCustomPage : Shared.Model -> Route () -> View Msg
viewCustomPage shared route =
View.none
```

Expand Down
8 changes: 5 additions & 3 deletions docs/reference/auth-action.md
Expand Up @@ -65,12 +65,14 @@ Auth.Action.loadExternalUrl : String -> Auth.Action.Action
```


### `Auth.Action.showLoadingPage`
### `Auth.Action.loadCustomPage`

Sometimes it is helpful to wait on the current page while validating if a JWT token is expired. The `showLoadingPage` function will display a static view to the user while they wait.
Sometimes it is helpful to wait on the current page while validating if a JWT token is expired. The `loadCustomPage` function will display a static view to the user while they wait.

__Note:__ You can use `Auth.viewCustomPage` to specify what to render in each scenario

#### Type definition

```elm
Auth.Action.showLoadingPage : View Never -> Auth.Action.Action
Auth.Action.loadCustomPage : Auth.Action.Action
```
8 changes: 4 additions & 4 deletions examples/05-user-auth/src/Auth.elm
@@ -1,4 +1,4 @@
module Auth exposing (User, onPageLoad, viewLoadingPage)
module Auth exposing (User, onPageLoad, viewCustomPage)

import Auth.Action
import Dict
Expand Down Expand Up @@ -32,8 +32,8 @@ onPageLoad shared route =
}


{-| Renders whenever `Auth.Action.showLoadingPage` is returned from `onPageLoad`.
{-| Renders whenever `Auth.Action.loadCustomPage` is returned from `onPageLoad`.
-}
viewLoadingPage : Shared.Model -> Route () -> View Never
viewLoadingPage shared route =
viewCustomPage : Shared.Model -> Route () -> View Never
viewCustomPage shared route =
View.fromString "Loading..."
7 changes: 7 additions & 0 deletions examples/20-auth-error-page/.gitignore
@@ -0,0 +1,7 @@
/dist
/.elm-land
/.env
/elm-stuff
/node_modules
.DS_Store
*.pem
16 changes: 16 additions & 0 deletions examples/20-auth-error-page/README.md
@@ -0,0 +1,16 @@
# 20-auth-error-page
> Built with [Elm Land](https://elm.land) 🌈
## Local development

```bash
# Requires Node.js v18+ (https://nodejs.org)
npx elm-land server
```

## Deploying to production

Elm Land projects are most commonly deployed as static websites.

Please visit [the "Deployment" guide](https://elm.land/guide/deploying) to learn more
about deploying your app for free using Netlify or Vercel.
27 changes: 27 additions & 0 deletions examples/20-auth-error-page/elm-land.json
@@ -0,0 +1,27 @@
{
"app": {
"elm": {
"development": { "debugger": true },
"production": { "debugger": false }
},
"env": [],
"html": {
"attributes": {
"html": { "lang": "en" },
"head": {}
},
"title": "Elm Land",
"meta": [
{ "charset": "UTF-8" },
{ "http-equiv": "X-UA-Compatible", "content": "IE=edge" },
{ "name": "viewport", "content": "width=device-width, initial-scale=1.0" }
],
"link": [],
"script": []
},
"router": {
"useHashRouting": false
},
"proxy": null
}
}
25 changes: 25 additions & 0 deletions examples/20-auth-error-page/elm.json
@@ -0,0 +1,25 @@
{
"type": "application",
"source-directories": [
"src",
".elm-land/src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/json": "1.1.3",
"elm/url": "1.0.0"
},
"indirect": {
"elm/time": "1.0.0",
"elm/virtual-dom": "1.0.3"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
51 changes: 51 additions & 0 deletions examples/20-auth-error-page/src/Auth.elm
@@ -0,0 +1,51 @@
module Auth exposing (User, onPageLoad, viewCustomPage)

import Auth.Action
import Dict
import Html
import Route exposing (Route)
import Route.Path
import Shared
import Shared.Model
import User
import View exposing (View)


type alias User =
User.User


{-| Called before an auth-only page is loaded.
-}
onPageLoad : Shared.Model -> Route () -> Auth.Action.Action User
onPageLoad shared route =
case shared.authStatus of
Shared.Model.NotLoggedIn ->
Auth.Action.loadCustomPage

Shared.Model.LoggedInAs user ->
Auth.Action.loadPageWithUser user

Shared.Model.TokenExpired ->
Auth.Action.loadCustomPage


{-| Renders whenever `Auth.Action.showCustomView` is returned from `onPageLoad`.
-}
viewCustomPage : Shared.Model -> Route () -> View Never
viewCustomPage shared route =
case shared.authStatus of
Shared.Model.NotLoggedIn ->
{ title = "Permission denied"
, body = [ Html.text "You need to be logged in to see this!" ]
}

Shared.Model.LoggedInAs user ->
{ title = "Loading..."
, body = []
}

Shared.Model.TokenExpired ->
{ title = "Token expired"
, body = [ Html.text "Your token expired, here's an error page!" ]
}
71 changes: 71 additions & 0 deletions examples/20-auth-error-page/src/Pages/Home_.elm
@@ -0,0 +1,71 @@
module Pages.Home_ exposing (Model, Msg, page)

import Auth
import Effect exposing (Effect)
import Html
import Page exposing (Page)
import Route exposing (Route)
import Shared
import View exposing (View)


page : Auth.User -> Shared.Model -> Route () -> Page Model Msg
page user shared route =
Page.new
{ init = init
, update = update
, subscriptions = subscriptions
, view = view
}



-- INIT


type alias Model =
{}


init : () -> ( Model, Effect Msg )
init () =
( {}
, Effect.none
)



-- UPDATE


type Msg
= NoOp


update : Msg -> Model -> ( Model, Effect Msg )
update msg model =
case msg of
NoOp ->
( model
, Effect.none
)



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none



-- VIEW


view : Model -> View Msg
view model =
{ title = "Pages.Home_"
, body = [ Html.text "/" ]
}
75 changes: 75 additions & 0 deletions examples/20-auth-error-page/src/Shared.elm
@@ -0,0 +1,75 @@
module Shared exposing
( Flags, decoder
, Model, Msg
, init, update, subscriptions
)

{-|
@docs Flags, decoder
@docs Model, Msg
@docs init, update, subscriptions
-}

import Effect exposing (Effect)
import Json.Decode
import Route exposing (Route)
import Route.Path
import Shared.Model
import Shared.Msg



-- FLAGS


type alias Flags =
{}


decoder : Json.Decode.Decoder Flags
decoder =
Json.Decode.succeed {}



-- INIT


type alias Model =
Shared.Model.Model


init : Result Json.Decode.Error Flags -> Route () -> ( Model, Effect Msg )
init flagsResult route =
( { authStatus = Shared.Model.TokenExpired -- 👈 Change this to see the different custom pages
}
, Effect.none
)



-- UPDATE


type alias Msg =
Shared.Msg.Msg


update : Route () -> Msg -> Model -> ( Model, Effect Msg )
update route msg model =
case msg of
Shared.Msg.NoOp ->
( model
, Effect.none
)



-- SUBSCRIPTIONS


subscriptions : Route () -> Model -> Sub Msg
subscriptions route model =
Sub.none
14 changes: 14 additions & 0 deletions examples/20-auth-error-page/src/Shared/Model.elm
@@ -0,0 +1,14 @@
module Shared.Model exposing (AuthStatus(..), Model)

import User exposing (User)


type alias Model =
{ authStatus : AuthStatus
}


type AuthStatus
= NotLoggedIn
| LoggedInAs User
| TokenExpired
14 changes: 14 additions & 0 deletions examples/20-auth-error-page/src/Shared/Msg.elm
@@ -0,0 +1,14 @@
module Shared.Msg exposing (Msg(..))

{-| -}


{-| Normally, this value would live in "Shared.elm"
but that would lead to a circular dependency import cycle.
For that reason, both `Shared.Model` and `Shared.Msg` are in their
own file, so they can be imported by `Effect.elm`
-}
type Msg
= NoOp
7 changes: 7 additions & 0 deletions examples/20-auth-error-page/src/User.elm
@@ -0,0 +1,7 @@
module User exposing (User)


type alias User =
{ id : Int
, email : String
}

0 comments on commit a080254

Please sign in to comment.