✨ Support delete own account and other tweaks (#614)
Co-authored-by: Esteban Maya Cadavid <emaya@trueblue.com>
This commit is contained in:
@@ -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)
|
||||||
raise HTTPException(
|
session.exec(statement)
|
||||||
status_code=400, detail="Users are not allowed to delete themselves"
|
|
||||||
)
|
|
||||||
session.delete(user)
|
session.delete(user)
|
||||||
session.commit()
|
session.commit()
|
||||||
return Message(message="User deleted successfully")
|
return Message(message="User deleted successfully")
|
||||||
|
elif user == current_user and current_user.is_superuser:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail="Super users are not allowed to delete themselves"
|
||||||
|
)
|
||||||
|
@@ -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');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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 (
|
||||||
|
@@ -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}>
|
||||||
|
@@ -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 = () => {
|
||||||
|
@@ -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 });
|
||||||
|
@@ -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 });
|
||||||
|
Reference in New Issue
Block a user