2025-07-16 18:46:08 +05:30
|
|
|
import React, {
|
|
|
|
|
useEffect,
|
|
|
|
|
useRef,
|
|
|
|
|
forwardRef,
|
|
|
|
|
useImperativeHandle,
|
|
|
|
|
} from "react";
|
2025-07-11 16:54:37 +05:30
|
|
|
|
|
|
|
|
interface CustomReCaptchaProps {
|
|
|
|
|
siteKey: string;
|
|
|
|
|
onVerify: (token: string) => void;
|
|
|
|
|
onExpired?: () => void;
|
|
|
|
|
onError?: () => void;
|
|
|
|
|
className?: string;
|
2025-07-16 18:46:08 +05:30
|
|
|
theme?: "light" | "dark";
|
|
|
|
|
size?: "normal" | "compact";
|
2025-07-11 16:54:37 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ReCaptchaRef {
|
|
|
|
|
reset: () => void;
|
|
|
|
|
execute: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
|
interface Window {
|
|
|
|
|
grecaptcha: any;
|
2025-07-16 18:46:08 +05:30
|
|
|
onReCaptchaLoad?: () => void;
|
2025-07-11 16:54:37 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 18:46:08 +05:30
|
|
|
const CustomReCaptcha = forwardRef<ReCaptchaRef, CustomReCaptchaProps>(
|
|
|
|
|
(
|
|
|
|
|
{
|
|
|
|
|
siteKey,
|
|
|
|
|
onVerify,
|
|
|
|
|
onExpired,
|
|
|
|
|
onError,
|
|
|
|
|
className = "",
|
|
|
|
|
theme = "dark",
|
|
|
|
|
size = "normal",
|
2025-07-11 16:54:37 +05:30
|
|
|
},
|
2025-07-16 18:46:08 +05:30
|
|
|
ref
|
|
|
|
|
) => {
|
|
|
|
|
const captchaRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const widgetId = useRef<number | null>(null);
|
|
|
|
|
const isLoadedRef = useRef(false);
|
|
|
|
|
|
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
|
|
|
reset: () => {
|
|
|
|
|
if (window.grecaptcha && widgetId.current !== null) {
|
|
|
|
|
window.grecaptcha.reset(widgetId.current);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
execute: () => {
|
|
|
|
|
if (window.grecaptcha && widgetId.current !== null) {
|
|
|
|
|
window.grecaptcha.execute(widgetId.current);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const loadReCaptcha = () => {
|
|
|
|
|
if (window.grecaptcha) {
|
|
|
|
|
renderReCaptcha();
|
|
|
|
|
return;
|
2025-07-11 16:54:37 +05:30
|
|
|
}
|
2025-07-16 18:46:08 +05:30
|
|
|
|
|
|
|
|
// Check if script is already loading
|
|
|
|
|
if (
|
|
|
|
|
document.querySelector(
|
|
|
|
|
'script[src^="https://www.google.com/recaptcha/api.js"]'
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
// If already loading, set up a callback for when it loads
|
|
|
|
|
window.onReCaptchaLoad = renderReCaptcha;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load reCAPTCHA script
|
|
|
|
|
const script = document.createElement("script");
|
|
|
|
|
script.src =
|
|
|
|
|
"https://www.google.com/recaptcha/api.js?onload=onReCaptchaLoad&render=explicit";
|
|
|
|
|
script.async = true;
|
|
|
|
|
script.defer = true;
|
|
|
|
|
|
|
|
|
|
// Set up callback for when script loads
|
|
|
|
|
window.onReCaptchaLoad = () => {
|
|
|
|
|
renderReCaptcha();
|
|
|
|
|
window.onReCaptchaLoad = undefined; // Clean up
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
script.onerror = () => {
|
|
|
|
|
console.error("Failed to load reCAPTCHA script");
|
|
|
|
|
if (onError) onError();
|
|
|
|
|
window.onReCaptchaLoad = undefined; // Clean up
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
document.head.appendChild(script);
|
2025-07-11 16:54:37 +05:30
|
|
|
};
|
|
|
|
|
|
2025-07-16 18:46:08 +05:30
|
|
|
const renderReCaptcha = () => {
|
|
|
|
|
if (!captchaRef.current || isLoadedRef.current) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
widgetId.current = window.grecaptcha.render(captchaRef.current, {
|
|
|
|
|
sitekey: siteKey,
|
|
|
|
|
callback: onVerify,
|
|
|
|
|
"expired-callback": onExpired,
|
|
|
|
|
"error-callback": onError,
|
|
|
|
|
theme: theme,
|
|
|
|
|
size: size,
|
|
|
|
|
});
|
|
|
|
|
isLoadedRef.current = true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error rendering reCAPTCHA:", error);
|
|
|
|
|
if (onError) onError();
|
2025-07-11 16:54:37 +05:30
|
|
|
}
|
|
|
|
|
};
|
2025-07-16 18:46:08 +05:30
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
loadReCaptcha();
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
// Cleanup
|
|
|
|
|
if (window.grecaptcha && widgetId.current !== null) {
|
|
|
|
|
try {
|
|
|
|
|
window.grecaptcha.reset(widgetId.current);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Ignore cleanup errors
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Clean up the global callback
|
|
|
|
|
window.onReCaptchaLoad = undefined;
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Add styles to document head instead of using styled-jsx
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const styleId = "custom-recaptcha-styles";
|
|
|
|
|
|
|
|
|
|
// Check if styles are already added
|
|
|
|
|
if (document.getElementById(styleId)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const styleElement = document.createElement("style");
|
|
|
|
|
styleElement.id = styleId;
|
|
|
|
|
styleElement.textContent = `
|
2025-07-11 16:54:37 +05:30
|
|
|
.grecaptcha-badge {
|
|
|
|
|
visibility: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
iframe[src*="recaptcha"] {
|
|
|
|
|
border-radius: 8px !important;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.g-recaptcha {
|
|
|
|
|
transform: scale(1);
|
|
|
|
|
transform-origin: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.g-recaptcha > div {
|
|
|
|
|
border-radius: 8px !important;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
|
|
|
}
|
|
|
|
|
`;
|
2025-07-16 18:46:08 +05:30
|
|
|
|
|
|
|
|
document.head.appendChild(styleElement);
|
|
|
|
|
|
|
|
|
|
// Cleanup function to remove styles when component unmounts
|
|
|
|
|
return () => {
|
|
|
|
|
const existingStyle = document.getElementById(styleId);
|
|
|
|
|
if (existingStyle) {
|
|
|
|
|
document.head.removeChild(existingStyle);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={`flex justify-center ${className}`}>
|
|
|
|
|
<div
|
|
|
|
|
className="bg-gray-800/30 border border-gray-600/50 rounded-xl p-6 shadow-lg backdrop-blur-sm"
|
|
|
|
|
style={
|
|
|
|
|
{
|
|
|
|
|
"--recaptcha-border-radius": "12px",
|
|
|
|
|
} as React.CSSProperties
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<div ref={captchaRef} />
|
|
|
|
|
</div>
|
2025-07-11 16:54:37 +05:30
|
|
|
</div>
|
2025-07-16 18:46:08 +05:30
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
);
|
2025-07-11 16:54:37 +05:30
|
|
|
|
2025-07-16 18:46:08 +05:30
|
|
|
CustomReCaptcha.displayName = "CustomReCaptcha";
|
2025-07-11 16:54:37 +05:30
|
|
|
|
2025-07-16 18:46:08 +05:30
|
|
|
export default CustomReCaptcha;
|