Browse Source

Support delete own account and other tweaks (#614)

Co-authored-by: Esteban Maya Cadavid <emaya@trueblue.com>
pull/13907/head
Alejandra 1 year ago
committed by GitHub
parent
commit
2346b81c51
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 19
      src/backend/app/api/api_v1/endpoints/users.py
  2. 3
      src/new-frontend/src/components/Common/Sidebar.tsx
  3. 4
      src/new-frontend/src/components/Common/UserMenu.tsx
  4. 20
      src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx
  5. 4
      src/new-frontend/src/hooks/useAuth.tsx
  6. 2
      src/new-frontend/src/store/items-store.tsx
  7. 2
      src/new-frontend/src/store/users-store.tsx

19
src/backend/app/api/api_v1/endpoints/users.py

@ -1,7 +1,7 @@
from typing import Any from typing import Any
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import func, select from sqlmodel import func, select, delete
from app import crud from app import crud
from app.api.deps import ( from app.api.deps import (
@ -21,6 +21,7 @@ from app.models import (
UsersOut, UsersOut,
UserUpdate, UserUpdate,
UserUpdateMe, UserUpdateMe,
Item
) )
from app.utils import send_new_account_email from app.utils import send_new_account_email
@ -194,12 +195,14 @@ def delete_user(
user = session.get(User, user_id) user = session.get(User, user_id)
if not user: if not user:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
if not current_user.is_superuser:
raise HTTPException(status_code=400, detail="Not enough permissions") if (user == current_user and not current_user.is_superuser) or (user != current_user and current_user.is_superuser):
if user == current_user: statement = delete(Item).where(Item.owner_id == user_id)
session.exec(statement)
session.delete(user)
session.commit()
return Message(message="User deleted successfully")
elif user == current_user and current_user.is_superuser:
raise HTTPException( raise HTTPException(
status_code=400, detail="Users are not allowed to delete themselves" status_code=400, detail="Super users are not allowed to delete themselves"
) )
session.delete(user)
session.commit()
return Message(message="User deleted successfully")

3
src/new-frontend/src/components/Common/Sidebar.tsx

@ -2,7 +2,6 @@ import React from 'react';
import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, Text, useColorModeValue, useDisclosure } from '@chakra-ui/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 { FiLogOut, FiMenu } from 'react-icons/fi';
import { useNavigate } from 'react-router-dom';
import Logo from '../../assets/images/fastapi-logo.svg'; import Logo from '../../assets/images/fastapi-logo.svg';
import useAuth from '../../hooks/useAuth'; import useAuth from '../../hooks/useAuth';
@ -16,11 +15,9 @@ const Sidebar: React.FC = () => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const { user } = useUserStore(); const { user } = useUserStore();
const { logout } = useAuth(); const { logout } = useAuth();
const navigate = useNavigate();
const handleLogout = async () => { const handleLogout = async () => {
logout() logout()
navigate('/login');
}; };

4
src/new-frontend/src/components/Common/UserMenu.tsx

@ -3,17 +3,15 @@ import React from 'react';
import { Box, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react'; import { Box, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react';
import { FaUserAstronaut } from 'react-icons/fa'; import { FaUserAstronaut } from 'react-icons/fa';
import { FiLogOut, FiUser } from 'react-icons/fi'; import { FiLogOut, FiUser } from 'react-icons/fi';
import { Link, useNavigate } from 'react-router-dom'; import { Link } from 'react-router-dom';
import useAuth from '../../hooks/useAuth'; import useAuth from '../../hooks/useAuth';
const UserMenu: React.FC = () => { const UserMenu: React.FC = () => {
const navigate = useNavigate();
const { logout } = useAuth(); const { logout } = useAuth();
const handleLogout = async () => { const handleLogout = async () => {
logout() logout()
navigate('/login');
}; };
return ( return (

20
src/new-frontend/src/components/UserSettings/DeleteConfirmation.tsx

@ -2,7 +2,10 @@ import React, { useState } from 'react';
import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { ApiError } from '../../client';
import useAuth from '../../hooks/useAuth';
import useCustomToast from '../../hooks/useCustomToast'; import useCustomToast from '../../hooks/useCustomToast';
import { useUserStore } from '../../store/user-store';
interface DeleteProps { interface DeleteProps {
isOpen: boolean; isOpen: boolean;
@ -12,18 +15,19 @@ interface DeleteProps {
const DeleteConfirmation: React.FC<DeleteProps> = ({ isOpen, onClose }) => { const DeleteConfirmation: React.FC<DeleteProps> = ({ isOpen, onClose }) => {
const showToast = useCustomToast(); const showToast = useCustomToast();
const cancelRef = React.useRef<HTMLButtonElement | null>(null); const cancelRef = React.useRef<HTMLButtonElement | null>(null);
const [isLoading, setIsLoading] = useState(false); const { handleSubmit, formState: { isSubmitting } } = useForm();
const { handleSubmit } = useForm(); const { user, deleteUser } = useUserStore();
const { logout } = useAuth();
const onSubmit = async () => { const onSubmit = async () => {
setIsLoading(true);
try { try {
// TODO: Delete user account when API is ready await deleteUser(user!.id);
logout();
onClose(); onClose();
showToast('Success', 'Your account has been successfully deleted.', 'success');
} catch (err) { } catch (err) {
showToast('An error occurred', 'An error occurred while deleting your account.', 'error'); const errDetail = (err as ApiError).body.detail;
} finally { showToast('Something went wrong.', `${errDetail}`, 'error');
setIsLoading(false);
} }
} }
@ -47,7 +51,7 @@ const DeleteConfirmation: React.FC<DeleteProps> = ({ isOpen, onClose }) => {
</AlertDialogBody> </AlertDialogBody>
<AlertDialogFooter gap={3}> <AlertDialogFooter gap={3}>
<Button bg='ui.danger' color='white' _hover={{ opacity: 0.8 }} type='submit' isLoading={isLoading}> <Button bg='ui.danger' color='white' _hover={{ opacity: 0.8 }} type='submit' isLoading={isSubmitting}>
Confirm Confirm
</Button> </Button>
<Button ref={cancelRef} onClick={onClose} isDisabled={isLoading}> <Button ref={cancelRef} onClick={onClose} isDisabled={isLoading}>

4
src/new-frontend/src/hooks/useAuth.tsx

@ -2,11 +2,14 @@ import { useUserStore } from '../store/user-store';
import { Body_login_login_access_token as AccessToken, LoginService } from '../client'; import { Body_login_login_access_token as AccessToken, LoginService } from '../client';
import { useUsersStore } from '../store/users-store'; import { useUsersStore } from '../store/users-store';
import { useItemsStore } from '../store/items-store'; import { useItemsStore } from '../store/items-store';
import { useNavigate } from 'react-router-dom';
const useAuth = () => { const useAuth = () => {
const { user, getUser, resetUser } = useUserStore(); const { user, getUser, resetUser } = useUserStore();
const { resetUsers } = useUsersStore(); const { resetUsers } = useUsersStore();
const { resetItems } = useItemsStore(); const { resetItems } = useItemsStore();
const navigate = useNavigate();
const login = async (data: AccessToken) => { const login = async (data: AccessToken) => {
const response = await LoginService.loginAccessToken({ const response = await LoginService.loginAccessToken({
@ -21,6 +24,7 @@ const useAuth = () => {
resetUser(); resetUser();
resetUsers(); resetUsers();
resetItems(); resetItems();
navigate('/login');
}; };
const isLoggedIn = () => { const isLoggedIn = () => {

2
src/new-frontend/src/store/items-store.tsx

@ -14,7 +14,7 @@ export const useItemsStore = create<ItemsStore>((set) => ({
items: [], items: [],
getItems: async () => { getItems: async () => {
const itemsResponse = await ItemsService.readItems({ skip: 0, limit: 10 }); const itemsResponse = await ItemsService.readItems({ skip: 0, limit: 10 });
set({ items: itemsResponse }); set({ items: itemsResponse.data });
}, },
addItem: async (item: ItemCreate) => { addItem: async (item: ItemCreate) => {
const itemResponse = await ItemsService.createItem({ requestBody: item }); const itemResponse = await ItemsService.createItem({ requestBody: item });

2
src/new-frontend/src/store/users-store.tsx

@ -14,7 +14,7 @@ export const useUsersStore = create<UsersStore>((set) => ({
users: [], users: [],
getUsers: async () => { getUsers: async () => {
const usersResponse = await UsersService.readUsers({ skip: 0, limit: 10 }); const usersResponse = await UsersService.readUsers({ skip: 0, limit: 10 });
set({ users: usersResponse }); set({ users: usersResponse.data });
}, },
addUser: async (user: UserCreate) => { addUser: async (user: UserCreate) => {
const userResponse = await UsersService.createUser({ requestBody: user }); const userResponse = await UsersService.createUser({ requestBody: user });

Loading…
Cancel
Save