From 2d138b46229bd4727d54c187895abd610c92551d Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:58:36 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Format=20with=20Prettier=20(#646?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- new-frontend/src/components/Admin/AddUser.tsx | 279 +++++++++++------- .../src/components/Admin/EditUser.tsx | 261 ++++++++++------ .../src/components/Common/ActionsMenu.tsx | 102 ++++--- .../src/components/Common/DeleteAlert.tsx | 171 ++++++----- new-frontend/src/components/Common/Navbar.tsx | 54 ++-- .../src/components/Common/NotFound.tsx | 56 ++-- .../src/components/Common/Sidebar.tsx | 171 +++++++---- .../src/components/Common/SidebarItems.tsx | 101 +++---- .../src/components/Common/UserMenu.tsx | 90 +++--- new-frontend/src/components/Items/AddItem.tsx | 197 +++++++------ .../src/components/Items/EditItem.tsx | 183 +++++++----- .../components/UserSettings/Appearance.tsx | 60 ++-- .../UserSettings/ChangePassword.tsx | 189 ++++++++---- .../components/UserSettings/DeleteAccount.tsx | 59 ++-- .../UserSettings/DeleteConfirmation.tsx | 177 ++++++----- .../UserSettings/UserInformation.tsx | 231 +++++++++------ new-frontend/src/hooks/useAuth.ts | 55 ++-- new-frontend/src/hooks/useCustomToast.ts | 34 ++- new-frontend/src/main.tsx | 22 +- new-frontend/src/routes/__root.tsx | 17 +- new-frontend/src/routes/_layout.tsx | 55 ++-- new-frontend/src/routes/_layout/admin.tsx | 179 ++++++----- new-frontend/src/routes/_layout/index.tsx | 35 +-- new-frontend/src/routes/_layout/items.tsx | 138 +++++---- new-frontend/src/routes/_layout/settings.tsx | 88 +++--- new-frontend/src/routes/login.tsx | 131 +++++--- new-frontend/src/routes/recover-password.tsx | 87 ++++-- new-frontend/src/routes/reset-password.tsx | 197 ++++++++----- new-frontend/src/theme.tsx | 38 +-- 29 files changed, 2081 insertions(+), 1376 deletions(-) diff --git a/new-frontend/src/components/Admin/AddUser.tsx b/new-frontend/src/components/Admin/AddUser.tsx index 68b550c..fcd80f9 100644 --- a/new-frontend/src/components/Admin/AddUser.tsx +++ b/new-frontend/src/components/Admin/AddUser.tsx @@ -1,117 +1,194 @@ -import React from 'react'; +import React from 'react' +import { + Button, + Checkbox, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' -import { Button, Checkbox, Flex, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { UserCreate, UsersService } from '../../client'; -import { ApiError } from '../../client/core/ApiError'; -import useCustomToast from '../../hooks/useCustomToast'; +import { UserCreate, UsersService } from '../../client' +import { ApiError } from '../../client/core/ApiError' +import useCustomToast from '../../hooks/useCustomToast' interface AddUserProps { - isOpen: boolean; - onClose: () => void; + isOpen: boolean + onClose: () => void } interface UserCreateForm extends UserCreate { - confirm_password: string; - + confirm_password: string } const AddUser: React.FC = ({ isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all', - defaultValues: { - email: '', - full_name: '', - password: '', - confirm_password: '', - is_superuser: false, - is_active: false - } - }); + const queryClient = useQueryClient() + const showToast = useCustomToast() + const { + register, + handleSubmit, + reset, + getValues, + formState: { errors, isSubmitting }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + email: '', + full_name: '', + password: '', + confirm_password: '', + is_superuser: false, + is_active: false, + }, + }) - const addUser = async (data: UserCreate) => { - await UsersService.createUser({ requestBody: data }) - } + const addUser = async (data: UserCreate) => { + await UsersService.createUser({ requestBody: data }) + } - const mutation = useMutation(addUser, { - onSuccess: () => { - showToast('Success!', 'User created successfully.', 'success'); - reset(); - onClose(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('users'); - } - }); + const mutation = useMutation(addUser, { + onSuccess: () => { + showToast('Success!', 'User created successfully.', 'success') + reset() + onClose() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('users') + }, + }) - const onSubmit: SubmitHandler = (data) => { - mutation.mutate(data); - } + const onSubmit: SubmitHandler = (data) => { + mutation.mutate(data) + } - return ( - <> - + + + + Add User + + + + Email + + {errors.email && ( + {errors.email.message} + )} + + + Full name + + {errors.full_name && ( + {errors.full_name.message} + )} + + + Set Password + + {errors.password && ( + {errors.password.message} + )} + + + Confirm Password + + value === getValues().password || + 'The passwords do not match', + })} + placeholder="Password" + type="password" + /> + {errors.confirm_password && ( + + {errors.confirm_password.message} + + )} + + + + + Is superuser? + + + + + Is active? + + + + + + - - - - - - ) + Save + + + + + + + ) } -export default AddUser; \ No newline at end of file +export default AddUser diff --git a/new-frontend/src/components/Admin/EditUser.tsx b/new-frontend/src/components/Admin/EditUser.tsx index 3ca5ce0..c4661cd 100644 --- a/new-frontend/src/components/Admin/EditUser.tsx +++ b/new-frontend/src/components/Admin/EditUser.tsx @@ -1,116 +1,183 @@ -import React from 'react'; +import React from 'react' +import { + Button, + Checkbox, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' -import { Button, Checkbox, Flex, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { ApiError, UserOut, UserUpdate, UsersService } from '../../client'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, UserOut, UserUpdate, UsersService } from '../../client' +import useCustomToast from '../../hooks/useCustomToast' interface EditUserProps { - user: UserOut; - isOpen: boolean; - onClose: () => void; + user: UserOut + isOpen: boolean + onClose: () => void } interface UserUpdateForm extends UserUpdate { - confirm_password: string; + confirm_password: string } const EditUser: React.FC = ({ user, isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); + const queryClient = useQueryClient() + const showToast = useCustomToast() - const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting, isDirty } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all', - defaultValues: user - }); + const { + register, + handleSubmit, + reset, + getValues, + formState: { errors, isSubmitting, isDirty }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: user, + }) - const updateUser = async (data: UserUpdateForm) => { - await UsersService.updateUser({ userId: user.id, requestBody: data }); - } + const updateUser = async (data: UserUpdateForm) => { + await UsersService.updateUser({ userId: user.id, requestBody: data }) + } - const mutation = useMutation(updateUser, { - onSuccess: () => { - showToast('Success!', 'User updated successfully.', 'success'); - onClose(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('users'); - } - }); + const mutation = useMutation(updateUser, { + onSuccess: () => { + showToast('Success!', 'User updated successfully.', 'success') + onClose() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('users') + }, + }) - const onSubmit: SubmitHandler = async (data) => { - if (data.password === '') { - delete data.password; - } - mutation.mutate(data) + const onSubmit: SubmitHandler = async (data) => { + if (data.password === '') { + delete data.password } + mutation.mutate(data) + } - const onCancel = () => { - reset(); - onClose(); - } + const onCancel = () => { + reset() + onClose() + } - return ( - <> - - - - Edit User - - - - Email - - {errors.email && {errors.email.message}} - - - Full name - - - - Set Password - - {errors.password && {errors.password.message}} - - - Confirm Password - value === getValues().password || 'The passwords do not match' - })} placeholder='••••••••' type='password' /> - {errors.confirm_password && {errors.confirm_password.message}} - - - - Is superuser? - - - Is active? - - - + return ( + <> + + + + Edit User + + + + Email + + {errors.email && ( + {errors.email.message} + )} + + + Full name + + + + Set Password + + {errors.password && ( + {errors.password.message} + )} + + + Confirm Password + + value === getValues().password || + 'The passwords do not match', + })} + placeholder="Password" + type="password" + /> + {errors.confirm_password && ( + + {errors.confirm_password.message} + + )} + + + + + Is superuser? + + + + + Is active? + + + + - - - - - - - - ) + + + + + + + + ) } -export default EditUser; \ No newline at end of file +export default EditUser diff --git a/new-frontend/src/components/Common/ActionsMenu.tsx b/new-frontend/src/components/Common/ActionsMenu.tsx index 625f5d7..2994dc6 100644 --- a/new-frontend/src/components/Common/ActionsMenu.tsx +++ b/new-frontend/src/components/Common/ActionsMenu.tsx @@ -1,42 +1,76 @@ -import React from 'react'; - -import { Button, Menu, MenuButton, MenuItem, MenuList, useDisclosure } from '@chakra-ui/react'; -import { BsThreeDotsVertical } from 'react-icons/bs'; -import { FiEdit, FiTrash } from 'react-icons/fi'; - -import EditUser from '../Admin/EditUser'; -import EditItem from '../Items/EditItem'; -import Delete from './DeleteAlert'; -import { ItemOut, UserOut } from '../../client'; +import React from 'react' +import { + Button, + Menu, + MenuButton, + MenuItem, + MenuList, + useDisclosure, +} from '@chakra-ui/react' +import { BsThreeDotsVertical } from 'react-icons/bs' +import { FiEdit, FiTrash } from 'react-icons/fi' +import EditUser from '../Admin/EditUser' +import EditItem from '../Items/EditItem' +import Delete from './DeleteAlert' +import { ItemOut, UserOut } from '../../client' interface ActionsMenuProps { - type: string; - value: ItemOut | UserOut; - disabled?: boolean; + type: string + value: ItemOut | UserOut + disabled?: boolean } const ActionsMenu: React.FC = ({ type, value, disabled }) => { - const editUserModal = useDisclosure(); - const deleteModal = useDisclosure(); + const editUserModal = useDisclosure() + const deleteModal = useDisclosure() - return ( - <> - - } variant='unstyled'> - - - }>Edit {type} - } color='ui.danger'>Delete {type} - - { - type === 'User' ? - : - } - - - - ); -}; + return ( + <> + + } + variant="unstyled" + > + + } + > + Edit {type} + + } + color="ui.danger" + > + Delete {type} + + + {type === 'User' ? ( + + ) : ( + + )} + + + + ) +} -export default ActionsMenu; +export default ActionsMenu diff --git a/new-frontend/src/components/Common/DeleteAlert.tsx b/new-frontend/src/components/Common/DeleteAlert.tsx index 6187aff..b659602 100644 --- a/new-frontend/src/components/Common/DeleteAlert.tsx +++ b/new-frontend/src/components/Common/DeleteAlert.tsx @@ -1,85 +1,116 @@ -import React from 'react'; +import React from 'react' +import { + AlertDialog, + AlertDialogBody, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + Button, +} from '@chakra-ui/react' +import { useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' -import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; -import { useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { ItemsService, UsersService } from '../../client'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ItemsService, UsersService } from '../../client' +import useCustomToast from '../../hooks/useCustomToast' interface DeleteProps { - type: string; - id: number - isOpen: boolean; - onClose: () => void; + type: string + id: number + isOpen: boolean + onClose: () => void } const Delete: React.FC = ({ type, id, isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const cancelRef = React.useRef(null); - const { handleSubmit, formState: { isSubmitting } } = useForm(); + const queryClient = useQueryClient() + const showToast = useCustomToast() + const cancelRef = React.useRef(null) + const { + handleSubmit, + formState: { isSubmitting }, + } = useForm() - const deleteEntity = async (id: number) => { - if (type === 'Item') { - await ItemsService.deleteItem({ id: id }); - } else if (type === 'User') { - await UsersService.deleteUser({ userId: id }); - } else { - throw new Error(`Unexpected type: ${type}`); - } + const deleteEntity = async (id: number) => { + if (type === 'Item') { + await ItemsService.deleteItem({ id: id }) + } else if (type === 'User') { + await UsersService.deleteUser({ userId: id }) + } else { + throw new Error(`Unexpected type: ${type}`) } + } - const mutation = useMutation(deleteEntity, { - onSuccess: () => { - showToast('Success', `The ${type.toLowerCase()} was deleted successfully.`, 'success'); - onClose(); - }, - onError: () => { - showToast('An error occurred.', `An error occurred while deleting the ${type.toLowerCase()}.`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries(type === 'Item' ? 'items' : 'users'); - } - }) + const mutation = useMutation(deleteEntity, { + onSuccess: () => { + showToast( + 'Success', + `The ${type.toLowerCase()} was deleted successfully.`, + 'success', + ) + onClose() + }, + onError: () => { + showToast( + 'An error occurred.', + `An error occurred while deleting the ${type.toLowerCase()}.`, + 'error', + ) + }, + onSettled: () => { + queryClient.invalidateQueries(type === 'Item' ? 'items' : 'users') + }, + }) - const onSubmit = async () => { - mutation.mutate(id); - } + const onSubmit = async () => { + mutation.mutate(id) + } - return ( - <> - - - - - Delete {type} - + return ( + <> + + + + Delete {type} - - {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. - + + {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. + - - - - - - - - - ) + + + + + + + + + ) } -export default Delete; \ No newline at end of file +export default Delete diff --git a/new-frontend/src/components/Common/Navbar.tsx b/new-frontend/src/components/Common/Navbar.tsx index 109ee00..1a118d9 100644 --- a/new-frontend/src/components/Common/Navbar.tsx +++ b/new-frontend/src/components/Common/Navbar.tsx @@ -1,37 +1,43 @@ -import React from 'react'; +import React from 'react' +import { Button, Flex, Icon, useDisclosure } from '@chakra-ui/react' +import { FaPlus } from 'react-icons/fa' -import { Button, Flex, Icon, useDisclosure } from '@chakra-ui/react'; -import { FaPlus } from 'react-icons/fa'; - -import AddUser from '../Admin/AddUser'; -import AddItem from '../Items/AddItem'; +import AddUser from '../Admin/AddUser' +import AddItem from '../Items/AddItem' interface NavbarProps { - type: string; + type: string } const Navbar: React.FC = ({ type }) => { - const addUserModal = useDisclosure(); - const addItemModal = useDisclosure(); + const addUserModal = useDisclosure() + const addItemModal = useDisclosure() - return ( - <> - - {/* TODO: Complete search functionality */} - {/* + return ( + <> + + {/* TODO: Complete search functionality */} + {/* */} - - - - - - ); -}; + + + + + + ) +} -export default Navbar; +export default Navbar diff --git a/new-frontend/src/components/Common/NotFound.tsx b/new-frontend/src/components/Common/NotFound.tsx index c7a7886..e3ce2bc 100644 --- a/new-frontend/src/components/Common/NotFound.tsx +++ b/new-frontend/src/components/Common/NotFound.tsx @@ -1,22 +1,42 @@ -import { Button, Container, Text } from '@chakra-ui/react'; -import { Link } from '@tanstack/react-router'; +import React from 'react' +import { Button, Container, Text } from '@chakra-ui/react' +import { Link } from '@tanstack/react-router' const NotFound: React.FC = () => { - - return ( - <> - - 404 - Oops! - Page not found. - - - - ); + return ( + <> + + + 404 + + Oops! + Page not found. + + + + ) } -export default NotFound; - - +export default NotFound diff --git a/new-frontend/src/components/Common/Sidebar.tsx b/new-frontend/src/components/Common/Sidebar.tsx index c1338dc..3fabb52 100644 --- a/new-frontend/src/components/Common/Sidebar.tsx +++ b/new-frontend/src/components/Common/Sidebar.tsx @@ -1,70 +1,117 @@ -import React from 'react'; +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 { useQueryClient } from 'react-query' -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 { useQueryClient } from 'react-query'; - -import Logo from '../../assets/images/fastapi-logo.svg'; -import { UserOut } from '../../client'; -import useAuth from '../../hooks/useAuth'; -import SidebarItems from './SidebarItems'; +import Logo from '../../assets/images/fastapi-logo.svg' +import { UserOut } from '../../client' +import useAuth from '../../hooks/useAuth' +import SidebarItems from './SidebarItems' const Sidebar: React.FC = () => { - const queryClient = useQueryClient(); - const bgColor = useColorModeValue('white', '#1a202c'); - const textColor = useColorModeValue('gray', 'white'); - const secBgColor = useColorModeValue('ui.secondary', '#252d3d'); - const currentUser = queryClient.getQueryData('currentUser'); - const { isOpen, onOpen, onClose } = useDisclosure(); - const { logout } = useAuth(); - - const handleLogout = async () => { - logout() - }; + const queryClient = useQueryClient() + const bgColor = useColorModeValue('white', '#1a202c') + const textColor = useColorModeValue('gray', 'white') + const secBgColor = useColorModeValue('ui.secondary', '#252d3d') + const currentUser = queryClient.getQueryData('currentUser') + const { isOpen, onOpen, onClose } = useDisclosure() + const { logout } = useAuth() + const handleLogout = async () => { + logout() + } - return ( - <> - {/* Mobile */} - } /> - - - - - - - - logo - - - - Log out - - - { - currentUser?.email && - Logged in as: {currentUser.email} - } - - - - - - {/* Desktop */} - - - - Logo - - - { - currentUser?.email && - Logged in as: {currentUser.email} - } + return ( + <> + {/* Mobile */} + } + /> + + + + + + + + logo + + + + Log out - - - ); + + {currentUser?.email && ( + + Logged in as: {currentUser.email} + + )} + + + + + + {/* Desktop */} + + + + Logo + + + {currentUser?.email && ( + + Logged in as: {currentUser.email} + + )} + + + + ) } -export default Sidebar; +export default Sidebar diff --git a/new-frontend/src/components/Common/SidebarItems.tsx b/new-frontend/src/components/Common/SidebarItems.tsx index d0d8819..ae6e593 100644 --- a/new-frontend/src/components/Common/SidebarItems.tsx +++ b/new-frontend/src/components/Common/SidebarItems.tsx @@ -1,60 +1,57 @@ -import React from 'react'; +import React from 'react' +import { Box, Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react' +import { FiBriefcase, FiHome, FiSettings, FiUsers } from 'react-icons/fi' +import { Link } from '@tanstack/react-router' +import { useQueryClient } from 'react-query' -import { Box, Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react'; -import { FiBriefcase, FiHome, FiSettings, FiUsers } from 'react-icons/fi'; -import { Link } from '@tanstack/react-router'; -import { useQueryClient } from 'react-query'; - -import { UserOut } from '../../client'; +import { UserOut } from '../../client' const items = [ - { icon: FiHome, title: 'Dashboard', path: '/' }, - { icon: FiBriefcase, title: 'Items', path: '/items' }, - { icon: FiSettings, title: 'User Settings', path: '/settings' }, -]; + { icon: FiHome, title: 'Dashboard', path: '/' }, + { icon: FiBriefcase, title: 'Items', path: '/items' }, + { icon: FiSettings, title: 'User Settings', path: '/settings' }, +] interface SidebarItemsProps { - onClose?: () => void; + onClose?: () => void } const SidebarItems: React.FC = ({ onClose }) => { - const queryClient = useQueryClient(); - const textColor = useColorModeValue('ui.main', '#E2E8F0'); - const bgActive = useColorModeValue('#E2E8F0', '#4A5568'); - const currentUser = queryClient.getQueryData('currentUser'); - - - const finalItems = currentUser?.is_superuser ? [...items, { icon: FiUsers, title: 'Admin', path: '/admin' }] : items; - - const listItems = finalItems.map((item) => ( - - - {item.title} - - )); - - return ( - <> - - {listItems} - - - - ); -}; - -export default SidebarItems; + const queryClient = useQueryClient() + const textColor = useColorModeValue('ui.main', '#E2E8F0') + const bgActive = useColorModeValue('#E2E8F0', '#4A5568') + const currentUser = queryClient.getQueryData('currentUser') + + const finalItems = currentUser?.is_superuser + ? [...items, { icon: FiUsers, title: 'Admin', path: '/admin' }] + : items + + const listItems = finalItems.map((item) => ( + + + {item.title} + + )) + + return ( + <> + {listItems} + + ) +} + +export default SidebarItems diff --git a/new-frontend/src/components/Common/UserMenu.tsx b/new-frontend/src/components/Common/UserMenu.tsx index c5154a9..1b2020a 100644 --- a/new-frontend/src/components/Common/UserMenu.tsx +++ b/new-frontend/src/components/Common/UserMenu.tsx @@ -1,43 +1,59 @@ -import React from 'react'; +import React from 'react' +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 { Box, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react'; -import { FaUserAstronaut } from 'react-icons/fa'; -import { FiLogOut, FiUser } from 'react-icons/fi'; - -import useAuth from '../../hooks/useAuth'; -import { Link } from '@tanstack/react-router'; +import useAuth from '../../hooks/useAuth' +import { Link } from '@tanstack/react-router' const UserMenu: React.FC = () => { - const { logout } = useAuth(); + const { logout } = useAuth() - const handleLogout = async () => { - logout() - }; + const handleLogout = async () => { + logout() + } - return ( - <> - {/* Desktop */} - - - } - bg='ui.main' - isRound - /> - - } as={Link} to='settings'> - My profile - - } onClick={handleLogout} color='ui.danger' fontWeight='bold'> - Log out - - - - - - ); -}; + return ( + <> + {/* Desktop */} + + + } + bg="ui.main" + isRound + /> + + } as={Link} to="settings"> + My profile + + } + onClick={handleLogout} + color="ui.danger" + fontWeight="bold" + > + Log out + + + + + + ) +} -export default UserMenu; +export default UserMenu diff --git a/new-frontend/src/components/Items/AddItem.tsx b/new-frontend/src/components/Items/AddItem.tsx index 8cb0a67..b12f43d 100644 --- a/new-frontend/src/components/Items/AddItem.tsx +++ b/new-frontend/src/components/Items/AddItem.tsx @@ -1,98 +1,123 @@ -import React from 'react'; +import React from 'react' +import { + Button, + FormControl, + FormErrorMessage, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' -import { Button, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { ApiError, ItemCreate, ItemsService } from '../../client'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, ItemCreate, ItemsService } from '../../client' +import useCustomToast from '../../hooks/useCustomToast' interface AddItemProps { - isOpen: boolean; - onClose: () => void; + isOpen: boolean + onClose: () => void } const AddItem: React.FC = ({ isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const { register, handleSubmit, reset, formState: { errors, isSubmitting } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all', - defaultValues: { - title: '', - description: '', - }, - }); + const queryClient = useQueryClient() + const showToast = useCustomToast() + const { + register, + handleSubmit, + reset, + formState: { errors, isSubmitting }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + title: '', + description: '', + }, + }) - const addItem = async (data: ItemCreate) => { - await ItemsService.createItem({ requestBody: data }) - } + const addItem = async (data: ItemCreate) => { + await ItemsService.createItem({ requestBody: data }) + } - const mutation = useMutation(addItem, { - onSuccess: () => { - showToast('Success!', 'Item created successfully.', 'success'); - reset(); - onClose(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('items'); - } - }); + const mutation = useMutation(addItem, { + onSuccess: () => { + showToast('Success!', 'Item created successfully.', 'success') + reset() + onClose() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('items') + }, + }) - const onSubmit: SubmitHandler = (data) => { - mutation.mutate(data); - } + const onSubmit: SubmitHandler = (data) => { + mutation.mutate(data) + } - return ( - <> - - - - Add Item - - - - Title - - {errors.title && {errors.title.message}} - - - Description - - - + return ( + <> + + + + Add Item + + + + Title + + {errors.title && ( + {errors.title.message} + )} + + + Description + + + - - - - - - - - ); -}; + + + + + + + + ) +} -export default AddItem; +export default AddItem diff --git a/new-frontend/src/components/Items/EditItem.tsx b/new-frontend/src/components/Items/EditItem.tsx index 44f4dfb..94adc61 100644 --- a/new-frontend/src/components/Items/EditItem.tsx +++ b/new-frontend/src/components/Items/EditItem.tsx @@ -1,87 +1,124 @@ -import React from 'react'; +import React from 'react' +import { + Button, + FormControl, + FormErrorMessage, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' -import { Button, FormControl, FormErrorMessage, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; - -import { useMutation, useQueryClient } from 'react-query'; -import { ApiError, ItemOut, ItemUpdate, ItemsService } from '../../client'; -import useCustomToast from '../../hooks/useCustomToast'; +import { useMutation, useQueryClient } from 'react-query' +import { ApiError, ItemOut, ItemUpdate, ItemsService } from '../../client' +import useCustomToast from '../../hooks/useCustomToast' interface EditItemProps { - item: ItemOut; - isOpen: boolean; - onClose: () => void; + item: ItemOut + isOpen: boolean + onClose: () => void } const EditItem: React.FC = ({ item, isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const { register, handleSubmit, reset, formState: { isSubmitting, errors, isDirty } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all', - defaultValues: item - }); + const queryClient = useQueryClient() + const showToast = useCustomToast() + const { + register, + handleSubmit, + reset, + formState: { isSubmitting, errors, isDirty }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: item, + }) - const updateItem = async (data: ItemUpdate) => { - await ItemsService.updateItem({ id: item.id, requestBody: data }); - } + const updateItem = async (data: ItemUpdate) => { + await ItemsService.updateItem({ id: item.id, requestBody: data }) + } - const mutation = useMutation(updateItem, { - onSuccess: () => { - showToast('Success!', 'Item updated successfully.', 'success'); - onClose(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('items'); - } - }); + const mutation = useMutation(updateItem, { + onSuccess: () => { + showToast('Success!', 'Item updated successfully.', 'success') + onClose() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('items') + }, + }) - const onSubmit: SubmitHandler = async (data) => { - mutation.mutate(data) - } + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } - const onCancel = () => { - reset(); - onClose(); - } + const onCancel = () => { + reset() + onClose() + } - return ( - <> - + + + + Edit Item + + + + Title + + {errors.title && ( + {errors.title.message} + )} + + + Description + + + + + - - - - - - ) + Save + + + + + + + ) } -export default EditItem; \ No newline at end of file +export default EditItem diff --git a/new-frontend/src/components/UserSettings/Appearance.tsx b/new-frontend/src/components/UserSettings/Appearance.tsx index b1f8fb7..100426c 100644 --- a/new-frontend/src/components/UserSettings/Appearance.tsx +++ b/new-frontend/src/components/UserSettings/Appearance.tsx @@ -1,29 +1,39 @@ -import React from 'react'; - -import { Badge, Container, Heading, Radio, RadioGroup, Stack, useColorMode } from '@chakra-ui/react'; +import React from 'react' +import { + Badge, + Container, + Heading, + Radio, + RadioGroup, + Stack, + useColorMode, +} from '@chakra-ui/react' const Appearance: React.FC = () => { - const { colorMode, toggleColorMode } = useColorMode(); + const { colorMode, toggleColorMode } = useColorMode() - return ( - <> - - - Appearance - - - - {/* TODO: Add system default option */} - - Light modeDefault - - - Dark mode - - - - - - ); + return ( + <> + + + Appearance + + + + {/* TODO: Add system default option */} + + Light mode + + Default + + + + Dark mode + + + + + + ) } -export default Appearance; \ No newline at end of file +export default Appearance diff --git a/new-frontend/src/components/UserSettings/ChangePassword.tsx b/new-frontend/src/components/UserSettings/ChangePassword.tsx index 355d2c0..ca6f15c 100644 --- a/new-frontend/src/components/UserSettings/ChangePassword.tsx +++ b/new-frontend/src/components/UserSettings/ChangePassword.tsx @@ -1,74 +1,137 @@ -import React from 'react'; +import React from 'react' +import { + Box, + Button, + Container, + FormControl, + FormErrorMessage, + FormLabel, + Heading, + Input, + useColorModeValue, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation } from 'react-query' -import { Box, Button, Container, FormControl, FormErrorMessage, FormLabel, Heading, Input, useColorModeValue } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation } from 'react-query'; - -import { ApiError, UpdatePassword, UsersService } from '../../client'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, UpdatePassword, UsersService } from '../../client' +import useCustomToast from '../../hooks/useCustomToast' interface UpdatePasswordForm extends UpdatePassword { - confirm_password: string; + confirm_password: string } const ChangePassword: React.FC = () => { - const color = useColorModeValue('gray.700', 'white'); - const showToast = useCustomToast(); - const { register, handleSubmit, reset, getValues, formState: { errors, isSubmitting } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all' - }); + const color = useColorModeValue('gray.700', 'white') + const showToast = useCustomToast() + const { + register, + handleSubmit, + reset, + getValues, + formState: { errors, isSubmitting }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + }) - const UpdatePassword = async (data: UpdatePassword) => { - await UsersService.updatePasswordMe({ requestBody: data }) - } + const UpdatePassword = async (data: UpdatePassword) => { + await UsersService.updatePasswordMe({ requestBody: data }) + } - const mutation = useMutation(UpdatePassword, { - onSuccess: () => { - showToast('Success!', 'Password updated.', 'success'); - reset(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - } - }) + const mutation = useMutation(UpdatePassword, { + onSuccess: () => { + showToast('Success!', 'Password updated.', 'success') + reset() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + }) - const onSubmit: SubmitHandler = async (data) => { - mutation.mutate(data); - } + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } - return ( - <> - - - Change Password - - - - Current password - - {errors.current_password && {errors.current_password.message}} - - - Set Password - - {errors.new_password && {errors.new_password.message}} - - - Confirm Password - value === getValues().new_password || 'The passwords do not match' - })} placeholder='Password' type='password' /> - {errors.confirm_password && {errors.confirm_password.message}} - - - - - - ); + return ( + <> + + + Change Password + + + + + Current password + + + {errors.current_password && ( + + {errors.current_password.message} + + )} + + + Set Password + + {errors.new_password && ( + {errors.new_password.message} + )} + + + Confirm Password + + value === getValues().new_password || + 'The passwords do not match', + })} + placeholder="Password" + type="password" + /> + {errors.confirm_password && ( + + {errors.confirm_password.message} + + )} + + + + + + ) } -export default ChangePassword; \ No newline at end of file +export default ChangePassword diff --git a/new-frontend/src/components/UserSettings/DeleteAccount.tsx b/new-frontend/src/components/UserSettings/DeleteAccount.tsx index 4c149d7..a9c9a0f 100644 --- a/new-frontend/src/components/UserSettings/DeleteAccount.tsx +++ b/new-frontend/src/components/UserSettings/DeleteAccount.tsx @@ -1,27 +1,42 @@ -import React from 'react'; +import React from 'react' +import { + Button, + Container, + Heading, + Text, + useDisclosure, +} from '@chakra-ui/react' -import { Button, Container, Heading, Text, useDisclosure } from '@chakra-ui/react'; - -import DeleteConfirmation from './DeleteConfirmation'; +import DeleteConfirmation from './DeleteConfirmation' const DeleteAccount: React.FC = () => { - const confirmationModal = useDisclosure(); + const confirmationModal = useDisclosure() - return ( - <> - - - Delete Account - - - Are you sure you want to delete your account? This action cannot be undone. - - - - - - ); + return ( + <> + + + Delete Account + + + Are you sure you want to delete your account? This action cannot be + undone. + + + + + + ) } -export default DeleteAccount; \ No newline at end of file +export default DeleteAccount diff --git a/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx b/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx index f7d8865..f7ca666 100644 --- a/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx +++ b/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx @@ -1,86 +1,105 @@ -import React from 'react'; - -import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; -import { useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { ApiError, UserOut, UsersService } from '../../client'; -import useAuth from '../../hooks/useAuth'; -import useCustomToast from '../../hooks/useCustomToast'; +import React from 'react' +import { + AlertDialog, + AlertDialogBody, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + Button, +} from '@chakra-ui/react' +import { useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' + +import { ApiError, UserOut, UsersService } from '../../client' +import useAuth from '../../hooks/useAuth' +import useCustomToast from '../../hooks/useCustomToast' interface DeleteProps { - isOpen: boolean; - onClose: () => void; + isOpen: boolean + onClose: () => void } const DeleteConfirmation: React.FC = ({ isOpen, onClose }) => { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const cancelRef = React.useRef(null); - const { handleSubmit, formState: { isSubmitting } } = useForm(); - const currentUser = queryClient.getQueryData('currentUser'); - const { logout } = useAuth(); - - const deleteCurrentUser = async (id: number) => { - await UsersService.deleteUser({ userId: id }); - } - - const mutation = useMutation(deleteCurrentUser, { - onSuccess: () => { - showToast('Success', 'Your account has been successfully deleted.', 'success'); - logout(); - onClose(); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('currentUser'); - } - }) - - - const onSubmit = async () => { - mutation.mutate(currentUser!.id); - } - - return ( - <> - - - - - Confirmation Required - - - - All your account data will be permanently deleted. If you are sure, please click 'Confirm' to proceed. - - - - - - - - - - - ) + const queryClient = useQueryClient() + const showToast = useCustomToast() + const cancelRef = React.useRef(null) + const { + handleSubmit, + formState: { isSubmitting }, + } = useForm() + const currentUser = queryClient.getQueryData('currentUser') + const { logout } = useAuth() + + const deleteCurrentUser = async (id: number) => { + await UsersService.deleteUser({ userId: id }) + } + + const mutation = useMutation(deleteCurrentUser, { + onSuccess: () => { + showToast( + 'Success', + 'Your account has been successfully deleted.', + 'success', + ) + logout() + onClose() + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('currentUser') + }, + }) + + const onSubmit = async () => { + mutation.mutate(currentUser!.id) + } + + return ( + <> + + + + Confirmation Required + + + All your account data will be{' '} + permanently deleted. If you are sure, please + click 'Confirm' to proceed. + + + + + + + + + + + ) } -export default DeleteConfirmation; - - - - +export default DeleteConfirmation diff --git a/new-frontend/src/components/UserSettings/UserInformation.tsx b/new-frontend/src/components/UserSettings/UserInformation.tsx index 6404a7f..bbfba01 100644 --- a/new-frontend/src/components/UserSettings/UserInformation.tsx +++ b/new-frontend/src/components/UserSettings/UserInformation.tsx @@ -1,106 +1,147 @@ -import React, { useState } from 'react'; +import React, { useState } from 'react' +import { + Box, + Button, + Container, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Heading, + Input, + Text, + useColorModeValue, +} from '@chakra-ui/react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation, useQueryClient } from 'react-query' -import { Box, Button, Container, Flex, FormControl, FormErrorMessage, FormLabel, Heading, Input, Text, useColorModeValue } from '@chakra-ui/react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation, useQueryClient } from 'react-query'; - -import { ApiError, UserOut, UserUpdateMe, UsersService } from '../../client'; -import useAuth from '../../hooks/useAuth'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, UserOut, UserUpdateMe, UsersService } from '../../client' +import useAuth from '../../hooks/useAuth' +import useCustomToast from '../../hooks/useCustomToast' const UserInformation: React.FC = () => { - const queryClient = useQueryClient(); - const color = useColorModeValue('gray.700', 'white'); - const showToast = useCustomToast(); - const [editMode, setEditMode] = useState(false); - const { user: currentUser } = useAuth(); - const { register, handleSubmit, reset, formState: { isSubmitting, errors, isDirty } } = useForm({ - mode: 'onBlur', criteriaMode: 'all', defaultValues: { - full_name: currentUser?.full_name, - email: currentUser?.email - } - }) + const queryClient = useQueryClient() + const color = useColorModeValue('gray.700', 'white') + const showToast = useCustomToast() + const [editMode, setEditMode] = useState(false) + const { user: currentUser } = useAuth() + const { + register, + handleSubmit, + reset, + formState: { isSubmitting, errors, isDirty }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + full_name: currentUser?.full_name, + email: currentUser?.email, + }, + }) - const toggleEditMode = () => { - setEditMode(!editMode); - }; + const toggleEditMode = () => { + setEditMode(!editMode) + } - const updateInfo = async (data: UserUpdateMe) => { - await UsersService.updateUserMe({ requestBody: data }) - } + const updateInfo = async (data: UserUpdateMe) => { + await UsersService.updateUserMe({ requestBody: data }) + } - const mutation = useMutation(updateInfo, { - onSuccess: () => { - showToast('Success!', 'User updated successfully.', 'success'); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - }, - onSettled: () => { - queryClient.invalidateQueries('users'); - queryClient.invalidateQueries('currentUser'); - } - }); + const mutation = useMutation(updateInfo, { + onSuccess: () => { + showToast('Success!', 'User updated successfully.', 'success') + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + onSettled: () => { + queryClient.invalidateQueries('users') + queryClient.invalidateQueries('currentUser') + }, + }) - const onSubmit: SubmitHandler = async (data) => { - mutation.mutate(data) - } + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } - const onCancel = () => { - reset(); - toggleEditMode(); - } + const onCancel = () => { + reset() + toggleEditMode() + } - return ( - <> - - - User Information - - - - Full name - { - editMode ? - : - - {currentUser?.full_name || 'N/A'} - - } - - - Email - { - editMode ? - : - - {currentUser!.email} - - } - {errors.email && {errors.email.message}} - - - - {editMode && - } - - - - - ); + return ( + <> + + + User Information + + + + + Full name + + {editMode ? ( + + ) : ( + + {currentUser?.full_name || 'N/A'} + + )} + + + + Email + + {editMode ? ( + + ) : ( + + {currentUser!.email} + + )} + {errors.email && ( + {errors.email.message} + )} + + + + {editMode && ( + + )} + + + + + ) } -export default UserInformation; \ No newline at end of file +export default UserInformation diff --git a/new-frontend/src/hooks/useAuth.ts b/new-frontend/src/hooks/useAuth.ts index 2d21948..a2b3444 100644 --- a/new-frontend/src/hooks/useAuth.ts +++ b/new-frontend/src/hooks/useAuth.ts @@ -1,33 +1,42 @@ -import { useQuery } from 'react-query'; -import { useNavigate } from '@tanstack/react-router'; +import { useQuery } from 'react-query' +import { useNavigate } from '@tanstack/react-router' -import { Body_login_login_access_token as AccessToken, LoginService, UserOut, UsersService } from '../client'; +import { + Body_login_login_access_token as AccessToken, + LoginService, + UserOut, + UsersService, +} from '../client' const isLoggedIn = () => { - return localStorage.getItem('access_token') !== null; -}; + return localStorage.getItem('access_token') !== null +} const useAuth = () => { - const navigate = useNavigate(); - const { data: user, isLoading } = useQuery('currentUser', UsersService.readUserMe, { - enabled: isLoggedIn(), - }); + const navigate = useNavigate() + const { data: user, isLoading } = useQuery( + 'currentUser', + UsersService.readUserMe, + { + enabled: isLoggedIn(), + }, + ) - const login = async (data: AccessToken) => { - const response = await LoginService.loginAccessToken({ - formData: data, - }); - localStorage.setItem('access_token', response.access_token); - navigate({ to: '/' }); - }; + const login = async (data: AccessToken) => { + const response = await LoginService.loginAccessToken({ + formData: data, + }) + localStorage.setItem('access_token', response.access_token) + navigate({ to: '/' }) + } - const logout = () => { - localStorage.removeItem('access_token'); - navigate({ to: '/login' }); - }; + const logout = () => { + localStorage.removeItem('access_token') + navigate({ to: '/login' }) + } - return { login, logout, user, isLoading }; + return { login, logout, user, isLoading } } -export { isLoggedIn }; -export default useAuth; \ No newline at end of file +export { isLoggedIn } +export default useAuth diff --git a/new-frontend/src/hooks/useCustomToast.ts b/new-frontend/src/hooks/useCustomToast.ts index 2483fd3..d9f7f61 100644 --- a/new-frontend/src/hooks/useCustomToast.ts +++ b/new-frontend/src/hooks/useCustomToast.ts @@ -1,21 +1,23 @@ -import { useCallback } from 'react'; - -import { useToast } from '@chakra-ui/react'; +import { useCallback } from 'react' +import { useToast } from '@chakra-ui/react' const useCustomToast = () => { - const toast = useToast(); + const toast = useToast() - const showToast = useCallback((title: string, description: string, status: 'success' | 'error') => { - toast({ - title, - description, - status, - isClosable: true, - position: 'bottom-right' - }); - }, [toast]); + const showToast = useCallback( + (title: string, description: string, status: 'success' | 'error') => { + toast({ + title, + description, + status, + isClosable: true, + position: 'bottom-right', + }) + }, + [toast], + ) - return showToast; -}; + return showToast +} -export default useCustomToast; \ No newline at end of file +export default useCustomToast diff --git a/new-frontend/src/main.tsx b/new-frontend/src/main.tsx index a7a7305..4c80602 100644 --- a/new-frontend/src/main.tsx +++ b/new-frontend/src/main.tsx @@ -1,19 +1,19 @@ -import ReactDOM from 'react-dom/client'; -import { ChakraProvider } from '@chakra-ui/react'; -import { QueryClient, QueryClientProvider } from 'react-query'; +import ReactDOM from 'react-dom/client' +import { ChakraProvider } from '@chakra-ui/react' +import { QueryClient, QueryClientProvider } from 'react-query' import { RouterProvider, createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -import { OpenAPI } from './client'; -import theme from './theme'; -import { StrictMode } from 'react'; +import { OpenAPI } from './client' +import theme from './theme' +import { StrictMode } from 'react' -OpenAPI.BASE = import.meta.env.VITE_API_URL; +OpenAPI.BASE = import.meta.env.VITE_API_URL OpenAPI.TOKEN = async () => { - return localStorage.getItem('access_token') || ''; + return localStorage.getItem('access_token') || '' } -const queryClient = new QueryClient(); +const queryClient = new QueryClient() const router = createRouter({ routeTree }) declare module '@tanstack/react-router' { @@ -29,5 +29,5 @@ ReactDOM.createRoot(document.getElementById('root')!).render( - -); \ No newline at end of file + , +) diff --git a/new-frontend/src/routes/__root.tsx b/new-frontend/src/routes/__root.tsx index d78cf13..9aff46f 100644 --- a/new-frontend/src/routes/__root.tsx +++ b/new-frontend/src/routes/__root.tsx @@ -1,13 +1,14 @@ import { createRootRoute, Outlet } from '@tanstack/react-router' import { TanStackRouterDevtools } from '@tanstack/router-devtools' + import NotFound from '../components/Common/NotFound' export const Route = createRootRoute({ - component: () => ( - <> - - - - ), - notFoundComponent: () => , -}) \ No newline at end of file + component: () => ( + <> + + + + ), + notFoundComponent: () => , +}) diff --git a/new-frontend/src/routes/_layout.tsx b/new-frontend/src/routes/_layout.tsx index d1f48e6..b6f85a1 100644 --- a/new-frontend/src/routes/_layout.tsx +++ b/new-frontend/src/routes/_layout.tsx @@ -1,38 +1,37 @@ -import { Flex, Spinner } from '@chakra-ui/react'; -import { Outlet, createFileRoute, redirect } from '@tanstack/react-router'; - -import Sidebar from '../components/Common/Sidebar'; -import UserMenu from '../components/Common/UserMenu'; -import useAuth, { isLoggedIn } from '../hooks/useAuth'; +import { Flex, Spinner } from '@chakra-ui/react' +import { Outlet, createFileRoute, redirect } from '@tanstack/react-router' +import Sidebar from '../components/Common/Sidebar' +import UserMenu from '../components/Common/UserMenu' +import useAuth, { isLoggedIn } from '../hooks/useAuth' export const Route = createFileRoute('/_layout')({ - component: Layout, - beforeLoad: async () => { - if (!isLoggedIn()) { - throw redirect({ - to: '/login', - }) - } + component: Layout, + beforeLoad: async () => { + if (!isLoggedIn()) { + throw redirect({ + to: '/login', + }) } + }, }) function Layout() { - const { isLoading } = useAuth(); + const { isLoading } = useAuth() - return ( - - - {isLoading ? ( - - - - ) : ( - - )} - + return ( + + + {isLoading ? ( + + - ); -}; + ) : ( + + )} + + + ) +} -export default Layout; \ No newline at end of file +export default Layout diff --git a/new-frontend/src/routes/_layout/admin.tsx b/new-frontend/src/routes/_layout/admin.tsx index b3d6a90..eb183aa 100644 --- a/new-frontend/src/routes/_layout/admin.tsx +++ b/new-frontend/src/routes/_layout/admin.tsx @@ -1,82 +1,117 @@ -import { Badge, Box, Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; -import { createFileRoute } from '@tanstack/react-router'; -import { useQuery, useQueryClient } from 'react-query'; +import { + Badge, + Box, + Container, + Flex, + Heading, + Spinner, + Table, + TableContainer, + Tbody, + Td, + Th, + Thead, + Tr, +} from '@chakra-ui/react' +import { createFileRoute } from '@tanstack/react-router' +import { useQuery, useQueryClient } from 'react-query' -import { ApiError, UserOut, UsersService } from '../../client'; -import ActionsMenu from '../../components/Common/ActionsMenu'; -import Navbar from '../../components/Common/Navbar'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, UserOut, UsersService } from '../../client' +import ActionsMenu from '../../components/Common/ActionsMenu' +import Navbar from '../../components/Common/Navbar' +import useCustomToast from '../../hooks/useCustomToast' export const Route = createFileRoute('/_layout/admin')({ - component: Admin, + component: Admin, }) function Admin() { - const queryClient = useQueryClient(); - const showToast = useCustomToast(); - const currentUser = queryClient.getQueryData('currentUser'); - const { data: users, isLoading, isError, error } = useQuery('users', () => UsersService.readUsers({})) + const queryClient = useQueryClient() + const showToast = useCustomToast() + const currentUser = queryClient.getQueryData('currentUser') + const { + data: users, + isLoading, + isError, + error, + } = useQuery('users', () => UsersService.readUsers({})) - if (isError) { - const errDetail = (error as ApiError).body?.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - } + if (isError) { + const errDetail = (error as ApiError).body?.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + } - return ( - <> - {isLoading ? ( - // TODO: Add skeleton - - - - ) : ( - users && - - - User Management - - - - - - - - - - - - - - - {users.data.map((user) => ( - - - - - - - - ))} - -
Full nameEmailRoleStatusActions
{user.full_name || 'N/A'}{currentUser?.id === user.id && You}{user.email}{user.is_superuser ? 'Superuser' : 'User'} - - - {user.is_active ? 'Active' : 'Inactive'} - - - -
-
-
- )} - - ) + return ( + <> + {isLoading ? ( + // TODO: Add skeleton + + + + ) : ( + users && ( + + + User Management + + + + + + + + + + + + + + + {users.data.map((user) => ( + + + + + + + + ))} + +
Full nameEmailRoleStatusActions
+ {user.full_name || 'N/A'} + {currentUser?.id === user.id && ( + + You + + )} + {user.email}{user.is_superuser ? 'Superuser' : 'User'} + + + {user.is_active ? 'Active' : 'Inactive'} + + + +
+
+
+ ) + )} + + ) } -export default Admin; +export default Admin diff --git a/new-frontend/src/routes/_layout/index.tsx b/new-frontend/src/routes/_layout/index.tsx index 6901801..852fb37 100644 --- a/new-frontend/src/routes/_layout/index.tsx +++ b/new-frontend/src/routes/_layout/index.tsx @@ -1,27 +1,28 @@ +import { Container, Text } from '@chakra-ui/react' +import { useQueryClient } from 'react-query' +import { createFileRoute } from '@tanstack/react-router' -import { Container, Text } from '@chakra-ui/react'; -import { useQueryClient } from 'react-query'; -import { createFileRoute } from '@tanstack/react-router'; - -import { UserOut } from '../../client'; +import { UserOut } from '../../client' export const Route = createFileRoute('/_layout/')({ - component: Dashboard, + component: Dashboard, }) function Dashboard() { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() - const currentUser = queryClient.getQueryData('currentUser'); + const currentUser = queryClient.getQueryData('currentUser') - return ( - <> - - Hi, {currentUser?.full_name || currentUser?.email} 👋🏼 - Welcome back, nice to see you again! - - - ) + return ( + <> + + + Hi, {currentUser?.full_name || currentUser?.email} 👋🏼 + + Welcome back, nice to see you again! + + + ) } -export default Dashboard; \ No newline at end of file +export default Dashboard diff --git a/new-frontend/src/routes/_layout/items.tsx b/new-frontend/src/routes/_layout/items.tsx index 7a29609..9022d14 100644 --- a/new-frontend/src/routes/_layout/items.tsx +++ b/new-frontend/src/routes/_layout/items.tsx @@ -1,67 +1,91 @@ -import { Container, Flex, Heading, Spinner, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; -import { createFileRoute } from '@tanstack/react-router'; -import { useQuery } from 'react-query'; +import { + Container, + Flex, + Heading, + Spinner, + Table, + TableContainer, + Tbody, + Td, + Th, + Thead, + Tr, +} from '@chakra-ui/react' +import { createFileRoute } from '@tanstack/react-router' +import { useQuery } from 'react-query' -import { ApiError, ItemsService } from '../../client'; -import ActionsMenu from '../../components/Common/ActionsMenu'; -import Navbar from '../../components/Common/Navbar'; -import useCustomToast from '../../hooks/useCustomToast'; +import { ApiError, ItemsService } from '../../client' +import ActionsMenu from '../../components/Common/ActionsMenu' +import Navbar from '../../components/Common/Navbar' +import useCustomToast from '../../hooks/useCustomToast' export const Route = createFileRoute('/_layout/items')({ - component: Items, + component: Items, }) function Items() { - const showToast = useCustomToast(); - const { data: items, isLoading, isError, error } = useQuery('items', () => ItemsService.readItems({})) + const showToast = useCustomToast() + const { + data: items, + isLoading, + isError, + error, + } = useQuery('items', () => ItemsService.readItems({})) - if (isError) { - const errDetail = (error as ApiError).body?.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - } + if (isError) { + const errDetail = (error as ApiError).body?.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + } - return ( - <> - {isLoading ? ( - // TODO: Add skeleton - - - - ) : ( - items && - - - Items Management - - - - - - - - - - - - - - {items.data.map((item) => ( - - - - - - - ))} - -
IDTitleDescriptionActions
{item.id}{item.title}{item.description || 'N/A'} - -
-
-
- )} - - ) + return ( + <> + {isLoading ? ( + // TODO: Add skeleton + + + + ) : ( + items && ( + + + Items Management + + + + + + + + + + + + + + {items.data.map((item) => ( + + + + + + + ))} + +
IDTitleDescriptionActions
{item.id}{item.title} + {item.description || 'N/A'} + + +
+
+
+ ) + )} + + ) } -export default Items; \ No newline at end of file +export default Items diff --git a/new-frontend/src/routes/_layout/settings.tsx b/new-frontend/src/routes/_layout/settings.tsx index 14d9e6a..fc392a9 100644 --- a/new-frontend/src/routes/_layout/settings.tsx +++ b/new-frontend/src/routes/_layout/settings.tsx @@ -1,50 +1,60 @@ -import { Container, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react'; -import { createFileRoute } from '@tanstack/react-router'; -import { useQueryClient } from 'react-query'; +import { + Container, + Heading, + Tab, + TabList, + TabPanel, + TabPanels, + Tabs, +} from '@chakra-ui/react' +import { createFileRoute } from '@tanstack/react-router' +import { useQueryClient } from 'react-query' -import { UserOut } from '../../client'; -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 { UserOut } from '../../client' +import Appearance from '../../components/UserSettings/Appearance' +import ChangePassword from '../../components/UserSettings/ChangePassword' +import DeleteAccount from '../../components/UserSettings/DeleteAccount' +import UserInformation from '../../components/UserSettings/UserInformation' const tabsConfig = [ - { title: 'My profile', component: UserInformation }, - { title: 'Password', component: ChangePassword }, - { title: 'Appearance', component: Appearance }, - { title: 'Danger zone', component: DeleteAccount }, -]; + { title: 'My profile', component: UserInformation }, + { title: 'Password', component: ChangePassword }, + { title: 'Appearance', component: Appearance }, + { title: 'Danger zone', component: DeleteAccount }, +] export const Route = createFileRoute('/_layout/settings')({ - component: UserSettings, + component: UserSettings, }) function UserSettings() { - const queryClient = useQueryClient(); - const currentUser = queryClient.getQueryData('currentUser'); - const finalTabs = currentUser?.is_superuser ? tabsConfig.slice(0, 3) : tabsConfig; + const queryClient = useQueryClient() + const currentUser = queryClient.getQueryData('currentUser') + const finalTabs = currentUser?.is_superuser + ? tabsConfig.slice(0, 3) + : tabsConfig - return ( - - - User Settings - - - - {finalTabs.map((tab, index) => ( - {tab.title} - ))} - - - {finalTabs.map((tab, index) => ( - - - - ))} - - - - ); + return ( + + + User Settings + + + + {finalTabs.map((tab, index) => ( + {tab.title} + ))} + + + {finalTabs.map((tab, index) => ( + + + + ))} + + + + ) } -export default UserSettings; \ No newline at end of file +export default UserSettings diff --git a/new-frontend/src/routes/login.tsx b/new-frontend/src/routes/login.tsx index ce4b229..e4be03c 100644 --- a/new-frontend/src/routes/login.tsx +++ b/new-frontend/src/routes/login.tsx @@ -1,14 +1,30 @@ -import React from 'react'; +import React from 'react' +import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons' +import { + Button, + Center, + Container, + FormControl, + FormErrorMessage, + Icon, + Image, + Input, + InputGroup, + InputRightElement, + Link, + useBoolean, +} from '@chakra-ui/react' +import { + Link as RouterLink, + createFileRoute, + redirect, +} from '@tanstack/react-router' +import { SubmitHandler, useForm } from 'react-hook-form' -import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'; -import { Button, Center, Container, FormControl, FormErrorMessage, Icon, Image, Input, InputGroup, InputRightElement, Link, useBoolean } from '@chakra-ui/react'; -import { Link as RouterLink, createFileRoute, redirect } from '@tanstack/react-router'; -import { SubmitHandler, useForm } from 'react-hook-form'; - -import Logo from '../assets/images/fastapi-logo.svg'; -import { ApiError } from '../client'; -import { Body_login_login_access_token as AccessToken } from '../client/models/Body_login_login_access_token'; -import useAuth, { isLoggedIn } from '../hooks/useAuth'; +import Logo from '../assets/images/fastapi-logo.svg' +import { ApiError } from '../client' +import { Body_login_login_access_token as AccessToken } from '../client/models/Body_login_login_access_token' +import useAuth, { isLoggedIn } from '../hooks/useAuth' export const Route = createFileRoute('/login')({ component: Login, @@ -18,82 +34,111 @@ export const Route = createFileRoute('/login')({ to: '/', }) } - } + }, }) function Login() { - const [show, setShow] = useBoolean(); - const { login } = useAuth(); - const [error, setError] = React.useState(null); - const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({ + const [show, setShow] = useBoolean() + const { login } = useAuth() + const [error, setError] = React.useState(null) + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ mode: 'onBlur', criteriaMode: 'all', defaultValues: { username: '', - password: '' - } - }); + password: '', + }, + }) const onSubmit: SubmitHandler = async (data) => { try { - await login(data); + await login(data) } catch (err) { - const errDetail = (err as ApiError).body.detail; + const errDetail = (err as ApiError).body.detail setError(errDetail) } - }; + } return ( <> - FastAPI logo - - - {errors.username && {errors.username.message}} + FastAPI logo + + + {errors.username && ( + {errors.username.message} + )} - + - + {show ? : } - {error && - {error} - } + {error && {error}}
- + Forgot password?
-
- ); -}; + ) +} -export default Login; +export default Login diff --git a/new-frontend/src/routes/recover-password.tsx b/new-frontend/src/routes/recover-password.tsx index f29b2eb..38c998d 100644 --- a/new-frontend/src/routes/recover-password.tsx +++ b/new-frontend/src/routes/recover-password.tsx @@ -1,13 +1,21 @@ -import { Button, Container, FormControl, FormErrorMessage, Heading, Input, Text } from '@chakra-ui/react'; -import { createFileRoute, redirect } from '@tanstack/react-router'; -import { SubmitHandler, useForm } from 'react-hook-form'; +import { + Button, + Container, + FormControl, + FormErrorMessage, + Heading, + Input, + Text, +} from '@chakra-ui/react' +import { createFileRoute, redirect } from '@tanstack/react-router' +import { SubmitHandler, useForm } from 'react-hook-form' -import { LoginService } from '../client'; -import useCustomToast from '../hooks/useCustomToast'; -import { isLoggedIn } from '../hooks/useAuth'; +import { LoginService } from '../client' +import useCustomToast from '../hooks/useCustomToast' +import { isLoggedIn } from '../hooks/useAuth' interface FormData { - email: string; + email: string } export const Route = createFileRoute('/recover-password')({ @@ -18,46 +26,73 @@ export const Route = createFileRoute('/recover-password')({ to: '/', }) } - } + }, }) function RecoverPassword() { - const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm(); - const showToast = useCustomToast(); + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm() + const showToast = useCustomToast() const onSubmit: SubmitHandler = async (data) => { await LoginService.recoverPassword({ email: data.email, - }); - showToast('Email sent.', 'We sent an email with a link to get back into your account.', 'success'); - }; + }) + showToast( + 'Email sent.', + 'We sent an email with a link to get back into your account.', + 'success', + ) + } return ( - + Password Recovery - + A password recovery email will be sent to the registered account. - - {errors.email && {errors.email.message}} + + {errors.email && ( + {errors.email.message} + )} - - ); -}; + ) +} -export default RecoverPassword; +export default RecoverPassword diff --git a/new-frontend/src/routes/reset-password.tsx b/new-frontend/src/routes/reset-password.tsx index 5be798e..a39db3a 100644 --- a/new-frontend/src/routes/reset-password.tsx +++ b/new-frontend/src/routes/reset-password.tsx @@ -1,95 +1,134 @@ +import { + Button, + Container, + FormControl, + FormErrorMessage, + FormLabel, + Heading, + Input, + Text, +} from '@chakra-ui/react' +import { createFileRoute, redirect } from '@tanstack/react-router' +import { SubmitHandler, useForm } from 'react-hook-form' +import { useMutation } from 'react-query' -import { Button, Container, FormControl, FormErrorMessage, FormLabel, Heading, Input, Text } from '@chakra-ui/react'; -import { createFileRoute, redirect } from '@tanstack/react-router'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useMutation } from 'react-query'; - -import { ApiError, LoginService, NewPassword } from '../client'; -import { isLoggedIn } from '../hooks/useAuth'; -import useCustomToast from '../hooks/useCustomToast'; +import { ApiError, LoginService, NewPassword } from '../client' +import { isLoggedIn } from '../hooks/useAuth' +import useCustomToast from '../hooks/useCustomToast' interface NewPasswordForm extends NewPassword { - confirm_password: string; + confirm_password: string } export const Route = createFileRoute('/reset-password')({ - component: ResetPassword, - beforeLoad: async () => { - if (isLoggedIn()) { - throw redirect({ - to: '/', - }) - } + component: ResetPassword, + beforeLoad: async () => { + if (isLoggedIn()) { + throw redirect({ + to: '/', + }) } + }, }) function ResetPassword() { - const { register, handleSubmit, getValues, formState: { errors } } = useForm({ - mode: 'onBlur', - criteriaMode: 'all', - defaultValues: { - new_password: '', - } - }); - const showToast = useCustomToast(); - - const resetPassword = async (data: NewPassword) => { - const token = new URLSearchParams(window.location.search).get('token'); - await LoginService.resetPassword({ - requestBody: { new_password: data.new_password, token: token! } - }); - } + const { + register, + handleSubmit, + getValues, + formState: { errors }, + } = useForm({ + mode: 'onBlur', + criteriaMode: 'all', + defaultValues: { + new_password: '', + }, + }) + const showToast = useCustomToast() - const mutation = useMutation(resetPassword, { - onSuccess: () => { - showToast('Success!', 'Password updated.', 'success'); - }, - onError: (err: ApiError) => { - const errDetail = err.body.detail; - showToast('Something went wrong.', `${errDetail}`, 'error'); - } + const resetPassword = async (data: NewPassword) => { + const token = new URLSearchParams(window.location.search).get('token') + await LoginService.resetPassword({ + requestBody: { new_password: data.new_password, token: token! }, }) + } + const mutation = useMutation(resetPassword, { + onSuccess: () => { + showToast('Success!', 'Password updated.', 'success') + }, + onError: (err: ApiError) => { + const errDetail = err.body.detail + showToast('Something went wrong.', `${errDetail}`, 'error') + }, + }) - const onSubmit: SubmitHandler = async (data) => { - mutation.mutate(data); - }; + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } - return ( - - - Reset Password - - - Please enter your new password and confirm it to reset your password. - - - Set Password - - {errors.new_password && {errors.new_password.message}} - - - Confirm Password - value === getValues().new_password || 'The passwords do not match' - })} placeholder='Password' type='password' /> - {errors.confirm_password && {errors.confirm_password.message}} - - - - ); -}; + return ( + + + Reset Password + + + Please enter your new password and confirm it to reset your password. + + + Set Password + + {errors.new_password && ( + {errors.new_password.message} + )} + + + Confirm Password + + value === getValues().new_password || + 'The passwords do not match', + })} + placeholder="Password" + type="password" + /> + {errors.confirm_password && ( + {errors.confirm_password.message} + )} + + + + ) +} -export default ResetPassword; \ No newline at end of file +export default ResetPassword diff --git a/new-frontend/src/theme.tsx b/new-frontend/src/theme.tsx index b741399..2908f7d 100644 --- a/new-frontend/src/theme.tsx +++ b/new-frontend/src/theme.tsx @@ -1,27 +1,27 @@ import { extendTheme } from '@chakra-ui/react' const theme = extendTheme({ - colors: { - ui: { - main: '#009688', - secondary: '#EDF2F7', - success: '#48BB78', - danger: '#E53E3E', - } + colors: { + ui: { + main: '#009688', + secondary: '#EDF2F7', + success: '#48BB78', + danger: '#E53E3E', }, - components: { - Tabs: { - variants: { - enclosed: { - tab: { - _selected: { - color: 'ui.main', - }, - }, - }, + }, + components: { + Tabs: { + variants: { + enclosed: { + tab: { + _selected: { + color: 'ui.main', }, + }, }, + }, }, -}); + }, +}) -export default theme; \ No newline at end of file +export default theme