205 lines
6.0 KiB
TypeScript
205 lines
6.0 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { useTranslations } from "next-intl";
|
|
import {
|
|
InputOTP,
|
|
InputOTPGroup,
|
|
InputOTPSeparator,
|
|
InputOTPSlot,
|
|
} from "@/components/ui/input-otp";
|
|
import { RegistrationOTPFormProps, UserCategory } from "@/types/registration";
|
|
import { useOTP } from "@/hooks/use-registration";
|
|
|
|
export const RegistrationOTPForm: React.FC<RegistrationOTPFormProps> = ({
|
|
email,
|
|
category,
|
|
memberIdentity,
|
|
onSuccess,
|
|
onError,
|
|
onResend,
|
|
className,
|
|
}) => {
|
|
const t = useTranslations("LandingPage");
|
|
const { verifyOTP, resendOTP, loading, error, formattedTime, canResend } =
|
|
useOTP();
|
|
const [otpValue, setOtpValue] = useState("");
|
|
|
|
const handleOTPChange = (value: string) => {
|
|
setOtpValue(value);
|
|
};
|
|
|
|
const handleVerifyOTP = async () => {
|
|
if (otpValue.length !== 6) {
|
|
onError?.("Please enter a complete 6-digit OTP");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const userData = await verifyOTP(
|
|
email,
|
|
otpValue,
|
|
category,
|
|
memberIdentity
|
|
);
|
|
if (userData?.error) return false;
|
|
onSuccess(email);
|
|
} catch (error: any) {
|
|
onError?.(error.message || "OTP verification failed");
|
|
}
|
|
};
|
|
|
|
const handleResendOTP = async () => {
|
|
if (!canResend) {
|
|
onError?.("Please wait before requesting a new OTP");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const success = await resendOTP(email, category, memberIdentity);
|
|
if (success) {
|
|
onResend?.();
|
|
}
|
|
} catch (error: any) {
|
|
onError?.(error.message || "Failed to resend OTP");
|
|
}
|
|
};
|
|
|
|
const handleTypeOTP = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
const { key } = event;
|
|
const target = event.currentTarget;
|
|
|
|
if (key === "Enter") {
|
|
event.preventDefault();
|
|
handleVerifyOTP();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className={className}>
|
|
<div className="space-y-6">
|
|
{/* OTP Instructions */}
|
|
{/* <div className="px-8 lg:px-20 mb-6">
|
|
<p className="text-black dark:text-white text-2xl px-0 lg:px-20 font-semibold">
|
|
{t("enterOTP", { defaultValue: "Masukkan kode OTP" })}
|
|
</p>
|
|
<p className="text-red-500 text-sm px-0 lg:px-20">
|
|
{t("checkInbox", { defaultValue: "Silahkan cek inbox atau kotak spam pada email Anda." })}
|
|
</p>
|
|
<p className="text-gray-600 text-sm px-0 lg:px-20 mt-2">
|
|
OTP sent to: <span className="font-medium">{email}</span>
|
|
</p>
|
|
</div> */}
|
|
|
|
{/* OTP Input */}
|
|
<div className="flex justify-center mb-6">
|
|
<InputOTP
|
|
maxLength={6}
|
|
value={otpValue}
|
|
onChange={handleOTPChange}
|
|
disabled={loading}
|
|
className="gap-2"
|
|
>
|
|
<InputOTPGroup>
|
|
<InputOTPSlot
|
|
index={0}
|
|
onKeyDown={handleTypeOTP}
|
|
className="w-12 h-12 text-lg"
|
|
/>
|
|
<InputOTPSlot
|
|
index={1}
|
|
onKeyDown={handleTypeOTP}
|
|
className="w-12 h-12 text-lg"
|
|
/>
|
|
</InputOTPGroup>
|
|
<InputOTPSeparator />
|
|
<InputOTPGroup>
|
|
<InputOTPSlot
|
|
index={2}
|
|
onKeyDown={handleTypeOTP}
|
|
className="w-12 h-12 text-lg"
|
|
/>
|
|
<InputOTPSlot
|
|
index={3}
|
|
onKeyDown={handleTypeOTP}
|
|
className="w-12 h-12 text-lg"
|
|
/>
|
|
</InputOTPGroup>
|
|
<InputOTPSeparator />
|
|
<InputOTPGroup>
|
|
<InputOTPSlot
|
|
index={4}
|
|
onKeyDown={handleTypeOTP}
|
|
className="w-12 h-12 text-lg"
|
|
/>
|
|
<InputOTPSlot
|
|
index={5}
|
|
onKeyDown={handleTypeOTP}
|
|
className="w-12 h-12 text-lg"
|
|
/>
|
|
</InputOTPGroup>
|
|
</InputOTP>
|
|
</div>
|
|
|
|
{/* Error Message */}
|
|
{error && (
|
|
<p className="text-red-500 text-center">
|
|
<b>{error}</b>
|
|
</p>
|
|
)}
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex flex-row px-0 lg:px-28 justify-between items-center my-4">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={handleResendOTP}
|
|
disabled={!canResend || loading}
|
|
className="bg-slate-300 dark:bg-black text-center rounded-lg mr-1 w-[200px] py-2 text-base"
|
|
>
|
|
{canResend
|
|
? t("resend", { defaultValue: "Resend OTP" })
|
|
: `${t("resending", {
|
|
defaultValue: "Resending",
|
|
})} (${formattedTime})`}
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
onClick={handleVerifyOTP}
|
|
disabled={otpValue.length !== 6 || loading}
|
|
className="bg-red-700 w-[200px] py-2 text-center text-white rounded-lg ml-1 hover:bg-red-800"
|
|
>
|
|
{loading ? (
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
|
Verifying...
|
|
</div>
|
|
) : (
|
|
t("next", { defaultValue: "Next" })
|
|
)}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Help Text */}
|
|
<div className="text-center px-8">
|
|
<p className="text-sm text-gray-600">
|
|
{t("otpHelp", {
|
|
defaultValue:
|
|
"Didn't receive the code? Check your spam folder or",
|
|
})}{" "}
|
|
<button
|
|
type="button"
|
|
onClick={handleResendOTP}
|
|
disabled={!canResend || loading}
|
|
className="text-blue-600 hover:text-blue-800 underline disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{t("resend", { defaultValue: "resend OTP" })}
|
|
</button>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|