diff options
author | William Carroll <wpcarro@gmail.com> | 2020-08-02T14·15+0100 |
---|---|---|
committer | William Carroll <wpcarro@gmail.com> | 2020-08-02T14·15+0100 |
commit | fe609bbe5804be229a7e5c0d276654fb3e45179b (patch) | |
tree | e939713105443914e045974c89e8f3cdf0c3294c /client/src | |
parent | 81c3db20d4775a115f148ed64c5bc1e54c5a3b65 (diff) |
Support CRUDing records on Admin page
TL;DR: - Prefer the more precise verbiage, "Accounts", to "Users" - Add username field to Trip instead of relying on session.username - Ensure that decodeRole can JD.fail for invalid inputs
Diffstat (limited to 'client/src')
-rw-r--r-- | client/src/Admin.elm | 95 | ||||
-rw-r--r-- | client/src/Manager.elm | 10 | ||||
-rw-r--r-- | client/src/State.elm | 147 | ||||
-rw-r--r-- | client/src/User.elm | 2 |
4 files changed, 169 insertions, 85 deletions
diff --git a/client/src/Admin.elm b/client/src/Admin.elm index e8e33bde617b..17155c1d8e22 100644 --- a/client/src/Admin.elm +++ b/client/src/Admin.elm @@ -1,21 +1,50 @@ module Admin exposing (render) +import Common +import Date import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (..) import RemoteData import State -import Common import Tailwind import UI import Utils +allTrips : State.Model -> Html State.Msg +allTrips model = + case model.trips of + RemoteData.NotAsked -> + UI.absentData { handleFetch = State.AttemptGetTrips } + + RemoteData.Loading -> + UI.paragraph "Loading..." + + RemoteData.Failure e -> + UI.paragraph ("Error: " ++ Utils.explainHttpError e) + + RemoteData.Success xs -> + ul [] + (xs + |> List.map + (\trip -> + li [] + [ UI.paragraph (Date.toIsoString trip.startDate ++ " - " ++ Date.toIsoString trip.endDate ++ ", " ++ trip.username ++ " is going " ++ trip.destination) + , UI.textButton + { label = "delete" + , handleClick = State.AttemptDeleteTrip trip + } + ] + ) + ) + + allUsers : State.Model -> Html State.Msg allUsers model = - case model.users of + case model.accounts of RemoteData.NotAsked -> - UI.absentData { handleFetch = State.AttemptGetUsers } + UI.absentData { handleFetch = State.AttemptGetAccounts } RemoteData.Loading -> UI.paragraph "Loading..." @@ -24,14 +53,23 @@ allUsers model = UI.paragraph ("Error: " ++ Utils.explainHttpError e) RemoteData.Success xs -> - div [] - [ UI.header 3 "Admins" - , users xs.admin - , UI.header 3 "Managers" - , users xs.manager - , UI.header 3 "Users" - , users xs.user - ] + ul [] + (xs + |> List.map + (\account -> + li [] + [ UI.paragraph + (account.username + ++ " - " + ++ State.roleToString account.role + ) + , UI.textButton + { label = "delete" + , handleClick = State.AttemptDeleteAccount account.username + } + ] + ) + ) users : List String -> Html State.Msg @@ -45,7 +83,7 @@ users xs = , div [ [ "flex-1" ] |> Tailwind.use |> class ] [ UI.simpleButton { label = "Delete" - , handleClick = State.AttemptDeleteUser x + , handleClick = State.AttemptDeleteAccount x } ] ] @@ -63,21 +101,32 @@ render model = |> Tailwind.use |> class ] - [ UI.header 2 "Welcome back!" - , UI.simpleButton - { label = "Logout" - , handleClick = State.AttemptLogout - } + [ UI.header 2 "Welcome!" , div [] - [ UI.baseButton - { label = "Switch to users" - , handleClick = State.UpdateAdminTab State.Users - , enabled = not (model.adminTab == State.Users) - , extraClasses = [] + [ UI.textButton + { label = "Logout" + , handleClick = State.AttemptLogout } ] + , div [ [ "py-3" ] |> Tailwind.use |> class ] + [ case model.adminTab of + State.Accounts -> + UI.textButton + { label = "Switch to trips" + , handleClick = State.UpdateAdminTab State.Trips + } + + State.Trips -> + UI.textButton + { label = "Switch to accounts" + , handleClick = State.UpdateAdminTab State.Accounts + } + ] , case model.adminTab of - State.Users -> + State.Accounts -> allUsers model + + State.Trips -> + allTrips model , Common.allErrors model ] diff --git a/client/src/Manager.elm b/client/src/Manager.elm index 7cf5dc3107c3..67cf9414374f 100644 --- a/client/src/Manager.elm +++ b/client/src/Manager.elm @@ -14,11 +14,8 @@ import Utils render : State.Model -> Html State.Msg render model = - case model.session of - Nothing -> - text "You are unauthorized to view this page." - - Just session -> + Common.withSession model + (\session -> div [ class ([ "container" @@ -30,10 +27,11 @@ render model = ] [ h1 [] [ UI.header 2 ("Welcome back, " ++ session.username ++ "!") - , UI.simpleButton + , UI.textButton { label = "Logout" , handleClick = State.AttemptLogout } , Common.allErrors model ] ] + ) diff --git a/client/src/State.elm b/client/src/State.elm index a8970df24d03..8898918cc39e 100644 --- a/client/src/State.elm +++ b/client/src/State.elm @@ -44,20 +44,21 @@ type Msg | LinkClicked Browser.UrlRequest | UrlChanged Url.Url -- Outbound network - | AttemptGetUsers + | AttemptGetAccounts + | AttemptGetTrips | AttemptSignUp | AttemptLogin | AttemptLogout - | AttemptDeleteUser String + | AttemptDeleteAccount String | AttemptCreateTrip Date.Date Date.Date - | AttemptDeleteTrip String Date.Date + | AttemptDeleteTrip Trip -- Inbound network - | GotUsers (WebData AllUsers) + | GotAccounts (WebData (List Account)) | GotTrips (WebData (List Trip)) | GotSignUp (Result Http.Error Session) | GotLogin (Result Http.Error Session) | GotLogout (Result Http.Error String) - | GotDeleteUser (Result Http.Error String) + | GotDeleteAccount (Result Http.Error String) | GotCreateTrip (Result Http.Error ()) | GotDeleteTrip (Result Http.Error ()) @@ -75,10 +76,9 @@ type Role | Admin -type alias AllUsers = - { user : List String - , manager : List String - , admin : List String +type alias Account = + { username : String + , role : Role } @@ -98,7 +98,8 @@ type alias Review = type AdminTab - = Users + = Accounts + | Trips type LoginTab @@ -107,7 +108,8 @@ type LoginTab type alias Trip = - { destination : String + { username : String + , destination : String , startDate : Date.Date , endDate : Date.Date , comment : String @@ -123,7 +125,7 @@ type alias Model = , email : String , password : String , role : Maybe Role - , users : WebData AllUsers + , accounts : WebData (List Account) , startDatePicker : DatePicker.DatePicker , endDatePicker : DatePicker.DatePicker , tripDestination : String @@ -191,8 +193,8 @@ decodeRole = "admin" -> JD.succeed Admin - _ -> - JD.succeed User + x -> + JD.fail ("Invalid input: " ++ x) in JD.string |> JD.andThen toRole @@ -298,12 +300,12 @@ deleteTrip { username, destination, startDate } = } -deleteUser : String -> Cmd Msg -deleteUser username = +deleteAccount : String -> Cmd Msg +deleteAccount username = Utils.deleteWithCredentials - { url = endpoint [ "user", username ] [] + { url = endpoint [ "accounts" ] [ UrlBuilder.string "username" username ] , body = Http.emptyBody - , expect = Http.expectString GotDeleteUser + , expect = Http.expectString GotDeleteAccount } @@ -336,8 +338,9 @@ fetchTrips = Http.expectJson (RemoteData.fromResult >> GotTrips) (JD.list - (JD.map4 + (JD.map5 Trip + (JD.field "username" JD.string) (JD.field "destination" JD.string) (JD.field "startDate" decodeDate) (JD.field "endDate" decodeDate) @@ -347,18 +350,19 @@ fetchTrips = } -fetchUsers : Cmd Msg -fetchUsers = +fetchAccounts : Cmd Msg +fetchAccounts = Utils.getWithCredentials - { url = endpoint [ "all-usernames" ] [] + { url = endpoint [ "accounts" ] [] , expect = Http.expectJson - (RemoteData.fromResult >> GotUsers) - (JD.map3 - AllUsers - (JD.field "user" (JD.list JD.string)) - (JD.field "manager" (JD.list JD.string)) - (JD.field "admin" (JD.list JD.string)) + (RemoteData.fromResult >> GotAccounts) + (JD.list + (JD.map2 + Account + (JD.field "username" JD.string) + (JD.field "role" decodeRole) + ) ) } @@ -424,7 +428,7 @@ prod _ url key = , email = "" , password = "" , role = Nothing - , users = RemoteData.NotAsked + , accounts = RemoteData.NotAsked , tripDestination = "" , tripStartDate = Nothing , tripEndDate = Nothing @@ -432,7 +436,7 @@ prod _ url key = , trips = RemoteData.NotAsked , startDatePicker = startDatePicker , endDatePicker = endDatePicker - , adminTab = Users + , adminTab = Accounts , loginTab = LoginForm , loginError = Nothing , logoutError = Nothing @@ -461,12 +465,14 @@ userHome flags url key = , session = Just { username = "mimi", role = User } , trips = RemoteData.Success - [ { destination = "Barcelona" + [ { username = "mimi" + , destination = "Barcelona" , startDate = Date.fromCalendarDate 2020 Time.Sep 25 , endDate = Date.fromCalendarDate 2020 Time.Oct 5 , comment = "Blah" } - , { destination = "Paris" + , { username = "mimi" + , destination = "Paris" , startDate = Date.fromCalendarDate 2021 Time.Jan 1 , endDate = Date.fromCalendarDate 2021 Time.Feb 1 , comment = "Bon voyage!" @@ -477,6 +483,34 @@ userHome flags url key = ) +managerHome : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +managerHome flags url key = + let + ( model, cmd ) = + prod flags url key + in + ( { model + | route = Just ManagerHome + , session = Just { username = "bill", role = Manager } + } + , cmd + ) + + +adminHome : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) +adminHome flags url key = + let + ( model, cmd ) = + prod flags url key + in + ( { model + | route = Just AdminHome + , session = Just { username = "wpcarro", role = Admin } + } + , cmd + ) + + port printPage : () -> Cmd msg @@ -484,7 +518,7 @@ port printPage : () -> Cmd msg -} init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) init flags url key = - userHome flags url key + adminHome flags url key {-| Now that we have state, we need a function to change the state. @@ -625,17 +659,22 @@ update msg model = ( { model | url = url , route = route + , accounts = RemoteData.Loading } - , Cmd.none + , fetchAccounts ) Just AdminHome -> ( { model | url = url , route = route - , users = RemoteData.Loading + , accounts = RemoteData.Loading + , trips = RemoteData.Loading } - , Cmd.none + , Cmd.batch + [ fetchAccounts + , fetchTrips + ] ) _ -> @@ -647,20 +686,20 @@ update msg model = ) -- GET /accounts - AttemptGetUsers -> - ( { model | users = RemoteData.Loading }, fetchUsers ) + AttemptGetAccounts -> + ( { model | accounts = RemoteData.Loading }, fetchAccounts ) - GotUsers xs -> - ( { model | users = xs }, Cmd.none ) + GotAccounts xs -> + ( { model | accounts = xs }, Cmd.none ) -- DELETE /accounts - AttemptDeleteUser username -> - ( model, deleteUser username ) + AttemptDeleteAccount username -> + ( model, deleteAccount username ) - GotDeleteUser result -> + GotDeleteAccount result -> case result of Ok _ -> - ( model, fetchUsers ) + ( model, fetchAccounts ) Err e -> ( { model | deleteUserError = Just e } @@ -708,18 +747,13 @@ update msg model = ) -- DELETE /trips - AttemptDeleteTrip destination startDate -> + AttemptDeleteTrip trip -> ( model - , case model.session of - Nothing -> - Cmd.none - - Just session -> - deleteTrip - { username = session.username - , destination = destination - , startDate = startDate - } + , deleteTrip + { username = trip.username + , destination = trip.destination + , startDate = trip.startDate + } ) GotDeleteTrip result -> @@ -755,6 +789,9 @@ update msg model = ) -- GET /trips + AttemptGetTrips -> + ( { model | trips = RemoteData.Loading }, fetchTrips ) + GotTrips xs -> ( { model | trips = xs }, Cmd.none ) diff --git a/client/src/User.elm b/client/src/User.elm index 660c3aa7dce0..0c87e85bf98b 100644 --- a/client/src/User.elm +++ b/client/src/User.elm @@ -89,7 +89,7 @@ renderTrip trip = , UI.wrapNoPrint (UI.textButton { label = "Delete" - , handleClick = State.AttemptDeleteTrip trip.destination trip.startDate + , handleClick = State.AttemptDeleteTrip trip } ) ] |