From e44777f91904f71bf44e29cb3a2b168ce80c0255 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 26 Feb 2024 09:39:09 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Restructure=20folders,=20allow=20ed?= =?UTF-8?q?iting=20of=20users/items,=20and=20implement=20other=20refactors?= =?UTF-8?q?=20and=20improvements=20(#603)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reorganize project directory structure * Allow edit users/items, add useAuth and useCustomToast, password confirmation * Minor improvements for consistency * Add 'Cancel' button to UserInformation in editMode * Refactor UserSettings * Enable user password changes and improve error handling * Enable user information update * Add logout to Sidebar in mobile devices, conditional tabs depending on role and other improvements * Add badges * Remove comment * Appearance tab updates * Change badge color * Reset inputs when clicking on 'Cancel' button * Disable actions menu for Superuser when logged in * Modify Logout and update stores --- .../src/components/Admin/AddUser.tsx | 93 ++++++++++++++++ .../src/components/Admin/EditUser.tsx | 100 ++++++++++++++++++ .../components/{ => Common}/ActionsMenu.tsx | 22 ++-- .../Common}/DeleteAlert.tsx | 24 ++--- .../src/components/{ => Common}/Navbar.tsx | 4 +- .../src/components/Common/Sidebar.tsx | 71 +++++++++++++ .../components/{ => Common}/SidebarItems.tsx | 2 +- .../src/components/{ => Common}/UserMenu.tsx | 26 ++--- .../{modals => components/Items}/AddItem.tsx | 42 ++++---- .../src/components/Items/EditItem.tsx | 74 +++++++++++++ src/new-frontend/src/components/Sidebar.tsx | 59 ----------- .../components/UserSettings/Appearance.tsx | 29 +++++ .../UserSettings/ChangePassword.tsx | 58 ++++++++++ .../UserSettings}/DeleteAccount.tsx | 8 +- .../UserSettings}/DeleteConfirmation.tsx | 20 ++-- .../UserSettings/UserInformation.tsx | 88 +++++++++++++++ src/new-frontend/src/hooks/useAuth.tsx | 33 ++++++ src/new-frontend/src/hooks/useCustomToast.tsx | 20 ++++ src/new-frontend/src/modals/AddUser.tsx | 97 ----------------- src/new-frontend/src/modals/EditItem.tsx | 48 --------- src/new-frontend/src/modals/EditUser.tsx | 60 ----------- src/new-frontend/src/pages/Admin.tsx | 50 ++++----- src/new-frontend/src/pages/Dashboard.tsx | 4 +- src/new-frontend/src/pages/ErrorPage.tsx | 21 ++-- src/new-frontend/src/pages/Items.tsx | 35 +++--- src/new-frontend/src/pages/Layout.tsx | 33 ++---- src/new-frontend/src/pages/Login.tsx | 59 +++++------ .../src/pages/RecoverPassword.tsx | 14 +-- src/new-frontend/src/pages/UserSettings.tsx | 71 ++++++------- src/new-frontend/src/panels/Appearance.tsx | 28 ----- .../src/panels/ChangePassword.tsx | 35 ------ .../src/panels/UserInformation.tsx | 50 --------- src/new-frontend/src/store/items-store.tsx | 19 +++- src/new-frontend/src/store/user-store.tsx | 13 ++- src/new-frontend/src/store/users-store.tsx | 13 ++- 35 files changed, 802 insertions(+), 621 deletions(-) create mode 100644 src/new-frontend/src/components/Admin/AddUser.tsx create mode 100644 src/new-frontend/src/components/Admin/EditUser.tsx rename src/new-frontend/src/components/{ => Common}/ActionsMenu.tsx (52%) rename src/new-frontend/src/{modals => components/Common}/DeleteAlert.tsx (74%) rename src/new-frontend/src/components/{ => Common}/Navbar.tsx (94%) create mode 100644 src/new-frontend/src/components/Common/Sidebar.tsx rename src/new-frontend/src/components/{ => Common}/SidebarItems.tsx (96%) rename src/new-frontend/src/components/{ => Common}/UserMenu.tsx (50%) rename src/new-frontend/src/{modals => components/Items}/AddItem.tsx (64%) create mode 100644 src/new-frontend/src/components/Items/EditItem.tsx delete mode 100644 src/new-frontend/src/components/Sidebar.tsx create mode 100644 src/new-frontend/src/components/UserSettings/Appearance.tsx create mode 100644 src/new-frontend/src/components/UserSettings/ChangePassword.tsx rename src/new-frontend/src/{panels => components/UserSettings}/DeleteAccount.tsx (74%) rename src/new-frontend/src/{modals => components/UserSettings}/DeleteConfirmation.tsx (69%) create mode 100644 src/new-frontend/src/components/UserSettings/UserInformation.tsx create mode 100644 src/new-frontend/src/hooks/useAuth.tsx create mode 100644 src/new-frontend/src/hooks/useCustomToast.tsx delete mode 100644 src/new-frontend/src/modals/AddUser.tsx delete mode 100644 src/new-frontend/src/modals/EditItem.tsx delete mode 100644 src/new-frontend/src/modals/EditUser.tsx delete mode 100644 src/new-frontend/src/panels/Appearance.tsx delete mode 100644 src/new-frontend/src/panels/ChangePassword.tsx delete mode 100644 src/new-frontend/src/panels/UserInformation.tsx diff --git a/src/new-frontend/src/components/Admin/AddUser.tsx b/src/new-frontend/src/components/Admin/AddUser.tsx new file mode 100644 index 000000000..315d6ddfe --- /dev/null +++ b/src/new-frontend/src/components/Admin/AddUser.tsx @@ -0,0 +1,93 @@ +import React from 'react'; + +import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; +import { SubmitHandler, useForm } from 'react-hook-form'; + +import { UserCreate } from '../../client'; +import useCustomToast from '../../hooks/useCustomToast'; +import { useUsersStore } from '../../store/users-store'; +import { ApiError } from '../../client/core/ApiError'; + +interface AddUserProps { + isOpen: boolean; + onClose: () => void; +} + +interface UserCreateForm extends UserCreate { + confirmPassword: string; + +} + +const AddUser: React.FC = ({ isOpen, onClose }) => { + const showToast = useCustomToast(); + const { register, handleSubmit, reset, formState: { isSubmitting } } = useForm(); + const { addUser } = useUsersStore(); + + const onSubmit: SubmitHandler = async (data) => { + if (data.password === data.confirmPassword) { + try { + await addUser(data); + showToast('Success!', 'User created successfully.', 'success'); + reset(); + onClose(); + } catch (err) { + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); + } + } else { + // TODO: Complete when form validation is implemented + console.log("Passwords don't match") + } + } + + return ( + <> + + + + Add User + + + + Email + + + + Full name + + + + Set Password + + + + Confirm Password + + + + + Is superuser? + + + Is active? + + + + + + + + + + + ) +} + +export default AddUser; \ No newline at end of file diff --git a/src/new-frontend/src/components/Admin/EditUser.tsx b/src/new-frontend/src/components/Admin/EditUser.tsx new file mode 100644 index 000000000..465a856c5 --- /dev/null +++ b/src/new-frontend/src/components/Admin/EditUser.tsx @@ -0,0 +1,100 @@ +import React from 'react'; + +import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; +import { SubmitHandler, useForm } from 'react-hook-form'; + +import { ApiError, UserUpdate } from '../../client'; +import useCustomToast from '../../hooks/useCustomToast'; +import { useUsersStore } from '../../store/users-store'; + +interface EditUserProps { + user_id: number; + isOpen: boolean; + onClose: () => void; +} + +interface UserUpdateForm extends UserUpdate { + confirm_password: string; +} + +const EditUser: React.FC = ({ user_id, isOpen, onClose }) => { + const showToast = useCustomToast(); + const { register, handleSubmit, reset, formState: { isSubmitting } } = useForm(); + const { editUser, users } = useUsersStore(); + + const currentUser = users.find((user) => user.id === user_id); + + const onSubmit: SubmitHandler = async (data) => { + if (data.password === data.confirm_password) { + try { + await editUser(user_id, data); + showToast('Success!', 'User updated successfully.', 'success'); + reset(); + onClose(); + } catch (err) { + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); + } + } else { + // TODO: Complete when form validation is implemented + console.log("Passwords don't match") + } + } + + const onCancel = () => { + reset(); + onClose(); + } + + return ( + <> + + + + Edit User + + + + Email + + + + Full name + + + + Password + + + + Confirmation Password + + + + + Is superuser? + + + Is active? + + + + + + + + + + + + ) +} + +export default EditUser; \ No newline at end of file diff --git a/src/new-frontend/src/components/ActionsMenu.tsx b/src/new-frontend/src/components/Common/ActionsMenu.tsx similarity index 52% rename from src/new-frontend/src/components/ActionsMenu.tsx rename to src/new-frontend/src/components/Common/ActionsMenu.tsx index 2a3a2a7d5..267b48e2c 100644 --- a/src/new-frontend/src/components/ActionsMenu.tsx +++ b/src/new-frontend/src/components/Common/ActionsMenu.tsx @@ -2,33 +2,35 @@ import React from 'react'; import { Button, Menu, MenuButton, MenuItem, MenuList, useDisclosure } from '@chakra-ui/react'; import { BsThreeDotsVertical } from 'react-icons/bs'; -import { FiTrash, FiEdit } from 'react-icons/fi'; +import { FiEdit, FiTrash } from 'react-icons/fi'; + +import EditUser from '../Admin/EditUser'; +import EditItem from '../Items/EditItem'; +import Delete from './DeleteAlert'; -import Delete from '../modals/DeleteAlert'; -import EditUser from '../modals/EditUser'; -import EditItem from '../modals/EditItem'; interface ActionsMenuProps { type: string; id: number; + disabled: boolean; } -const ActionsMenu: React.FC = ({ type, id }) => { +const ActionsMenu: React.FC = ({ type, id, disabled }) => { const editUserModal = useDisclosure(); const deleteModal = useDisclosure(); return ( <> - } variant="unstyled"> + } variant='unstyled'> - }>Edit {type} - } color="ui.danger">Delete {type} + }>Edit {type} + } color='ui.danger'>Delete {type} { - type === "User" ? - : + type === 'User' ? + : } diff --git a/src/new-frontend/src/modals/DeleteAlert.tsx b/src/new-frontend/src/components/Common/DeleteAlert.tsx similarity index 74% rename from src/new-frontend/src/modals/DeleteAlert.tsx rename to src/new-frontend/src/components/Common/DeleteAlert.tsx index a86ee580a..68ec81c7b 100644 --- a/src/new-frontend/src/modals/DeleteAlert.tsx +++ b/src/new-frontend/src/components/Common/DeleteAlert.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; -import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button, useToast } from '@chakra-ui/react'; +import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; -import { useItemsStore } from '../store/items-store'; -import { useUsersStore } from '../store/users-store'; +import useCustomToast from '../../hooks/useCustomToast'; +import { useItemsStore } from '../../store/items-store'; +import { useUsersStore } from '../../store/users-store'; interface DeleteProps { type: string; @@ -14,7 +15,7 @@ interface DeleteProps { } const Delete: React.FC = ({ type, id, isOpen, onClose }) => { - const toast = useToast(); + const showToast = useCustomToast(); const cancelRef = React.useRef(null); const [isLoading, setIsLoading] = useState(false); const { handleSubmit } = useForm(); @@ -25,20 +26,10 @@ const Delete: React.FC = ({ type, id, isOpen, onClose }) => { setIsLoading(true); try { type === 'Item' ? await deleteItem(id) : await deleteUser(id); - toast({ - title: "Success", - description: `The ${type.toLowerCase()} was deleted successfully.`, - status: "success", - isClosable: true, - }); + showToast('Success', `The ${type.toLowerCase()} was deleted successfully.`, 'success'); onClose(); } catch (err) { - toast({ - title: "An error occurred.", - description: `An error occurred while deleting the ${type.toLowerCase()}.`, - status: "error", - isClosable: true, - }); + showToast('An error occurred.', `An error occurred while deleting the ${type.toLowerCase()}.`, 'error'); } finally { setIsLoading(false); } @@ -60,6 +51,7 @@ const Delete: React.FC = ({ type, id, isOpen, onClose }) => { + {type === 'User' && All items associated with this user will also be permantly deleted. } Are you sure? You will not be able to undo this action. diff --git a/src/new-frontend/src/components/Navbar.tsx b/src/new-frontend/src/components/Common/Navbar.tsx similarity index 94% rename from src/new-frontend/src/components/Navbar.tsx rename to src/new-frontend/src/components/Common/Navbar.tsx index bc130f1f7..a5f0b0d49 100644 --- a/src/new-frontend/src/components/Navbar.tsx +++ b/src/new-frontend/src/components/Common/Navbar.tsx @@ -3,8 +3,8 @@ import React from 'react'; import { Button, Flex, Icon, Input, InputGroup, InputLeftElement, useDisclosure } from '@chakra-ui/react'; import { FaPlus, FaSearch } from "react-icons/fa"; -import AddUser from '../modals/AddUser'; -import AddItem from '../modals/AddItem'; +import AddUser from '../Admin/AddUser'; +import AddItem from '../Items/AddItem'; interface NavbarProps { type: string; diff --git a/src/new-frontend/src/components/Common/Sidebar.tsx b/src/new-frontend/src/components/Common/Sidebar.tsx new file mode 100644 index 000000000..c89158d11 --- /dev/null +++ b/src/new-frontend/src/components/Common/Sidebar.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, Text, useColorModeValue, useDisclosure } from '@chakra-ui/react'; +import { FiLogOut, FiMenu } from 'react-icons/fi'; +import { useNavigate } from 'react-router-dom'; + +import Logo from '../../assets/images/fastapi-logo.svg'; +import useAuth from '../../hooks/useAuth'; +import { useUserStore } from '../../store/user-store'; +import SidebarItems from './SidebarItems'; + +const Sidebar: React.FC = () => { + const bgColor = useColorModeValue('white', '#1a202c'); + const textColor = useColorModeValue('gray', 'white'); + const secBgColor = useColorModeValue('ui.secondary', '#252d3d'); + const { isOpen, onOpen, onClose } = useDisclosure(); + const { user } = useUserStore(); + const { logout } = useAuth(); + const navigate = useNavigate(); + + const handleLogout = async () => { + logout() + navigate('/login'); + }; + + + return ( + <> + {/* Mobile */} + } /> + + + + + + + + logo + + + + Log out + + + { + user?.email && + Logged in as: {user.email} + } + + + + + + {/* Desktop */} + + + + Logo + + + { + user?.email && + Logged in as: {user.email} + } + + + + ); +} + +export default Sidebar; diff --git a/src/new-frontend/src/components/SidebarItems.tsx b/src/new-frontend/src/components/Common/SidebarItems.tsx similarity index 96% rename from src/new-frontend/src/components/SidebarItems.tsx rename to src/new-frontend/src/components/Common/SidebarItems.tsx index 19c1d93ce..c6d71fef5 100644 --- a/src/new-frontend/src/components/SidebarItems.tsx +++ b/src/new-frontend/src/components/Common/SidebarItems.tsx @@ -4,7 +4,7 @@ import { Box, Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react'; import { FiBriefcase, FiHome, FiSettings, FiUsers } from 'react-icons/fi'; import { Link, useLocation } from 'react-router-dom'; -import { useUserStore } from '../store/user-store'; +import { useUserStore } from '../../store/user-store'; const items = [ { icon: FiHome, title: 'Dashboard', path: "/" }, diff --git a/src/new-frontend/src/components/UserMenu.tsx b/src/new-frontend/src/components/Common/UserMenu.tsx similarity index 50% rename from src/new-frontend/src/components/UserMenu.tsx rename to src/new-frontend/src/components/Common/UserMenu.tsx index 7f515fc3e..137968acc 100644 --- a/src/new-frontend/src/components/UserMenu.tsx +++ b/src/new-frontend/src/components/Common/UserMenu.tsx @@ -1,38 +1,38 @@ import React from 'react'; -import { IconButton } from '@chakra-ui/button'; -import { Box } from '@chakra-ui/layout'; -import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu'; +import { Box, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react'; import { FaUserAstronaut } from 'react-icons/fa'; import { FiLogOut, FiUser } from 'react-icons/fi'; -import { useNavigate } from 'react-router'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; + +import useAuth from '../../hooks/useAuth'; const UserMenu: React.FC = () => { const navigate = useNavigate(); + const { logout } = useAuth(); const handleLogout = async () => { - localStorage.removeItem("access_token"); - navigate("/login"); - // TODO: reset all Zustand states + logout() + navigate('/login'); }; return ( <> - + {/* Desktop */} + } - bg="ui.main" + icon={} + bg='ui.main' isRound /> - } as={Link} to="settings"> + } as={Link} to='settings'> My profile - } onClick={handleLogout} color="ui.danger" fontWeight="bold"> + } onClick={handleLogout} color='ui.danger' fontWeight='bold'> Log out diff --git a/src/new-frontend/src/modals/AddItem.tsx b/src/new-frontend/src/components/Items/AddItem.tsx similarity index 64% rename from src/new-frontend/src/modals/AddItem.tsx rename to src/new-frontend/src/components/Items/AddItem.tsx index c7967019c..99fc621c2 100644 --- a/src/new-frontend/src/modals/AddItem.tsx +++ b/src/new-frontend/src/components/Items/AddItem.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; -import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useToast } from '@chakra-ui/react'; +import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { ItemCreate } from '../client'; -import { useItemsStore } from '../store/items-store'; +import { ApiError, ItemCreate } from '../../client'; +import useCustomToast from '../../hooks/useCustomToast'; +import { useItemsStore } from '../../store/items-store'; interface AddItemProps { isOpen: boolean; @@ -12,7 +13,7 @@ interface AddItemProps { } const AddItem: React.FC = ({ isOpen, onClose }) => { - const toast = useToast(); + const showToast = useCustomToast(); const [isLoading, setIsLoading] = useState(false); const { register, handleSubmit, reset } = useForm(); const { addItem } = useItemsStore(); @@ -21,21 +22,12 @@ const AddItem: React.FC = ({ isOpen, onClose }) => { setIsLoading(true); try { await addItem(data); - toast({ - title: 'Success!', - description: 'Item created successfully.', - status: 'success', - isClosable: true, - }); + showToast('Success!', 'Item created successfully.', 'success'); reset(); onClose(); } catch (err) { - toast({ - title: 'Something went wrong.', - description: 'Failed to create item. Please try again.', - status: 'error', - isClosable: true, - }); + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); } finally { setIsLoading(false); } @@ -50,30 +42,32 @@ const AddItem: React.FC = ({ isOpen, onClose }) => { isCentered > - + Add Item - Title + Title - Description + Description - + + + + + + ) +} + +export default EditItem; \ No newline at end of file diff --git a/src/new-frontend/src/components/Sidebar.tsx b/src/new-frontend/src/components/Sidebar.tsx deleted file mode 100644 index 8450fb0a2..000000000 --- a/src/new-frontend/src/components/Sidebar.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; - -import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, useDisclosure, Text, useColorModeValue } from '@chakra-ui/react'; -import { FiMenu } from 'react-icons/fi'; - -import Logo from "../assets/images/fastapi-logo.svg"; -import SidebarItems from './SidebarItems'; -import { useUserStore } from '../store/user-store'; - - -const Sidebar: React.FC = () => { - const bgColor = useColorModeValue("white", "#1a202c"); - const textColor = useColorModeValue("gray", "white"); - const secBgColor = useColorModeValue("ui.secondary", "#252d3d"); - - const { isOpen, onOpen, onClose } = useDisclosure(); - const { user } = useUserStore(); - - return ( - <> - {/* Mobile */} - } /> - - - - - - - - Logo - - - { - user?.email && - Logged in as: {user.email} - } - - - - - - {/* Desktop */} - - - - Logo - - - { - user?.email && - Logged in as: {user.email} - } - - - - ); -} - -export default Sidebar; diff --git a/src/new-frontend/src/components/UserSettings/Appearance.tsx b/src/new-frontend/src/components/UserSettings/Appearance.tsx new file mode 100644 index 000000000..9659dd158 --- /dev/null +++ b/src/new-frontend/src/components/UserSettings/Appearance.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import { Badge, Container, Heading, Radio, RadioGroup, Stack, useColorMode } from '@chakra-ui/react'; + +const Appearance: React.FC = () => { + const { colorMode, toggleColorMode } = useColorMode(); + + return ( + <> + + + Appearance + + + + {/* TODO: Add system default option */} + + Light ModeDefault + + + Dark Mode + + + + + + ); +} +export default Appearance; \ No newline at end of file diff --git a/src/new-frontend/src/components/UserSettings/ChangePassword.tsx b/src/new-frontend/src/components/UserSettings/ChangePassword.tsx new file mode 100644 index 000000000..eb7ca0ad3 --- /dev/null +++ b/src/new-frontend/src/components/UserSettings/ChangePassword.tsx @@ -0,0 +1,58 @@ +import React from 'react'; + +import { Box, Button, Container, FormControl, FormLabel, Heading, Input, useColorModeValue } from '@chakra-ui/react'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { ApiError, UpdatePassword } from '../../client'; +import useCustomToast from '../../hooks/useCustomToast'; +import { useUserStore } from '../../store/user-store'; + +interface UpdatePasswordForm extends UpdatePassword { + confirm_password: string; +} + +const ChangePassword: React.FC = () => { + const color = useColorModeValue('gray.700', 'white'); + const showToast = useCustomToast(); + const { register, handleSubmit, reset, formState: { isSubmitting } } = useForm(); + const { editPassword } = useUserStore(); + + const onSubmit: SubmitHandler = async (data) => { + try { + await editPassword(data); + showToast('Success!', 'Password updated.', 'success'); + reset(); + } catch (err) { + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); + } + + } + + return ( + <> + + + Change Password + + + + Current password + + + + New password + + + + Confirm new password + + + + + + + ); +} +export default ChangePassword; \ No newline at end of file diff --git a/src/new-frontend/src/panels/DeleteAccount.tsx b/src/new-frontend/src/components/UserSettings/DeleteAccount.tsx similarity index 74% rename from src/new-frontend/src/panels/DeleteAccount.tsx rename to src/new-frontend/src/components/UserSettings/DeleteAccount.tsx index 9852c402e..4c149d778 100644 --- a/src/new-frontend/src/panels/DeleteAccount.tsx +++ b/src/new-frontend/src/components/UserSettings/DeleteAccount.tsx @@ -2,21 +2,21 @@ import React from 'react'; import { Button, Container, Heading, Text, useDisclosure } from '@chakra-ui/react'; -import DeleteConfirmation from '../modals/DeleteConfirmation'; +import DeleteConfirmation from './DeleteConfirmation'; const DeleteAccount: React.FC = () => { const confirmationModal = useDisclosure(); return ( <> - - + + Delete Account Are you sure you want to delete your account? This action cannot be undone. - diff --git a/src/new-frontend/src/modals/DeleteConfirmation.tsx b/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx similarity index 69% rename from src/new-frontend/src/modals/DeleteConfirmation.tsx rename to src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx index 2f9cf9ca0..521b144e2 100644 --- a/src/new-frontend/src/modals/DeleteConfirmation.tsx +++ b/src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx @@ -1,7 +1,8 @@ import React, { useState } from 'react'; -import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button, useToast } from '@chakra-ui/react'; +import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; +import useCustomToast from '../../hooks/useCustomToast'; interface DeleteProps { isOpen: boolean; @@ -9,7 +10,7 @@ interface DeleteProps { } const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { - const toast = useToast(); + const showToast = useCustomToast(); const cancelRef = React.useRef(null); const [isLoading, setIsLoading] = useState(false); const { handleSubmit } = useForm(); @@ -20,12 +21,7 @@ const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { // TODO: Delete user account when API is ready onClose(); } catch (err) { - toast({ - title: "An error occurred.", - description: `An error occurred while deleting your account.`, - status: "error", - isClosable: true, - }); + showToast('An error occurred', 'An error occurred while deleting your account.', 'error'); } finally { setIsLoading(false); } @@ -37,21 +33,21 @@ const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { isOpen={isOpen} onClose={onClose} leastDestructiveRef={cancelRef} - size={{ base: "sm", md: "md" }} + size={{ base: 'sm', md: 'md' }} isCentered > - + Confirmation Required - All your account data will be permanently deleted. If you're sure, please click 'Confirm' to proceed. + All your account data will be permanently deleted. If you're sure, please click 'Confirm' to proceed. - + {editMode && + } + + + + + ); +} + +export default UserInformation; \ No newline at end of file diff --git a/src/new-frontend/src/hooks/useAuth.tsx b/src/new-frontend/src/hooks/useAuth.tsx new file mode 100644 index 000000000..279b29910 --- /dev/null +++ b/src/new-frontend/src/hooks/useAuth.tsx @@ -0,0 +1,33 @@ +import { useUserStore } from '../store/user-store'; +import { Body_login_login_access_token as AccessToken, LoginService } from '../client'; +import { useUsersStore } from '../store/users-store'; +import { useItemsStore } from '../store/items-store'; + +const useAuth = () => { + const { user, getUser, resetUser } = useUserStore(); + const { resetUsers } = useUsersStore(); + const { resetItems } = useItemsStore(); + + const login = async (data: AccessToken) => { + const response = await LoginService.loginAccessToken({ + formData: data, + }); + localStorage.setItem('access_token', response.access_token); + await getUser(); + }; + + const logout = () => { + localStorage.removeItem('access_token'); + resetUser(); + resetUsers(); + resetItems(); + }; + + const isLoggedIn = () => { + return user !== null; + }; + + return { login, logout, isLoggedIn }; +} + +export default useAuth; \ No newline at end of file diff --git a/src/new-frontend/src/hooks/useCustomToast.tsx b/src/new-frontend/src/hooks/useCustomToast.tsx new file mode 100644 index 000000000..41e4e7884 --- /dev/null +++ b/src/new-frontend/src/hooks/useCustomToast.tsx @@ -0,0 +1,20 @@ +import { useCallback } from 'react'; + +import { useToast } from '@chakra-ui/react'; + +const useCustomToast = () => { + const toast = useToast(); + + const showToast = useCallback((title: string, description: string, status: 'success' | 'error') => { + toast({ + title, + description, + status, + isClosable: true, + }); + }, [toast]); + + return showToast; +}; + +export default useCustomToast; \ No newline at end of file diff --git a/src/new-frontend/src/modals/AddUser.tsx b/src/new-frontend/src/modals/AddUser.tsx deleted file mode 100644 index 33e38b21a..000000000 --- a/src/new-frontend/src/modals/AddUser.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React, { useState } from 'react'; - -import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useToast } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; - -import { UserCreate } from '../client'; -import { useUsersStore } from '../store/users-store'; - -interface AddUserProps { - isOpen: boolean; - onClose: () => void; -} - -const AddUser: React.FC = ({ isOpen, onClose }) => { - const toast = useToast(); - const [isLoading, setIsLoading] = useState(false); - const { register, handleSubmit, reset } = useForm(); - const { addUser } = useUsersStore(); - - const onSubmit: SubmitHandler = async (data) => { - setIsLoading(true); - try { - await addUser(data); - toast({ - title: 'Success!', - description: 'User created successfully.', - status: 'success', - isClosable: true, - }); - reset(); - onClose(); - - } catch (err) { - toast({ - title: 'Something went wrong.', - description: 'Failed to create user. Please try again.', - status: 'error', - isClosable: true, - }); - } finally { - setIsLoading(false); - } - } - - return ( - <> - - - - {/* TODO: Check passwords */} - Add User - - - - Email - - - - Full name - - - - Set Password - - - - Confirm Password - - - - - Is superuser? - - - Is active? - - - - - - - - - - - - ) -} - -export default AddUser; \ No newline at end of file diff --git a/src/new-frontend/src/modals/EditItem.tsx b/src/new-frontend/src/modals/EditItem.tsx deleted file mode 100644 index 73753bf46..000000000 --- a/src/new-frontend/src/modals/EditItem.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; - -import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; - -interface EditItemProps { - isOpen: boolean; - onClose: () => void; -} - -const EditItem: React.FC = ({ isOpen, onClose }) => { - - return ( - <> - - - - Edit Item - - - - Item - - - - - Description - - - - - - - - - - - - ) -} - -export default EditItem; \ No newline at end of file diff --git a/src/new-frontend/src/modals/EditUser.tsx b/src/new-frontend/src/modals/EditUser.tsx deleted file mode 100644 index 385d39f03..000000000 --- a/src/new-frontend/src/modals/EditUser.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; - -import { Button, Checkbox, Flex, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useDisclosure } from '@chakra-ui/react'; - -interface EditUserProps { - isOpen: boolean; - onClose: () => void; -} - -const EditUser: React.FC = ({ isOpen, onClose }) => { - - return ( - <> - - - - Edit User - - - - Email - - - - - Full name - - - - Password - - - - - Is superuser? - - - Is active? - - - - - - - - - - - - ) -} - -export default EditUser; \ No newline at end of file diff --git a/src/new-frontend/src/pages/Admin.tsx b/src/new-frontend/src/pages/Admin.tsx index ee97f863f..69ec315cd 100644 --- a/src/new-frontend/src/pages/Admin.tsx +++ b/src/new-frontend/src/pages/Admin.tsx @@ -1,15 +1,19 @@ import React, { useEffect, useState } from 'react'; -import { Box, Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr, useToast } from '@chakra-ui/react'; +import { Badge, Box, Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; -import ActionsMenu from '../components/ActionsMenu'; -import Navbar from '../components/Navbar'; +import { ApiError } from '../client'; +import ActionsMenu from '../components/Common/ActionsMenu'; +import Navbar from '../components/Common/Navbar'; +import useCustomToast from '../hooks/useCustomToast'; +import { useUserStore } from '../store/user-store'; import { useUsersStore } from '../store/users-store'; const Admin: React.FC = () => { - const toast = useToast(); + const showToast = useCustomToast(); const [isLoading, setIsLoading] = useState(false); const { users, getUsers } = useUsersStore(); + const { user: currentUser } = useUserStore(); useEffect(() => { const fetchUsers = async () => { @@ -17,12 +21,8 @@ const Admin: React.FC = () => { try { await getUsers(); } catch (err) { - toast({ - title: 'Something went wrong.', - description: 'Failed to fetch users. Please try again.', - status: 'error', - isClosable: true, - }); + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); } finally { setIsLoading(false); } @@ -36,18 +36,18 @@ const Admin: React.FC = () => { <> {isLoading ? ( // TODO: Add skeleton - - + + ) : ( users && - - + + User Management - + - +
@@ -60,23 +60,23 @@ const Admin: React.FC = () => { {users.map((user) => ( - + - + ))} diff --git a/src/new-frontend/src/pages/Dashboard.tsx b/src/new-frontend/src/pages/Dashboard.tsx index c5b452c7d..9bbb46a08 100644 --- a/src/new-frontend/src/pages/Dashboard.tsx +++ b/src/new-frontend/src/pages/Dashboard.tsx @@ -10,8 +10,8 @@ const Dashboard: React.FC = () => { return ( <> - - Hi, {user?.full_name || user?.email} 👋🏼 + + Hi, {user?.full_name || user?.email} 👋🏼 Welcome back, nice to see you again! diff --git a/src/new-frontend/src/pages/ErrorPage.tsx b/src/new-frontend/src/pages/ErrorPage.tsx index f8bd796ec..cc744df2c 100644 --- a/src/new-frontend/src/pages/ErrorPage.tsx +++ b/src/new-frontend/src/pages/ErrorPage.tsx @@ -1,6 +1,5 @@ -import { Button, Container, Text } from "@chakra-ui/react"; - -import { Link, useRouteError } from "react-router-dom"; +import { Button, Container, Text } from '@chakra-ui/react'; +import { Link, useRouteError } from 'react-router-dom'; const ErrorPage: React.FC = () => { const error = useRouteError(); @@ -8,14 +7,14 @@ const ErrorPage: React.FC = () => { return ( <> - - Oops! - Houston, we have a problem. - An unexpected error has occurred. - {error.statusText || error.message} - + + Oops! + Houston, we have a problem. + An unexpected error has occurred. + {/* {error.statusText || error.message} */} + ); diff --git a/src/new-frontend/src/pages/Items.tsx b/src/new-frontend/src/pages/Items.tsx index e3e273b0c..3aab0d21a 100644 --- a/src/new-frontend/src/pages/Items.tsx +++ b/src/new-frontend/src/pages/Items.tsx @@ -1,14 +1,15 @@ import React, { useEffect, useState } from 'react'; -import { Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr, useToast } from '@chakra-ui/react'; +import { Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; -import ActionsMenu from '../components/ActionsMenu'; -import Navbar from '../components/Navbar'; +import { ApiError } from '../client'; +import ActionsMenu from '../components/Common/ActionsMenu'; +import Navbar from '../components/Common/Navbar'; +import useCustomToast from '../hooks/useCustomToast'; import { useItemsStore } from '../store/items-store'; - const Items: React.FC = () => { - const toast = useToast(); + const showToast = useCustomToast(); const [isLoading, setIsLoading] = useState(false); const { items, getItems } = useItemsStore(); @@ -18,12 +19,8 @@ const Items: React.FC = () => { try { await getItems(); } catch (err) { - toast({ - title: 'Something went wrong.', - description: 'Failed to fetch items. Please try again.', - status: 'error', - isClosable: true, - }); + const errDetail = (err as ApiError).body.detail; + showToast('Something went wrong.', `${errDetail}`, 'error'); } finally { setIsLoading(false); } @@ -38,18 +35,18 @@ const Items: React.FC = () => { <> {isLoading ? ( // TODO: Add skeleton - - + + ) : ( items && - - + + Items Management - + -
Full name
{user.full_name || "N/A"}{user.full_name || 'N/A'}{currentUser?.id === user.id && You} {user.email}{user.is_superuser ? "Superuser" : "User"}{user.is_superuser ? 'Superuser' : 'User'} - {user.is_active ? "Active" : "Inactive"} + {user.is_active ? 'Active' : 'Inactive'} - +
+
@@ -63,9 +60,9 @@ const Items: React.FC = () => { - + ))} diff --git a/src/new-frontend/src/pages/Layout.tsx b/src/new-frontend/src/pages/Layout.tsx index 06471fb05..db2815001 100644 --- a/src/new-frontend/src/pages/Layout.tsx +++ b/src/new-frontend/src/pages/Layout.tsx @@ -1,37 +1,26 @@ -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; +import { Flex } from '@chakra-ui/react'; import { Outlet } from 'react-router-dom'; -import Sidebar from '../components/Sidebar'; -import { Flex, useToast } from '@chakra-ui/react'; +import Sidebar from '../components/Common/Sidebar'; +import UserMenu from '../components/Common/UserMenu'; import { useUserStore } from '../store/user-store'; -import UserMenu from '../components/UserMenu'; const Layout: React.FC = () => { - const toast = useToast(); const { getUser } = useUserStore(); useEffect(() => { - const fetchUser = async () => { - const token = localStorage.getItem('access_token'); - if (token) { - try { - await getUser(); - } catch (err) { - toast({ - title: 'Something went wrong.', - description: 'Failed to fetch user. Please try again.', - status: 'error', - isClosable: true, - }); - } - } + const token = localStorage.getItem('access_token'); + if (token) { + (async () => { + await getUser(); + })(); } - fetchUser(); - }, []); + }, [getUser]); return ( - + diff --git a/src/new-frontend/src/pages/Login.tsx b/src/new-frontend/src/pages/Login.tsx index b9e89803e..83053ddc3 100644 --- a/src/new-frontend/src/pages/Login.tsx +++ b/src/new-frontend/src/pages/Login.tsx @@ -1,68 +1,67 @@ -import React from "react"; +import React from 'react'; -import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; -import { Button, Center, Container, FormControl, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from "@chakra-ui/react"; -import { SubmitHandler, useForm } from "react-hook-form"; -import { Link as ReactRouterLink, useNavigate } from "react-router-dom"; +import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'; +import { Button, Center, Container, FormControl, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from '@chakra-ui/react'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { Link as ReactRouterLink, useNavigate } from 'react-router-dom'; -import Logo from "../assets/images/fastapi-logo.svg"; -import { LoginService } from "../client"; -import { Body_login_login_access_token as AccessToken } from "../client/models/Body_login_login_access_token"; +import Logo from '../assets/images/fastapi-logo.svg'; +import { Body_login_login_access_token as AccessToken } from '../client/models/Body_login_login_access_token'; +import useAuth from '../hooks/useAuth'; const Login: React.FC = () => { const [show, setShow] = useBoolean(); const navigate = useNavigate(); const { register, handleSubmit } = useForm(); + const { login } = useAuth(); + const onSubmit: SubmitHandler = async (data) => { - const response = await LoginService.loginAccessToken({ - formData: data, - }); - localStorage.setItem("access_token", response.access_token); - navigate("/"); + await login(data); + navigate('/'); }; return ( <> - - - + + + - + - + {show ? : }
- + Forgot password?
-
diff --git a/src/new-frontend/src/pages/RecoverPassword.tsx b/src/new-frontend/src/pages/RecoverPassword.tsx index 23c44124b..cbe8ff77e 100644 --- a/src/new-frontend/src/pages/RecoverPassword.tsx +++ b/src/new-frontend/src/pages/RecoverPassword.tsx @@ -1,9 +1,10 @@ import React from "react"; -import { Button, Container, FormControl, Heading, Input, Text, useToast } from "@chakra-ui/react"; +import { Button, Container, FormControl, Heading, Input, Text } from "@chakra-ui/react"; import { SubmitHandler, useForm } from "react-hook-form"; import { LoginService } from "../client"; +import useCustomToast from "../hooks/useCustomToast"; interface FormData { email: string; @@ -11,20 +12,15 @@ interface FormData { const RecoverPassword: React.FC = () => { const { register, handleSubmit } = useForm(); - const toast = useToast(); + const showToast = useCustomToast(); const onSubmit: SubmitHandler = async (data) => { const response = await LoginService.recoverPassword({ email: data.email, }); - console.log(response); + console.log(response) - toast({ - title: "Email sent.", - description: "We sent an email with a link to get back into your account.", - status: "success", - isClosable: true, - }); + showToast("Email sent.", "We sent an email with a link to get back into your account.", "success"); }; return ( diff --git a/src/new-frontend/src/pages/UserSettings.tsx b/src/new-frontend/src/pages/UserSettings.tsx index 49c551ae6..ea096cf77 100644 --- a/src/new-frontend/src/pages/UserSettings.tsx +++ b/src/new-frontend/src/pages/UserSettings.tsx @@ -1,49 +1,46 @@ import React from 'react'; import { Container, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react'; +import Appearance from '../components/UserSettings/Appearance'; +import ChangePassword from '../components/UserSettings/ChangePassword'; +import DeleteAccount from '../components/UserSettings/DeleteAccount'; +import UserInformation from '../components/UserSettings/UserInformation'; +import { useUserStore } from '../store/user-store'; + +const tabsConfig = [ + { title: 'My profile', component: UserInformation }, + { title: 'Password', component: ChangePassword }, + { title: 'Appearance', component: Appearance }, + { title: 'Danger zone', component: DeleteAccount }, +]; -import Appearance from '../panels/Appearance'; -import ChangePassword from '../panels/ChangePassword'; -import DeleteAccount from '../panels/DeleteAccount'; -import UserInformation from '../panels/UserInformation'; - +const UserSettings: React.FC = () => { + const { user } = useUserStore(); + const finalTabs = user?.is_superuser ? tabsConfig.slice(0, 3) : tabsConfig; -const UserSettings: React.FC = () => { return ( - <> - - - User Settings - - - - My profile - Password - Appearance - Danger zone - - - - - - - + + + User Settings + + + + {finalTabs.map((tab, index) => ( + {tab.title} + ))} + + + {finalTabs.map((tab, index) => ( + + - - - - - - - - - - - + ))} + + + ); }; -export default UserSettings; - +export default UserSettings; \ No newline at end of file diff --git a/src/new-frontend/src/panels/Appearance.tsx b/src/new-frontend/src/panels/Appearance.tsx deleted file mode 100644 index 07f988069..000000000 --- a/src/new-frontend/src/panels/Appearance.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import { Container, Heading, Radio, RadioGroup, Stack, useColorMode } from '@chakra-ui/react'; - -const Appearance: React.FC = () => { - const { colorMode, toggleColorMode } = useColorMode(); - - return ( - <> - - - Appearance - - - - - Light (default) - - - Dark - - - - - - ); -} -export default Appearance; \ No newline at end of file diff --git a/src/new-frontend/src/panels/ChangePassword.tsx b/src/new-frontend/src/panels/ChangePassword.tsx deleted file mode 100644 index 19bef94d6..000000000 --- a/src/new-frontend/src/panels/ChangePassword.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; - -import { Box, Button, Container, FormControl, FormLabel, Heading, Input, useColorModeValue } from '@chakra-ui/react'; - -const ChangePassword: React.FC = () => { - const color = useColorModeValue("gray.700", "white"); - - return ( - <> - - - Change Password - - - - Old password - - - - New password - - - - Confirm new password - - - - - - - ); -} -export default ChangePassword; \ No newline at end of file diff --git a/src/new-frontend/src/panels/UserInformation.tsx b/src/new-frontend/src/panels/UserInformation.tsx deleted file mode 100644 index ff7391250..000000000 --- a/src/new-frontend/src/panels/UserInformation.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useState } from 'react'; - -import { Button, Container, FormControl, FormLabel, Heading, Input, Text, useColorModeValue } from '@chakra-ui/react'; - -import { useUserStore } from '../store/user-store'; - -const UserInformation: React.FC = () => { - const color = useColorModeValue("gray.700", "white"); - const [editMode, setEditMode] = useState(false); - const { user } = useUserStore(); - - - const toggleEditMode = () => { - setEditMode(!editMode); - }; - - return ( - <> - - - User Information - - - Full name - { - editMode ? - : - - {user?.full_name || "N/A"} - - } - - - Email - { - editMode ? - : - - {user?.email || "N/A"} - - } - - - - - ); -} -export default UserInformation; \ No newline at end of file diff --git a/src/new-frontend/src/store/items-store.tsx b/src/new-frontend/src/store/items-store.tsx index dbd6ac708..9848d6919 100644 --- a/src/new-frontend/src/store/items-store.tsx +++ b/src/new-frontend/src/store/items-store.tsx @@ -1,11 +1,13 @@ -import { create } from "zustand"; -import { ItemCreate, ItemOut, ItemsService } from "../client"; +import { create } from 'zustand'; +import { ItemCreate, ItemOut, ItemUpdate, ItemsService } from '../client'; -interface ItemsStore { +interface ItemsStore { items: ItemOut[]; getItems: () => Promise; addItem: (item: ItemCreate) => Promise; + editItem: (id: number, item: ItemUpdate) => Promise; deleteItem: (id: number) => Promise; + resetItems: () => void; } export const useItemsStore = create((set) => ({ @@ -15,11 +17,20 @@ export const useItemsStore = create((set) => ({ set({ items: itemsResponse }); }, addItem: async (item: ItemCreate) => { - const itemResponse = await ItemsService.createItem({ requestBody: item}); + const itemResponse = await ItemsService.createItem({ requestBody: item }); set((state) => ({ items: [...state.items, itemResponse] })); }, + editItem: async (id: number, item: ItemUpdate) => { + const itemResponse = await ItemsService.updateItem({ id: id, requestBody: item }); + set((state) => ({ + items: state.items.map((item) => (item.id === id ? itemResponse : item)) + })); + }, deleteItem: async (id: number) => { await ItemsService.deleteItem({ id }); set((state) => ({ items: state.items.filter((item) => item.id !== id) })); + }, + resetItems: () => { + set({ items: [] }); } })); \ No newline at end of file diff --git a/src/new-frontend/src/store/user-store.tsx b/src/new-frontend/src/store/user-store.tsx index 0d4312de2..4e41dd993 100644 --- a/src/new-frontend/src/store/user-store.tsx +++ b/src/new-frontend/src/store/user-store.tsx @@ -1,9 +1,11 @@ -import { create } from "zustand"; -import { UserOut, UsersService } from "../client"; +import { create } from 'zustand'; +import { UpdatePassword, UserOut, UserUpdateMe, UsersService } from '../client'; interface UserStore { user: UserOut | null; getUser: () => Promise; + editUser: (user: UserUpdateMe) => Promise; + editPassword: (password: UpdatePassword) => Promise; resetUser: () => void; } @@ -13,6 +15,13 @@ export const useUserStore = create((set) => ({ const user = await UsersService.readUserMe(); set({ user }); }, + editUser: async (user: UserUpdateMe) => { + const updatedUser = await UsersService.updateUserMe({ requestBody: user }); + set((state) => ({ user: { ...state.user, ...updatedUser } })); + }, + editPassword: async (password: UpdatePassword) => { + await UsersService.updatePasswordMe({ requestBody: password }); + }, resetUser: () => { set({ user: null }); } diff --git a/src/new-frontend/src/store/users-store.tsx b/src/new-frontend/src/store/users-store.tsx index eb3ee61e7..4a6cf66bd 100644 --- a/src/new-frontend/src/store/users-store.tsx +++ b/src/new-frontend/src/store/users-store.tsx @@ -1,11 +1,13 @@ import { create } from "zustand"; -import { UserCreate, UserOut, UsersService } from "../client"; +import { UserCreate, UserOut, UserUpdate, UsersService } from "../client"; interface UsersStore { users: UserOut[]; getUsers: () => Promise; addUser: (user: UserCreate) => Promise; + editUser: (id: number, user: UserUpdate) => Promise; deleteUser: (id: number) => Promise; + resetUsers: () => void; } export const useUsersStore = create((set) => ({ @@ -18,8 +20,17 @@ export const useUsersStore = create((set) => ({ const userResponse = await UsersService.createUser({ requestBody: user }); set((state) => ({ users: [...state.users, userResponse] })); }, + editUser: async (id: number, user: UserUpdate) => { + const userResponse = await UsersService.updateUser({ userId: id, requestBody: user }); + set((state) => ({ + users: state.users.map((user) => (user.id === id ? userResponse : user)) + })); + }, deleteUser: async (id: number) => { await UsersService.deleteUser({ userId: id }); set((state) => ({ users: state.users.filter((user) => user.id !== id) })); + }, + resetUsers: () => { + set({ users: [] }); } })) \ No newline at end of file
ID
{item.id} {item.title}{item.description || "N/A"}{item.description || 'N/A'} - +