diff --git a/src/app/dashboard/(auth)/users/_modals/DeleteModal/DeleteModal.tsx b/src/app/dashboard/(auth)/users/_modals/DeleteModal/DeleteModal.tsx deleted file mode 100644 index f19fcd9..0000000 --- a/src/app/dashboard/(auth)/users/_modals/DeleteModal/DeleteModal.tsx +++ /dev/null @@ -1,88 +0,0 @@ -"use client"; -import { UserFormData } from "@/features/dashboard/users/formSchemas/userFormDataSchema"; -import { useRouter } from "next/navigation"; -import React, { useState } from "react"; -import { - Avatar, - Button, - Center, - Flex, - Modal, - ScrollArea, - Text, - Stack, - TextInput, - Title, -} from "@mantine/core"; -import deleteUser from "@/features/dashboard/users/actions/deleteUser"; -import { showNotification } from "@/utils/notifications"; - -interface Props { - data: UserFormData; -} - -export default function DeleteModal(props: Props) { - const router = useRouter(); - - const [isSubmitting, setSubmitting] = useState(false); - - /** - * Closes the modal. It won't close if a submission is in progress. - */ - const closeModal = () => { - if (isSubmitting) return; - router.replace("?"); - }; - - const confirmAction = () => { - setSubmitting(true) - deleteUser(props.data.id) - .then((response) => { - if (response.success){ - showNotification(response.message); - router.replace("?") - return; - } else { - showNotification(response.message, "error") - } - }) - .catch(() => { - //TODO: Handle Error - }) - .finally(() => { - setSubmitting(false) - }) - } - - return ( - - - Are you sure you want to delete user{" "} - - {props.data.name} - - ? This action is irreversible. - - {/* Buttons */} - - - - - - ); -} diff --git a/src/app/dashboard/(auth)/users/_modals/DeleteModal/index.ts b/src/app/dashboard/(auth)/users/_modals/DeleteModal/index.ts deleted file mode 100644 index a4052f8..0000000 --- a/src/app/dashboard/(auth)/users/_modals/DeleteModal/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import DeleteModal from "./DeleteModal"; - -export default DeleteModal; diff --git a/src/app/dashboard/(auth)/users/_modals/DetailModal/DetailModal.tsx b/src/app/dashboard/(auth)/users/_modals/DetailModal/DetailModal.tsx deleted file mode 100644 index 9003280..0000000 --- a/src/app/dashboard/(auth)/users/_modals/DetailModal/DetailModal.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import { FormModal } from '..' -import { UserFormData } from '@/features/dashboard/users/formSchemas/userFormDataSchema' - -interface Props { - data: UserFormData -} - -export default function DetailModal(props: Props) { - return -} diff --git a/src/app/dashboard/(auth)/users/_modals/DetailModal/index.ts b/src/app/dashboard/(auth)/users/_modals/DetailModal/index.ts deleted file mode 100644 index 2871960..0000000 --- a/src/app/dashboard/(auth)/users/_modals/DetailModal/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import DetailModal from "./DetailModal"; - -export default DetailModal; diff --git a/src/app/dashboard/(auth)/users/_modals/EditModal/EditModal.tsx b/src/app/dashboard/(auth)/users/_modals/EditModal/EditModal.tsx deleted file mode 100644 index 9e8ff6a..0000000 --- a/src/app/dashboard/(auth)/users/_modals/EditModal/EditModal.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import { FormModal } from '..' -import { UserFormData } from '@/features/dashboard/users/formSchemas/userFormDataSchema' - -interface Props { - data: UserFormData -} - -export default function EditModal(props: Props) { - return -} diff --git a/src/app/dashboard/(auth)/users/_modals/EditModal/index.ts b/src/app/dashboard/(auth)/users/_modals/EditModal/index.ts deleted file mode 100644 index 1dfd72f..0000000 --- a/src/app/dashboard/(auth)/users/_modals/EditModal/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import EditModal from "./EditModal"; - -export default EditModal; diff --git a/src/app/dashboard/(auth)/users/_modals/FormModal/FormModal.tsx b/src/app/dashboard/(auth)/users/_modals/FormModal/FormModal.tsx deleted file mode 100644 index f2e6a0a..0000000 --- a/src/app/dashboard/(auth)/users/_modals/FormModal/FormModal.tsx +++ /dev/null @@ -1,149 +0,0 @@ -"use client"; -import React, { useState } from "react"; -import { useForm } from "@mantine/form"; -import { useRouter } from "next/navigation"; -import { - Avatar, - Button, - Center, - Flex, - Modal, - ScrollArea, - Stack, - TextInput, - Title, -} from "@mantine/core"; -import { TbDeviceFloppy } from "react-icons/tb"; -import editUser from "@/features/dashboard/users/actions/editUser"; -import userFormDataSchema, { - UserFormData, -} from "@/features/dashboard/users/formSchemas/userFormDataSchema"; -import { showNotification } from "@/utils/notifications"; -import stringToColorHex from "@/utils/stringToColorHex"; -import { zodResolver } from "@mantine/form"; - -interface Props { - readonly?: boolean; - modalTitle: string; - data: UserFormData; -} - -export default function FormModal(props: Props) { - const router = useRouter(); - - const [isSubmitting, setSubmitting] = useState(false); - - const form = useForm({ - initialValues: props.data, - validate: zodResolver(userFormDataSchema), - }); - - /** - * Closes the modal. It won't close if a submission is in progress. - */ - const closeModal = () => { - if (isSubmitting) return; - router - .replace("?") - }; - - /** - * Handles the form submission. - * - * @param data The data from the form. - */ - const handleSubmit = (data: UserFormData) => { - setSubmitting(true); - editUser(data) - .then((response) => { - if (response.success) { - showNotification(response.message); - router.replace("?") - return; - } else { - if (response.errors) { - form.setErrors(response.errors); - return; - } - showNotification(response.message, "error"); - return; - } - }) - .catch(() => { - //TODO: Handle Error - }) - .finally(() => { - setSubmitting(false); - }); - }; - - return ( - {props.modalTitle}} - scrollAreaComponent={ScrollArea.Autosize} - > -
- - {/* Avatar */} -
- - {props.data.name?.[0].toUpperCase()} - -
- - {/* ID */} - - - {/* Name */} - - - {/* Email */} - - - {/* Buttons */} - - - {!props.readonly && ( - - )} - -
-
-
- ); -} diff --git a/src/app/dashboard/(auth)/users/_modals/FormModal/index.ts b/src/app/dashboard/(auth)/users/_modals/FormModal/index.ts deleted file mode 100644 index f10c3b0..0000000 --- a/src/app/dashboard/(auth)/users/_modals/FormModal/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import FormModal from "./FormModal"; - -export default FormModal; diff --git a/src/app/dashboard/(auth)/users/_modals/index.ts b/src/app/dashboard/(auth)/users/_modals/index.ts deleted file mode 100644 index 0a33e94..0000000 --- a/src/app/dashboard/(auth)/users/_modals/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { default as DeleteModal } from "./DeleteModal" -export { default as DetailModal } from "./DetailModal" -export { default as EditModal } from "./EditModal" -export { default as FormModal } from "./FormModal" diff --git a/src/app/dashboard/(auth)/users/_tables/UsersTable/UsersTable.tsx b/src/app/dashboard/(auth)/users/_tables/UsersTable/UsersTable.tsx deleted file mode 100644 index ca0596c..0000000 --- a/src/app/dashboard/(auth)/users/_tables/UsersTable/UsersTable.tsx +++ /dev/null @@ -1,74 +0,0 @@ -"use client"; -import React from "react"; - -import { - flexRender, - getCoreRowModel, - useReactTable, -} from "@tanstack/react-table"; -import columns, { UserRow } from "./columns"; -import { Table, Text } from "@mantine/core"; - -interface Props { - users: UserRow[]; -} - -export default function UsersTable({ users }: Props) { - const table = useReactTable({ - data: users, - columns, - getCoreRowModel: getCoreRowModel(), - defaultColumn: { - cell: (props) => {props.getValue() as React.ReactNode}, - }, - }); - - return ( - - {/* Thead */} - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ))} - - ))} - - - {/* TBody */} - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - ))} - -
- ); -} diff --git a/src/app/dashboard/(auth)/users/_tables/UsersTable/columns.tsx b/src/app/dashboard/(auth)/users/_tables/UsersTable/columns.tsx deleted file mode 100644 index 4c5aaf7..0000000 --- a/src/app/dashboard/(auth)/users/_tables/UsersTable/columns.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { createColumnHelper } from "@tanstack/react-table"; -import Link from "next/link"; -import { ActionIcon, Anchor, Avatar, Badge, Flex, Group, Text, Tooltip } from "@mantine/core"; -import { TbEye, TbPencil, TbTrash } from "react-icons/tb"; -import stringToColorHex from "@/utils/stringToColorHex"; - -export interface UserRow { - id: string, - name: string | null, - email: string | null, - photoUrl: string | null, -} - -const columnHelper = createColumnHelper() - -const columns = [ - columnHelper.display({ - id: "sequence", - header: "#", - cell: props => props.row.index + 1, - size: 1 - }), - - columnHelper.accessor('name', { - header: "Name", - cell: (props) => - {props.getValue()?.[0].toUpperCase()} - {props.getValue()} - - }), - - columnHelper.accessor('email', { - header: "Email", - cell: (props) => {props.getValue()} - }), - - columnHelper.display({ - id: "status", - header: "Status", - cell: (props) => Active - }), - - columnHelper.display({ - id: 'actions', - header: "Actions", - size: 10, - meta: { - className: "w-fit" - }, - cell: (props) => - - {/* Detail */} - - - - - - - {/* Edit */} - - - - - - - {/* Delete */} - - - - - - - }) -]; - -export default columns; diff --git a/src/modules/userManagement/actions/upsertUser.ts b/src/modules/userManagement/actions/upsertUser.ts index f1e8911..1521c81 100644 --- a/src/modules/userManagement/actions/upsertUser.ts +++ b/src/modules/userManagement/actions/upsertUser.ts @@ -3,12 +3,15 @@ import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue"; import prisma from "@/db"; import { revalidatePath } from "next/cache"; -import userFormDataSchema, { UserFormData } from "../formSchemas/userFormSchema"; +import userFormDataSchema, { + UserFormData, +} from "../formSchemas/userFormSchema"; import checkPermission from "@/modules/dashboard/services/checkPermission"; import unauthorized from "@/modules/dashboard/utils/unauthorized"; import DashboardError from "@/modules/dashboard/errors/DashboardError"; import handleCatch from "@/modules/dashboard/utils/handleCatch"; import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction"; +import hashPassword from "@/modules/auth/utils/hashPassword"; /** * Upserts a user based on the provided UserFormData. @@ -34,32 +37,37 @@ export default async function upsertUser( const validatedFields = userFormDataSchema.safeParse(data); if (!validatedFields.success) { throw new DashboardError({ - errorCode: "INVALID_FORM_DATA", - formErrors: mapObjectToFirstValue(validatedFields.error.flatten().fieldErrors) - }) + errorCode: "INVALID_FORM_DATA", + formErrors: mapObjectToFirstValue( + validatedFields.error.flatten().fieldErrors + ), + }); } const userData = { - id: validatedFields.data.id ? validatedFields.data.id : undefined, + id: validatedFields.data.id ? validatedFields.data.id : undefined, name: validatedFields.data.name, - photoProfile: validatedFields.data.photoProfileUrl ?? "", - email: validatedFields.data.email + photoProfile: validatedFields.data.photoProfileUrl ?? "", + email: validatedFields.data.email, }; + const passwordHash = await hashPassword(validatedFields.data.password!); // Database operation if (isInsert) { - if (await prisma.user.findFirst({ - where: { - email: userData.email - } - })){ - throw new DashboardError({ - errorCode: "INVALID_FORM_DATA", - formErrors: { - email: "The user is already exists" - } - }) - } - await prisma.user.create({ data: userData }); + if ( + await prisma.user.findFirst({ + where: { + email: userData.email, + }, + }) + ) { + throw new DashboardError({ + errorCode: "INVALID_FORM_DATA", + formErrors: { + email: "The user is already exists", + }, + }); + } + await prisma.user.create({ data: { ...userData, passwordHash } }); } else { await prisma.user.update({ where: { id: validatedFields.data.id! }, @@ -78,6 +86,6 @@ export default async function upsertUser( }.`, }; } catch (error) { - return handleCatch(error) + return handleCatch(error); } } diff --git a/src/modules/userManagement/formSchemas/userFormSchema.ts b/src/modules/userManagement/formSchemas/userFormSchema.ts index 24b1f0e..c080296 100644 --- a/src/modules/userManagement/formSchemas/userFormSchema.ts +++ b/src/modules/userManagement/formSchemas/userFormSchema.ts @@ -1,17 +1,19 @@ import { z } from "zod"; export interface UserFormData { - id: string; + id: string | undefined; name: string; photoProfileUrl: string; email: string; + password: string | undefined } const userFormDataSchema = z.object({ - id: z.string().nullable(), + id: z.string().optional(), name: z.string(), photoProfileUrl: z.union([z.string(), z.null()]), email: z.string().email(), + password: z.string().min(8).optional(), }); export default userFormDataSchema; diff --git a/src/modules/userManagement/modals/UserFormModal.tsx b/src/modules/userManagement/modals/UserFormModal.tsx index bfe1090..b4c20ed 100644 --- a/src/modules/userManagement/modals/UserFormModal.tsx +++ b/src/modules/userManagement/modals/UserFormModal.tsx @@ -15,6 +15,7 @@ import { Alert, Center, Avatar, + PasswordInput, } from "@mantine/core"; import { useForm, zodResolver } from "@mantine/form"; import { useRouter } from "next/navigation"; @@ -55,6 +56,7 @@ export default function UserFormModal(props: ModalProps) { email: "", name: "", photoProfileUrl: "", + password: "", }, validate: zodResolver(userFormDataSchema), validateInputOnChange: false, @@ -128,7 +130,7 @@ export default function UserFormModal(props: ModalProps) { onClose={closeModal} title={props.title} scrollAreaComponent={ScrollArea.Autosize} - size="xl" + size="md" >
@@ -137,7 +139,7 @@ export default function UserFormModal(props: ModalProps) {
@@ -178,6 +180,16 @@ export default function UserFormModal(props: ModalProps) { /> + {/* Password */} + {!form.values.id && !isFetching && ( + + )} + {/* Buttons */}