Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use elmish #451

Merged
merged 9 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading