Firebase React Phone Auth Recaptcha уже была отображена в этом элементеJavascript

Форум по Javascript
Ответить
Anonymous
 Firebase React Phone Auth Recaptcha уже была отображена в этом элементе

Сообщение Anonymous »

Я работаю над проектом, используя React, TS и Firebase. Ниже приведен полный код формы компонента студенческой формы, в которой мне нужно проверить номер телефона студента через OTP. Проблема заключается в том, что OTP отправляется нормально без какой -либо ошибки в первый раз, но когда кнопка повторной или нажимает, это дает ошибку. Кто-нибудь может помочь?

Код: Выделить всё

// At the top before imports
declare global {
interface Window {
grecaptcha: any;
recaptchaVerifier?: import("firebase/auth").RecaptchaVerifier| null ;
recaptchaWidgetId: number;
confirmationResult: import("firebase/auth").ConfirmationResult;
}
}

import { useParams } from "react-router-dom";
import { useEffect, useState } from "react";
import {
doc,
getDoc,
setDoc,
collection,
serverTimestamp,
query,
where,
getDocs,
} from "firebase/firestore";
import { auth, db, storage } from "../utils/firebase";
import { useForm } from "react-hook-form";
import { sendEmailOtp } from "../utils/sendOtp";
import { verifyEmailOtp } from "../utils/verifyOtp";
import { RecaptchaVerifier, signInWithPhoneNumber } from "firebase/auth";
import { getDownloadURL, ref, uploadBytes } from "firebase/storage";
import { generateCertificate } from "../utils/generateCertificate";

const regex = {
name: /^[A-Za-z\s.'-]{2,50}$/,
email: /^\S+@\S+\.\S+$/,
phone: /^[6-9]\d{9}$/,
course: /^[A-Za-z0-9\s().,-]{2,50}$/,
otp: /^\d{6}$/,
};

type FeedbackFormFields = {
name: string;
email: string;
phone: string;
course: string;
rating: number;
comments: string;
};

export default function FeedbackForm() {
const { id } = useParams();
const [formData, setFormData] = useState(null);
const [loading, setLoading] = useState(true);
const [invalid, setInvalid] = useState(false);
const [submitted, setSubmitted] = useState(false);
const [error, setError] = useState("");

const [emailOtpSent, setEmailOtpSent] = useState(false);
const [emailCooldown, setEmailCooldown] = useState(0);
const [emailOtpInput, setEmailOtpInput] = useState("");
const [emailOtpVerified, setEmailOtpVerified] = useState(false);

const [phoneOtpSent, setPhoneOtpSent] = useState(false);
const [phoneCooldown, setPhoneCooldown] = useState(0);
const [phoneOtpInput, setPhoneOtpInput] = useState("");
const [phoneOtpVerified, setPhoneOtpVerified] = useState(false);

const [loadingOtp, setLoadingOtp] = useState(false);
const [loadingOtpVerification, setLoadingOtpVerification] = useState(false);
const [loadingPhoneOtp, setLoadingPhoneOtp] = useState(false);
const [loadingPhoneVerification, setLoadingPhoneVerification] =
useState(false);
const [submitting, setSubmitting] = useState(false);
const [progress, setProgress] = useState(0);
const savedDraft = localStorage.getItem(`feedback_draft_${id}`);
const parsedDraft = savedDraft ? JSON.parse(savedDraft) : {};

const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm({
defaultValues: {
rating: 5,
...parsedDraft, // merge saved data with default
},
});

const watchedFields = watch();

useEffect(() => {
const subscription = watch((values) => {
localStorage.setItem(`feedback_draft_${id}`, JSON.stringify(values));
});

return () => subscription.unsubscribe(); // Cleanup
}, [watch, id]);

useEffect(() => {
const fetchForm = async () => {
if (!id) return;
const docRef = doc(db, "workshops", id);
const docSnap = await getDoc(docRef);
if (!docSnap.exists() || !docSnap.data()?.isActive) setInvalid(true);
else setFormData(docSnap.data());
setLoading(false);
};
fetchForm();
}, [id]);

useEffect(() => {
const totalFields = 5;
let filled = 0;

if (watchedFields.name?.match(regex.name)) filled++;
if (watchedFields.email?.match(regex.email)) filled++;
// if (emailOtpVerified) filled++;
if (watchedFields.phone?.match(regex.phone)) filled++;
// if (phoneOtpVerified) filled++;
if (watchedFields.course?.match(regex.course)) filled++;
if (watchedFields.rating) filled++;
// if (typeof watchedFields.comments === "string" && watchedFields.comments.trim()) filled++;

setProgress(Math.round((filled / totalFields) * 100));
}, [watchedFields]);

useEffect(() => {
return () =>  {
if (window.recaptchaVerifier) {
window.recaptchaVerifier.clear();
}
if (typeof window.recaptchaWidgetId !== "undefined" && window.grecaptcha) {
window.grecaptcha.reset(window.recaptchaWidgetId);
}
};
}, []);

useEffect(() => {
const interval = setInterval(() => {
setEmailCooldown((prev) => (prev > 0 ? prev - 1 : 0));
setPhoneCooldown((prev) => (prev > 0 ? prev - 1 : 0));
}, 1000);

return () => clearInterval(interval);
}, []);

const setupRecaptcha = async () => {
// Clear old if already created
if (window.recaptchaVerifier) {
if (window.recaptchaWidgetId !== undefined) {
window.grecaptcha.reset(window.recaptchaWidgetId);
}
window.recaptchaVerifier.clear(); // clean internal state
window.recaptchaVerifier = null;
}

// Create a new verifier
if(!window.recaptchaVerifier)
window.recaptchaVerifier = new RecaptchaVerifier(
auth,
"recaptcha-container",
{
size: "invisible",
callback: (response: string) => {
console.log("reCAPTCHA solved:", response);
},
}
);

// Store the widget ID for resetting later
window.recaptchaWidgetId = await window.recaptchaVerifier.render();
};

// const handleSendOtp = async () => {
//   try {
//     if(!watchedFields.email) setError("Please provide the email.")
//     setLoadingOtp(true);
//     const result = await sendEmailOtp(watchedFields.email);
//     if (result.success) {
//       setEmailOtpSent(true);
//       alert("OTP sent to your email");
//     }
//   } catch {
//     alert("Failed to send email OTP");
//   } finally {
//     setLoadingOtp(false);
//   }
// };
const handleSendOtp = async () => {
try {
if (!watchedFields.email) {
setError("Please provide the email.");
return;
}

setLoadingOtp(true);
const result = await sendEmailOtp(watchedFields.email);
if (result.success) {
setEmailOtpSent(true);
setEmailCooldown(60); // ⏳ 60s cooldown starts here
alert("OTP sent to your email");
}
} catch {
alert("Failed to send email OTP");
} finally {
setLoadingOtp(false);
}
};

const handleVerifyEmailOtp = async () => {
try {
if (!regex.otp.test(emailOtpInput)) {
alert("Enter a valid 6-digit email OTP");
return;
}

setLoadingOtpVerification(true);
const result = await verifyEmailOtp(watchedFields.email, emailOtpInput);
if (result.success) {
setEmailOtpVerified(true);
alert("Email verified!");
}
} catch {
alert("Invalid or expired email OTP.");
} finally {
setLoadingOtpVerification(false);
}
};

// const handleSendPhoneOtp = async () => {
//   try {
//     setLoadingPhoneOtp(true);
//     await setupRecaptcha();
//     const confirmationResult = await signInWithPhoneNumber(
//       auth,
//       `+91${watchedFields.phone}`,
//       window.recaptchaVerifier
//     );
//     window.confirmationResult = confirmationResult;
//     setPhoneOtpSent(true);
//     alert("OTP sent to phone");
//   } catch (error) {
//     console.error(error);
//     alert("Failed to send phone OTP");
//   } finally {
//     setLoadingPhoneOtp(false);
//   }
// };

const handleSendPhoneOtp = async () => {
try {
setLoadingPhoneOtp(true);
await setupRecaptcha();
const confirmationResult = await signInWithPhoneNumber(
auth,
`+91${watchedFields.phone}`,
window.recaptchaVerifier!
);
window.confirmationResult = confirmationResult;
setPhoneOtpSent(true);
setPhoneCooldown(60); // Start 60s cooldown
alert("OTP sent to phone");
} catch (error) {
console.error(error);
alert("Failed to send phone OTP");
} finally {
setLoadingPhoneOtp(false);
}
};

const handleVerifyPhoneOtp = async () =>  {
try {
if (!regex.otp.test(phoneOtpInput)) {
alert("Enter a valid 6-digit phone OTP");
return;
}

setLoadingPhoneVerification(true);
const result = await window.confirmationResult.confirm(phoneOtpInput);
if (result.user) {
setPhoneOtpVerified(true);
alert("Phone number verified!");
}
} catch {
alert("Invalid or expired phone OTP.");
} finally {
setLoadingPhoneVerification(false);
}
};

const onSubmit = async (data: FeedbackFormFields) => {
setError("");
setSubmitting(true);

if (!emailOtpVerified) {
setError("Please verify your email before submitting.");
setSubmitting(false);
return;
}

if (!phoneOtpVerified) {
setError("Please verify your phone before submitting.");
setSubmitting(false);
return;
}

try {
const feedbackRef = collection(db, "submissions");
const q = query(
feedbackRef,
where("formId", "==", id),
where("email", "==", data.email)
);
const snapshot = await getDocs(q);

if (!snapshot.empty) {
setError("You have already submitted this form.");
console.log("already submitted");
return;
}

const templateSnap = await getDocs(
query(
collection(db, "certificateTemplates"),
where("workshopId", "==", id)
)
);
if (templateSnap.empty) {
console.log("template not found");
return;
}

const templateData = templateSnap.docs[0].data();
const pdfBytes = await generateCertificate(
templateData.downloadURL,
templateData.fieldPositions || [],
{
name: data.name,
college: formData.college,
date: formData.date,
workshopName: formData.workshopName,
}
);

const certRef = ref(storage, `certificates/${id}_${data.email}.pdf`);
await uploadBytes(
certRef,
new Blob([new Uint8Array(pdfBytes)], { type: "application/pdf" })
);
const url = await getDownloadURL(certRef);

console.log(formData, id);

await setDoc(doc(db, "submissions", `${id}_${data.email}`), {
formId: id,
submittedAt: serverTimestamp(),
emailVerified: true,
phoneVerified: true,
certificateURL: url,
workshopName: formData.workshopName,
college: formData.college,
date: formData.date,
time: formData.time,
...data,
});
localStorage.removeItem(`feedback_draft_${id}`);

setSubmitted(true);
} catch (err) {
console.error("Submission failed", err);
setError("Submission failed.  Try again later.");
} finally {
setSubmitting(false);
// setSubmitted(true);
}
};
if (loading) return Loading...;
if (invalid)
return (

Invalid or Inactive Form

);
if (submitted)
return (

Thank you for your feedback!

Your certificate will be sent to your registered email and WhatsApp
number shortly.

{/*  {
window.open(url);
}}
>
Preview
 */}

);

return (

{/* Progress Bar */}


Form Progress: {progress}%






{formData?.workshopName}
{formData?.college}

{formData?.date} at {formData?.time}


{/* Name */}


Full Name *


{errors.name && (
{errors.name.message}
)}

{/* Email + OTP */}


Email *


{errors.email && (
{errors.email.message}
)}

{!emailOtpVerified && (

{!emailOtpSent || emailCooldown === 0 ? (

{loadingOtp
? "Sending..."
: emailOtpSent
? "Resend OTP"
: "Send OTP"}

) : (

Resend OTP in{" "}
{emailCooldown}s

)}

)}

{emailOtpSent && !emailOtpVerified &&  (

 setEmailOtpInput(e.target.value)}
/>

{loadingOtpVerification ? "Verifying..." : "Verify OTP"}


)}

{emailOtpVerified && (
Email verified ✅
)}


{/* Phone + OTP */}


Phone *


{errors.phone && (
{errors.phone.message}
)}

{!phoneOtpVerified && (

{!phoneOtpSent || phoneCooldown === 0 ? (

{loadingPhoneOtp
? "Sending..."
: phoneOtpSent
? "Resend OTP"
: "Send OTP"}

) : (

Resend OTP in{" "}
{phoneCooldown}s

)}

)}

{phoneOtpSent && !phoneOtpVerified && (

 setPhoneOtpInput(e.target.value)}
/>

{loadingPhoneVerification ? "Verifying..." : "Verify OTP"}


)}

{phoneOtpVerified && (
Phone verified ✅
)}


{/* Course */}


Course *


{errors.course &&  (
{errors.course.message}
)}

{/* Rating */}


Rating *


Excellent
Very Good
Good
Fair
Poor


{/* Comments */}

Comments


{/* Error Message */}
{error && 
{error}
}

{/* Submit Button */}

{submitting ? (



className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
/>

Submitting...

) : (
"Submit Feedback"
)}



{/* Recaptcha */}

);
}
ошибка:
Изображение ошибки

Подробнее здесь: https://stackoverflow.com/questions/796 ... is-element
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Javascript»