about summary refs log tree commit diff
path: root/client/src
diff options
context:
space:
mode:
authorWilliam Carroll <wpcarro@gmail.com>2020-08-02T14·15+0100
committerWilliam Carroll <wpcarro@gmail.com>2020-08-02T14·15+0100
commitfe609bbe5804be229a7e5c0d276654fb3e45179b (patch)
treee939713105443914e045974c89e8f3cdf0c3294c /client/src
parent81c3db20d4775a115f148ed64c5bc1e54c5a3b65 (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.elm95
-rw-r--r--client/src/Manager.elm10
-rw-r--r--client/src/State.elm147
-rw-r--r--client/src/User.elm2
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
                 }
             )
         ]