Files
Wdipl-react/components/CustomReCaptcha.tsx

199 lines
4.8 KiB
TypeScript
Raw Permalink Normal View History

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;
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;
onReCaptchaLoad?: () => void;
2025-07-11 16:54:37 +05:30
}
}
const CustomReCaptcha = forwardRef<ReCaptchaRef, CustomReCaptchaProps>(
(
{
siteKey,
onVerify,
onExpired,
onError,
className = "",
theme = "dark",
size = "normal",
2025-07-11 16:54:37 +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
}
// 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
};
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
}
};
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);
}
`;
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-11 16:54:37 +05:30
CustomReCaptcha.displayName = "CustomReCaptcha";
2025-07-11 16:54:37 +05:30
export default CustomReCaptcha;