refactor handler and queries
This commit is contained in:
parent
e84a6f52c0
commit
2faf6ce83e
|
@ -16,7 +16,7 @@ import {
|
||||||
import { NavPreMain } from "./navigations/nav-pre-main";
|
import { NavPreMain } from "./navigations/nav-pre-main";
|
||||||
import { navData } from "@/prisma/data/nav";
|
import { navData } from "@/prisma/data/nav";
|
||||||
import { TeamSwitcher } from "../../../_components/team-switcher";
|
import { TeamSwitcher } from "../../../_components/team-switcher";
|
||||||
import { useGetCurrentUserQuery } from "../dashboard/user-management/queries";
|
import { useGetCurrentUserQuery } from "../dashboard/user-management/_queries/queries";
|
||||||
|
|
||||||
|
|
||||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||||
|
|
|
@ -27,8 +27,8 @@ import { IconLogout, IconSettings, IconSparkles } from "@tabler/icons-react";
|
||||||
import type { IUserSchema } from "@/src/entities/models/users/users.model";
|
import type { IUserSchema } from "@/src/entities/models/users/users.model";
|
||||||
// import { signOut } from "@/app/(pages)/(auth)/action";
|
// import { signOut } from "@/app/(pages)/(auth)/action";
|
||||||
import { SettingsDialog } from "../settings/setting-dialog";
|
import { SettingsDialog } from "../settings/setting-dialog";
|
||||||
import { useSignOutHandler } from "@/app/(pages)/(auth)/handler";
|
|
||||||
import { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogCancel, AlertDialogAction } from "@/app/_components/ui/alert-dialog";
|
import { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogCancel, AlertDialogAction } from "@/app/_components/ui/alert-dialog";
|
||||||
|
import { useSignOutHandler } from "@/app/(pages)/(auth)/_handlers/use-sign-out";
|
||||||
|
|
||||||
export function NavUser({ user }: { user: IUserSchema | null }) {
|
export function NavUser({ user }: { user: IUserSchema | null }) {
|
||||||
const { isMobile } = useSidebar();
|
const { isMobile } = useSidebar();
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/app/_components/ui/dialog"
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/app/_components/ui/dialog"
|
||||||
import { Button } from "@/app/_components/ui/button"
|
import { Button } from "@/app/_components/ui/button"
|
||||||
import { Mail, Lock, Loader2 } from "lucide-react"
|
import { Mail, Lock, Loader2 } from "lucide-react"
|
||||||
import { useAddUserDialogHandler } from "../handler"
|
|
||||||
import { ReactHookFormField } from "@/app/_components/react-hook-form-field"
|
import { ReactHookFormField } from "@/app/_components/react-hook-form-field"
|
||||||
|
import { useAddUserDialogHandler } from "../_handlers/use-add-user-dialog"
|
||||||
|
|
||||||
interface AddUserDialogProps {
|
interface AddUserDialogProps {
|
||||||
open: boolean
|
open: boolean
|
||||||
|
|
|
@ -16,10 +16,10 @@ import { Textarea } from "@/app/_components/ui/textarea";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { inviteUser } from "@/app/(pages)/(admin)/dashboard/user-management/action";
|
import { inviteUser } from "@/app/(pages)/(admin)/dashboard/user-management/action";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useInviteUserHandler } from "../handler";
|
|
||||||
import { ReactHookFormField } from "@/app/_components/react-hook-form-field";
|
import { ReactHookFormField } from "@/app/_components/react-hook-form-field";
|
||||||
import { Loader2, MailIcon } from "lucide-react";
|
import { Loader2, MailIcon } from "lucide-react";
|
||||||
import { Separator } from "@/app/_components/ui/separator";
|
import { Separator } from "@/app/_components/ui/separator";
|
||||||
|
import { useInviteUserHandler } from "../_handlers/use-invite-user";
|
||||||
|
|
||||||
|
|
||||||
interface InviteUserDialogProps {
|
interface InviteUserDialogProps {
|
||||||
|
|
|
@ -39,9 +39,9 @@ import {
|
||||||
} from "@/app/(pages)/(admin)/dashboard/user-management/action";
|
} from "@/app/(pages)/(admin)/dashboard/user-management/action";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { sendMagicLink, sendPasswordRecovery } from "@/app/(pages)/(auth)/action";
|
import { sendMagicLink, sendPasswordRecovery } from "@/app/(pages)/(auth)/action";
|
||||||
import { useUserDetailSheetHandlers } from "../handler";
|
|
||||||
import { IUserSchema } from "@/src/entities/models/users/users.model";
|
import { IUserSchema } from "@/src/entities/models/users/users.model";
|
||||||
import { formatDate } from "@/app/_utils/common";
|
import { formatDate } from "@/app/_utils/common";
|
||||||
|
import { useUserDetailSheetHandlers } from "../_handlers/use-detail-sheet";
|
||||||
|
|
||||||
interface UserDetailSheetProps {
|
interface UserDetailSheetProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
@ -56,104 +56,6 @@ export function UserDetailSheet({
|
||||||
user,
|
user,
|
||||||
onUserUpdated,
|
onUserUpdated,
|
||||||
}: UserDetailSheetProps) {
|
}: UserDetailSheetProps) {
|
||||||
// const [isDeleting, setIsDeleting] = useState(false);
|
|
||||||
// const [isLoading, setIsLoading] = useState({
|
|
||||||
// deleteUser: false,
|
|
||||||
// sendPasswordRecovery: false,
|
|
||||||
// sendMagicLink: false,
|
|
||||||
// toggleBan: false,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const deleteUserMutation = useMutation({
|
|
||||||
// mutationFn: () => deleteUser(user.id),
|
|
||||||
// onMutate: () => {
|
|
||||||
// setIsLoading((prev) => ({ ...prev, deleteUser: true }));
|
|
||||||
// setIsDeleting(true);
|
|
||||||
// },
|
|
||||||
// onSuccess: () => {
|
|
||||||
// toast.success("User deleted successfully");
|
|
||||||
// onUserUpdate();
|
|
||||||
// onOpenChange(false);
|
|
||||||
// },
|
|
||||||
// onError: () => {
|
|
||||||
// toast.error("Failed to delete user");
|
|
||||||
// },
|
|
||||||
// onSettled: () => {
|
|
||||||
// setIsLoading((prev) => ({ ...prev, deleteUser: false }));
|
|
||||||
// setIsDeleting(false);
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const sendPasswordRecoveryMutation = useMutation({
|
|
||||||
// mutationFn: () => {
|
|
||||||
// if (!user.email) {
|
|
||||||
// throw new Error("User does not have an email address");
|
|
||||||
// }
|
|
||||||
// return sendPasswordRecovery(user.email);
|
|
||||||
// },
|
|
||||||
// onMutate: () => {
|
|
||||||
// setIsLoading((prev) => ({ ...prev, sendPasswordRecovery: true }));
|
|
||||||
// },
|
|
||||||
// onSuccess: () => {
|
|
||||||
// toast.success("Password recovery email sent");
|
|
||||||
// },
|
|
||||||
// onError: () => {
|
|
||||||
// toast.error("Failed to send password recovery email");
|
|
||||||
// },
|
|
||||||
// onSettled: () => {
|
|
||||||
// setIsLoading((prev) => ({ ...prev, sendPasswordRecovery: false }));
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const sendMagicLinkMutation = useMutation({
|
|
||||||
// mutationFn: () => {
|
|
||||||
// if (!user.email) {
|
|
||||||
// throw new Error("User does not have an email address");
|
|
||||||
// }
|
|
||||||
// return sendMagicLink(user.email);
|
|
||||||
// },
|
|
||||||
// onMutate: () => {
|
|
||||||
// setIsLoading((prev) => ({ ...prev, sendMagicLink: true }));
|
|
||||||
// },
|
|
||||||
// onSuccess: () => {
|
|
||||||
// toast.success("Magic link sent successfully");
|
|
||||||
// },
|
|
||||||
// onError: () => {
|
|
||||||
// toast.error("Failed to send magic link");
|
|
||||||
// },
|
|
||||||
// onSettled: () => {
|
|
||||||
// setIsLoading((prev) => ({ ...prev, sendMagicLink: false }));
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const toggleBanMutation = useMutation({
|
|
||||||
// mutationFn: () => {
|
|
||||||
// if (user.banned_until) {
|
|
||||||
// return unbanUser(user.id);
|
|
||||||
// } else {
|
|
||||||
// const ban_duration = "7h"; // Example: Ban duration set to 7 days
|
|
||||||
// return banUser({ id: user.id, ban_duration });
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// onMutate: () => {
|
|
||||||
// setIsLoading((prev) => ({ ...prev, toggleBan: true }));
|
|
||||||
// },
|
|
||||||
// onSuccess: () => {
|
|
||||||
// toast.success("User ban status updated");
|
|
||||||
// onUserUpdate();
|
|
||||||
// },
|
|
||||||
// onError: () => {
|
|
||||||
// toast.error("Failed to update user ban status");
|
|
||||||
// },
|
|
||||||
// onSettled: () => {
|
|
||||||
// setIsLoading((prev) => ({ ...prev, toggleBan: false }));
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const handleCopyItem = (item: string) => {
|
|
||||||
// navigator.clipboard.writeText(item);
|
|
||||||
// toast.success("Copied to clipboard");
|
|
||||||
// };
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleDeleteUser,
|
handleDeleteUser,
|
||||||
|
@ -364,7 +266,7 @@ export function UserDetailSheet({
|
||||||
<Button
|
<Button
|
||||||
variant={user.banned_until ? "outline" : "outline"}
|
variant={user.banned_until ? "outline" : "outline"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleToggleBan}
|
onClick={() => handleToggleBan()}
|
||||||
disabled={isBanPending || isUnbanPending}
|
disabled={isBanPending || isUnbanPending}
|
||||||
>
|
>
|
||||||
{isBanPending || isUnbanPending ? (
|
{isBanPending || isUnbanPending ? (
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { useMutation } from "@tanstack/react-query"
|
||||||
import { updateUser } from "../action"
|
import { updateUser } from "../action"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { UpdateUserSchema } from "@/src/entities/models/users/update-user.model"
|
import { UpdateUserSchema } from "@/src/entities/models/users/update-user.model"
|
||||||
import { useUserProfileSheetHandlers } from "../handler"
|
import { useUserProfileSheetHandlers } from "../_handlers/use-profile-sheet"
|
||||||
|
|
||||||
type UserProfileFormValues = z.infer<typeof UpdateUserSchema>
|
type UserProfileFormValues = z.infer<typeof UpdateUserSchema>
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ export function UserProfileSheet({ open, onOpenChange, userData, onUserUpdated }
|
||||||
const {
|
const {
|
||||||
form,
|
form,
|
||||||
handleUpdateUser,
|
handleUpdateUser,
|
||||||
isUpdatePending,
|
isPending,
|
||||||
} = useUserProfileSheetHandlers({ open, userData, onOpenChange, onUserUpdated })
|
} = useUserProfileSheetHandlers({ open, userData, onOpenChange, onUserUpdated })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -214,14 +214,14 @@ export function UserProfileSheet({ open, onOpenChange, userData, onUserUpdated }
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => !isUpdatePending && onOpenChange(false)}
|
onClick={() => !isPending && onOpenChange(false)}
|
||||||
disabled={isUpdatePending}
|
disabled={isPending}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="xs" type="submit" disabled={isUpdatePending}>
|
<Button size="xs" type="submit" disabled={isPending}>
|
||||||
{isUpdatePending && <Loader2 className="mr-1 h-4 w-4 animate-spin" />}
|
{isPending && <Loader2 className="mr-1 h-4 w-4 animate-spin" />}
|
||||||
{isUpdatePending ? "Saving..." : "Save"}
|
{isPending ? "Saving..." : "Save"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -28,9 +28,9 @@ import { InviteUserDialog } from "./invite-user";
|
||||||
import { AddUserDialog } from "./add-user-dialog";
|
import { AddUserDialog } from "./add-user-dialog";
|
||||||
import { UserDetailSheet } from "./sheet";
|
import { UserDetailSheet } from "./sheet";
|
||||||
import { UserProfileSheet } from "./update-user";
|
import { UserProfileSheet } from "./update-user";
|
||||||
import { filterUsers, useUserManagementHandlers } from "../handler";
|
|
||||||
import { createUserColumns } from "./users-table";
|
import { createUserColumns } from "./users-table";
|
||||||
import { useGetUsersQuery } from "../queries";
|
import { useGetUsersQuery } from "../_queries/queries";
|
||||||
|
import { filterUsers, useUserManagementHandlers } from "../_handlers/use-user-management";
|
||||||
|
|
||||||
export default function UserManagement() {
|
export default function UserManagement() {
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ export default function UserManagement() {
|
||||||
handleUserUpdate,
|
handleUserUpdate,
|
||||||
clearFilters,
|
clearFilters,
|
||||||
getActiveFilterCount,
|
getActiveFilterCount,
|
||||||
} = useUserManagementHandlers(refetch)
|
} = useUserManagementHandlers()
|
||||||
|
|
||||||
// Apply filters to users
|
// Apply filters to users
|
||||||
const filteredUsers = useMemo(() => {
|
const filteredUsers = useMemo(() => {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { Card, CardContent } from "@/app/_components/ui/card";
|
import { Card, CardContent } from "@/app/_components/ui/card";
|
||||||
import { Users, UserCheck, UserX } from "lucide-react";
|
import { Users, UserCheck, UserX } from "lucide-react";
|
||||||
import { IUserSchema } from "@/src/entities/models/users/users.model";
|
import { IUserSchema } from "@/src/entities/models/users/users.model";
|
||||||
import { useGetUsersQuery } from "../queries";
|
import { useGetUsersQuery } from "../_queries/queries";
|
||||||
|
|
||||||
|
|
||||||
function calculateUserStats(users: IUserSchema[] | undefined) {
|
function calculateUserStats(users: IUserSchema[] | undefined) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { Input } from "@/app/_components/ui/input"
|
||||||
import { Avatar } from "@/app/_components/ui/avatar"
|
import { Avatar } from "@/app/_components/ui/avatar"
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import { Badge } from "@/app/_components/ui/badge"
|
import { Badge } from "@/app/_components/ui/badge"
|
||||||
import { useUserDetailSheetHandlers, useUsersHandlers } from "../handler"
|
import { useBanUserMutation, useDeleteUserMutation, useUnbanUserMutation } from "../_queries/mutations"
|
||||||
|
|
||||||
export type UserTableColumn = ColumnDef<IUserSchema, IUserSchema>
|
export type UserTableColumn = ColumnDef<IUserSchema, IUserSchema>
|
||||||
|
|
||||||
|
@ -26,11 +26,9 @@ export const createUserColumns = (
|
||||||
handleUserUpdate: (user: IUserSchema) => void,
|
handleUserUpdate: (user: IUserSchema) => void,
|
||||||
): UserTableColumn[] => {
|
): UserTableColumn[] => {
|
||||||
|
|
||||||
const {
|
const { mutateAsync: deleteUser } = useDeleteUserMutation();
|
||||||
deleteUser,
|
const { mutateAsync: banUser } = useBanUserMutation();
|
||||||
banUser,
|
const { mutateAsync: unbanUser } = useUnbanUserMutation();
|
||||||
unbanUser,
|
|
||||||
} = useUsersHandlers();
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -329,9 +327,9 @@ export const createUserColumns = (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (row.original.banned_until != null) {
|
if (row.original.banned_until != null) {
|
||||||
unbanUser(row.original.id)
|
unbanUser({ id: row.original.id })
|
||||||
} else {
|
} else {
|
||||||
banUser(row.original.id)
|
banUser({ id: row.original.id, ban_duration: "24h" })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useCreateUserMutation } from "../_queries/mutations";
|
||||||
|
import { CreateUserSchema, ICreateUserSchema } from "@/src/entities/models/users/create-user.model";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
export const useAddUserDialogHandler = ({ onUserAdded, onOpenChange }: {
|
||||||
|
onUserAdded: () => void;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { mutateAsync: createdUser, isPending } = useCreateUserMutation()
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { errors: errors },
|
||||||
|
setError,
|
||||||
|
getValues,
|
||||||
|
clearErrors,
|
||||||
|
watch,
|
||||||
|
} = useForm<ICreateUserSchema>({
|
||||||
|
resolver: zodResolver(CreateUserSchema),
|
||||||
|
defaultValues: {
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
email_confirm: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emailConfirm = watch("email_confirm");
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit(async (data) => {
|
||||||
|
|
||||||
|
await createdUser(data, {
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||||
|
|
||||||
|
onUserAdded();
|
||||||
|
onOpenChange(false);
|
||||||
|
reset();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
reset();
|
||||||
|
toast.error(error.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleOpenChange = (open: boolean) => {
|
||||||
|
if (!open) {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
onOpenChange(open);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
register,
|
||||||
|
handleSubmit: onSubmit,
|
||||||
|
reset,
|
||||||
|
errors,
|
||||||
|
isPending,
|
||||||
|
getValues,
|
||||||
|
clearErrors,
|
||||||
|
emailConfirm,
|
||||||
|
handleOpenChange,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
import { IUserSchema } from "@/src/entities/models/users/users.model";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { useBanUserMutation, useDeleteUserMutation, useUnbanUserMutation } from "../_queries/mutations";
|
||||||
|
import { useSendMagicLinkMutation, useSendPasswordRecoveryMutation } from "@/app/(pages)/(auth)/_queries/mutations";
|
||||||
|
import { ValidBanDuration } from "@/app/_lib/types/ban-duration";
|
||||||
|
import { handleCopyItem } from "@/app/_utils/common";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
export const useUserDetailSheetHandlers = ({ open, user, onUserUpdated, onOpenChange }: {
|
||||||
|
open: boolean;
|
||||||
|
user: IUserSchema;
|
||||||
|
onUserUpdated: () => void;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { mutateAsync: deleteUser, isPending: isDeletePending } = useDeleteUserMutation();
|
||||||
|
const { mutateAsync: sendPasswordRecovery, isPending: isSendPasswordRecoveryPending } = useSendPasswordRecoveryMutation();
|
||||||
|
const { mutateAsync: sendMagicLink, isPending: isSendMagicLinkPending } = useSendMagicLinkMutation();
|
||||||
|
const { mutateAsync: banUser, isPending: isBanPending } = useBanUserMutation();
|
||||||
|
const { mutateAsync: unbanUser, isPending: isUnbanPending } = useUnbanUserMutation();
|
||||||
|
|
||||||
|
const handleDeleteUser = async () => {
|
||||||
|
await deleteUser(user.id, {
|
||||||
|
onSuccess: () => {
|
||||||
|
onOpenChange(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendPasswordRecovery = async () => {
|
||||||
|
if (!user.email) {
|
||||||
|
toast.error("User has no email address");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await sendPasswordRecovery(user.email);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendMagicLink = async () => {
|
||||||
|
if (!user.email) {
|
||||||
|
toast.error("User has no email address");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await sendMagicLink(user.email);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBanUser = async (ban_duration: ValidBanDuration = "24h") => {
|
||||||
|
await banUser({ id: user.id, ban_duration: ban_duration }, {
|
||||||
|
onSuccess: () => {
|
||||||
|
onUserUpdated();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUnbanUser = async () => {
|
||||||
|
await unbanUser({ id: user.id }, {
|
||||||
|
onSuccess: onUserUpdated
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleBan = async (ban_duration: ValidBanDuration = "24h") => {
|
||||||
|
if (user.banned_until) {
|
||||||
|
await unbanUser({ id: user.id }, {
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||||
|
|
||||||
|
toast(`${user.email} has been unbanned`);
|
||||||
|
onUserUpdated();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await banUser({ id: user.id, ban_duration: ban_duration }, {
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||||
|
|
||||||
|
toast(`${user.email} has been banned`);
|
||||||
|
onUserUpdated();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleDeleteUser,
|
||||||
|
handleSendPasswordRecovery,
|
||||||
|
handleSendMagicLink,
|
||||||
|
handleBanUser,
|
||||||
|
handleUnbanUser,
|
||||||
|
handleToggleBan,
|
||||||
|
handleCopyItem,
|
||||||
|
isDeletePending,
|
||||||
|
isSendPasswordRecoveryPending,
|
||||||
|
isSendMagicLinkPending,
|
||||||
|
isBanPending,
|
||||||
|
isUnbanPending,
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { defaulIInviteUserSchemaValues, IInviteUserSchema, InviteUserSchema } from "@/src/entities/models/users/invite-user.model";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { useInviteUserMutation } from "../_queries/mutations";
|
||||||
|
|
||||||
|
export const useInviteUserHandler = ({ onUserInvited, onOpenChange }: {
|
||||||
|
onUserInvited: () => void;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { mutateAsync: inviteUser, isPending } = useInviteUserMutation();
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { errors: errors },
|
||||||
|
setError,
|
||||||
|
getValues,
|
||||||
|
clearErrors,
|
||||||
|
watch,
|
||||||
|
} = useForm<IInviteUserSchema>({
|
||||||
|
resolver: zodResolver(InviteUserSchema),
|
||||||
|
defaultValues: defaulIInviteUserSchemaValues
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit(async (data) => {
|
||||||
|
|
||||||
|
const { email } = data;
|
||||||
|
|
||||||
|
await inviteUser(email, {
|
||||||
|
onSuccess: () => {
|
||||||
|
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||||
|
|
||||||
|
toast.success("Invitation sent");
|
||||||
|
|
||||||
|
onUserInvited();
|
||||||
|
onOpenChange(false);
|
||||||
|
reset();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
reset();
|
||||||
|
toast.error("Failed to send invitation");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleOpenChange = (open: boolean) => {
|
||||||
|
if (!open) {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
onOpenChange(open);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
register,
|
||||||
|
handleSubmit: onSubmit,
|
||||||
|
handleOpenChange,
|
||||||
|
reset,
|
||||||
|
getValues,
|
||||||
|
clearErrors,
|
||||||
|
watch,
|
||||||
|
errors,
|
||||||
|
isPending,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { IUpdateUserSchema, UpdateUserSchema } from "@/src/entities/models/users/update-user.model";
|
||||||
|
import { IUserSchema } from "@/src/entities/models/users/users.model";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useUpdateUserMutation } from "../_queries/mutations";
|
||||||
|
|
||||||
|
export const useUserProfileSheetHandlers = ({ open, onOpenChange, userData, onUserUpdated }: {
|
||||||
|
open: boolean;
|
||||||
|
userData: IUserSchema;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
onUserUpdated: () => void;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const {
|
||||||
|
mutateAsync: updateUser,
|
||||||
|
isPending,
|
||||||
|
} = useUpdateUserMutation()
|
||||||
|
|
||||||
|
// Initialize form with user data
|
||||||
|
const form = useForm<IUpdateUserSchema>({
|
||||||
|
resolver: zodResolver(UpdateUserSchema),
|
||||||
|
defaultValues: {
|
||||||
|
email: userData?.email || undefined,
|
||||||
|
encrypted_password: userData?.encrypted_password || undefined,
|
||||||
|
role: (userData?.role as "user" | "staff" | "admin") || "user",
|
||||||
|
phone: userData?.phone || undefined,
|
||||||
|
invited_at: userData?.invited_at || undefined,
|
||||||
|
confirmed_at: userData?.confirmed_at || undefined,
|
||||||
|
// recovery_sent_at: userData?.recovery_sent_at || undefined,
|
||||||
|
last_sign_in_at: userData?.last_sign_in_at || undefined,
|
||||||
|
created_at: userData?.created_at || undefined,
|
||||||
|
updated_at: userData?.updated_at || undefined,
|
||||||
|
is_anonymous: userData?.is_anonymous || false,
|
||||||
|
profile: {
|
||||||
|
// id: userData?.profile?.id || undefined,
|
||||||
|
// user_id: userData?.profile?.user_id || undefined,
|
||||||
|
avatar: userData?.profile?.avatar || undefined,
|
||||||
|
username: userData?.profile?.username || undefined,
|
||||||
|
first_name: userData?.profile?.first_name || undefined,
|
||||||
|
last_name: userData?.profile?.last_name || undefined,
|
||||||
|
bio: userData?.profile?.bio || undefined,
|
||||||
|
address: userData?.profile?.address || {
|
||||||
|
street: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
country: "",
|
||||||
|
postal_code: "",
|
||||||
|
},
|
||||||
|
birth_date: userData?.profile?.birth_date ? new Date(userData.profile.birth_date) : undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleUpdateUser = async () => {
|
||||||
|
await updateUser({ id: userData.id, data: form.getValues() }, {
|
||||||
|
onSuccess: () => {
|
||||||
|
onUserUpdated();
|
||||||
|
onOpenChange(false);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
onOpenChange(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleUpdateUser,
|
||||||
|
form,
|
||||||
|
isPending,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
import { IUserFilterOptionsSchema, IUserSchema } from "@/src/entities/models/users/users.model"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
|
export const useUserManagementHandlers = () => {
|
||||||
|
const [searchQuery, setSearchQuery] = useState("")
|
||||||
|
const [detailUser, setDetailUser] = useState<IUserSchema | null>(null)
|
||||||
|
const [updateUser, setUpdateUser] = useState<IUserSchema | null>(null)
|
||||||
|
const [isSheetOpen, setIsSheetOpen] = useState(false)
|
||||||
|
const [isUpdateOpen, setIsUpdateOpen] = useState(false)
|
||||||
|
const [isAddUserOpen, setIsAddUserOpen] = useState(false)
|
||||||
|
const [isInviteUserOpen, setIsInviteUserOpen] = useState(false)
|
||||||
|
|
||||||
|
// Filter states
|
||||||
|
const [filters, setFilters] = useState<IUserFilterOptionsSchema>({
|
||||||
|
email: "",
|
||||||
|
phone: "",
|
||||||
|
lastSignIn: "",
|
||||||
|
createdAt: "",
|
||||||
|
status: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle opening the detail sheet
|
||||||
|
const handleUserClick = (user: IUserSchema) => {
|
||||||
|
setDetailUser(user)
|
||||||
|
setIsSheetOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle opening the update sheet
|
||||||
|
const handleUserUpdate = (user: IUserSchema) => {
|
||||||
|
setUpdateUser(user)
|
||||||
|
setIsUpdateOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close detail sheet when update sheet opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (isUpdateOpen) {
|
||||||
|
setIsSheetOpen(false)
|
||||||
|
}
|
||||||
|
}, [isUpdateOpen])
|
||||||
|
|
||||||
|
// Reset detail user when sheet closes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSheetOpen) {
|
||||||
|
// Use a small delay to prevent flickering if another sheet is opening
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (!isSheetOpen && !isUpdateOpen) {
|
||||||
|
setDetailUser(null)
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}
|
||||||
|
}, [isSheetOpen, isUpdateOpen])
|
||||||
|
|
||||||
|
// Reset update user when update sheet closes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isUpdateOpen) {
|
||||||
|
// Use a small delay to prevent flickering if another sheet is opening
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (!isUpdateOpen) {
|
||||||
|
setUpdateUser(null)
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}
|
||||||
|
}, [isUpdateOpen])
|
||||||
|
|
||||||
|
const clearFilters = () => {
|
||||||
|
setFilters({
|
||||||
|
email: "",
|
||||||
|
phone: "",
|
||||||
|
lastSignIn: "",
|
||||||
|
createdAt: "",
|
||||||
|
status: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getActiveFilterCount = () => {
|
||||||
|
return Object.values(filters).filter(
|
||||||
|
(value) => (typeof value === "string" && value !== "") || (Array.isArray(value) && value.length > 0),
|
||||||
|
).length
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchQuery,
|
||||||
|
setSearchQuery,
|
||||||
|
detailUser,
|
||||||
|
updateUser,
|
||||||
|
isSheetOpen,
|
||||||
|
setIsSheetOpen,
|
||||||
|
isUpdateOpen,
|
||||||
|
setIsUpdateOpen,
|
||||||
|
isAddUserOpen,
|
||||||
|
setIsAddUserOpen,
|
||||||
|
isInviteUserOpen,
|
||||||
|
setIsInviteUserOpen,
|
||||||
|
filters,
|
||||||
|
setFilters,
|
||||||
|
handleUserClick,
|
||||||
|
handleUserUpdate,
|
||||||
|
clearFilters,
|
||||||
|
getActiveFilterCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const filterUsers = (users: IUserSchema[], searchQuery: string, filters: IUserFilterOptionsSchema): IUserSchema[] => {
|
||||||
|
return users.filter((user) => {
|
||||||
|
|
||||||
|
// Global search
|
||||||
|
if (searchQuery) {
|
||||||
|
const query = searchQuery.toLowerCase()
|
||||||
|
const matchesSearch =
|
||||||
|
user.email?.toLowerCase().includes(query) ||
|
||||||
|
user.phone?.toLowerCase().includes(query) ||
|
||||||
|
user.id.toLowerCase().includes(query)
|
||||||
|
|
||||||
|
if (!matchesSearch) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email filter
|
||||||
|
if (filters.email && !user.email?.toLowerCase().includes(filters.email.toLowerCase())) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phone filter
|
||||||
|
if (filters.phone && !user.phone?.toLowerCase().includes(filters.phone.toLowerCase())) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last sign in filter
|
||||||
|
if (filters.lastSignIn) {
|
||||||
|
if (filters.lastSignIn === "never" && user.last_sign_in_at) {
|
||||||
|
return false
|
||||||
|
} else if (filters.lastSignIn === "today") {
|
||||||
|
const today = new Date()
|
||||||
|
today.setHours(0, 0, 0, 0)
|
||||||
|
const signInDate = user.last_sign_in_at ? new Date(user.last_sign_in_at) : null
|
||||||
|
if (!signInDate || signInDate < today) return false
|
||||||
|
} else if (filters.lastSignIn === "week") {
|
||||||
|
const weekAgo = new Date()
|
||||||
|
weekAgo.setDate(weekAgo.getDate() - 7)
|
||||||
|
const signInDate = user.last_sign_in_at ? new Date(user.last_sign_in_at) : null
|
||||||
|
if (!signInDate || signInDate < weekAgo) return false
|
||||||
|
} else if (filters.lastSignIn === "month") {
|
||||||
|
const monthAgo = new Date()
|
||||||
|
monthAgo.setMonth(monthAgo.getMonth() - 1)
|
||||||
|
const signInDate = user.last_sign_in_at ? new Date(user.last_sign_in_at) : null
|
||||||
|
if (!signInDate || signInDate < monthAgo) return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Created at filter
|
||||||
|
if (filters.createdAt) {
|
||||||
|
if (filters.createdAt === "today") {
|
||||||
|
const today = new Date()
|
||||||
|
today.setHours(0, 0, 0, 0)
|
||||||
|
const createdAt = user.created_at ? (user.created_at ? new Date(user.created_at) : new Date()) : new Date()
|
||||||
|
if (createdAt < today) return false
|
||||||
|
} else if (filters.createdAt === "week") {
|
||||||
|
const weekAgo = new Date()
|
||||||
|
weekAgo.setDate(weekAgo.getDate() - 7)
|
||||||
|
const createdAt = user.created_at ? new Date(user.created_at) : new Date()
|
||||||
|
if (createdAt < weekAgo) return false
|
||||||
|
} else if (filters.createdAt === "month") {
|
||||||
|
const monthAgo = new Date()
|
||||||
|
monthAgo.setMonth(monthAgo.getMonth() - 1)
|
||||||
|
const createdAt = user.created_at ? new Date(user.created_at) : new Date()
|
||||||
|
if (createdAt < monthAgo) return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status filter
|
||||||
|
if (filters.status.length > 0) {
|
||||||
|
const userStatus = user.banned_until ? "banned" : !user.email_confirmed_at ? "unconfirmed" : "active"
|
||||||
|
|
||||||
|
if (!filters.status.includes(userStatus)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { ICreateUserSchema } from "@/src/entities/models/users/create-user.model";
|
||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import { banUser, createUser, deleteUser, inviteUser, unbanUser, updateUser } from "../action";
|
||||||
|
import { IUpdateUserSchema } from "@/src/entities/models/users/update-user.model";
|
||||||
|
import { ICredentialsInviteUserSchema } from "@/src/entities/models/users/invite-user.model";
|
||||||
|
import { IBanUserSchema, ICredentialsBanUserSchema } from "@/src/entities/models/users/ban-user.model";
|
||||||
|
import { ICredentialsUnbanUserSchema } from "@/src/entities/models/users/unban-user.model";
|
||||||
|
import { ValidBanDuration } from "@/app/_lib/types/ban-duration";
|
||||||
|
|
||||||
|
export const useCreateUserMutation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ["user", "create"],
|
||||||
|
mutationFn: (data: ICreateUserSchema) => createUser(data),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateUserMutation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ["user", "update"],
|
||||||
|
mutationFn: (args: { id: string; data: IUpdateUserSchema }) => updateUser(args.id, args.data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDeleteUserMutation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ["user", "delete"],
|
||||||
|
mutationFn: (id: string) => deleteUser(id),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useInviteUserMutation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ["user", "invite"],
|
||||||
|
mutationFn: (email: string) => inviteUser({ email }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const useBanUserMutation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ["user", "ban"],
|
||||||
|
mutationFn: (args: { id: string; ban_duration: ValidBanDuration }) => banUser({ id: args.id }, { ban_duration: args.ban_duration }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUnbanUserMutation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ["user", "unban"],
|
||||||
|
mutationFn: (credential: ICredentialsUnbanUserSchema) => unbanUser(credential),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { IUserSchema } from "@/src/entities/models/users/users.model";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { getCurrentUser, getUserByEmail, getUserById, getUserByUsername, getUsers } from "../action";
|
||||||
|
|
||||||
|
export const useGetUsersQuery = () => {
|
||||||
|
return useQuery<IUserSchema[]>({
|
||||||
|
queryKey: ["users"],
|
||||||
|
queryFn: () => getUsers()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGetUserByEmailQuery = (email: string) => {
|
||||||
|
return useQuery<IUserSchema>({
|
||||||
|
queryKey: ["user", "email", email],
|
||||||
|
queryFn: () => getUserByEmail({ email }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGetUserByIdQuery = (id: string) => {
|
||||||
|
return useQuery<IUserSchema>({
|
||||||
|
queryKey: ["user", "id", id],
|
||||||
|
queryFn: () => getUserById({ id }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGetUserByUsernameQuery = (username: string) => {
|
||||||
|
return useQuery<IUserSchema>({
|
||||||
|
queryKey: ["user", "username", username],
|
||||||
|
queryFn: () => getUserByUsername({ username }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGetCurrentUserQuery = () => {
|
||||||
|
return useQuery<IUserSchema>({
|
||||||
|
queryKey: ["user", "current"],
|
||||||
|
queryFn: () => getCurrentUser(),
|
||||||
|
})
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,212 +1,212 @@
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
// import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import {
|
// import {
|
||||||
banUser,
|
// banUser,
|
||||||
getCurrentUser,
|
// getCurrentUser,
|
||||||
getUserByEmail,
|
// getUserByEmail,
|
||||||
getUserById,
|
// getUserById,
|
||||||
getUsers,
|
// getUsers,
|
||||||
unbanUser,
|
// unbanUser,
|
||||||
inviteUser,
|
// inviteUser,
|
||||||
createUser,
|
// createUser,
|
||||||
updateUser,
|
// updateUser,
|
||||||
deleteUser,
|
// deleteUser,
|
||||||
getUserByUsername
|
// getUserByUsername
|
||||||
} from "./action";
|
// } from "./action";
|
||||||
import { IUserSchema } from "@/src/entities/models/users/users.model";
|
// import { IUserSchema } from "@/src/entities/models/users/users.model";
|
||||||
import { IBanDuration, IBanUserSchema, ICredentialsBanUserSchema } from "@/src/entities/models/users/ban-user.model";
|
// import { IBanDuration, IBanUserSchema, ICredentialsBanUserSchema } from "@/src/entities/models/users/ban-user.model";
|
||||||
import { ICredentialsUnbanUserSchema, IUnbanUserSchema } from "@/src/entities/models/users/unban-user.model";
|
// import { ICredentialsUnbanUserSchema, IUnbanUserSchema } from "@/src/entities/models/users/unban-user.model";
|
||||||
import { ICreateUserSchema } from "@/src/entities/models/users/create-user.model";
|
// import { ICreateUserSchema } from "@/src/entities/models/users/create-user.model";
|
||||||
import { IUpdateUserSchema } from "@/src/entities/models/users/update-user.model";
|
// import { IUpdateUserSchema } from "@/src/entities/models/users/update-user.model";
|
||||||
import { ICredentialsInviteUserSchema } from "@/src/entities/models/users/invite-user.model";
|
// import { ICredentialsInviteUserSchema } from "@/src/entities/models/users/invite-user.model";
|
||||||
import { ICredentialGetUserByEmailSchema, ICredentialGetUserByIdSchema, ICredentialGetUserByUsernameSchema, IGetUserByEmailSchema, IGetUserByIdSchema, IGetUserByUsernameSchema } from "@/src/entities/models/users/read-user.model";
|
// import { ICredentialGetUserByEmailSchema, ICredentialGetUserByIdSchema, ICredentialGetUserByUsernameSchema, IGetUserByEmailSchema, IGetUserByIdSchema, IGetUserByUsernameSchema } from "@/src/entities/models/users/read-user.model";
|
||||||
|
|
||||||
const useUsersAction = () => {
|
// const useUsersAction = () => {
|
||||||
|
|
||||||
// For all users (no parameters needed)
|
// // For all users (no parameters needed)
|
||||||
const getUsersQuery = useQuery<IUserSchema[]>({
|
// const getUsersQuery = useQuery<IUserSchema[]>({
|
||||||
queryKey: ["users"],
|
// queryKey: ["users"],
|
||||||
queryFn: async () => await getUsers()
|
// queryFn: () => getUsers()
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Current user query doesn't need parameters
|
// // Current user query doesn't need parameters
|
||||||
const getCurrentUserQuery = useQuery<IUserSchema>({
|
// const getCurrentUserQuery = useQuery<IUserSchema>({
|
||||||
queryKey: ["user", "current"],
|
// queryKey: ["user", "current"],
|
||||||
queryFn: async () => await getCurrentUser()
|
// queryFn: () => getCurrentUser()
|
||||||
});
|
// });
|
||||||
|
|
||||||
const getUserByIdQuery = (credential: ICredentialGetUserByIdSchema) => useQuery<IUserSchema>({
|
// const getUserByIdQuery = (credential: ICredentialGetUserByIdSchema) => useQuery<IUserSchema>({
|
||||||
queryKey: ["user", "id", credential.id],
|
// queryKey: ["user", "id", credential.id],
|
||||||
queryFn: async () => await getUserById(credential)
|
// queryFn: () => getUserById(credential)
|
||||||
});
|
// });
|
||||||
|
|
||||||
const getUserByEmailQuery = (credential: IGetUserByEmailSchema) => useQuery<IUserSchema>({
|
// const getUserByEmailQuery = (credential: IGetUserByEmailSchema) => useQuery<IUserSchema>({
|
||||||
queryKey: ["user", "email", credential.email],
|
// queryKey: ["user", "email", credential.email],
|
||||||
queryFn: async () => await getUserByEmail(credential)
|
// queryFn: () => getUserByEmail(credential)
|
||||||
});
|
// });
|
||||||
|
|
||||||
const getUserByUsernameQuery = (credential: IGetUserByUsernameSchema) => useQuery<IUserSchema>({
|
// const getUserByUsernameQuery = (credential: IGetUserByUsernameSchema) => useQuery<IUserSchema>({
|
||||||
queryKey: ["user", "username", credential.username],
|
// queryKey: ["user", "username", credential.username],
|
||||||
queryFn: async () => await getUserByUsername(credential)
|
// queryFn: () => getUserByUsername(credential)
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Mutations that don't need dynamic parameters
|
// // Mutations that don't need dynamic parameters
|
||||||
const banUserMutation = useMutation({
|
// const banUserMutation = (credential: ICredentialsBanUserSchema, data: IBanUserSchema) => useMutation({
|
||||||
mutationKey: ["banUser"],
|
// mutationKey: ["banUser"],
|
||||||
mutationFn: async ({ credential, data }: { credential: ICredentialsBanUserSchema; data: IBanUserSchema }) => await banUser(credential, data)
|
// mutationFn: () => banUser(credential, data)
|
||||||
});
|
// });
|
||||||
|
|
||||||
const unbanUserMutation = useMutation({
|
// const unbanUserMutation = useMutation({
|
||||||
mutationKey: ["unbanUser"],
|
// mutationKey: ["unbanUser"],
|
||||||
mutationFn: async (credential: ICredentialsUnbanUserSchema) => await unbanUser(credential)
|
// mutationFn: async (credential: ICredentialsUnbanUserSchema) => await unbanUser(credential)
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Create functions that return configured hooks
|
// // Create functions that return configured hooks
|
||||||
const inviteUserMutation = useMutation({
|
// const inviteUserMutation = useMutation({
|
||||||
mutationKey: ["inviteUser"],
|
// mutationKey: ["inviteUser"],
|
||||||
mutationFn: async (credential: ICredentialsInviteUserSchema) => await inviteUser(credential)
|
// mutationFn: async (credential: ICredentialsInviteUserSchema) => await inviteUser(credential)
|
||||||
});
|
// });
|
||||||
|
|
||||||
const createUserMutation = useMutation({
|
// const createUserMutation = useMutation({
|
||||||
mutationKey: ["createUser"],
|
// mutationKey: ["createUser"],
|
||||||
mutationFn: async (data: ICreateUserSchema) => await createUser(data)
|
// mutationFn: async (data: ICreateUserSchema) => await createUser(data)
|
||||||
});
|
// });
|
||||||
|
|
||||||
const updateUserMutation = useMutation({
|
// const updateUserMutation = useMutation({
|
||||||
mutationKey: ["updateUser"],
|
// mutationKey: ["updateUser"],
|
||||||
mutationFn: async (params: { id: string; data: IUpdateUserSchema }) => updateUser(params.id, params.data)
|
// mutationFn: async (params: { id: string; data: IUpdateUserSchema }) => updateUser(params.id, params.data)
|
||||||
});
|
// });
|
||||||
|
|
||||||
const deleteUserMutation = useMutation({
|
// const deleteUserMutation = useMutation({
|
||||||
mutationKey: ["deleteUser"],
|
// mutationKey: ["deleteUser"],
|
||||||
mutationFn: async (id: string) => await deleteUser(id)
|
// mutationFn: async (id: string) => await deleteUser(id)
|
||||||
});
|
// });
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
getUsers: getUsersQuery,
|
// getUsers: getUsersQuery,
|
||||||
getCurrentUser: getCurrentUserQuery,
|
// getCurrentUser: getCurrentUserQuery,
|
||||||
getUserById: getUserByIdQuery,
|
// getUserById: getUserByIdQuery,
|
||||||
getUserByEmailQuery,
|
// getUserByEmailQuery,
|
||||||
getUserByUsernameQuery,
|
// getUserByUsernameQuery,
|
||||||
banUser: banUserMutation,
|
// banUser: banUserMutation,
|
||||||
unbanUser: unbanUserMutation,
|
// unbanUser: unbanUserMutation,
|
||||||
inviteUser: inviteUserMutation,
|
// inviteUser: inviteUserMutation,
|
||||||
createUser: createUserMutation,
|
// createUser: createUserMutation,
|
||||||
updateUser: updateUserMutation,
|
// updateUser: updateUserMutation,
|
||||||
deleteUser: deleteUserMutation
|
// deleteUser: deleteUserMutation
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useGetUsersQuery = () => {
|
// export const useGetUsersQuery = () => {
|
||||||
const { getUsers } = useUsersAction();
|
// const { getUsers } = useUsersAction();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
data: getUsers.data,
|
// data: getUsers.data,
|
||||||
isPending: getUsers.isPending,
|
// isPending: getUsers.isPending,
|
||||||
error: getUsers.error,
|
// error: getUsers.error,
|
||||||
refetch: getUsers.refetch,
|
// refetch: getUsers.refetch,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useGetCurrentUserQuery = () => {
|
// export const useGetCurrentUserQuery = () => {
|
||||||
const { getCurrentUser } = useUsersAction();
|
// const { getCurrentUser } = useUsersAction();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
data: getCurrentUser.data,
|
// data: getCurrentUser.data,
|
||||||
isPending: getCurrentUser.isPending,
|
// isPending: getCurrentUser.isPending,
|
||||||
error: getCurrentUser.error,
|
// error: getCurrentUser.error,
|
||||||
refetch: getCurrentUser.refetch,
|
// refetch: getCurrentUser.refetch,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useGetUserByIdQuery = (credential: ICredentialGetUserByIdSchema) => {
|
// export const useGetUserByIdQuery = (credential: ICredentialGetUserByIdSchema) => {
|
||||||
const { getUserById } = useUsersAction();
|
// const { getUserById } = useUsersAction();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
data: getUserById(credential).data,
|
// data: getUserById(credential).data,
|
||||||
isPending: getUserById(credential).isPending,
|
// isPending: getUserById(credential).isPending,
|
||||||
error: getUserById(credential).error,
|
// error: getUserById(credential).error,
|
||||||
refetch: getUserById(credential).refetch,
|
// refetch: getUserById(credential).refetch,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useGetUserByEmailQuery = (credential: ICredentialGetUserByEmailSchema) => {
|
// export const useGetUserByEmailQuery = (credential: ICredentialGetUserByEmailSchema) => {
|
||||||
const { getUserByEmailQuery } = useUsersAction();
|
// const { getUserByEmailQuery } = useUsersAction();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
data: getUserByEmailQuery(credential).data,
|
// data: getUserByEmailQuery(credential).data,
|
||||||
isPending: getUserByEmailQuery(credential).isPending,
|
// isPending: getUserByEmailQuery(credential).isPending,
|
||||||
error: getUserByEmailQuery(credential).error,
|
// error: getUserByEmailQuery(credential).error,
|
||||||
refetch: getUserByEmailQuery(credential).refetch,
|
// refetch: getUserByEmailQuery(credential).refetch,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useGetUserByUsernameQuery = (credential: ICredentialGetUserByUsernameSchema) => {
|
// export const useGetUserByUsernameQuery = (credential: ICredentialGetUserByUsernameSchema) => {
|
||||||
const { getUserByUsernameQuery } = useUsersAction();
|
// const { getUserByUsernameQuery } = useUsersAction();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
data: getUserByUsernameQuery(credential).data,
|
// data: getUserByUsernameQuery(credential).data,
|
||||||
isPending: getUserByUsernameQuery(credential).isPending,
|
// isPending: getUserByUsernameQuery(credential).isPending,
|
||||||
error: getUserByUsernameQuery(credential).error,
|
// error: getUserByUsernameQuery(credential).error,
|
||||||
refetch: getUserByUsernameQuery(credential).refetch,
|
// refetch: getUserByUsernameQuery(credential).refetch,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useCreateUserMutation = () => {
|
// export const useCreateUserMutation = () => {
|
||||||
const { createUser } = useUsersAction();
|
// const { createUser } = useUsersAction();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
createUser: createUser.mutateAsync,
|
// createUser: createUser.mutateAsync,
|
||||||
isPending: createUser.isPending,
|
// isPending: createUser.isPending,
|
||||||
errors: createUser.error,
|
// errors: createUser.error,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useInviteUserMutation = () => {
|
// export const useInviteUserMutation = () => {
|
||||||
const { inviteUser } = useUsersAction();
|
// const { inviteUser } = useUsersAction();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
inviteUser: inviteUser.mutateAsync,
|
// inviteUser: inviteUser.mutateAsync,
|
||||||
isPending: inviteUser.isPending,
|
// isPending: inviteUser.isPending,
|
||||||
errors: inviteUser.error,
|
// errors: inviteUser.error,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useUpdateUserMutation = () => {
|
// export const useUpdateUserMutation = () => {
|
||||||
const { updateUser } = useUsersAction();
|
// const { updateUser } = useUsersAction();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
updateUser: updateUser.mutateAsync,
|
// updateUser: updateUser.mutateAsync,
|
||||||
isPending: updateUser.isPending,
|
// isPending: updateUser.isPending,
|
||||||
errors: updateUser.error,
|
// errors: updateUser.error,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useBanUserMutation = () => {
|
// export const useBanUserMutation = () => {
|
||||||
const { banUser } = useUsersAction();
|
// const { banUser } = useUsersAction();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
banUser: banUser.mutateAsync,
|
// banUser: banUser.mutateAsync,
|
||||||
isPending: banUser.isPending,
|
// isPending: banUser.isPending,
|
||||||
errors: banUser.error,
|
// errors: banUser.error,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useUnbanUserMutation = () => {
|
// export const useUnbanUserMutation = () => {
|
||||||
const { unbanUser } = useUsersAction();
|
// const { unbanUser } = useUsersAction();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
unbanUser: unbanUser.mutateAsync,
|
// unbanUser: unbanUser.mutateAsync,
|
||||||
isPending: unbanUser.isPending,
|
// isPending: unbanUser.isPending,
|
||||||
errors: unbanUser.error,
|
// errors: unbanUser.error,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useDeleteUserMutation = () => {
|
// export const useDeleteUserMutation = () => {
|
||||||
const { deleteUser } = useUsersAction();
|
// const { deleteUser } = useUsersAction();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
deleteUser: deleteUser.mutateAsync,
|
// deleteUser: deleteUser.mutateAsync,
|
||||||
isPending: deleteUser.isPending,
|
// isPending: deleteUser.isPending,
|
||||||
errors: deleteUser.error,
|
// errors: deleteUser.error,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
|
@ -10,7 +10,7 @@ import { FormField } from "@/app/_components/form-field";
|
||||||
// import { useSignInController } from "@/src/interface-adapters/controllers/auth/sign-in.controller";
|
// import { useSignInController } from "@/src/interface-adapters/controllers/auth/sign-in.controller";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { signIn } from "../action";
|
import { signIn } from "../action";
|
||||||
import { useSignInHandler } from "../handler";
|
import { useSignInHandler } from "../_handlers/use-sign-in";
|
||||||
|
|
||||||
export function SignInForm({
|
export function SignInForm({
|
||||||
className,
|
className,
|
||||||
|
@ -33,7 +33,7 @@ export function SignInForm({
|
||||||
// setLoading(false);
|
// setLoading(false);
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const { isPending, handleSignIn, error, errors, clearError } = useSignInHandler();
|
const { register, isPending, handleSignIn, error, errors } = useSignInHandler();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -81,9 +81,7 @@ export function SignInForm({
|
||||||
label="Email"
|
label="Email"
|
||||||
input={
|
input={
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
{...register("email")}
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
placeholder="you@example.com"
|
placeholder="you@example.com"
|
||||||
className={`bg-[#1C1C1C] border-gray-800`}
|
className={`bg-[#1C1C1C] border-gray-800`}
|
||||||
error={!!errors}
|
error={!!errors}
|
||||||
|
|
|
@ -16,8 +16,8 @@ import {
|
||||||
import { cn } from "@/app/_lib/utils";
|
import { cn } from "@/app/_lib/utils";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { Controller } from "react-hook-form";
|
import { Controller } from "react-hook-form";
|
||||||
import { useVerifyOtpHandler } from "../handler";
|
|
||||||
import { Button } from "@/app/_components/ui/button";
|
import { Button } from "@/app/_components/ui/button";
|
||||||
|
import { useVerifyOtpHandler } from "../_handlers/use-verify-otp";
|
||||||
|
|
||||||
interface VerifyOtpFormProps extends React.HTMLAttributes<HTMLDivElement> {}
|
interface VerifyOtpFormProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { useNavigations } from "@/app/_hooks/use-navigations";
|
||||||
|
import { useSignInMutation } from "../_queries/mutations";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { ISignInPasswordlessSchema, SignInPasswordlessSchema } from "@/src/entities/models/auth/sign-in.model";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
|
export function useSignInHandler() {
|
||||||
|
const { mutateAsync: signIn, isPending, error: errors } = useSignInMutation();
|
||||||
|
const { router } = useNavigations();
|
||||||
|
|
||||||
|
const [error, setError] = useState<string>();
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
reset,
|
||||||
|
formState: { errors: formErrors },
|
||||||
|
setError: setFormError,
|
||||||
|
} = useForm<ISignInPasswordlessSchema>({
|
||||||
|
defaultValues: {
|
||||||
|
email: "",
|
||||||
|
},
|
||||||
|
resolver: zodResolver(SignInPasswordlessSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (isPending) return;
|
||||||
|
|
||||||
|
setError(undefined);
|
||||||
|
|
||||||
|
const formData = new FormData(event.currentTarget);
|
||||||
|
const email = formData.get('email')?.toString();
|
||||||
|
|
||||||
|
const res = await signIn(formData);
|
||||||
|
|
||||||
|
if (!res?.error) {
|
||||||
|
toast('An email has been sent to you. Please check your inbox.');
|
||||||
|
if (email) router.push(`/verify-otp?email=${encodeURIComponent(email)}`);
|
||||||
|
} else {
|
||||||
|
setError(res.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// const onSubmit = handleSubmit(async (data) => {
|
||||||
|
// if (isPending) return;
|
||||||
|
|
||||||
|
// console.log(data);
|
||||||
|
|
||||||
|
// setError(undefined);
|
||||||
|
|
||||||
|
// const { email } = data;
|
||||||
|
|
||||||
|
// const formData = new FormData();
|
||||||
|
// formData.append('email', email);
|
||||||
|
|
||||||
|
// const res = await signIn(formData);
|
||||||
|
|
||||||
|
// if (!res?.error) {
|
||||||
|
// toast('An email has been sent to you. Please check your inbox.');
|
||||||
|
// router.push(`/verify-otp?email=${encodeURIComponent(email)}`);
|
||||||
|
// } else {
|
||||||
|
// setError(res.error);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
return {
|
||||||
|
// formData,
|
||||||
|
// handleChange,
|
||||||
|
reset,
|
||||||
|
register,
|
||||||
|
handleSignIn: handleSubmit,
|
||||||
|
error,
|
||||||
|
isPending,
|
||||||
|
errors: !!error || errors,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useSignOutMutation } from "../_queries/mutations";
|
||||||
|
import { useNavigations } from "@/app/_hooks/use-navigations";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { AuthenticationError } from "@/src/entities/errors/auth";
|
||||||
|
|
||||||
|
export function useSignOutHandler() {
|
||||||
|
const { mutateAsync: signOut, isPending, error: errors } = useSignOutMutation();
|
||||||
|
const { router } = useNavigations();
|
||||||
|
const [error, setError] = useState<string>();
|
||||||
|
|
||||||
|
const handleSignOut = async () => {
|
||||||
|
if (isPending) return;
|
||||||
|
|
||||||
|
setError(undefined);
|
||||||
|
|
||||||
|
await signOut(undefined, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('You have been signed out successfully');
|
||||||
|
router.push('/sign-in');
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
if (error instanceof AuthenticationError) {
|
||||||
|
setError(error.message);
|
||||||
|
toast.error(error.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleSignOut,
|
||||||
|
error,
|
||||||
|
isPending: isPending,
|
||||||
|
errors: !!error || errors,
|
||||||
|
clearError: () => setError(undefined),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { useNavigations } from "@/app/_hooks/use-navigations";
|
||||||
|
import { IVerifyOtpSchema, verifyOtpSchema } from "@/src/entities/models/auth/verify-otp.model";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { useVerifyOtpMutation } from "../_queries/mutations";
|
||||||
|
|
||||||
|
export function useVerifyOtpHandler(email: string) {
|
||||||
|
const { mutateAsync: verifyOtp, isPending } = useVerifyOtpMutation();
|
||||||
|
const { router } = useNavigations();
|
||||||
|
const [error, setError] = useState<string>();
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit: hookFormSubmit,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
setValue,
|
||||||
|
reset
|
||||||
|
} = useForm<IVerifyOtpSchema>({
|
||||||
|
resolver: zodResolver(verifyOtpSchema),
|
||||||
|
defaultValues: {
|
||||||
|
email,
|
||||||
|
token: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleOtpChange = (
|
||||||
|
value: string,
|
||||||
|
onChange: (value: string) => void
|
||||||
|
) => {
|
||||||
|
onChange(value);
|
||||||
|
|
||||||
|
if (value.length === 6) {
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear error when user starts typing
|
||||||
|
if (error) {
|
||||||
|
setError(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = hookFormSubmit(async (data) => {
|
||||||
|
if (isPending) return;
|
||||||
|
|
||||||
|
setError(undefined);
|
||||||
|
|
||||||
|
// Create FormData object
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('email', data.email);
|
||||||
|
formData.append('token', data.token);
|
||||||
|
|
||||||
|
await verifyOtp(formData, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('OTP verified successfully');
|
||||||
|
// Navigate to dashboard on success
|
||||||
|
router.push('/dashboard');
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setError(error.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
handleVerifyOtp: handleSubmit,
|
||||||
|
handleOtpChange,
|
||||||
|
errors: {
|
||||||
|
...errors,
|
||||||
|
token: error ? { message: error } : errors.token,
|
||||||
|
},
|
||||||
|
isPending: isPending,
|
||||||
|
clearError: () => setError(undefined),
|
||||||
|
reset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { useMutation } from "@tanstack/react-query"
|
||||||
|
import { sendMagicLink, sendPasswordRecovery, signIn, signOut, verifyOtp } from "../action"
|
||||||
|
|
||||||
|
export const useSignInMutation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ["signIn"],
|
||||||
|
mutationFn: async (formData: FormData) => await signIn(formData),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSignOutMutation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ["signOut"],
|
||||||
|
mutationFn: async () => await signOut(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSendMagicLinkMutation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ["sendMagicLink"],
|
||||||
|
mutationFn: async (email: string) => await sendMagicLink(email),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSendPasswordRecoveryMutation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ["sendPasswordRecovery"],
|
||||||
|
mutationFn: async (email: string) => await sendPasswordRecovery(email),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useVerifyOtpMutation = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ["verifyOtp"],
|
||||||
|
mutationFn: async (formData: FormData) => await verifyOtp(formData),
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,161 +1,161 @@
|
||||||
import { AuthenticationError } from "@/src/entities/errors/auth";
|
// import { AuthenticationError } from "@/src/entities/errors/auth";
|
||||||
import { useState } from "react";
|
// import { useState } from "react";
|
||||||
import { useAuthActions } from './queries';
|
// import { useAuthActions } from './queries';
|
||||||
import { useForm } from 'react-hook-form';
|
// import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';;
|
// import { zodResolver } from '@hookform/resolvers/zod';;
|
||||||
import { toast } from 'sonner';
|
// import { toast } from 'sonner';
|
||||||
import { useNavigations } from '@/app/_hooks/use-navigations';
|
// import { useNavigations } from '@/app/_hooks/use-navigations';
|
||||||
import {
|
// import {
|
||||||
IVerifyOtpSchema,
|
// IVerifyOtpSchema,
|
||||||
verifyOtpSchema,
|
// verifyOtpSchema,
|
||||||
} from '@/src/entities/models/auth/verify-otp.model';
|
// } from '@/src/entities/models/auth/verify-otp.model';
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Hook untuk menangani proses sign in
|
// * Hook untuk menangani proses sign in
|
||||||
*
|
// *
|
||||||
* @returns {Object} Object berisi handler dan state untuk form sign in
|
// * @returns {Object} Object berisi handler dan state untuk form sign in
|
||||||
* @example
|
// * @example
|
||||||
* const { handleSubmit, isPending, error } = useSignInHandler();
|
// * const { handleSubmit, isPending, error } = useSignInHandler();
|
||||||
* <form onSubmit={handleSubmit}>...</form>
|
// * <form onSubmit={handleSubmit}>...</form>
|
||||||
*/
|
// */
|
||||||
export function useSignInHandler() {
|
// export function useSignInHandler() {
|
||||||
const { signIn } = useAuthActions();
|
// const { signIn } = useAuthActions();
|
||||||
const { router } = useNavigations();
|
// const { router } = useNavigations();
|
||||||
|
|
||||||
const [error, setError] = useState<string>();
|
// const [error, setError] = useState<string>();
|
||||||
|
|
||||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
// const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
if (signIn.isPending) return;
|
// if (signIn.isPending) return;
|
||||||
|
|
||||||
setError(undefined);
|
// setError(undefined);
|
||||||
|
|
||||||
const formData = new FormData(event.currentTarget);
|
// const formData = new FormData(event.currentTarget);
|
||||||
const email = formData.get('email')?.toString();
|
// const email = formData.get('email')?.toString();
|
||||||
|
|
||||||
const res = await signIn.mutateAsync(formData);
|
// const res = await signIn.mutateAsync(formData);
|
||||||
|
|
||||||
if (!res?.error) {
|
// if (!res?.error) {
|
||||||
toast('An email has been sent to you. Please check your inbox.');
|
// toast('An email has been sent to you. Please check your inbox.');
|
||||||
if (email) router.push(`/verify-otp?email=${encodeURIComponent(email)}`);
|
// if (email) router.push(`/verify-otp?email=${encodeURIComponent(email)}`);
|
||||||
} else {
|
// } else {
|
||||||
setError(res.error);
|
// setError(res.error);
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
};
|
// };
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
// formData,
|
// // formData,
|
||||||
// handleChange,
|
// // handleChange,
|
||||||
handleSignIn: handleSubmit,
|
// handleSignIn: handleSubmit,
|
||||||
error,
|
// error,
|
||||||
isPending: signIn.isPending,
|
// isPending: signIn.isPending,
|
||||||
errors: !!error || signIn.error,
|
// errors: !!error || signIn.error,
|
||||||
clearError: () => setError(undefined),
|
// clearError: () => setError(undefined),
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export function useVerifyOtpHandler(email: string) {
|
// export function useVerifyOtpHandler(email: string) {
|
||||||
const { router } = useNavigations();
|
// const { router } = useNavigations();
|
||||||
const { verifyOtp } = useAuthActions();
|
// const { verifyOtp } = useAuthActions();
|
||||||
const [error, setError] = useState<string>();
|
// const [error, setError] = useState<string>();
|
||||||
|
|
||||||
const {
|
// const {
|
||||||
register,
|
// register,
|
||||||
handleSubmit: hookFormSubmit,
|
// handleSubmit: hookFormSubmit,
|
||||||
control,
|
// control,
|
||||||
formState: { errors },
|
// formState: { errors },
|
||||||
setValue,
|
// setValue,
|
||||||
} = useForm<IVerifyOtpSchema>({
|
// } = useForm<IVerifyOtpSchema>({
|
||||||
resolver: zodResolver(verifyOtpSchema),
|
// resolver: zodResolver(verifyOtpSchema),
|
||||||
defaultValues: {
|
// defaultValues: {
|
||||||
email,
|
// email,
|
||||||
token: '',
|
// token: '',
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
|
|
||||||
const handleOtpChange = (
|
// const handleOtpChange = (
|
||||||
value: string,
|
// value: string,
|
||||||
onChange: (value: string) => void
|
// onChange: (value: string) => void
|
||||||
) => {
|
// ) => {
|
||||||
onChange(value);
|
// onChange(value);
|
||||||
|
|
||||||
if (value.length === 6) {
|
// if (value.length === 6) {
|
||||||
handleSubmit();
|
// handleSubmit();
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Clear error when user starts typing
|
// // Clear error when user starts typing
|
||||||
if (error) {
|
// if (error) {
|
||||||
setError(undefined);
|
// setError(undefined);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleSubmit = hookFormSubmit(async (data) => {
|
// const handleSubmit = hookFormSubmit(async (data) => {
|
||||||
if (verifyOtp.isPending) return;
|
// if (verifyOtp.isPending) return;
|
||||||
|
|
||||||
setError(undefined);
|
// setError(undefined);
|
||||||
|
|
||||||
// Create FormData object
|
// // Create FormData object
|
||||||
const formData = new FormData();
|
// const formData = new FormData();
|
||||||
formData.append('email', data.email);
|
// formData.append('email', data.email);
|
||||||
formData.append('token', data.token);
|
// formData.append('token', data.token);
|
||||||
|
|
||||||
await verifyOtp.mutateAsync(formData, {
|
// await verifyOtp.mutateAsync(formData, {
|
||||||
onSuccess: () => {
|
// onSuccess: () => {
|
||||||
toast.success('OTP verified successfully');
|
// toast.success('OTP verified successfully');
|
||||||
// Navigate to dashboard on success
|
// // Navigate to dashboard on success
|
||||||
router.push('/dashboard');
|
// router.push('/dashboard');
|
||||||
},
|
// },
|
||||||
onError: (error) => {
|
// onError: (error) => {
|
||||||
setError(error.message);
|
// setError(error.message);
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
register,
|
// register,
|
||||||
control,
|
// control,
|
||||||
handleVerifyOtp: handleSubmit,
|
// handleVerifyOtp: handleSubmit,
|
||||||
handleOtpChange,
|
// handleOtpChange,
|
||||||
errors: {
|
// errors: {
|
||||||
...errors,
|
// ...errors,
|
||||||
token: error ? { message: error } : errors.token,
|
// token: error ? { message: error } : errors.token,
|
||||||
},
|
// },
|
||||||
isPending: verifyOtp.isPending,
|
// isPending: verifyOtp.isPending,
|
||||||
clearError: () => setError(undefined),
|
// clearError: () => setError(undefined),
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export function useSignOutHandler() {
|
// export function useSignOutHandler() {
|
||||||
const { signOut } = useAuthActions();
|
// const { signOut } = useAuthActions();
|
||||||
const { router } = useNavigations();
|
// const { router } = useNavigations();
|
||||||
const [error, setError] = useState<string>();
|
// const [error, setError] = useState<string>();
|
||||||
|
|
||||||
const handleSignOut = async () => {
|
// const handleSignOut = async () => {
|
||||||
if (signOut.isPending) return;
|
// if (signOut.isPending) return;
|
||||||
|
|
||||||
setError(undefined);
|
// setError(undefined);
|
||||||
|
|
||||||
await signOut.mutateAsync(undefined, {
|
// await signOut.mutateAsync(undefined, {
|
||||||
onSuccess: () => {
|
// onSuccess: () => {
|
||||||
toast.success('You have been signed out successfully');
|
// toast.success('You have been signed out successfully');
|
||||||
router.push('/sign-in');
|
// router.push('/sign-in');
|
||||||
},
|
// },
|
||||||
onError: (error) => {
|
// onError: (error) => {
|
||||||
if (error instanceof AuthenticationError) {
|
// if (error instanceof AuthenticationError) {
|
||||||
setError(error.message);
|
// setError(error.message);
|
||||||
toast.error(error.message);
|
// toast.error(error.message);
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
};
|
// };
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
handleSignOut,
|
// handleSignOut,
|
||||||
error,
|
// error,
|
||||||
isPending: signOut.isPending,
|
// isPending: signOut.isPending,
|
||||||
errors: !!error || signOut.error,
|
// errors: !!error || signOut.error,
|
||||||
clearError: () => setError(undefined),
|
// clearError: () => setError(undefined),
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
|
@ -1,89 +1,89 @@
|
||||||
import { useMutation } from '@tanstack/react-query';
|
// import { useMutation } from '@tanstack/react-query';
|
||||||
import { sendMagicLink, sendPasswordRecovery, signIn, signOut, verifyOtp } from './action';
|
// import { sendMagicLink, sendPasswordRecovery, signIn, signOut, verifyOtp } from './action';
|
||||||
|
|
||||||
export function useAuthActions() {
|
// export function useAuthActions() {
|
||||||
// Sign In Mutation
|
// // Sign In Mutation
|
||||||
const signInMutation = useMutation({
|
// const signInMutation = useMutation({
|
||||||
mutationKey: ["signIn"],
|
// mutationKey: ["signIn"],
|
||||||
mutationFn: async (formData: FormData) => await signIn(formData)
|
// mutationFn: async (formData: FormData) => await signIn(formData)
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Verify OTP Mutation
|
// // Verify OTP Mutation
|
||||||
const verifyOtpMutation = useMutation({
|
// const verifyOtpMutation = useMutation({
|
||||||
mutationKey: ["verifyOtp"],
|
// mutationKey: ["verifyOtp"],
|
||||||
mutationFn: async (formData: FormData) => await verifyOtp(formData)
|
// mutationFn: async (formData: FormData) => await verifyOtp(formData)
|
||||||
});
|
// });
|
||||||
|
|
||||||
const signOutMutation = useMutation({
|
// const signOutMutation = useMutation({
|
||||||
mutationKey: ["signOut"],
|
// mutationKey: ["signOut"],
|
||||||
mutationFn: async () => await signOut()
|
// mutationFn: async () => await signOut()
|
||||||
});
|
// });
|
||||||
|
|
||||||
const sendMagicLinkMutation = useMutation({
|
// const sendMagicLinkMutation = useMutation({
|
||||||
mutationKey: ["sendMagicLink"],
|
// mutationKey: ["sendMagicLink"],
|
||||||
mutationFn: async (email: string) => await sendMagicLink(email)
|
// mutationFn: async (email: string) => await sendMagicLink(email)
|
||||||
});
|
// });
|
||||||
|
|
||||||
const sendPasswordRecoveryMutation = useMutation({
|
// const sendPasswordRecoveryMutation = useMutation({
|
||||||
mutationKey: ["sendPasswordRecovery"],
|
// mutationKey: ["sendPasswordRecovery"],
|
||||||
mutationFn: async (email: string) => await sendPasswordRecovery(email)
|
// mutationFn: async (email: string) => await sendPasswordRecovery(email)
|
||||||
});
|
// });
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
signIn: signInMutation,
|
// signIn: signInMutation,
|
||||||
verifyOtp: verifyOtpMutation,
|
// verifyOtp: verifyOtpMutation,
|
||||||
signOut: signOutMutation,
|
// signOut: signOutMutation,
|
||||||
sendMagicLink: sendMagicLinkMutation,
|
// sendMagicLink: sendMagicLinkMutation,
|
||||||
sendPasswordRecovery: sendPasswordRecoveryMutation
|
// sendPasswordRecovery: sendPasswordRecoveryMutation
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useSignInMutation = () => {
|
// export const useSignInMutation = () => {
|
||||||
const { signIn } = useAuthActions();
|
// const { signIn } = useAuthActions();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
signIn: signIn.mutateAsync,
|
// signIn: signIn.mutateAsync,
|
||||||
isPending: signIn.isPending,
|
// isPending: signIn.isPending,
|
||||||
error: signIn.error,
|
// error: signIn.error,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useVerifyOtpMutation = () => {
|
// export const useVerifyOtpMutation = () => {
|
||||||
const { verifyOtp } = useAuthActions();
|
// const { verifyOtp } = useAuthActions();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
verifyOtp: verifyOtp.mutateAsync,
|
// verifyOtp: verifyOtp.mutateAsync,
|
||||||
isPending: verifyOtp.isPending,
|
// isPending: verifyOtp.isPending,
|
||||||
error: verifyOtp.error,
|
// error: verifyOtp.error,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useSignOutMutation = () => {
|
// export const useSignOutMutation = () => {
|
||||||
const { signOut } = useAuthActions();
|
// const { signOut } = useAuthActions();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
signOut: signOut.mutateAsync,
|
// signOut: signOut.mutateAsync,
|
||||||
isPending: signOut.isPending
|
// isPending: signOut.isPending
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useSendMagicLinkMutation = () => {
|
// export const useSendMagicLinkMutation = () => {
|
||||||
const { sendMagicLink } = useAuthActions();
|
// const { sendMagicLink } = useAuthActions();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
sendMagicLink: sendMagicLink.mutateAsync,
|
// sendMagicLink: sendMagicLink.mutateAsync,
|
||||||
isPending: sendMagicLink.isPending,
|
// isPending: sendMagicLink.isPending,
|
||||||
error: sendMagicLink.error,
|
// error: sendMagicLink.error,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const useSendPasswordRecoveryMutation = () => {
|
// export const useSendPasswordRecoveryMutation = () => {
|
||||||
const { sendPasswordRecovery } = useAuthActions();
|
// const { sendPasswordRecovery } = useAuthActions();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
sendPasswordRecovery: sendPasswordRecovery.mutateAsync,
|
// sendPasswordRecovery: sendPasswordRecovery.mutateAsync,
|
||||||
isPending: sendPasswordRecovery.isPending,
|
// isPending: sendPasswordRecovery.isPending,
|
||||||
error: sendPasswordRecovery.error,
|
// error: sendPasswordRecovery.error,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { DateFormatOptions, DateFormatPattern } from "../_lib/types/date-format.interface";
|
import { DateFormatOptions, DateFormatPattern } from "../_lib/types/date-format.interface";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirects to a specified path with an encoded message as a query parameter.
|
* Redirects to a specified path with an encoded message as a query parameter.
|
||||||
|
@ -118,3 +119,32 @@ export const formatDate = (
|
||||||
? format(dateObj, formatPattern, { locale })
|
? format(dateObj, formatPattern, { locale })
|
||||||
: format(dateObj, formatPattern);
|
: format(dateObj, formatPattern);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const handleCopyItem = (item: string, options?: {
|
||||||
|
onSuccess?: () => void,
|
||||||
|
onError?: (error: unknown) => void
|
||||||
|
}) => {
|
||||||
|
if (!navigator.clipboard) {
|
||||||
|
const error = new Error("Clipboard not supported");
|
||||||
|
toast.error("Clipboard not supported");
|
||||||
|
options?.onError?.(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
const error = new Error("Nothing to copy");
|
||||||
|
toast.error("Nothing to copy");
|
||||||
|
options?.onError?.(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(item)
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Copied to clipboard");
|
||||||
|
options?.onSuccess?.();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error("Failed to copy to clipboard");
|
||||||
|
options?.onError?.(error);
|
||||||
|
});
|
||||||
|
};
|
Loading…
Reference in New Issue