diff --git a/src/App.tsx b/src/App.tsx index 0f7b653..b9797b0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Helmet } from 'react-helmet'; -import { Routes, Route, Navigate } from 'react-router-dom'; +import { Routes, Route, Navigate, Outlet } from 'react-router-dom'; import { Toaster } from 'react-hot-toast'; import { useAuth } from 'src/services/auth'; @@ -10,7 +10,13 @@ import { LoginCallback } from './modules/login/LoginCallback'; // eslint-disable-next-line @typescript-eslint/no-unused-vars function App() { - const { authToken } = useAuth(); + const { authToken, currentUser, isAdmin } = useAuth(); + + const redirectToLogin = !authToken || !currentUser?.app_roles; + + const ProtectedRoute = () => { + return isAdmin ? : ; + }; return ( <> @@ -26,7 +32,7 @@ function App() {
- {!authToken ? ( + {redirectToLogin ? ( } /> } /> @@ -36,7 +42,9 @@ function App() { } /> - } /> + }> + } /> + } /> diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 141da56..a58e2d8 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -8,20 +8,28 @@ import clsx from 'clsx'; import { CurrentUserModal } from './components/CurrentUserModal'; const navigation = [ - { name: 'Dashboard', to: '/dashboard' }, - { name: 'Users', to: '/users' }, + { name: 'Dashboard', to: '/dashboard', requiresAdmin: false }, + { name: 'Users', to: '/users', requiresAdmin: true }, ]; function classNames(...classes: any[]) { return classes.filter(Boolean).join(' '); } +function filterNavigationByDashboardRole(isAdmin: boolean) { + if (isAdmin) { + return navigation; + } + + return navigation.filter((item) => !item.requiresAdmin); +} + // eslint-disable-next-line @typescript-eslint/no-empty-interface interface HeaderProps {} const Header: React.FC = () => { const [currentUserModal, setCurrentUserModal] = useState(false); - const { logOut, currentUser } = useAuth(); + const { logOut, currentUser, isAdmin } = useAuth(); const { pathname } = useLocation(); @@ -30,6 +38,8 @@ const Header: React.FC = () => { }; const currentUserModalClose = () => setCurrentUserModal(false); + const navigationItems = filterNavigationByDashboardRole(isAdmin); + return ( <> @@ -55,7 +65,7 @@ const Header: React.FC = () => {
{/* Current: "border-primary-500 text-gray-900", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" */} - {navigation.map((item) => ( + {navigationItems.map((item) => ( { const { editUserById, userModalLoading } = useUsers(); + const { isAdmin } = useAuth(); const { control, reset, handleSubmit } = useForm({ - defaultValues: { - name: null, - email: null, - id: null, - role_id: null, - status: null, - }, + defaultValues: initialUserForm, + }); + + const { fields } = useFieldArray({ + control, + name: 'app_roles', }); useEffect(() => { - if (user) { + if (user && !_.isEmpty(user)) { reset(user); } return () => { - reset({ name: null, email: null, id: null }); + reset(initialUserForm); }; }, [user, reset]); @@ -54,7 +56,7 @@ export const CurrentUserModal = ({ open, onClose, user }: UserModalProps) => { return (
-
+

Profile

@@ -69,77 +71,90 @@ export const CurrentUserModal = ({ open, onClose, user }: UserModalProps) => {
-
- + ))} +
+ +
+ {/* + + + + +
+
+ + )} +
+
+ + {isAdmin && ( +
+
+

App Access

-
- -
- -
- {/* - - - - +
+
+
    + {fields + .filter((field) => field.name !== 'dashboard') + .map((item, index) => ( +
  • +
    +
    + {item.name +

    + {_.find(appAccessList, ['name', item.name!])?.label} +

    +
    +
    + - - - - -
    -
    -
  • - ); - })} -
-
-
-
+ )}
diff --git a/src/components/Header/components/CurrentUserModal/consts.ts b/src/components/Header/components/CurrentUserModal/consts.ts index 080c939..abda708 100644 --- a/src/components/Header/components/CurrentUserModal/consts.ts +++ b/src/components/Header/components/CurrentUserModal/consts.ts @@ -1,18 +1,55 @@ +import { UserRole } from 'src/services/users'; + export const appAccessList = [ { + name: 'wekan', image: '/assets/wekan.svg', - name: 'Wekan', + label: 'Wekan', }, { + name: 'wordpress', image: '/assets/wordpress.svg', - name: 'Wordpress', + label: 'Wordpress', }, { + name: 'next-cloud', image: '/assets/nextcloud.svg', - name: 'NextCloud', + label: 'NextCloud', }, { + name: 'zulip', image: '/assets/zulip.svg', - name: 'Zulip', + label: 'Zulip', }, ]; + +const initialAppRoles = [ + { + name: 'dashboard', + role: UserRole.User, + }, + { + name: 'wekan', + role: UserRole.User, + }, + { + name: 'wordpress', + role: UserRole.User, + }, + { + name: 'next-cloud', + role: UserRole.User, + }, + { + name: 'zulip', + role: UserRole.User, + }, +]; + +export const initialUserForm = { + id: '', + name: '', + email: '', + app_roles: initialAppRoles, + status: '', +}; diff --git a/src/components/Header/components/CurrentUserModal/types.ts b/src/components/Header/components/CurrentUserModal/types.ts index 00bc7d6..aa11c0e 100644 --- a/src/components/Header/components/CurrentUserModal/types.ts +++ b/src/components/Header/components/CurrentUserModal/types.ts @@ -3,5 +3,5 @@ import { User } from 'src/services/users'; export type UserModalProps = { open: boolean; onClose: () => void; - user: User; + user: User | null; }; diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 09eb629..157b728 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -148,26 +148,28 @@ export const Table = >({ ); }) ) : ( - -
-
- - - - + + +
+
+ + + + +
+

Loading users

-

Loading users

-
- + + )} diff --git a/src/modules/users/Users.tsx b/src/modules/users/Users.tsx index 899065a..6ba3265 100644 --- a/src/modules/users/Users.tsx +++ b/src/modules/users/Users.tsx @@ -5,6 +5,7 @@ import { CogIcon, TrashIcon } from '@heroicons/react/outline'; import { useUsers } from 'src/services/users'; import { Table } from 'src/components'; import { debounce } from 'lodash'; +import { useAuth } from 'src/services/auth'; import { UserModal } from './components/UserModal'; export const Users: React.FC = () => { @@ -13,6 +14,7 @@ export const Users: React.FC = () => { const [userId, setUserId] = useState(null); const [search, setSearch] = useState(''); const { users, loadUsers, userTableLoading } = useUsers(); + const { isAdmin } = useAuth(); const handleSearch = (event: any) => { setSearch(event.target.value); @@ -60,23 +62,27 @@ export const Users: React.FC = () => { Cell: (props: any) => { const { row } = props; - return ( -
- -
- ); + if (isAdmin) { + return ( +
+ +
+ ); + } + + return null; }, width: 'auto', }, ], - [], + [isAdmin], ); const selectedRows = useCallback((rows: Record) => { @@ -88,16 +94,19 @@ export const Users: React.FC = () => {

Users

-
- -
+ + {isAdmin && ( +
+ +
+ )}
@@ -153,7 +162,7 @@ export const Users: React.FC = () => {
- +
); diff --git a/src/modules/users/components/UserModal/UserModal.tsx b/src/modules/users/components/UserModal/UserModal.tsx index 5b3814b..b31d218 100644 --- a/src/modules/users/components/UserModal/UserModal.tsx +++ b/src/modules/users/components/UserModal/UserModal.tsx @@ -1,26 +1,27 @@ import React, { useEffect, useState } from 'react'; +import _ from 'lodash'; import { TrashIcon } from '@heroicons/react/outline'; -import { useForm } from 'react-hook-form'; -import { Modal, Banner, ConfirmationModal } from 'src/components'; +import { useFieldArray, useForm } from 'react-hook-form'; +import { Modal, ConfirmationModal } from 'src/components'; import { Input, Select } from 'src/components/Form'; import { User, UserRole, useUsers } from 'src/services/users'; import { useAuth } from 'src/services/auth'; -import { appAccessList } from './consts'; +import { appAccessList, initialUserForm } from './consts'; import { UserModalProps } from './types'; -export const UserModal = ({ open, onClose, userId }: UserModalProps) => { +export const UserModal = ({ open, onClose, userId, setUserId }: UserModalProps) => { const [deleteModal, setDeleteModal] = useState(false); - const { user, loadUser, editUserById, createNewUser, userModalLoading, deleteUserById } = useUsers(); - const { currentUser } = useAuth(); + const { user, loadUser, editUserById, createNewUser, userModalLoading, deleteUserById, clearSelectedUser } = + useUsers(); + const { currentUser, isAdmin } = useAuth(); const { control, reset, handleSubmit } = useForm({ - defaultValues: { - name: null, - email: null, - id: null, - role_id: null, - status: null, - }, + defaultValues: initialUserForm, + }); + + const { fields } = useFieldArray({ + control, + name: 'app_roles', }); useEffect(() => { @@ -28,19 +29,19 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => { loadUser(userId); } - reset({ name: null, email: null, id: null, status: null }); + reset(initialUserForm); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [userId]); + }, [userId, open]); useEffect(() => { - if (user) { + if (!_.isEmpty(user)) { reset(user); } return () => { - reset({ name: null, email: null, id: null, status: null }); + reset(initialUserForm); }; - }, [user, reset]); + }, [user, reset, open]); const handleSave = async () => { try { @@ -48,13 +49,14 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => { await handleSubmit((data) => editUserById(data))(); } else { await handleSubmit((data) => createNewUser(data))(); - reset({ name: null, email: null, id: null, status: null }); } - - onClose(); } catch (e: any) { // Continue } + + onClose(); + clearSelectedUser(); + setUserId(null); }; const handleKeyPress = (e: any) => { @@ -65,6 +67,8 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => { const handleClose = () => { onClose(); + clearSelectedUser(); + setUserId(null); }; const deleteModalOpen = () => setDeleteModal(true); @@ -74,6 +78,9 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => { if (userId) { deleteUserById(userId); } + + clearSelectedUser(); + setUserId(null); handleClose(); deleteModalClose(); }; @@ -87,7 +94,7 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => { isLoading={userModalLoading} leftActions={ userId && - user.email !== currentUser.email && ( + user.email !== currentUser?.email && (