diff --git a/components/ModalContent/DeleteUserModal.tsx b/components/ModalContent/DeleteUserModal.tsx index 6dc7ee2a..73233732 100644 --- a/components/ModalContent/DeleteUserModal.tsx +++ b/components/ModalContent/DeleteUserModal.tsx @@ -1,8 +1,8 @@ -import toast from "react-hot-toast"; import Modal from "../Modal"; -import useUserStore from "@/store/admin/users"; import Button from "../ui/Button"; import { useTranslation } from "next-i18next"; +import { useDeleteUser } from "@/hooks/store/admin/users"; +import { useState } from "react"; type Props = { onClose: Function; @@ -11,22 +11,22 @@ type Props = { export default function DeleteUserModal({ onClose, userId }: Props) { const { t } = useTranslation(); - const { removeUser } = useUserStore(); - const deleteUser = async () => { - const load = toast.loading(t("deleting_user")); + const [submitLoader, setSubmitLoader] = useState(false); + const deleteUser = useDeleteUser(); - const response = await removeUser(userId); + const submit = async () => { + if (!submitLoader) { + setSubmitLoader(true); - toast.dismiss(load); + await deleteUser.mutateAsync(userId, { + onSuccess: () => { + onClose(); + }, + }); - if (response.ok) { - toast.success(t("user_deleted")); - } else { - toast.error(response.data as string); + setSubmitLoader(false); } - - onClose(); }; return ( @@ -45,7 +45,7 @@ export default function DeleteUserModal({ onClose, userId }: Props) { - diff --git a/components/ModalContent/NewUserModal.tsx b/components/ModalContent/NewUserModal.tsx index d83ef400..78c36f7b 100644 --- a/components/ModalContent/NewUserModal.tsx +++ b/components/ModalContent/NewUserModal.tsx @@ -1,9 +1,9 @@ import toast from "react-hot-toast"; import Modal from "../Modal"; -import useUserStore from "@/store/admin/users"; import TextInput from "../TextInput"; import { FormEvent, useState } from "react"; import { useTranslation, Trans } from "next-i18next"; +import { useAddUser } from "@/hooks/store/admin/users"; type Props = { onClose: Function; @@ -20,7 +20,9 @@ const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER === "true"; export default function NewUserModal({ onClose }: Props) { const { t } = useTranslation(); - const { addUser } = useUserStore(); + + const addUser = useAddUser(); + const [form, setForm] = useState({ name: "", username: "", @@ -44,24 +46,15 @@ export default function NewUserModal({ onClose }: Props) { }; if (checkFields()) { - if (form.password.length < 8) - return toast.error(t("password_length_error")); - setSubmitLoader(true); - const load = toast.loading(t("creating_account")); + await addUser.mutateAsync(form, { + onSuccess: () => { + onClose(); + }, + }); - const response = await addUser(form); - - toast.dismiss(load); setSubmitLoader(false); - - if (response.ok) { - toast.success(t("user_created")); - onClose(); - } else { - toast.error(response.data as string); - } } else { toast.error(t("fill_all_fields_error")); } diff --git a/hooks/store/admin/users.tsx b/hooks/store/admin/users.tsx new file mode 100644 index 00000000..ec1e0fd1 --- /dev/null +++ b/hooks/store/admin/users.tsx @@ -0,0 +1,89 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import toast from "react-hot-toast"; +import { useTranslation } from "next-i18next"; + +const useUsers = () => { + return useQuery({ + queryKey: ["users"], + queryFn: async () => { + const response = await fetch("/api/v1/users"); + if (!response.ok) { + if (response.status === 401) { + window.location.href = "/dashboard"; + } + throw new Error("Failed to fetch users."); + } + + const data = await response.json(); + return data.response; + }, + }); +}; + +const useAddUser = () => { + const queryClient = useQueryClient(); + const { t } = useTranslation(); + + return useMutation({ + mutationFn: async (body: any) => { + if (body.password.length < 8) throw new Error(t("password_length_error")); + + const load = toast.loading(t("creating_account")); + + const response = await fetch("/api/v1/users", { + method: "POST", + body: JSON.stringify(body), + headers: { + "Content-Type": "application/json", + }, + }); + + const data = await response.json(); + if (!response.ok) throw new Error(data.response); + + toast.dismiss(load); + + return data.response; + }, + onSuccess: (data) => { + queryClient.setQueryData(["users"], (oldData: any) => [...oldData, data]); + toast.success(t("user_created")); + }, + onError: (error) => { + toast.error(error.message); + }, + }); +}; + +const useDeleteUser = () => { + const queryClient = useQueryClient(); + const { t } = useTranslation(); + + return useMutation({ + mutationFn: async (userId: number) => { + const load = toast.loading(t("deleting_user")); + + const response = await fetch(`/api/v1/users/${userId}`, { + method: "DELETE", + }); + + const data = await response.json(); + if (!response.ok) throw new Error(data.response); + + toast.dismiss(load); + + return data.response; + }, + onSuccess: (data, variables) => { + queryClient.setQueryData(["users"], (oldData: any) => + oldData.filter((user: any) => user.id !== variables) + ); + toast.success(t("user_deleted")); + }, + onError: (error) => { + toast.error(error.message); + }, + }); +}; + +export { useUsers, useAddUser, useDeleteUser }; diff --git a/pages/admin.tsx b/pages/admin.tsx index 7211e08b..df62ffe4 100644 --- a/pages/admin.tsx +++ b/pages/admin.tsx @@ -1,11 +1,11 @@ import NewUserModal from "@/components/ModalContent/NewUserModal"; -import useUserStore from "@/store/admin/users"; import { User as U } from "@prisma/client"; import Link from "next/link"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { useTranslation } from "next-i18next"; import getServerSideProps from "@/lib/client/getServerSideProps"; import UserListing from "@/components/UserListing"; +import { useUsers } from "@/hooks/store/admin/users"; interface User extends U { subscriptions: { @@ -21,7 +21,7 @@ type UserModal = { export default function Admin() { const { t } = useTranslation(); - const { users, setUsers } = useUserStore(); + const { data: users = [] } = useUsers(); const [searchQuery, setSearchQuery] = useState(""); const [filteredUsers, setFilteredUsers] = useState(); @@ -33,10 +33,6 @@ export default function Admin() { const [newUserModal, setNewUserModal] = useState(false); - useEffect(() => { - setUsers(); - }, []); - return (
@@ -71,7 +67,7 @@ export default function Admin() { if (users) { setFilteredUsers( - users.filter((user) => + users.filter((user: any) => JSON.stringify(user) .toLowerCase() .includes(e.target.value.toLowerCase()) diff --git a/store/admin/users.ts b/store/admin/users.ts deleted file mode 100644 index 6925611d..00000000 --- a/store/admin/users.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { User as U } from "@prisma/client"; -import { create } from "zustand"; - -interface User extends U { - subscriptions: { - active: boolean; - }; -} - -type ResponseObject = { - ok: boolean; - data: object | string; -}; - -type UserStore = { - users: User[]; - setUsers: () => void; - addUser: (body: Partial) => Promise; - removeUser: (userId: number) => Promise; -}; - -const useUserStore = create((set) => ({ - users: [], - setUsers: async () => { - const response = await fetch("/api/v1/users"); - - const data = await response.json(); - - if (response.ok) set({ users: data.response }); - else if (response.status === 401) window.location.href = "/dashboard"; - }, - addUser: async (body) => { - const response = await fetch("/api/v1/users", { - method: "POST", - body: JSON.stringify(body), - headers: { - "Content-Type": "application/json", - }, - }); - - const data = await response.json(); - - if (response.ok) - set((state) => ({ - users: [...state.users, data.response], - })); - - return { ok: response.ok, data: data.response }; - }, - removeUser: async (userId) => { - const response = await fetch(`/api/v1/users/${userId}`, { - method: "DELETE", - }); - - const data = await response.json(); - - if (response.ok) - set((state) => ({ - users: state.users.filter((user) => user.id !== userId), - })); - - return { ok: response.ok, data: data.response }; - }, -})); - -export default useUserStore;