Skip to content

Commit

Permalink
Merge pull request #451 from SAFE-Stack/use-elmish
Browse files Browse the repository at this point in the history
Use elmish
  • Loading branch information
martinbryant authored Mar 8, 2024
2 parents 61b8777 + 04f6f5a commit 859f08f
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 93 deletions.
17 changes: 16 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sweetalert2": "^8.19.1"
"sweetalert2": "^8.19.1",
"use-sync-external-store": "^1.2.0"
},
"devDependencies": {
"@types/node": "^20.9.1",
Expand Down
1 change: 1 addition & 0 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ nuget Azure.Storage.Blobs
nuget Elmish.SweetAlert
nuget Feliz.DaisyUI
nuget Feliz.Router
nuget Feliz.UseElmish
nuget FSharp.Core ~> 8
nuget Fable.Remoting.Giraffe ~> 5
nuget FSToolkit.ErrorHandling
Expand Down
3 changes: 3 additions & 0 deletions paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ NUGET
Fable.Elmish (>= 4.0)
Feliz (>= 2.3)
FSharp.Core (>= 4.7.2)
Feliz.UseElmish (2.5)
Fable.Elmish (>= 4.0)
FSharp.Core (>= 4.7.2)
FParsec (1.1.1)
FSharp.Core (>= 4.3.4)
FSharp.Control.Reactive (5.0.5)
Expand Down
103 changes: 23 additions & 80 deletions src/Client/Index.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ open Page
open Shared

type PageTab =
| Home of Home.Model
| Login of Login.Model
| Wishlist of WishList.Model
| Home
| Login
| Wishlist of UserData
| NotFound

type User =
Expand All @@ -22,107 +22,51 @@ type User =
type Model = { Page: PageTab; User: User }

type Msg =
| HomePageMsg of Home.Msg
| LoginPageMsg of Login.Msg
| WishlistMsg of WishList.Msg
| UrlChanged of string list
| OnSessionChange
| Logout

let wishListApi token =
let bearer = $"Bearer {token}"

Remoting.createApi ()
|> Remoting.withAuthorizationHeader bearer
|> Remoting.withRouteBuilder Route.builder
|> Remoting.buildProxy<IWishListApi>

let guestApi =
Remoting.createApi ()
|> Remoting.withRouteBuilder Route.builder
|> Remoting.buildProxy<IGuestApi>

let initFromUrl model url =
let loggedInUser =
Session.loadUser () |> Option.map User |> Option.defaultValue Guest

match url with
| [] ->
let homeModel, homeMsg = Home.init guestApi

let model = {
Page = Home homeModel
User = model.User
}

let cmd = homeMsg |> Cmd.map HomePageMsg
model, cmd
let model = { Page = Home; User = loggedInUser }
model, Cmd.none
| [ "login" ] ->
let loginModel, loginMsg = Login.init ()

let model = {
Page = Login loginModel
User = model.User
}

let cmd = loginMsg |> Cmd.map LoginPageMsg
model, cmd
let model = { Page = Login; User = loggedInUser }
model, Cmd.none
| [ "wishlist" ] ->
match model.User with
match loggedInUser with
| User user ->
let wishlistModel, wishlistMsg =
WishList.init (wishListApi user.Token) user.UserName

let model = {
Page = Wishlist wishlistModel
User = model.User
Page = Wishlist user
User = loggedInUser
}

let cmd = wishlistMsg |> Cmd.map WishlistMsg
model, cmd
model, Cmd.none
| Guest -> model, Cmd.navigate "login"
| _ -> { Page = NotFound; User = model.User }, Cmd.none
| _ -> { Page = NotFound; User = loggedInUser }, Cmd.none

let init () =
let model, _ = Home.init guestApi
let user = Session.loadUser () |> Option.map User |> Option.defaultValue Guest

Router.currentUrl () |> initFromUrl { Page = Home model; User = user }
Router.currentUrl () |> initFromUrl { Page = Home; User = user }

let update msg model =
match model.Page, msg with
| Home homeModel, HomePageMsg homeMsg ->
let newModel, cmd = Home.update homeMsg homeModel

{
Page = Home newModel
User = model.User
},
cmd
| Login loginModel, LoginPageMsg loginMsg ->
let user =
match loginMsg with
| Login.LoggedIn user -> User user
| _ -> model.User

let newModel, cmd = Login.update guestApi loginMsg loginModel
{ Page = Login newModel; User = user }, cmd |> Cmd.map LoginPageMsg
| Wishlist wishlistModel, WishlistMsg wishlistMsg ->
let token =
match model.User with
| User data -> data.Token
| Guest -> ""

let newModel, cmd = WishList.update (wishListApi token) wishlistMsg wishlistModel

{
Page = Wishlist newModel
User = model.User
},
cmd |> Cmd.map WishlistMsg
| NotFound, _ -> { Page = NotFound; User = model.User }, Cmd.none
| _, UrlChanged url -> initFromUrl model url
| _, Logout ->
match msg with
| UrlChanged url -> initFromUrl model url
| Logout ->
Session.deleteUser ()
{ model with User = Guest }, Cmd.navigate ""
| _, OnSessionChange ->
| OnSessionChange ->
let session = Session.loadUser ()
let user = session |> Option.map User |> Option.defaultValue Guest

Expand All @@ -132,7 +76,6 @@ let update msg model =
|> Option.defaultValue (Cmd.navigate "login")

{ model with User = user }, cmd
| _, _ -> model, Cmd.none

open Feliz

Expand Down Expand Up @@ -177,9 +120,9 @@ let view model dispatch =
prop.className "overflow-y-auto"
prop.children [
match model.Page with
| Home homeModel -> Home.view homeModel (HomePageMsg >> dispatch)
| Login loginModel -> Login.view loginModel (LoginPageMsg >> dispatch)
| Wishlist wishlistModel -> WishList.view wishlistModel (WishlistMsg >> dispatch)
| Home -> Home.View guestApi
| Login -> Login.View guestApi
| Wishlist user -> WishList.View user
| NotFound -> Html.div [ prop.text "Not Found" ]
]
]
Expand Down
6 changes: 5 additions & 1 deletion src/Client/pages/Home.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Page.Home

open Elmish
open Feliz.DaisyUI
open Feliz.UseElmish
open Shared

type Model = { Books: Book seq }
Expand Down Expand Up @@ -43,7 +44,10 @@ let bookRow book =
]
]

let view model dispatch =
[<ReactComponent>]
let View api =
let model, dispatch = React.useElmish ((fun () -> init api), update, [||])

Html.div [
prop.className "overflow-y-auto"
prop.children [
Expand Down
6 changes: 5 additions & 1 deletion src/Client/pages/Login.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ open System
open Elmish
open Feliz.DaisyUI
open Feliz.Router
open Feliz.UseElmish
open FsToolkit.ErrorHandling
open Shared
open SAFE
Expand Down Expand Up @@ -68,7 +69,10 @@ let update (guestApi: IGuestApi) msg model =

open Feliz

let view model dispatch =
[<ReactComponent>]
let View guestApi =
let model, dispatch = React.useElmish (init, (update guestApi), [||])

Html.div [
prop.className "grid justify-center"
prop.children [
Expand Down
30 changes: 22 additions & 8 deletions src/Client/pages/WishList.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,18 @@ open System
open Elmish
open Elmish.SweetAlert
open Feliz.DaisyUI
open Feliz.UseElmish
open Shared
open SAFE
open Fable.Remoting.Client

let wishListApi token =
let bearer = $"Bearer {token}"

Remoting.createApi ()
|> Remoting.withAuthorizationHeader bearer
|> Remoting.withRouteBuilder Route.builder
|> Remoting.buildProxy<IWishListApi>

type Model = {
Wishlist: WishList
Expand All @@ -26,10 +36,10 @@ type Msg =
let alert message alertType =
SimpleAlert(message).Type(alertType) |> SweetAlert.Run

let init (wishListApi: IWishListApi) (userName: UserName) =
let init api (user: UserData) =
let model = {
Wishlist = {
UserName = userName
UserName = user.UserName
Books = List.empty
}
LastResetTime = DateTime.MinValue
Expand All @@ -38,21 +48,21 @@ let init (wishListApi: IWishListApi) (userName: UserName) =

let cmd =
Cmd.batch [
Cmd.OfAsync.either wishListApi.getWishlist userName GotWishlist UnhandledError
Cmd.OfAsync.either wishListApi.getLastResetTime () GotLastRestTime UnhandledError
Cmd.OfAsync.either api.getWishlist user.UserName GotWishlist UnhandledError
Cmd.OfAsync.either api.getLastResetTime () GotLastRestTime UnhandledError
]

model, cmd

let update booksApi msg model =
let update api msg model =
match msg with
| GotLastRestTime time -> { model with LastResetTime = time }, Cmd.none
| GotWishlist wishlist -> { model with Wishlist = wishlist }, Cmd.none
| RemoveBook title ->
let userName = model.Wishlist.UserName

let cmd =
Cmd.OfAsync.either booksApi.removeBook (userName, title) RemovedBook UnhandledError
Cmd.OfAsync.either api.removeBook (userName, title) RemovedBook UnhandledError

model, cmd
| RemovedBook title ->
Expand All @@ -71,7 +81,7 @@ let update booksApi msg model =
match model.Wishlist.VerifyNewBookIsNotADuplicate book with
| Ok _ ->
let userName = model.Wishlist.UserName
model, Cmd.OfAsync.either booksApi.addBook (userName, book) AddedBook UnhandledError
model, Cmd.OfAsync.either api.addBook (userName, book) AddedBook UnhandledError
| Error error -> model, Exception(error) |> UnhandledError |> Cmd.ofMsg
| NewBook.Cancel, _ -> { model with NewBook = None }, Cmd.none
| _, Some newBook ->
Expand Down Expand Up @@ -159,7 +169,11 @@ let table model dispatch =
]
]

let view model dispatch =
[<ReactComponent>]
let View user =
let api: IWishListApi = wishListApi user.Token

let model, dispatch = React.useElmish ((fun () -> init api user), update api, [||])
let user = model.Wishlist.UserName.Value
let lastReset = model.LastResetTime.ToString("yyyy-MM-dd HH:mm")

Expand Down
3 changes: 2 additions & 1 deletion src/Client/paket.references
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ Feliz.DaisyUI
Feliz.Router
FSToolkit.ErrorHandling
Elmish.SweetAlert
Fable.SimpleJson
Fable.SimpleJson
Feliz.UseElmish

0 comments on commit 859f08f

Please sign in to comment.