refactor handler and queries

This commit is contained in:
vergiLgood1 2025-03-28 22:37:51 +07:00
parent e84a6f52c0
commit 2faf6ce83e
27 changed files with 1959 additions and 1198 deletions

View File

@ -16,7 +16,7 @@ import {
import { NavPreMain } from "./navigations/nav-pre-main";
import { navData } from "@/prisma/data/nav";
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>) {

View File

@ -27,8 +27,8 @@ import { IconLogout, IconSettings, IconSparkles } from "@tabler/icons-react";
import type { IUserSchema } from "@/src/entities/models/users/users.model";
// import { signOut } from "@/app/(pages)/(auth)/action";
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 { useSignOutHandler } from "@/app/(pages)/(auth)/_handlers/use-sign-out";
export function NavUser({ user }: { user: IUserSchema | null }) {
const { isMobile } = useSidebar();

View File

@ -1,8 +1,8 @@
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/app/_components/ui/dialog"
import { Button } from "@/app/_components/ui/button"
import { Mail, Lock, Loader2 } from "lucide-react"
import { useAddUserDialogHandler } from "../handler"
import { ReactHookFormField } from "@/app/_components/react-hook-form-field"
import { useAddUserDialogHandler } from "../_handlers/use-add-user-dialog"
interface AddUserDialogProps {
open: boolean

View File

@ -16,10 +16,10 @@ import { Textarea } from "@/app/_components/ui/textarea";
import { useMutation } from "@tanstack/react-query";
import { inviteUser } from "@/app/(pages)/(admin)/dashboard/user-management/action";
import { toast } from "sonner";
import { useInviteUserHandler } from "../handler";
import { ReactHookFormField } from "@/app/_components/react-hook-form-field";
import { Loader2, MailIcon } from "lucide-react";
import { Separator } from "@/app/_components/ui/separator";
import { useInviteUserHandler } from "../_handlers/use-invite-user";
interface InviteUserDialogProps {

View File

@ -39,9 +39,9 @@ import {
} from "@/app/(pages)/(admin)/dashboard/user-management/action";
import { format } from "date-fns";
import { sendMagicLink, sendPasswordRecovery } from "@/app/(pages)/(auth)/action";
import { useUserDetailSheetHandlers } from "../handler";
import { IUserSchema } from "@/src/entities/models/users/users.model";
import { formatDate } from "@/app/_utils/common";
import { useUserDetailSheetHandlers } from "../_handlers/use-detail-sheet";
interface UserDetailSheetProps {
open: boolean;
@ -56,104 +56,6 @@ export function UserDetailSheet({
user,
onUserUpdated,
}: 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 {
handleDeleteUser,
@ -364,7 +266,7 @@ export function UserDetailSheet({
<Button
variant={user.banned_until ? "outline" : "outline"}
size="sm"
onClick={handleToggleBan}
onClick={() => handleToggleBan()}
disabled={isBanPending || isUnbanPending}
>
{isBanPending || isUnbanPending ? (

View File

@ -18,7 +18,7 @@ import { useMutation } from "@tanstack/react-query"
import { updateUser } from "../action"
import { toast } from "sonner"
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>
@ -34,7 +34,7 @@ export function UserProfileSheet({ open, onOpenChange, userData, onUserUpdated }
const {
form,
handleUpdateUser,
isUpdatePending,
isPending,
} = useUserProfileSheetHandlers({ open, userData, onOpenChange, onUserUpdated })
return (
@ -214,14 +214,14 @@ export function UserProfileSheet({ open, onOpenChange, userData, onUserUpdated }
type="button"
variant="outline"
size="xs"
onClick={() => !isUpdatePending && onOpenChange(false)}
disabled={isUpdatePending}
onClick={() => !isPending && onOpenChange(false)}
disabled={isPending}
>
Cancel
</Button>
<Button size="xs" type="submit" disabled={isUpdatePending}>
{isUpdatePending && <Loader2 className="mr-1 h-4 w-4 animate-spin" />}
{isUpdatePending ? "Saving..." : "Save"}
<Button size="xs" type="submit" disabled={isPending}>
{isPending && <Loader2 className="mr-1 h-4 w-4 animate-spin" />}
{isPending ? "Saving..." : "Save"}
</Button>
</div>
</form>

View File

@ -28,9 +28,9 @@ import { InviteUserDialog } from "./invite-user";
import { AddUserDialog } from "./add-user-dialog";
import { UserDetailSheet } from "./sheet";
import { UserProfileSheet } from "./update-user";
import { filterUsers, useUserManagementHandlers } from "../handler";
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() {
@ -61,7 +61,7 @@ export default function UserManagement() {
handleUserUpdate,
clearFilters,
getActiveFilterCount,
} = useUserManagementHandlers(refetch)
} = useUserManagementHandlers()
// Apply filters to users
const filteredUsers = useMemo(() => {

View File

@ -3,7 +3,7 @@
import { Card, CardContent } from "@/app/_components/ui/card";
import { Users, UserCheck, UserX } from "lucide-react";
import { IUserSchema } from "@/src/entities/models/users/users.model";
import { useGetUsersQuery } from "../queries";
import { useGetUsersQuery } from "../_queries/queries";
function calculateUserStats(users: IUserSchema[] | undefined) {

View File

@ -16,7 +16,7 @@ import { Input } from "@/app/_components/ui/input"
import { Avatar } from "@/app/_components/ui/avatar"
import Image from "next/image"
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>
@ -26,11 +26,9 @@ export const createUserColumns = (
handleUserUpdate: (user: IUserSchema) => void,
): UserTableColumn[] => {
const {
deleteUser,
banUser,
unbanUser,
} = useUsersHandlers();
const { mutateAsync: deleteUser } = useDeleteUserMutation();
const { mutateAsync: banUser } = useBanUserMutation();
const { mutateAsync: unbanUser } = useUnbanUserMutation();
return [
{
@ -329,9 +327,9 @@ export const createUserColumns = (
<DropdownMenuItem
onClick={() => {
if (row.original.banned_until != null) {
unbanUser(row.original.id)
unbanUser({ id: row.original.id })
} else {
banUser(row.original.id)
banUser({ id: row.original.id, ban_duration: "24h" })
}
}}
>

View File

@ -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,
};
}

View File

@ -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,
};
};

View File

@ -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,
};
}

View File

@ -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,
};
}

View File

@ -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
})
}

View File

@ -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),
})
}

View File

@ -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(),
})
}

View File

@ -1,212 +1,212 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import {
banUser,
getCurrentUser,
getUserByEmail,
getUserById,
getUsers,
unbanUser,
inviteUser,
createUser,
updateUser,
deleteUser,
getUserByUsername
} from "./action";
import { IUserSchema } from "@/src/entities/models/users/users.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 { ICreateUserSchema } from "@/src/entities/models/users/create-user.model";
import { IUpdateUserSchema } from "@/src/entities/models/users/update-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 { useMutation, useQuery } from "@tanstack/react-query";
// import {
// banUser,
// getCurrentUser,
// getUserByEmail,
// getUserById,
// getUsers,
// unbanUser,
// inviteUser,
// createUser,
// updateUser,
// deleteUser,
// getUserByUsername
// } from "./action";
// import { IUserSchema } from "@/src/entities/models/users/users.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 { ICreateUserSchema } from "@/src/entities/models/users/create-user.model";
// import { IUpdateUserSchema } from "@/src/entities/models/users/update-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";
const useUsersAction = () => {
// const useUsersAction = () => {
// For all users (no parameters needed)
const getUsersQuery = useQuery<IUserSchema[]>({
queryKey: ["users"],
queryFn: async () => await getUsers()
});
// // For all users (no parameters needed)
// const getUsersQuery = useQuery<IUserSchema[]>({
// queryKey: ["users"],
// queryFn: () => getUsers()
// });
// Current user query doesn't need parameters
const getCurrentUserQuery = useQuery<IUserSchema>({
queryKey: ["user", "current"],
queryFn: async () => await getCurrentUser()
});
// // Current user query doesn't need parameters
// const getCurrentUserQuery = useQuery<IUserSchema>({
// queryKey: ["user", "current"],
// queryFn: () => getCurrentUser()
// });
const getUserByIdQuery = (credential: ICredentialGetUserByIdSchema) => useQuery<IUserSchema>({
queryKey: ["user", "id", credential.id],
queryFn: async () => await getUserById(credential)
});
// const getUserByIdQuery = (credential: ICredentialGetUserByIdSchema) => useQuery<IUserSchema>({
// queryKey: ["user", "id", credential.id],
// queryFn: () => getUserById(credential)
// });
const getUserByEmailQuery = (credential: IGetUserByEmailSchema) => useQuery<IUserSchema>({
queryKey: ["user", "email", credential.email],
queryFn: async () => await getUserByEmail(credential)
});
// const getUserByEmailQuery = (credential: IGetUserByEmailSchema) => useQuery<IUserSchema>({
// queryKey: ["user", "email", credential.email],
// queryFn: () => getUserByEmail(credential)
// });
const getUserByUsernameQuery = (credential: IGetUserByUsernameSchema) => useQuery<IUserSchema>({
queryKey: ["user", "username", credential.username],
queryFn: async () => await getUserByUsername(credential)
});
// const getUserByUsernameQuery = (credential: IGetUserByUsernameSchema) => useQuery<IUserSchema>({
// queryKey: ["user", "username", credential.username],
// queryFn: () => getUserByUsername(credential)
// });
// Mutations that don't need dynamic parameters
const banUserMutation = useMutation({
mutationKey: ["banUser"],
mutationFn: async ({ credential, data }: { credential: ICredentialsBanUserSchema; data: IBanUserSchema }) => await banUser(credential, data)
});
// // Mutations that don't need dynamic parameters
// const banUserMutation = (credential: ICredentialsBanUserSchema, data: IBanUserSchema) => useMutation({
// mutationKey: ["banUser"],
// mutationFn: () => banUser(credential, data)
// });
const unbanUserMutation = useMutation({
mutationKey: ["unbanUser"],
mutationFn: async (credential: ICredentialsUnbanUserSchema) => await unbanUser(credential)
});
// const unbanUserMutation = useMutation({
// mutationKey: ["unbanUser"],
// mutationFn: async (credential: ICredentialsUnbanUserSchema) => await unbanUser(credential)
// });
// Create functions that return configured hooks
const inviteUserMutation = useMutation({
mutationKey: ["inviteUser"],
mutationFn: async (credential: ICredentialsInviteUserSchema) => await inviteUser(credential)
});
// // Create functions that return configured hooks
// const inviteUserMutation = useMutation({
// mutationKey: ["inviteUser"],
// mutationFn: async (credential: ICredentialsInviteUserSchema) => await inviteUser(credential)
// });
const createUserMutation = useMutation({
mutationKey: ["createUser"],
mutationFn: async (data: ICreateUserSchema) => await createUser(data)
});
// const createUserMutation = useMutation({
// mutationKey: ["createUser"],
// mutationFn: async (data: ICreateUserSchema) => await createUser(data)
// });
const updateUserMutation = useMutation({
mutationKey: ["updateUser"],
mutationFn: async (params: { id: string; data: IUpdateUserSchema }) => updateUser(params.id, params.data)
});
// const updateUserMutation = useMutation({
// mutationKey: ["updateUser"],
// mutationFn: async (params: { id: string; data: IUpdateUserSchema }) => updateUser(params.id, params.data)
// });
const deleteUserMutation = useMutation({
mutationKey: ["deleteUser"],
mutationFn: async (id: string) => await deleteUser(id)
});
// const deleteUserMutation = useMutation({
// mutationKey: ["deleteUser"],
// mutationFn: async (id: string) => await deleteUser(id)
// });
return {
getUsers: getUsersQuery,
getCurrentUser: getCurrentUserQuery,
getUserById: getUserByIdQuery,
getUserByEmailQuery,
getUserByUsernameQuery,
banUser: banUserMutation,
unbanUser: unbanUserMutation,
inviteUser: inviteUserMutation,
createUser: createUserMutation,
updateUser: updateUserMutation,
deleteUser: deleteUserMutation
};
}
// return {
// getUsers: getUsersQuery,
// getCurrentUser: getCurrentUserQuery,
// getUserById: getUserByIdQuery,
// getUserByEmailQuery,
// getUserByUsernameQuery,
// banUser: banUserMutation,
// unbanUser: unbanUserMutation,
// inviteUser: inviteUserMutation,
// createUser: createUserMutation,
// updateUser: updateUserMutation,
// deleteUser: deleteUserMutation
// };
// }
export const useGetUsersQuery = () => {
const { getUsers } = useUsersAction();
// export const useGetUsersQuery = () => {
// const { getUsers } = useUsersAction();
return {
data: getUsers.data,
isPending: getUsers.isPending,
error: getUsers.error,
refetch: getUsers.refetch,
};
}
// return {
// data: getUsers.data,
// isPending: getUsers.isPending,
// error: getUsers.error,
// refetch: getUsers.refetch,
// };
// }
export const useGetCurrentUserQuery = () => {
const { getCurrentUser } = useUsersAction();
// export const useGetCurrentUserQuery = () => {
// const { getCurrentUser } = useUsersAction();
return {
data: getCurrentUser.data,
isPending: getCurrentUser.isPending,
error: getCurrentUser.error,
refetch: getCurrentUser.refetch,
};
}
// return {
// data: getCurrentUser.data,
// isPending: getCurrentUser.isPending,
// error: getCurrentUser.error,
// refetch: getCurrentUser.refetch,
// };
// }
export const useGetUserByIdQuery = (credential: ICredentialGetUserByIdSchema) => {
const { getUserById } = useUsersAction();
// export const useGetUserByIdQuery = (credential: ICredentialGetUserByIdSchema) => {
// const { getUserById } = useUsersAction();
return {
data: getUserById(credential).data,
isPending: getUserById(credential).isPending,
error: getUserById(credential).error,
refetch: getUserById(credential).refetch,
};
}
// return {
// data: getUserById(credential).data,
// isPending: getUserById(credential).isPending,
// error: getUserById(credential).error,
// refetch: getUserById(credential).refetch,
// };
// }
export const useGetUserByEmailQuery = (credential: ICredentialGetUserByEmailSchema) => {
const { getUserByEmailQuery } = useUsersAction();
// export const useGetUserByEmailQuery = (credential: ICredentialGetUserByEmailSchema) => {
// const { getUserByEmailQuery } = useUsersAction();
return {
data: getUserByEmailQuery(credential).data,
isPending: getUserByEmailQuery(credential).isPending,
error: getUserByEmailQuery(credential).error,
refetch: getUserByEmailQuery(credential).refetch,
};
}
// return {
// data: getUserByEmailQuery(credential).data,
// isPending: getUserByEmailQuery(credential).isPending,
// error: getUserByEmailQuery(credential).error,
// refetch: getUserByEmailQuery(credential).refetch,
// };
// }
export const useGetUserByUsernameQuery = (credential: ICredentialGetUserByUsernameSchema) => {
const { getUserByUsernameQuery } = useUsersAction();
// export const useGetUserByUsernameQuery = (credential: ICredentialGetUserByUsernameSchema) => {
// const { getUserByUsernameQuery } = useUsersAction();
return {
data: getUserByUsernameQuery(credential).data,
isPending: getUserByUsernameQuery(credential).isPending,
error: getUserByUsernameQuery(credential).error,
refetch: getUserByUsernameQuery(credential).refetch,
};
}
// return {
// data: getUserByUsernameQuery(credential).data,
// isPending: getUserByUsernameQuery(credential).isPending,
// error: getUserByUsernameQuery(credential).error,
// refetch: getUserByUsernameQuery(credential).refetch,
// };
// }
export const useCreateUserMutation = () => {
const { createUser } = useUsersAction();
// export const useCreateUserMutation = () => {
// const { createUser } = useUsersAction();
return {
createUser: createUser.mutateAsync,
isPending: createUser.isPending,
errors: createUser.error,
}
}
// return {
// createUser: createUser.mutateAsync,
// isPending: createUser.isPending,
// errors: createUser.error,
// }
// }
export const useInviteUserMutation = () => {
const { inviteUser } = useUsersAction();
// export const useInviteUserMutation = () => {
// const { inviteUser } = useUsersAction();
return {
inviteUser: inviteUser.mutateAsync,
isPending: inviteUser.isPending,
errors: inviteUser.error,
}
}
// return {
// inviteUser: inviteUser.mutateAsync,
// isPending: inviteUser.isPending,
// errors: inviteUser.error,
// }
// }
export const useUpdateUserMutation = () => {
const { updateUser } = useUsersAction();
// export const useUpdateUserMutation = () => {
// const { updateUser } = useUsersAction();
return {
updateUser: updateUser.mutateAsync,
isPending: updateUser.isPending,
errors: updateUser.error,
}
}
// return {
// updateUser: updateUser.mutateAsync,
// isPending: updateUser.isPending,
// errors: updateUser.error,
// }
// }
export const useBanUserMutation = () => {
const { banUser } = useUsersAction();
// export const useBanUserMutation = () => {
// const { banUser } = useUsersAction();
return {
banUser: banUser.mutateAsync,
isPending: banUser.isPending,
errors: banUser.error,
}
}
// return {
// banUser: banUser.mutateAsync,
// isPending: banUser.isPending,
// errors: banUser.error,
// }
// }
export const useUnbanUserMutation = () => {
const { unbanUser } = useUsersAction();
// export const useUnbanUserMutation = () => {
// const { unbanUser } = useUsersAction();
return {
unbanUser: unbanUser.mutateAsync,
isPending: unbanUser.isPending,
errors: unbanUser.error,
}
}
// return {
// unbanUser: unbanUser.mutateAsync,
// isPending: unbanUser.isPending,
// errors: unbanUser.error,
// }
// }
export const useDeleteUserMutation = () => {
const { deleteUser } = useUsersAction();
// export const useDeleteUserMutation = () => {
// const { deleteUser } = useUsersAction();
return {
deleteUser: deleteUser.mutateAsync,
isPending: deleteUser.isPending,
errors: deleteUser.error,
}
}
// return {
// deleteUser: deleteUser.mutateAsync,
// isPending: deleteUser.isPending,
// errors: deleteUser.error,
// }
// }

View File

@ -10,7 +10,7 @@ import { FormField } from "@/app/_components/form-field";
// import { useSignInController } from "@/src/interface-adapters/controllers/auth/sign-in.controller";
import { useState } from "react";
import { signIn } from "../action";
import { useSignInHandler } from "../handler";
import { useSignInHandler } from "../_handlers/use-sign-in";
export function SignInForm({
className,
@ -33,7 +33,7 @@ export function SignInForm({
// setLoading(false);
// };
const { isPending, handleSignIn, error, errors, clearError } = useSignInHandler();
const { register, isPending, handleSignIn, error, errors } = useSignInHandler();
return (
<div>
@ -81,9 +81,7 @@ export function SignInForm({
label="Email"
input={
<Input
id="email"
type="email"
name="email"
{...register("email")}
placeholder="you@example.com"
className={`bg-[#1C1C1C] border-gray-800`}
error={!!errors}

View File

@ -16,8 +16,8 @@ import {
import { cn } from "@/app/_lib/utils";
import { Loader2 } from "lucide-react";
import { Controller } from "react-hook-form";
import { useVerifyOtpHandler } from "../handler";
import { Button } from "@/app/_components/ui/button";
import { useVerifyOtpHandler } from "../_handlers/use-verify-otp";
interface VerifyOtpFormProps extends React.HTMLAttributes<HTMLDivElement> {}

View File

@ -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,
};
}

View File

@ -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),
};
}

View File

@ -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,
};
}

View File

@ -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),
})
}

View File

@ -1,161 +1,161 @@
import { AuthenticationError } from "@/src/entities/errors/auth";
import { useState } from "react";
import { useAuthActions } from './queries';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';;
import { toast } from 'sonner';
import { useNavigations } from '@/app/_hooks/use-navigations';
import {
IVerifyOtpSchema,
verifyOtpSchema,
} from '@/src/entities/models/auth/verify-otp.model';
// import { AuthenticationError } from "@/src/entities/errors/auth";
// import { useState } from "react";
// import { useAuthActions } from './queries';
// import { useForm } from 'react-hook-form';
// import { zodResolver } from '@hookform/resolvers/zod';;
// import { toast } from 'sonner';
// import { useNavigations } from '@/app/_hooks/use-navigations';
// import {
// IVerifyOtpSchema,
// verifyOtpSchema,
// } from '@/src/entities/models/auth/verify-otp.model';
/**
* Hook untuk menangani proses sign in
*
* @returns {Object} Object berisi handler dan state untuk form sign in
* @example
* const { handleSubmit, isPending, error } = useSignInHandler();
* <form onSubmit={handleSubmit}>...</form>
*/
export function useSignInHandler() {
const { signIn } = useAuthActions();
const { router } = useNavigations();
// /**
// * Hook untuk menangani proses sign in
// *
// * @returns {Object} Object berisi handler dan state untuk form sign in
// * @example
// * const { handleSubmit, isPending, error } = useSignInHandler();
// * <form onSubmit={handleSubmit}>...</form>
// */
// export function useSignInHandler() {
// const { signIn } = useAuthActions();
// const { router } = useNavigations();
const [error, setError] = useState<string>();
// const [error, setError] = useState<string>();
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (signIn.isPending) return;
// const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
// event.preventDefault();
// if (signIn.isPending) return;
setError(undefined);
// setError(undefined);
const formData = new FormData(event.currentTarget);
const email = formData.get('email')?.toString();
// const formData = new FormData(event.currentTarget);
// const email = formData.get('email')?.toString();
const res = await signIn.mutateAsync(formData);
// const res = await signIn.mutateAsync(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);
}
// 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);
// }
};
// };
return {
// formData,
// handleChange,
handleSignIn: handleSubmit,
error,
isPending: signIn.isPending,
errors: !!error || signIn.error,
clearError: () => setError(undefined),
};
}
// return {
// // formData,
// // handleChange,
// handleSignIn: handleSubmit,
// error,
// isPending: signIn.isPending,
// errors: !!error || signIn.error,
// clearError: () => setError(undefined),
// };
// }
export function useVerifyOtpHandler(email: string) {
const { router } = useNavigations();
const { verifyOtp } = useAuthActions();
const [error, setError] = useState<string>();
// export function useVerifyOtpHandler(email: string) {
// const { router } = useNavigations();
// const { verifyOtp } = useAuthActions();
// const [error, setError] = useState<string>();
const {
register,
handleSubmit: hookFormSubmit,
control,
formState: { errors },
setValue,
} = useForm<IVerifyOtpSchema>({
resolver: zodResolver(verifyOtpSchema),
defaultValues: {
email,
token: '',
},
});
// const {
// register,
// handleSubmit: hookFormSubmit,
// control,
// formState: { errors },
// setValue,
// } = useForm<IVerifyOtpSchema>({
// resolver: zodResolver(verifyOtpSchema),
// defaultValues: {
// email,
// token: '',
// },
// });
const handleOtpChange = (
value: string,
onChange: (value: string) => void
) => {
onChange(value);
// const handleOtpChange = (
// value: string,
// onChange: (value: string) => void
// ) => {
// onChange(value);
if (value.length === 6) {
handleSubmit();
}
// if (value.length === 6) {
// handleSubmit();
// }
// Clear error when user starts typing
if (error) {
setError(undefined);
}
};
// // Clear error when user starts typing
// if (error) {
// setError(undefined);
// }
// };
const handleSubmit = hookFormSubmit(async (data) => {
if (verifyOtp.isPending) return;
// const handleSubmit = hookFormSubmit(async (data) => {
// if (verifyOtp.isPending) return;
setError(undefined);
// setError(undefined);
// Create FormData object
const formData = new FormData();
formData.append('email', data.email);
formData.append('token', data.token);
// // Create FormData object
// const formData = new FormData();
// formData.append('email', data.email);
// formData.append('token', data.token);
await verifyOtp.mutateAsync(formData, {
onSuccess: () => {
toast.success('OTP verified successfully');
// Navigate to dashboard on success
router.push('/dashboard');
},
onError: (error) => {
setError(error.message);
},
});
});
// await verifyOtp.mutateAsync(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: verifyOtp.isPending,
clearError: () => setError(undefined),
};
}
// return {
// register,
// control,
// handleVerifyOtp: handleSubmit,
// handleOtpChange,
// errors: {
// ...errors,
// token: error ? { message: error } : errors.token,
// },
// isPending: verifyOtp.isPending,
// clearError: () => setError(undefined),
// };
// }
export function useSignOutHandler() {
const { signOut } = useAuthActions();
const { router } = useNavigations();
const [error, setError] = useState<string>();
// export function useSignOutHandler() {
// const { signOut } = useAuthActions();
// const { router } = useNavigations();
// const [error, setError] = useState<string>();
const handleSignOut = async () => {
if (signOut.isPending) return;
// const handleSignOut = async () => {
// if (signOut.isPending) return;
setError(undefined);
// setError(undefined);
await signOut.mutateAsync(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);
}
},
});
};
// await signOut.mutateAsync(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: signOut.isPending,
errors: !!error || signOut.error,
clearError: () => setError(undefined),
};
}
// return {
// handleSignOut,
// error,
// isPending: signOut.isPending,
// errors: !!error || signOut.error,
// clearError: () => setError(undefined),
// };
// }

View File

@ -1,89 +1,89 @@
import { useMutation } from '@tanstack/react-query';
import { sendMagicLink, sendPasswordRecovery, signIn, signOut, verifyOtp } from './action';
// import { useMutation } from '@tanstack/react-query';
// import { sendMagicLink, sendPasswordRecovery, signIn, signOut, verifyOtp } from './action';
export function useAuthActions() {
// Sign In Mutation
const signInMutation = useMutation({
mutationKey: ["signIn"],
mutationFn: async (formData: FormData) => await signIn(formData)
});
// export function useAuthActions() {
// // Sign In Mutation
// const signInMutation = useMutation({
// mutationKey: ["signIn"],
// mutationFn: async (formData: FormData) => await signIn(formData)
// });
// Verify OTP Mutation
const verifyOtpMutation = useMutation({
mutationKey: ["verifyOtp"],
mutationFn: async (formData: FormData) => await verifyOtp(formData)
});
// // Verify OTP Mutation
// const verifyOtpMutation = useMutation({
// mutationKey: ["verifyOtp"],
// mutationFn: async (formData: FormData) => await verifyOtp(formData)
// });
const signOutMutation = useMutation({
mutationKey: ["signOut"],
mutationFn: async () => await signOut()
});
// const signOutMutation = useMutation({
// mutationKey: ["signOut"],
// mutationFn: async () => await signOut()
// });
const sendMagicLinkMutation = useMutation({
mutationKey: ["sendMagicLink"],
mutationFn: async (email: string) => await sendMagicLink(email)
});
// const sendMagicLinkMutation = useMutation({
// mutationKey: ["sendMagicLink"],
// mutationFn: async (email: string) => await sendMagicLink(email)
// });
const sendPasswordRecoveryMutation = useMutation({
mutationKey: ["sendPasswordRecovery"],
mutationFn: async (email: string) => await sendPasswordRecovery(email)
});
// const sendPasswordRecoveryMutation = useMutation({
// mutationKey: ["sendPasswordRecovery"],
// mutationFn: async (email: string) => await sendPasswordRecovery(email)
// });
return {
signIn: signInMutation,
verifyOtp: verifyOtpMutation,
signOut: signOutMutation,
sendMagicLink: sendMagicLinkMutation,
sendPasswordRecovery: sendPasswordRecoveryMutation
};
}
// return {
// signIn: signInMutation,
// verifyOtp: verifyOtpMutation,
// signOut: signOutMutation,
// sendMagicLink: sendMagicLinkMutation,
// sendPasswordRecovery: sendPasswordRecoveryMutation
// };
// }
export const useSignInMutation = () => {
const { signIn } = useAuthActions();
// export const useSignInMutation = () => {
// const { signIn } = useAuthActions();
return {
signIn: signIn.mutateAsync,
isPending: signIn.isPending,
error: signIn.error,
};
}
// return {
// signIn: signIn.mutateAsync,
// isPending: signIn.isPending,
// error: signIn.error,
// };
// }
export const useVerifyOtpMutation = () => {
const { verifyOtp } = useAuthActions();
// export const useVerifyOtpMutation = () => {
// const { verifyOtp } = useAuthActions();
return {
verifyOtp: verifyOtp.mutateAsync,
isPending: verifyOtp.isPending,
error: verifyOtp.error,
}
}
// return {
// verifyOtp: verifyOtp.mutateAsync,
// isPending: verifyOtp.isPending,
// error: verifyOtp.error,
// }
// }
export const useSignOutMutation = () => {
const { signOut } = useAuthActions();
// export const useSignOutMutation = () => {
// const { signOut } = useAuthActions();
return {
signOut: signOut.mutateAsync,
isPending: signOut.isPending
}
}
// return {
// signOut: signOut.mutateAsync,
// isPending: signOut.isPending
// }
// }
export const useSendMagicLinkMutation = () => {
const { sendMagicLink } = useAuthActions();
// export const useSendMagicLinkMutation = () => {
// const { sendMagicLink } = useAuthActions();
return {
sendMagicLink: sendMagicLink.mutateAsync,
isPending: sendMagicLink.isPending,
error: sendMagicLink.error,
}
}
// return {
// sendMagicLink: sendMagicLink.mutateAsync,
// isPending: sendMagicLink.isPending,
// error: sendMagicLink.error,
// }
// }
export const useSendPasswordRecoveryMutation = () => {
const { sendPasswordRecovery } = useAuthActions();
// export const useSendPasswordRecoveryMutation = () => {
// const { sendPasswordRecovery } = useAuthActions();
return {
sendPasswordRecovery: sendPasswordRecovery.mutateAsync,
isPending: sendPasswordRecovery.isPending,
error: sendPasswordRecovery.error,
}
}
// return {
// sendPasswordRecovery: sendPasswordRecovery.mutateAsync,
// isPending: sendPasswordRecovery.isPending,
// error: sendPasswordRecovery.error,
// }
// }

View File

@ -1,6 +1,7 @@
import { format } from "date-fns";
import { redirect } from "next/navigation";
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.
@ -118,3 +119,32 @@ export const formatDate = (
? format(dateObj, formatPattern, { locale })
: 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);
});
};