diff --git a/backend/src/auth/auth.controller.ts b/backend/src/auth/auth.controller.ts
index f1d544a1..6de47a1a 100644
--- a/backend/src/auth/auth.controller.ts
+++ b/backend/src/auth/auth.controller.ts
@@ -3,14 +3,20 @@ import {
Controller,
ForbiddenException,
HttpCode,
+ Patch,
Post,
+ UseGuards,
} from "@nestjs/common";
import { Throttle } from "@nestjs/throttler";
+import { User } from "@prisma/client";
import { ConfigService } from "src/config/config.service";
import { AuthService } from "./auth.service";
+import { GetUser } from "./decorator/getUser.decorator";
import { AuthRegisterDTO } from "./dto/authRegister.dto";
import { AuthSignInDTO } from "./dto/authSignIn.dto";
import { RefreshAccessTokenDTO } from "./dto/refreshAccessToken.dto";
+import { UpdatePasswordDTO } from "./dto/updatePassword.dto";
+import { JwtGuard } from "./guard/jwt.guard";
@Controller("auth")
export class AuthController {
@@ -34,6 +40,12 @@ export class AuthController {
return this.authService.signIn(dto);
}
+ @Patch("password")
+ @UseGuards(JwtGuard)
+ async updatePassword(@GetUser() user: User, @Body() dto: UpdatePasswordDTO) {
+ await this.authService.updatePassword(user, dto.oldPassword, dto.password);
+ }
+
@Post("token")
@HttpCode(200)
async refreshAccessToken(@Body() body: RefreshAccessTokenDTO) {
diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts
index f9da9aed..e6970697 100644
--- a/backend/src/auth/auth.service.ts
+++ b/backend/src/auth/auth.service.ts
@@ -1,5 +1,6 @@
import {
BadRequestException,
+ ForbiddenException,
Injectable,
UnauthorizedException,
} from "@nestjs/common";
@@ -68,6 +69,18 @@ export class AuthService {
return { accessToken, refreshToken };
}
+ async updatePassword(user: User, oldPassword: string, newPassword: string) {
+ if (argon.verify(user.password, oldPassword))
+ throw new ForbiddenException("Invalid password");
+
+ const hash = await argon.hash(newPassword);
+
+ this.prisma.user.update({
+ where: { id: user.id },
+ data: { password: hash },
+ });
+ }
+
async createAccessToken(user: User) {
return this.jwtService.sign(
{
diff --git a/backend/src/auth/dto/updatePassword.dto.ts b/backend/src/auth/dto/updatePassword.dto.ts
new file mode 100644
index 00000000..483ed84c
--- /dev/null
+++ b/backend/src/auth/dto/updatePassword.dto.ts
@@ -0,0 +1,8 @@
+import { PickType } from "@nestjs/mapped-types";
+import { IsString } from "class-validator";
+import { UserDTO } from "src/user/dto/user.dto";
+
+export class UpdatePasswordDTO extends PickType(UserDTO, ["password"]) {
+ @IsString()
+ oldPassword: string;
+}
\ No newline at end of file
diff --git a/backend/src/main.ts b/backend/src/main.ts
index c6bbf8a8..852eedf1 100644
--- a/backend/src/main.ts
+++ b/backend/src/main.ts
@@ -6,7 +6,7 @@ import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
- app.useGlobalPipes(new ValidationPipe());
+ app.useGlobalPipes(new ValidationPipe({whitelist: true}));
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
app.set("trust proxy", true);
diff --git a/backend/src/user/dto/createUser.dto.ts b/backend/src/user/dto/createUser.dto.ts
new file mode 100644
index 00000000..785a1dbd
--- /dev/null
+++ b/backend/src/user/dto/createUser.dto.ts
@@ -0,0 +1,14 @@
+import { Expose, plainToClass } from "class-transformer";
+import { Allow } from "class-validator";
+import { UserDTO } from "./user.dto";
+
+export class CreateUserDTO extends UserDTO{
+
+ @Expose()
+ @Allow()
+ isAdmin: boolean;
+
+ from(partial: Partial) {
+ return plainToClass(CreateUserDTO, partial, { excludeExtraneousValues: true });
+ }
+}
diff --git a/backend/src/user/dto/updateOwnUser.dto.ts b/backend/src/user/dto/updateOwnUser.dto.ts
index b3cfddbf..604d3804 100644
--- a/backend/src/user/dto/updateOwnUser.dto.ts
+++ b/backend/src/user/dto/updateOwnUser.dto.ts
@@ -2,5 +2,5 @@ import { OmitType, PartialType } from "@nestjs/mapped-types";
import { UserDTO } from "./user.dto";
export class UpdateOwnUserDTO extends PartialType(
- OmitType(UserDTO, ["isAdmin"] as const)
+ OmitType(UserDTO, ["isAdmin", "password"] as const)
) {}
diff --git a/backend/src/user/dto/user.dto.ts b/backend/src/user/dto/user.dto.ts
index 512d1746..85d661a8 100644
--- a/backend/src/user/dto/user.dto.ts
+++ b/backend/src/user/dto/user.dto.ts
@@ -1,17 +1,10 @@
import { Expose, plainToClass } from "class-transformer";
-import {
- IsEmail,
- IsNotEmpty,
- IsString,
- Length,
- Matches,
-} from "class-validator";
+import { IsEmail, Length, Matches, MinLength } from "class-validator";
export class UserDTO {
@Expose()
id: string;
- @Expose()
@Expose()
@Matches("^[a-zA-Z0-9_.]*$", undefined, {
message: "Username can only contain letters, numbers, dots and underscores",
@@ -23,8 +16,7 @@ export class UserDTO {
@IsEmail()
email: string;
- @IsNotEmpty()
- @IsString()
+ @MinLength(8)
password: string;
@Expose()
diff --git a/backend/src/user/user.controller.ts b/backend/src/user/user.controller.ts
index d517f8b0..08ebf73f 100644
--- a/backend/src/user/user.controller.ts
+++ b/backend/src/user/user.controller.ts
@@ -12,6 +12,8 @@ import { User } from "@prisma/client";
import { GetUser } from "src/auth/decorator/getUser.decorator";
import { AdministratorGuard } from "src/auth/guard/isAdmin.guard";
import { JwtGuard } from "src/auth/guard/jwt.guard";
+import { CreateUserDTO } from "./dto/createUser.dto";
+import { UpdateOwnUserDTO } from "./dto/updateOwnUser.dto";
import { UpdateUserDto } from "./dto/updateUser.dto";
import { UserDTO } from "./dto/user.dto";
import { UserSevice } from "./user.service";
@@ -29,7 +31,10 @@ export class UserController {
@Patch("me")
@UseGuards(JwtGuard)
- async updateCurrentUser(@GetUser() user: User, @Body() data: UpdateUserDto) {
+ async updateCurrentUser(
+ @GetUser() user: User,
+ @Body() data: UpdateOwnUserDTO
+ ) {
return new UserDTO().from(await this.userService.update(user.id, data));
}
@@ -48,7 +53,7 @@ export class UserController {
@Post()
@UseGuards(JwtGuard, AdministratorGuard)
- async create(@Body() user: UserDTO) {
+ async create(@Body() user: CreateUserDTO) {
return new UserDTO().from(await this.userService.create(user));
}
@@ -60,7 +65,7 @@ export class UserController {
@Delete(":id")
@UseGuards(JwtGuard, AdministratorGuard)
- async delete(@Param() id: string) {
+ async delete(@Param("id") id: string) {
return new UserDTO().from(await this.userService.delete(id));
}
}
diff --git a/backend/src/user/user.service.ts b/backend/src/user/user.service.ts
index fdb7ce59..02f382b1 100644
--- a/backend/src/user/user.service.ts
+++ b/backend/src/user/user.service.ts
@@ -2,6 +2,7 @@ import { BadRequestException, Injectable } from "@nestjs/common";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
import * as argon from "argon2";
import { PrismaService } from "src/prisma/prisma.service";
+import { CreateUserDTO } from "./dto/createUser.dto";
import { UpdateUserDto } from "./dto/updateUser.dto";
import { UserDTO } from "./dto/user.dto";
@@ -17,7 +18,7 @@ export class UserSevice {
return await this.prisma.user.findUnique({ where: { id } });
}
- async create(dto: UserDTO) {
+ async create(dto: CreateUserDTO) {
const hash = await argon.hash(dto.password);
try {
return await this.prisma.user.create({
diff --git a/frontend/src/components/admin/ManageUserTable.tsx b/frontend/src/components/admin/ManageUserTable.tsx
new file mode 100644
index 00000000..52e61ccf
--- /dev/null
+++ b/frontend/src/components/admin/ManageUserTable.tsx
@@ -0,0 +1,86 @@
+import { ActionIcon, Box, Group, Skeleton, Table } from "@mantine/core";
+import { useModals } from "@mantine/modals";
+import { TbCheck, TbEdit, TbTrash } from "react-icons/tb";
+import User from "../../types/user.type";
+import showUpdateUserModal from "./showUpdateUserModal";
+
+const ManageUserTable = ({
+ users,
+ getUsers,
+ deleteUser,
+ isLoading,
+}: {
+ users: User[];
+ getUsers: () => void;
+ deleteUser: (user: User) => void;
+ isLoading: boolean;
+}) => {
+ const modals = useModals();
+
+ return (
+
+
+
+
+ Username |
+ Email |
+ Admin |
+ |
+
+
+
+ {isLoading
+ ? skeletonRows
+ : users.map((user) => (
+
+ {user.username} |
+ {user.email} |
+ {user.isAdmin && } |
+
+
+
+ showUpdateUserModal(modals, user, getUsers)
+ }
+ >
+
+
+ deleteUser(user)}
+ >
+
+
+
+ |
+
+ ))}
+
+
+
+ );
+};
+
+const skeletonRows = [...Array(10)].map((v, i) => (
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+));
+
+export default ManageUserTable;
diff --git a/frontend/src/components/admin/showCreateUserModal.tsx b/frontend/src/components/admin/showCreateUserModal.tsx
new file mode 100644
index 00000000..6a7d4a2c
--- /dev/null
+++ b/frontend/src/components/admin/showCreateUserModal.tsx
@@ -0,0 +1,88 @@
+import {
+ Button,
+ Group,
+ Input,
+ PasswordInput,
+ Stack,
+ Switch,
+ TextInput,
+ Title,
+} from "@mantine/core";
+import { useForm, yupResolver } from "@mantine/form";
+import { ModalsContextProps } from "@mantine/modals/lib/context";
+import * as yup from "yup";
+import userService from "../../services/user.service";
+import toast from "../../utils/toast.util";
+
+const showCreateUserModal = (
+ modals: ModalsContextProps,
+ getUsers: () => void
+) => {
+ return modals.openModal({
+ title: Create user,
+ children: ,
+ });
+};
+
+const Body = ({
+ modals,
+ getUsers,
+}: {
+ modals: ModalsContextProps;
+ getUsers: () => void;
+}) => {
+ const form = useForm({
+ initialValues: {
+ username: "",
+ email: "",
+ password: "",
+ isAdmin: false,
+ },
+ validate: yupResolver(
+ yup.object().shape({
+ email: yup.string().email(),
+ username: yup.string().min(3),
+ password: yup.string().min(8),
+ })
+ ),
+ });
+
+ return (
+
+
+
+ );
+};
+
+export default showCreateUserModal;
diff --git a/frontend/src/components/admin/showUpdateConfigVariableModal.tsx b/frontend/src/components/admin/showUpdateConfigVariableModal.tsx
index f74e1744..561cf184 100644
--- a/frontend/src/components/admin/showUpdateConfigVariableModal.tsx
+++ b/frontend/src/components/admin/showUpdateConfigVariableModal.tsx
@@ -84,7 +84,7 @@ const Body = ({
getConfigVariables();
modals.closeAll();
})
- .catch((e) => toast.error(e.response.data.message));
+ .catch(toast.axiosError);
}}
>
Save
diff --git a/frontend/src/components/admin/showUpdateUserModal.tsx b/frontend/src/components/admin/showUpdateUserModal.tsx
new file mode 100644
index 00000000..57b50d66
--- /dev/null
+++ b/frontend/src/components/admin/showUpdateUserModal.tsx
@@ -0,0 +1,126 @@
+import {
+ Accordion,
+ Button,
+ Group,
+ PasswordInput,
+ Stack,
+ TextInput,
+ Title,
+} from "@mantine/core";
+import { useForm, yupResolver } from "@mantine/form";
+import { ModalsContextProps } from "@mantine/modals/lib/context";
+import * as yup from "yup";
+import userService from "../../services/user.service";
+import User from "../../types/user.type";
+import toast from "../../utils/toast.util";
+
+const showUpdateUserModal = (
+ modals: ModalsContextProps,
+ user: User,
+ getUsers: () => void
+) => {
+ return modals.openModal({
+ title: Update {user.username},
+ children: ,
+ });
+};
+
+const Body = ({
+ user,
+ modals,
+ getUsers,
+}: {
+ modals: ModalsContextProps;
+ user: User;
+ getUsers: () => void;
+}) => {
+ const accountForm = useForm({
+ initialValues: {
+ username: user?.username,
+ email: user?.email,
+ },
+ validate: yupResolver(
+ yup.object().shape({
+ email: yup.string().email(),
+ username: yup.string().min(3),
+ })
+ ),
+ });
+
+ const passwordForm = useForm({
+ initialValues: {
+ password: "",
+ },
+ validate: yupResolver(
+ yup.object().shape({
+ password: yup.string().min(8),
+ })
+ ),
+ });
+
+ return (
+
+
+
+
+ Passwort ändern
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default showUpdateUserModal;
diff --git a/frontend/src/components/auth/SignInForm.tsx b/frontend/src/components/auth/SignInForm.tsx
index e0b77618..2f48f296 100644
--- a/frontend/src/components/auth/SignInForm.tsx
+++ b/frontend/src/components/auth/SignInForm.tsx
@@ -35,7 +35,7 @@ const SignInForm = () => {
authService
.signIn(email, password)
.then(() => window.location.replace("/"))
- .catch((e) => toast.error(e.response.data.message));
+ .catch(toast.axiosError);
};
return (
diff --git a/frontend/src/components/auth/SignUpForm.tsx b/frontend/src/components/auth/SignUpForm.tsx
index 2e4a5e84..a8cc6733 100644
--- a/frontend/src/components/auth/SignUpForm.tsx
+++ b/frontend/src/components/auth/SignUpForm.tsx
@@ -20,7 +20,7 @@ const SignUpForm = () => {
const validationSchema = yup.object().shape({
email: yup.string().email().required(),
- username: yup.string().required(),
+ username: yup.string().min(3).required(),
password: yup.string().min(8).required(),
});
@@ -37,13 +37,13 @@ const SignUpForm = () => {
authService
.signIn(email, password)
.then(() => window.location.replace("/"))
- .catch((e) => toast.error(e.response.data.message));
+ .catch(toast.axiosError);
};
const signUp = (email: string, username: string, password: string) => {
authService
.signUp(email, username, password)
.then(() => signIn(email, password))
- .catch((e) => toast.error(e.response.data.message));
+ .catch(toast.axiosError);
};
return (
diff --git a/frontend/src/components/navBar/ActionAvatar.tsx b/frontend/src/components/navBar/ActionAvatar.tsx
index 0fc4df96..37eca730 100644
--- a/frontend/src/components/navBar/ActionAvatar.tsx
+++ b/frontend/src/components/navBar/ActionAvatar.tsx
@@ -1,6 +1,6 @@
import { ActionIcon, Avatar, Menu } from "@mantine/core";
import Link from "next/link";
-import { TbDoorExit, TbLink, TbSettings } from "react-icons/tb";
+import { TbDoorExit, TbLink, TbSettings, TbUser } from "react-icons/tb";
import useUser from "../../hooks/user.hook";
import authService from "../../services/auth.service";
@@ -22,10 +22,13 @@ const ActionAvatar = () => {
>
My shares
+
}>
+ My account
+
{user!.isAdmin && (
}
>
Administration
diff --git a/frontend/src/components/share/FileList.tsx b/frontend/src/components/share/FileList.tsx
index 8ffeef0c..daa03abc 100644
--- a/frontend/src/components/share/FileList.tsx
+++ b/frontend/src/components/share/FileList.tsx
@@ -13,23 +13,6 @@ const FileList = ({
shareId: string;
isLoading: boolean;
}) => {
- const skeletonRows = [...Array(5)].map((c, i) => (
-
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
- ));
-
const rows = files.map((file) => (
{file.name} |
@@ -69,4 +52,21 @@ const FileList = ({
);
};
+const skeletonRows = [...Array(5)].map((c, i) => (
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+));
+
export default FileList;
diff --git a/frontend/src/pages/account/index.tsx b/frontend/src/pages/account/index.tsx
new file mode 100644
index 00000000..85e8927b
--- /dev/null
+++ b/frontend/src/pages/account/index.tsx
@@ -0,0 +1,154 @@
+import {
+ Button,
+ Center,
+ Container,
+ Group,
+ Paper,
+ PasswordInput,
+ Stack,
+ Text,
+ TextInput,
+ Title,
+} from "@mantine/core";
+import { useForm, yupResolver } from "@mantine/form";
+import { useModals } from "@mantine/modals";
+import { useRouter } from "next/router";
+import * as yup from "yup";
+import useUser from "../../hooks/user.hook";
+import authService from "../../services/auth.service";
+import userService from "../../services/user.service";
+import toast from "../../utils/toast.util";
+
+const Account = () => {
+ const user = useUser();
+ const modals = useModals();
+ const router = useRouter();
+
+ const accountForm = useForm({
+ initialValues: {
+ username: user?.username,
+ email: user?.email,
+ },
+ validate: yupResolver(
+ yup.object().shape({
+ email: yup.string().email(),
+ username: yup.string().min(3),
+ })
+ ),
+ });
+
+ const passwordForm = useForm({
+ initialValues: {
+ oldPassword: "",
+ password: "",
+ },
+ validate: yupResolver(
+ yup.object().shape({
+ oldPassword: yup.string().min(8),
+ password: yup.string().min(8),
+ })
+ ),
+ });
+
+ if (!user) {
+ router.push("/");
+ return;
+ }
+
+ return (
+
+
+ My account
+
+
+
+ Account Info
+
+
+
+
+
+ Password
+
+
+
+
+
+
+
+ );
+};
+
+export default Account;
diff --git a/frontend/src/pages/admin/config.tsx b/frontend/src/pages/admin/config.tsx
index 6e336120..37e2b0b8 100644
--- a/frontend/src/pages/admin/config.tsx
+++ b/frontend/src/pages/admin/config.tsx
@@ -1,9 +1,12 @@
-import { Space } from "@mantine/core";
+import { Space, Title } from "@mantine/core";
import AdminConfigTable from "../../components/admin/AdminConfigTable";
const AdminConfig = () => {
return (
<>
+
+ Configuration
+
>
diff --git a/frontend/src/pages/admin/index.tsx b/frontend/src/pages/admin/index.tsx
new file mode 100644
index 00000000..6140f8d9
--- /dev/null
+++ b/frontend/src/pages/admin/index.tsx
@@ -0,0 +1,62 @@
+import { Col, Container, createStyles, Grid, Paper, Text } from "@mantine/core";
+import Link from "next/link";
+import { TbSettings, TbUsers } from "react-icons/tb";
+
+const managementOptions = [
+ {
+ title: "User management",
+ icon: TbUsers,
+ route: "/admin/users",
+ },
+ {
+ title: "Configuration",
+ icon: TbSettings,
+ route: "/admin/config",
+ },
+];
+
+const useStyles = createStyles((theme) => ({
+ item: {
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ justifyContent: "center",
+ textAlign: "center",
+ height: 90,
+ "&:hover": {
+ boxShadow: `${theme.shadows.sm} !important`,
+ transform: "scale(1.01)",
+ },
+ },
+}));
+
+const Admin = () => {
+ const { classes, theme } = useStyles();
+
+ return (
+
+
+
+ {managementOptions.map((item) => {
+ return (
+
+
+
+ {item.title}
+
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default Admin;
diff --git a/frontend/src/pages/admin/users.tsx b/frontend/src/pages/admin/users.tsx
new file mode 100644
index 00000000..2ab3a9a0
--- /dev/null
+++ b/frontend/src/pages/admin/users.tsx
@@ -0,0 +1,73 @@
+import { Button, Group, Space, Text, Title } from "@mantine/core";
+import { useModals } from "@mantine/modals";
+import { useEffect, useState } from "react";
+import { TbPlus } from "react-icons/tb";
+import ManageUserTable from "../../components/admin/ManageUserTable";
+import showCreateUserModal from "../../components/admin/showCreateUserModal";
+import userService from "../../services/user.service";
+import User from "../../types/user.type";
+import toast from "../../utils/toast.util";
+
+const Users = () => {
+ const [users, setUsers] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const modals = useModals();
+
+ const getUsers = () => {
+ setIsLoading(true);
+ userService.list().then((users) => {
+ setUsers(users);
+ setIsLoading(false);
+ });
+ };
+
+ const deleteUser = (user: User) => {
+ modals.openConfirmModal({
+ title: `Delete ${user.username}?`,
+ children: (
+
+ Do you really want to delete {user.username} and all his
+ shares?
+
+ ),
+ labels: { confirm: "Delete", cancel: "Cancel" },
+ confirmProps: { color: "red" },
+ onConfirm: async () => {
+ userService
+ .remove(user.id)
+ .then(() => setUsers(users.filter((v) => v.id != user.id)))
+ .catch(toast.axiosError);
+ },
+ });
+ };
+
+ useEffect(() => {
+ getUsers();
+ }, []);
+
+ return (
+ <>
+
+
+ User management
+
+
+
+
+
+
+ >
+ );
+};
+
+export default Users;
diff --git a/frontend/src/services/auth.service.ts b/frontend/src/services/auth.service.ts
index 19671a94..9f4cba30 100644
--- a/frontend/src/services/auth.service.ts
+++ b/frontend/src/services/auth.service.ts
@@ -44,9 +44,14 @@ const refreshAccessToken = async () => {
}
};
+const updatePassword = async (oldPassword: string, password: string) => {
+ await api.patch("/auth/password", { oldPassword, password });
+};
+
export default {
signIn,
signUp,
signOut,
refreshAccessToken,
+ updatePassword
};
diff --git a/frontend/src/services/user.service.ts b/frontend/src/services/user.service.ts
index 6c90e1f6..43060ff5 100644
--- a/frontend/src/services/user.service.ts
+++ b/frontend/src/services/user.service.ts
@@ -1,7 +1,36 @@
-import { CurrentUser } from "../types/user.type";
+import {
+ CreateUser,
+ CurrentUser,
+ UpdateCurrentUser,
+ UpdateUser,
+} from "../types/user.type";
import api from "./api.service";
import authService from "./auth.service";
+const list = async () => {
+ return (await api.get("/users")).data;
+};
+
+const create = async (user: CreateUser) => {
+ return (await api.post("/users", user)).data;
+};
+
+const update = async (id: string, user: UpdateUser) => {
+ return (await api.patch(`/users/${id}`, user)).data;
+};
+
+const remove = async (id: string) => {
+ await api.delete(`/users/${id}`);
+};
+
+const updateCurrentUser = async (user: UpdateCurrentUser) => {
+ return (await api.patch("/users/me", user)).data;
+};
+
+const removeCurrentUser = async () => {
+ await api.delete("/users/me");
+};
+
const getCurrentUser = async (): Promise => {
try {
await authService.refreshAccessToken();
@@ -12,5 +41,11 @@ const getCurrentUser = async (): Promise => {
};
export default {
+ list,
+ create,
+ update,
+ remove,
getCurrentUser,
+ updateCurrentUser,
+ removeCurrentUser,
};
diff --git a/frontend/src/types/user.type.ts b/frontend/src/types/user.type.ts
index f80447ab..a4879a8e 100644
--- a/frontend/src/types/user.type.ts
+++ b/frontend/src/types/user.type.ts
@@ -1,9 +1,29 @@
-export default interface User {
+type User = {
id: string;
- firstName?: string;
- lastName?: string;
+ username: string;
email: string;
isAdmin: boolean;
-}
+};
-export interface CurrentUser extends User {}
+export type CreateUser = {
+ username: string;
+ email: string;
+ password: string,
+ isAdmin?: boolean;
+};
+
+export type UpdateUser = {
+ username?: string;
+ email?: string;
+ password?: string,
+ isAdmin?: boolean;
+};
+
+export type UpdateCurrentUser = {
+ username?: string;
+ email?: string;
+};
+
+export type CurrentUser = User & {};
+
+export default User;
diff --git a/frontend/src/utils/toast.util.tsx b/frontend/src/utils/toast.util.tsx
index 3e1f56e5..069c3b31 100644
--- a/frontend/src/utils/toast.util.tsx
+++ b/frontend/src/utils/toast.util.tsx
@@ -10,6 +10,9 @@ const error = (message: string) =>
message: message,
});
+const axiosError = (axiosError: any) =>
+ error(axiosError?.response?.data?.message ?? "An unknown error occured");
+
const success = (message: string) =>
showNotification({
icon: ,
@@ -22,5 +25,6 @@ const success = (message: string) =>
const toast = {
error,
success,
+ axiosError,
};
export default toast;