implmentation clean architecture for auth services
This commit is contained in:
parent
aed9eba5d3
commit
1a39925c97
|
@ -19,7 +19,7 @@ import {
|
|||
import type * as TablerIcons from "@tabler/icons-react";
|
||||
|
||||
import { useNavigations } from "@/app/_hooks/use-navigations";
|
||||
import { formatUrl } from "@/app/_utils/utils";
|
||||
import { formatUrl } from "@/app/_utils/common";
|
||||
|
||||
interface SubSubItem {
|
||||
title: string;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { ChevronsUpDown } from "lucide-react";
|
||||
import { ChevronsUpDown, Loader2 } from "lucide-react";
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
|
@ -27,6 +27,8 @@ import { IconLogout, IconSettings, IconSparkles } from "@tabler/icons-react";
|
|||
import type { User } 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";
|
||||
|
||||
export function NavUser({ user }: { user: User | null }) {
|
||||
const { isMobile } = useSidebar();
|
||||
|
@ -62,6 +64,65 @@ export function NavUser({ user }: { user: User | null }) {
|
|||
// You might want to refresh the user data here
|
||||
};
|
||||
|
||||
const { handleSignOut, isPending, errors, error } = useSignOutHandler();
|
||||
|
||||
function LogoutButton({ handleSignOut, isPending }: { handleSignOut: () => void; isPending: boolean }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Dropdown Item */}
|
||||
<DropdownMenuItem
|
||||
onSelect={(e) => {
|
||||
e.preventDefault();
|
||||
setOpen(true); // Buka dialog saat diklik
|
||||
}}
|
||||
disabled={isPending}
|
||||
className="space-x-2"
|
||||
>
|
||||
<IconLogout className="size-4" />
|
||||
<span>Log out</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{/* Alert Dialog */}
|
||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Log out</AlertDialogTitle>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to log out?
|
||||
</AlertDialogDescription>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={() => setOpen(false)}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
handleSignOut();
|
||||
|
||||
// Tutup dialog setelah tombol Log out diklik
|
||||
if (!isPending) {
|
||||
setOpen(false);
|
||||
}
|
||||
}}
|
||||
className="btn btn-primary"
|
||||
disabled={isPending}
|
||||
>
|
||||
{isPending ? (
|
||||
<>
|
||||
<Loader2 className="size-4" />
|
||||
<span>Logging You Out...</span>
|
||||
</>
|
||||
) : (
|
||||
<span>Log out</span>
|
||||
)}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
|
@ -131,10 +192,7 @@ export function NavUser({ user }: { user: User | null }) {
|
|||
/>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onSubmit={() => { }} className="space-x-2">
|
||||
<IconLogout className="size-4" />
|
||||
<span>Log out</span>
|
||||
</DropdownMenuItem>
|
||||
<LogoutButton handleSignOut={handleSignOut} isPending={isPending} />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
|
|
|
@ -7,34 +7,33 @@ import { Input } from "@/app/_components/ui/input";
|
|||
import { SubmitButton } from "@/app/_components/submit-button";
|
||||
import Link from "next/link";
|
||||
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 { signIn } from "../action";
|
||||
import { useSignInHandler } from "../handler";
|
||||
|
||||
export function SignInForm({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<"form">) {
|
||||
// const [error, setError] = useState<string>();
|
||||
// const [loading, setLoading] = useState(false);
|
||||
|
||||
const [error, setError] = useState<string>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
// const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
// event.preventDefault();
|
||||
// if (loading) return;
|
||||
|
||||
const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (loading) return;
|
||||
// const formData = new FormData(event.currentTarget);
|
||||
|
||||
const formData = new FormData(event.currentTarget);
|
||||
// setLoading(true);
|
||||
// const res = await signIn(formData);
|
||||
// if (res && res.error) {
|
||||
// setError(res.error);
|
||||
// }
|
||||
// setLoading(false);
|
||||
// };
|
||||
|
||||
setLoading(true);
|
||||
const res = await signIn(formData);
|
||||
if (res && res.error) {
|
||||
setError(res.error);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
// const { register, isPending, handleSubmit, errors } = useSignInController();
|
||||
const { isPending, handleSubmit, error, errors, clearError } = useSignInHandler();
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -61,7 +60,7 @@ export function SignInForm({
|
|||
variant="outline"
|
||||
className="w-full bg-[#1C1C1C] text-white border-gray-800 hover:bg-[#2C2C2C] hover:border-gray-700"
|
||||
size="lg"
|
||||
disabled={loading}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Lock className="mr-2 h-5 w-5" />
|
||||
Continue with SSO
|
||||
|
@ -77,7 +76,7 @@ export function SignInForm({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={onSubmit} className="space-y-4" {...props} noValidate>
|
||||
<form onSubmit={handleSubmit} className="space-y-4" {...props} noValidate>
|
||||
<FormField
|
||||
label="Email"
|
||||
input={
|
||||
|
@ -86,19 +85,19 @@ export function SignInForm({
|
|||
type="email"
|
||||
name="email"
|
||||
placeholder="you@example.com"
|
||||
className={`bg-[#1C1C1C] border-gray-800 ${error ? "ring-red-500 focus-visible:ring-red-500" : ""
|
||||
}`}
|
||||
disabled={loading}
|
||||
className={`bg-[#1C1C1C] border-gray-800`}
|
||||
error={!!errors}
|
||||
disabled={isPending}
|
||||
/>
|
||||
}
|
||||
error={error ? error : undefined}
|
||||
error={error}
|
||||
/>
|
||||
<Button
|
||||
className="w-full bg-emerald-600 hover:bg-emerald-700 text-white"
|
||||
size="lg"
|
||||
disabled={loading}
|
||||
disabled={isPending}
|
||||
>
|
||||
{loading ? (
|
||||
{isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
Signing in...
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSlot,
|
||||
} from "@/app/_components/ui/input-otp";
|
||||
import { SubmitButton } from "@/app/_components/submit-button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
|
@ -14,16 +14,25 @@ import {
|
|||
CardTitle,
|
||||
} from "@/app/_components/ui/card";
|
||||
import { cn } from "@/app/_lib/utils";
|
||||
import { useVerifyOtpController } from "@/src/interface-adapters/controllers/auth/verify-otp.controller";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { Controller } from "react-hook-form";
|
||||
import { useVerifyOtpHandler } from "../handler";
|
||||
import { Button } from "@/app/_components/ui/button";
|
||||
|
||||
interface VerifyOtpFormProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
export function VerifyOtpForm({ className, ...props }: VerifyOtpFormProps) {
|
||||
const searchParams = useSearchParams()
|
||||
const email = searchParams.get("email") || ""
|
||||
const { control, register, isPending, handleSubmit, handleOtpChange, errors } = useVerifyOtpController(email)
|
||||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
handleOtpChange,
|
||||
errors,
|
||||
isPending
|
||||
} = useVerifyOtpHandler(email)
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||
|
@ -59,34 +68,42 @@ export function VerifyOtpForm({ className, ...props }: VerifyOtpFormProps) {
|
|||
</InputOTP>
|
||||
)}
|
||||
/>
|
||||
{errors.token ? <div className="flex w-full justify-center text-red-400 text-center text-sm">{errors.token.message}</div> : <div className="flex w-full justify-center text-background text-center text-sm">
|
||||
{errors.token ? (
|
||||
<div className="flex w-full justify-center text-red-400 text-center text-sm">
|
||||
{errors.token.message}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex w-full justify-center text-background text-center text-sm">
|
||||
Successfully verified!
|
||||
</div>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex w-full justify-center items-center text-gray-400 text-sm">
|
||||
Please enter the one-time password sent to {email}.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<SubmitButton
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full bg-emerald-600 hover:bg-emerald-700 text-white"
|
||||
disabled={isPending}
|
||||
>
|
||||
{isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
||||
Verifying...
|
||||
</>
|
||||
) : (
|
||||
"Verify OTP"
|
||||
)}
|
||||
</SubmitButton>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="text-balance text-center text-xs text-gray-400 [&_a]:text-emerald-500 [&_a]:underline [&_a]:underline-offset-4 [&_a]:hover:text-emerald-400">
|
||||
By clicking continue, you agree to our <a href="#">Terms of Service</a> and <a href="#">Privacy Policy</a>.
|
||||
By clicking continue, you agree to Sigap's{" "}
|
||||
<a href="#">Terms of Service</a> and <a href="#">Privacy Policy</a>.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -14,13 +14,17 @@ export async function signIn(formData: FormData) {
|
|||
recordResponse: true
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
const email = formData.get("email")?.toString()
|
||||
|
||||
try {
|
||||
const signInController = getInjection("ISignInController")
|
||||
await signInController({ email })
|
||||
|
||||
if (email) redirect(`/verify-otp?email=${encodeURIComponent(email)}`)
|
||||
// if (email) {
|
||||
// redirect(`/verify-otp?email=${encodeURIComponent(email)}`)
|
||||
// }
|
||||
|
||||
return { success: true }
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof InputParseError ||
|
||||
|
@ -31,6 +35,12 @@ export async function signIn(formData: FormData) {
|
|||
};
|
||||
}
|
||||
|
||||
if (err instanceof UnauthenticatedError) {
|
||||
return {
|
||||
error: 'User not found. Please tell your admin to create an account for you.',
|
||||
};
|
||||
}
|
||||
|
||||
const crashReporterService = getInjection('ICrashReporterService');
|
||||
crashReporterService.report(err);
|
||||
|
||||
|
@ -75,15 +85,22 @@ export async function signOut() {
|
|||
const signOutController = getInjection("ISignOutController")
|
||||
await signOutController()
|
||||
|
||||
revalidatePath("/")
|
||||
redirect("/sign-in") // Updated to match your route
|
||||
// revalidatePath("/")
|
||||
// redirect("/sign-in") // Updated to match your route
|
||||
|
||||
return { success: true }
|
||||
} catch (err) {
|
||||
// if (err instanceof AuthenticationError) {
|
||||
// return {
|
||||
// error: "An error occurred during sign out. Please try again later.",
|
||||
// }
|
||||
// }
|
||||
|
||||
const crashReporterService = getInjection("ICrashReporterService")
|
||||
crashReporterService.report(err)
|
||||
|
||||
return {
|
||||
error: "An error occurred during sign out. Please try again later.",
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -101,10 +118,12 @@ export async function verifyOtp(formData: FormData) {
|
|||
const verifyOtpController = getInjection("IVerifyOtpController")
|
||||
await verifyOtpController({ email, token })
|
||||
|
||||
redirect("/dashboard") // Updated to match your route
|
||||
// redirect("/dashboard")
|
||||
|
||||
return { success: true }
|
||||
} catch (err) {
|
||||
if (err instanceof InputParseError || err instanceof AuthenticationError) {
|
||||
return { error: err.message, success: false }
|
||||
return { error: err.message }
|
||||
}
|
||||
|
||||
const crashReporterService = getInjection("ICrashReporterService")
|
||||
|
@ -112,7 +131,6 @@ export async function verifyOtp(formData: FormData) {
|
|||
|
||||
return {
|
||||
error: "An error occurred during OTP verification. Please try again later.",
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
import { AuthenticationError } from "@/src/entities/errors/auth";
|
||||
import { useState } from "react";
|
||||
import { useAuthActions } from "./mutation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { defaultSignInPasswordlessValues, SignInFormData, SignInPasswordless, SignInPasswordlessSchema, SignInSchema } from "@/src/entities/models/auth/sign-in.model";
|
||||
import { createFormData } from "@/app/_utils/common";
|
||||
import { useFormHandler } from "@/app/_hooks/use-form-handler";
|
||||
import { toast } from "sonner";
|
||||
import { signIn } from "./action";
|
||||
import { useNavigations } from "@/app/_hooks/use-navigations";
|
||||
import { VerifyOtpFormData, 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();
|
||||
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (signIn.isPending) return;
|
||||
|
||||
setError(undefined);
|
||||
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const email = formData.get("email")?.toString()
|
||||
|
||||
try {
|
||||
await signIn.mutateAsync(formData, {
|
||||
onSuccess: () => {
|
||||
toast("An email has been sent to you. Please check your inbox.");
|
||||
if (email) router.push(`/verify-otp?email=${encodeURIComponent(email)}`);
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error.message);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setError(error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
// formData,
|
||||
// handleChange,
|
||||
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>()
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit: hookFormSubmit,
|
||||
control,
|
||||
formState: { errors },
|
||||
setValue
|
||||
} = useForm<VerifyOtpFormData>({
|
||||
resolver: zodResolver(verifyOtpSchema),
|
||||
defaultValues: {
|
||||
email,
|
||||
token: ""
|
||||
}
|
||||
})
|
||||
|
||||
const handleOtpChange = (value: string, onChange: (value: string) => void) => {
|
||||
onChange(value)
|
||||
|
||||
// Clear error when user starts typing
|
||||
if (error) {
|
||||
setError(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = hookFormSubmit(async (data) => {
|
||||
if (verifyOtp.isPending) return
|
||||
|
||||
setError(undefined)
|
||||
|
||||
// Create FormData object
|
||||
const formData = new FormData()
|
||||
formData.append("email", data.email)
|
||||
formData.append("token", data.token)
|
||||
|
||||
try {
|
||||
await verifyOtp.mutateAsync(formData, {
|
||||
onSuccess: () => {
|
||||
toast.success("OTP verified successfully")
|
||||
// Navigate to dashboard on success
|
||||
router.push("/dashboard")
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error.message)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setError(error.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
register,
|
||||
control,
|
||||
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>()
|
||||
|
||||
const handleSignOut = async () => {
|
||||
if (signOut.isPending) return
|
||||
|
||||
setError(undefined)
|
||||
|
||||
try {
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setError(error.message)
|
||||
toast.error(error.message)
|
||||
// toast.error("An error occurred during sign out. Please try again later.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
handleSignOut,
|
||||
error,
|
||||
isPending: signOut.isPending,
|
||||
errors: !!error || signOut.error,
|
||||
clearError: () => setError(undefined)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import { useMutation } from '@tanstack/react-query';
|
||||
import { signIn, signOut, verifyOtp } from './action';
|
||||
|
||||
export function useAuthActions() {
|
||||
// Sign In Mutation
|
||||
const signInMutation = useMutation({
|
||||
mutationFn: async (formData: FormData) => {
|
||||
const email = formData.get("email")?.toString()
|
||||
const response = await signIn(formData);
|
||||
|
||||
// If the server action returns an error, treat it as an error for React Query
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
return { email };
|
||||
}
|
||||
});
|
||||
|
||||
const verifyOtpMutation = useMutation({
|
||||
mutationFn: async (formData: FormData) => {
|
||||
const email = formData.get("email")?.toString()
|
||||
const token = formData.get("token")?.toString()
|
||||
const response = await verifyOtp(formData);
|
||||
|
||||
// If the server action returns an error, treat it as an error for React Query
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
return { email, token };
|
||||
}
|
||||
})
|
||||
|
||||
const signOutMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const response = await signOut();
|
||||
|
||||
// If the server action returns an error, treat it as an error for React Query
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
signIn: signInMutation,
|
||||
verifyOtp: verifyOtpMutation,
|
||||
signOut: signOutMutation
|
||||
};
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
import { SignInForm } from "@/app/(pages)/(auth)/_components/signin-form";
|
||||
import { Message } from "@/app/_components/form-message";
|
||||
import { Button } from "@/app/_components/ui/button";
|
||||
|
|
|
@ -2,13 +2,19 @@ import * as React from "react"
|
|||
|
||||
import { cn } from "@/app/_lib/utils"
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
error?: boolean
|
||||
}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, error, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
error && "ring-2 ring-red-500 border-red-500 focus-visible:ring-red-500",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { useState } from "react";
|
||||
import { UseFormSetValue } from "react-hook-form";
|
||||
|
||||
/**
|
||||
* Creates a reusable change handler for form inputs
|
||||
* @param setValue - The setValue function from react-hook-form
|
||||
* @returns Object with handleChange function and error management
|
||||
*/
|
||||
export function useFormHandler<T extends Record<string, any>>(
|
||||
setValue: UseFormSetValue<T>
|
||||
) {
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
||||
) => {
|
||||
const { name, value } = e.target;
|
||||
|
||||
// Update the form value
|
||||
setValue(name as any, value as any);
|
||||
|
||||
// Clear error when user starts typing
|
||||
if (errors[name]) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[name]: "",
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const setError = (name: string, message: string) => {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[name]: message,
|
||||
}));
|
||||
};
|
||||
|
||||
const clearErrors = () => {
|
||||
setErrors({});
|
||||
};
|
||||
|
||||
return {
|
||||
handleChange,
|
||||
errors,
|
||||
setError,
|
||||
clearErrors,
|
||||
};
|
||||
}
|
|
@ -35,3 +35,17 @@ export function formatUrl(url: string): string {
|
|||
|
||||
return "/" + url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a FormData object from the FormData object.
|
||||
* @returns {FormData} The FormData object.
|
||||
*/
|
||||
export function createFormData(): FormData {
|
||||
const data = new FormData();
|
||||
Object.entries(FormData).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
data.append(key, value);
|
||||
}
|
||||
});
|
||||
return data;
|
||||
};
|
|
@ -13,7 +13,6 @@ ApplicationContainer.load(Symbol('TransactionManagerModule'), createTransactionM
|
|||
ApplicationContainer.load(Symbol('AuthenticationModule'), createAuthenticationModule());
|
||||
ApplicationContainer.load(Symbol('UsersModule'), createUsersModule());
|
||||
|
||||
|
||||
export function getInjection<K extends keyof typeof DI_SYMBOLS>(
|
||||
symbol: K
|
||||
): DI_RETURN_TYPES[K] {
|
||||
|
|
|
@ -2,16 +2,15 @@ import { createModule } from '@evyweb/ioctopus';
|
|||
|
||||
import { AuthenticationService } from '@/src/infrastructure/services/authentication.service';
|
||||
|
||||
|
||||
import { signInUseCase } from '@/src/application/use-cases/auth/sign-in.use-case';
|
||||
import { signUpUseCase } from '@/src/application/use-cases/auth/sign-up.use-case';
|
||||
import { signOutUseCase } from '@/src/application/use-cases/auth/sign-out.use-case';
|
||||
|
||||
|
||||
|
||||
import { DI_SYMBOLS } from '@/di/types';
|
||||
import { signInController } from '@/src/interface-adapters/controllers/auth/sign-in.controller';
|
||||
import { signOutController } from '@/src/interface-adapters/controllers/auth/sign-out.controller';
|
||||
import { verifyOtpUseCase } from '@/src/application/use-cases/auth/verify-otp.use-case';
|
||||
import { verifyOtpController } from '@/src/interface-adapters/controllers/auth/verify-otp.controller';
|
||||
|
||||
export function createAuthenticationModule() {
|
||||
const authenticationModule = createModule();
|
||||
|
@ -20,6 +19,12 @@ export function createAuthenticationModule() {
|
|||
// authenticationModule
|
||||
// .bind(DI_SYMBOLS.IAuthenticationService)
|
||||
// .toClass(MockAuthenticationService, [DI_SYMBOLS.IUsersRepository]);
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.IAuthenticationService)
|
||||
.toClass(AuthenticationService, [
|
||||
DI_SYMBOLS.IUsersRepository,
|
||||
DI_SYMBOLS.IInstrumentationService,
|
||||
]);
|
||||
} else {
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.IAuthenticationService)
|
||||
|
@ -29,19 +34,13 @@ export function createAuthenticationModule() {
|
|||
]);
|
||||
}
|
||||
|
||||
// Use Cases
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.ISignInUseCase)
|
||||
.toHigherOrderFunction(signInUseCase, [
|
||||
DI_SYMBOLS.IInstrumentationService,
|
||||
DI_SYMBOLS.IAuthenticationService,
|
||||
DI_SYMBOLS.IUsersRepository,
|
||||
DI_SYMBOLS.IAuthenticationService,
|
||||
]);
|
||||
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.ISignOutUseCase)
|
||||
.toHigherOrderFunction(signOutUseCase, [
|
||||
DI_SYMBOLS.IInstrumentationService,
|
||||
DI_SYMBOLS.IAuthenticationService,
|
||||
]);
|
||||
|
||||
authenticationModule
|
||||
|
@ -52,6 +51,23 @@ export function createAuthenticationModule() {
|
|||
DI_SYMBOLS.IUsersRepository,
|
||||
]);
|
||||
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.IVerifyOtpUseCase)
|
||||
.toHigherOrderFunction(verifyOtpUseCase, [
|
||||
DI_SYMBOLS.IInstrumentationService,
|
||||
DI_SYMBOLS.IAuthenticationService,
|
||||
DI_SYMBOLS.IUsersRepository
|
||||
]);
|
||||
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.ISignOutUseCase)
|
||||
.toHigherOrderFunction(signOutUseCase, [
|
||||
DI_SYMBOLS.IInstrumentationService,
|
||||
DI_SYMBOLS.IAuthenticationService,
|
||||
]);
|
||||
|
||||
|
||||
// Controllers
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.ISignInController)
|
||||
.toHigherOrderFunction(signInController, [
|
||||
|
@ -59,11 +75,18 @@ export function createAuthenticationModule() {
|
|||
DI_SYMBOLS.ISignInUseCase,
|
||||
]);
|
||||
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.IVerifyOtpController)
|
||||
.toHigherOrderFunction(verifyOtpController, [
|
||||
DI_SYMBOLS.IInstrumentationService,
|
||||
DI_SYMBOLS.IVerifyOtpUseCase,
|
||||
]);
|
||||
|
||||
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.ISignOutController)
|
||||
.toHigherOrderFunction(signOutController, [
|
||||
DI_SYMBOLS.IInstrumentationService,
|
||||
DI_SYMBOLS.IAuthenticationService,
|
||||
DI_SYMBOLS.ISignOutUseCase,
|
||||
]);
|
||||
|
||||
|
|
|
@ -25,9 +25,9 @@ export const DI_SYMBOLS = {
|
|||
|
||||
// Use Cases
|
||||
ISignInUseCase: Symbol.for('ISignInUseCase'),
|
||||
ISignOutUseCase: Symbol.for('ISignOutUseCase'),
|
||||
ISignUpUseCase: Symbol.for('ISignUpUseCase'),
|
||||
IVerifyOtpUseCase: Symbol.for('IVerifyOtpUseCase'),
|
||||
ISignOutUseCase: Symbol.for('ISignOutUseCase'),
|
||||
|
||||
// Controllers
|
||||
ISignInController: Symbol.for('ISignInController'),
|
||||
|
@ -47,12 +47,12 @@ export interface DI_RETURN_TYPES {
|
|||
|
||||
// Use Cases
|
||||
ISignInUseCase: ISignInUseCase;
|
||||
ISignOutUseCase: ISignOutUseCase;
|
||||
ISignUpUseCase: ISignUpUseCase;
|
||||
IVerifyOtpUseCase: IVerifyOtpUseCase;
|
||||
ISignOutUseCase: ISignOutUseCase;
|
||||
|
||||
// Controllers
|
||||
ISignInController: ISignInController;
|
||||
ISignOutController: ISignOutController;
|
||||
IVerifyOtpController: IVerifyOtpController;
|
||||
ISignOutController: ISignOutController;
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
"@evyweb/ioctopus": "^1.2.0",
|
||||
"@hookform/resolvers": "^4.1.2",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"@prisma/instrumentation": "^6.5.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||
"@radix-ui/react-avatar": "^1.1.3",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
|
@ -2432,9 +2433,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@prisma/instrumentation": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.4.1.tgz",
|
||||
"integrity": "sha512-1SeN0IvMp5zm3RLJnEr+Zn67WDqUIPP1lF/PkLbi/X64vsnFyItcXNRBrYr0/sI2qLcH9iNzJUhyd3emdGizaQ==",
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.5.0.tgz",
|
||||
"integrity": "sha512-morJDtFRoAp5d/KENEm+K6Y3PQcn5bCvpJ5a9y3V3DNMrNy/ZSn2zulPGj+ld+Xj2UYVoaMJ8DpBX/o6iF6OiA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0"
|
||||
|
@ -4184,6 +4185,18 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/node/node_modules/@prisma/instrumentation": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.4.1.tgz",
|
||||
"integrity": "sha512-1SeN0IvMp5zm3RLJnEr+Zn67WDqUIPP1lF/PkLbi/X64vsnFyItcXNRBrYr0/sI2qLcH9iNzJUhyd3emdGizaQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": "^1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/opentelemetry": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-9.5.0.tgz",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"@evyweb/ioctopus": "^1.2.0",
|
||||
"@hookform/resolvers": "^4.1.2",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"@prisma/instrumentation": "^6.5.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||
"@radix-ui/react-avatar": "^1.1.3",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
|
|
|
@ -3,9 +3,24 @@ import { PrismaClient } from "@prisma/client";
|
|||
const prismaClientSingleton = () => {
|
||||
return new PrismaClient({
|
||||
log: [
|
||||
"query",
|
||||
]
|
||||
});
|
||||
{
|
||||
emit: 'event',
|
||||
level: 'query',
|
||||
},
|
||||
{
|
||||
emit: 'stdout',
|
||||
level: 'error',
|
||||
},
|
||||
{
|
||||
emit: 'stdout',
|
||||
level: 'info',
|
||||
},
|
||||
{
|
||||
emit: 'stdout',
|
||||
level: 'warn',
|
||||
},
|
||||
],
|
||||
})
|
||||
};
|
||||
|
||||
declare const globalThis: {
|
||||
|
@ -17,3 +32,9 @@ const db = globalThis.prismaGlobal ?? prismaClientSingleton();
|
|||
export default db;
|
||||
|
||||
if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = db;
|
||||
|
||||
db.$on('query', (e) => {
|
||||
console.log('Query: ' + e.query)
|
||||
console.log('Params: ' + e.params)
|
||||
console.log('Duration: ' + e.duration + 'ms')
|
||||
})
|
|
@ -10,8 +10,10 @@ Sentry.init({
|
|||
// Add optional integrations for additional features
|
||||
integrations: [
|
||||
Sentry.replayIntegration(),
|
||||
// Sentry.prismaIntegration(),
|
||||
],
|
||||
|
||||
|
||||
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
||||
tracesSampleRate: 1,
|
||||
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
import type { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"
|
||||
import { AuthenticationError } from "@/src/entities/errors/auth";
|
||||
import { InputParseError, NotFoundError } from "@/src/entities/errors/common";
|
||||
import { AuthenticationError, UnauthenticatedError } from "@/src/entities/errors/auth";
|
||||
import { type SignInFormData, SignInPasswordless, SignInSchema } from "@/src/entities/models/auth/sign-in.model"
|
||||
import { IAuthenticationService } from "@/src/application/services/authentication.service.interface";
|
||||
import { User } from "@/src/entities/models/users/users.model";
|
||||
import { Session } from "@/src/entities/models/auth/session.model";
|
||||
import { IUsersRepository } from "../../repositories/users.repository.interface";
|
||||
import { AuthResult } from "@/src/entities/models/auth/auth-result.model";
|
||||
import { UsersRepository } from "@/src/infrastructure/repositories/users.repository.impl";
|
||||
import { ICrashReporterService } from "../../services/crash-reporter.service.interface";
|
||||
|
||||
export type ISignInUseCase = ReturnType<typeof signInUseCase>
|
||||
|
||||
|
@ -16,26 +10,16 @@ export const signInUseCase =
|
|||
(
|
||||
instrumentationService: IInstrumentationService,
|
||||
authenticationService: IAuthenticationService,
|
||||
crashReporterService: ICrashReporterService,
|
||||
usersRepository: IUsersRepository
|
||||
) =>
|
||||
async (input: SignInPasswordless): Promise<void> => {
|
||||
return instrumentationService.startSpan({ name: "signIn Use Case", op: "function" },
|
||||
async () => {
|
||||
|
||||
console.log("Injected usersRepository:", usersRepository);
|
||||
|
||||
// Create a direct instance as a test
|
||||
const directRepo = new UsersRepository(
|
||||
instrumentationService,
|
||||
crashReporterService
|
||||
);
|
||||
console.log("Direct repo methods:", Object.keys(directRepo));
|
||||
|
||||
const existingUser = await usersRepository.getUserByEmail(input.email)
|
||||
|
||||
if (!existingUser) {
|
||||
throw new NotFoundError("User does not exist")
|
||||
throw new UnauthenticatedError("User not found. Please tell your admin to create an account for you.")
|
||||
}
|
||||
|
||||
// Attempt to sign in
|
||||
|
@ -43,12 +27,6 @@ export const signInUseCase =
|
|||
email: input.email
|
||||
})
|
||||
|
||||
const session = await authenticationService.getSession();
|
||||
|
||||
if (!session) {
|
||||
throw new NotFoundError("Session not found")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
)
|
||||
|
|
|
@ -28,7 +28,7 @@ export const defaultSignInWithPasswordValues: SignInWithPassword = {
|
|||
|
||||
export type SignInWithPassword = z.infer<typeof SignInWithPassword>
|
||||
|
||||
export const SignInPasswordless = SignInSchema.pick({
|
||||
export const SignInPasswordlessSchema = SignInSchema.pick({
|
||||
email: true,
|
||||
})
|
||||
|
||||
|
@ -37,7 +37,7 @@ export const defaultSignInPasswordlessValues: SignInPasswordless = {
|
|||
email: "",
|
||||
}
|
||||
|
||||
export type SignInPasswordless = z.infer<typeof SignInPasswordless>
|
||||
export type SignInPasswordless = z.infer<typeof SignInPasswordlessSchema>
|
||||
|
||||
|
||||
// Define the sign-in response schema using Zod
|
||||
|
|
|
@ -26,12 +26,13 @@ export class AuthenticationService implements IAuthenticationService {
|
|||
name: "signInPasswordless Use Case",
|
||||
}, async () => {
|
||||
try {
|
||||
|
||||
const supabase = await this.supabaseServer
|
||||
|
||||
const { email } = credentials
|
||||
const signIn = supabase.auth.signInWithOtp({ email })
|
||||
|
||||
const { data: { session }, error } = await this.instrumentationService.startSpan({
|
||||
const { error } = await this.instrumentationService.startSpan({
|
||||
name: "supabase.auth.signInWithOtp",
|
||||
op: "db:query",
|
||||
attributes: { "system": "supabase.auth" }
|
||||
|
@ -40,7 +41,6 @@ export class AuthenticationService implements IAuthenticationService {
|
|||
})
|
||||
|
||||
return
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err)
|
||||
throw err
|
||||
|
|
|
@ -8,7 +8,7 @@ import { useNavigations } from '@/app/_hooks/use-navigations';
|
|||
import { AuthenticationError } from '@/src/entities/errors/auth';
|
||||
import * as authRepository from '@/src/application/repositories/authentication.repository';
|
||||
|
||||
export function useAuthMutation() {
|
||||
export function useAuthActions() {
|
||||
const { router } = useNavigations();
|
||||
|
||||
// Sign In Mutation
|
||||
|
|
|
@ -10,7 +10,7 @@ import { useState, type FormEvent, type ChangeEvent } from "react";
|
|||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
// import { signIn } from "";
|
||||
import { useAuthMutation } from "./auth-controller";
|
||||
import { useAuthActions } from "./auth-controller";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { AuthenticationError } from "@/src/entities/errors/auth";
|
||||
|
@ -105,7 +105,7 @@ type SignInFormErrors = Partial<Record<keyof SignInFormData, string>>;
|
|||
// const [formData, setFormData] = useState<SignInFormData>(defaultSignInValues);
|
||||
// const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
// const { signIn } = useAuthMutation();
|
||||
// const { signIn } = useAuthActions();
|
||||
|
||||
// const form = useForm<SignInFormData>({
|
||||
// resolver: zodResolver(SignInSchema),
|
||||
|
@ -167,7 +167,7 @@ type SignInFormErrors = Partial<Record<keyof SignInFormData, string>>;
|
|||
// }
|
||||
|
||||
// export function useSignInController() {
|
||||
// const { signIn } = useAuthMutation();
|
||||
// const { signIn } = useAuthActions();
|
||||
|
||||
// // Gunakan react-hook-form untuk mengelola form state & error handling
|
||||
// const {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { IAuthenticationService } from "@/src/application/services/authentication.service.interface"
|
||||
import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"
|
||||
import { ISignOutUseCase } from "@/src/application/use-cases/auth/sign-out.use-case"
|
||||
|
||||
// Sign Out Controller
|
||||
export type ISignOutController = ReturnType<typeof signOutController>
|
||||
|
@ -7,12 +7,12 @@ export type ISignOutController = ReturnType<typeof signOutController>
|
|||
export const signOutController =
|
||||
(
|
||||
instrumentationService: IInstrumentationService,
|
||||
authenticationService: IAuthenticationService
|
||||
signOutUseCase: ISignOutUseCase
|
||||
) =>
|
||||
async () => {
|
||||
return await instrumentationService.startSpan({
|
||||
name: "signOut Controller"
|
||||
}, async () => {
|
||||
return await authenticationService.signOut()
|
||||
return await signOutUseCase()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,19 +1,3 @@
|
|||
// src/hooks/useVerifyOtpForm.ts
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
// import { verifyOtp } from "";
|
||||
import {
|
||||
defaultVerifyOtpValues,
|
||||
VerifyOtpFormData,
|
||||
verifyOtpSchema,
|
||||
} from "@/src/entities/models/auth/verify-otp.model";
|
||||
import { useNavigations } from "@/app/_hooks/use-navigations";
|
||||
import { toast } from "sonner";
|
||||
import { useAuthMutation } from "./auth-controller";
|
||||
import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface";
|
||||
import { IVerifyOtpUseCase } from "@/src/application/use-cases/auth/verify-otp.use-case";
|
||||
import { z } from "zod";
|
||||
|
@ -67,56 +51,56 @@ import { InputParseError } from "@/src/entities/errors/common";
|
|||
// };
|
||||
// }
|
||||
|
||||
export const useVerifyOtpController = (email: string) => {
|
||||
const { verifyOtp } = useAuthMutation()
|
||||
// export const useVerifyOtpController = (email: string) => {
|
||||
// const { verifyOtp } = useAuthActions()
|
||||
|
||||
const {
|
||||
control,
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitSuccessful },
|
||||
} = useForm<VerifyOtpFormData>({
|
||||
resolver: zodResolver(verifyOtpSchema),
|
||||
defaultValues: { ...defaultVerifyOtpValues, email: email },
|
||||
})
|
||||
// const {
|
||||
// control,
|
||||
// register,
|
||||
// handleSubmit,
|
||||
// reset,
|
||||
// formState: { errors, isSubmitSuccessful },
|
||||
// } = useForm<VerifyOtpFormData>({
|
||||
// resolver: zodResolver(verifyOtpSchema),
|
||||
// defaultValues: { ...defaultVerifyOtpValues, email: email },
|
||||
// })
|
||||
|
||||
// Clear form after successful submission
|
||||
useEffect(() => {
|
||||
if (isSubmitSuccessful) {
|
||||
reset({ ...defaultVerifyOtpValues, email })
|
||||
}
|
||||
}, [isSubmitSuccessful, reset, email])
|
||||
// // Clear form after successful submission
|
||||
// useEffect(() => {
|
||||
// if (isSubmitSuccessful) {
|
||||
// reset({ ...defaultVerifyOtpValues, email })
|
||||
// }
|
||||
// }, [isSubmitSuccessful, reset, email])
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
try {
|
||||
await verifyOtp.mutate(data)
|
||||
} catch (error) {
|
||||
console.error("OTP verification failed", error)
|
||||
}
|
||||
})
|
||||
// const onSubmit = handleSubmit(async (data) => {
|
||||
// try {
|
||||
// await verifyOtp.mutate(data)
|
||||
// } catch (error) {
|
||||
// console.error("OTP verification failed", error)
|
||||
// }
|
||||
// })
|
||||
|
||||
// Function to handle auto-submission when all digits are entered
|
||||
const handleOtpChange = (value: string, onChange: (value: string) => void) => {
|
||||
onChange(value)
|
||||
// // Function to handle auto-submission when all digits are entered
|
||||
// const handleOtpChange = (value: string, onChange: (value: string) => void) => {
|
||||
// onChange(value)
|
||||
|
||||
// Auto-submit when all 6 digits are entered
|
||||
if (value.length === 6) {
|
||||
setTimeout(() => {
|
||||
onSubmit()
|
||||
}, 300) // Small delay to allow the UI to update
|
||||
}
|
||||
}
|
||||
// // Auto-submit when all 6 digits are entered
|
||||
// if (value.length === 6) {
|
||||
// setTimeout(() => {
|
||||
// onSubmit()
|
||||
// }, 300) // Small delay to allow the UI to update
|
||||
// }
|
||||
// }
|
||||
|
||||
return {
|
||||
control,
|
||||
register,
|
||||
handleSubmit: onSubmit,
|
||||
handleOtpChange,
|
||||
errors,
|
||||
isPending: verifyOtp.isPending,
|
||||
}
|
||||
}
|
||||
// return {
|
||||
// control,
|
||||
// register,
|
||||
// handleSubmit: onSubmit,
|
||||
// handleOtpChange,
|
||||
// errors,
|
||||
// isPending: verifyOtp.isPending,
|
||||
// }
|
||||
// }
|
||||
|
||||
// Verify OTP Controller
|
||||
const verifyOtpInputSchema = z.object({
|
||||
|
|
Loading…
Reference in New Issue