fix: recaptcha badge
This commit is contained in:
1 parent
e24cf8d0be
commit
5f396f7c07
3 files changed
+121
-128
No files matched your search
+119
-118
@@ -253,132 +253,133 @@ export default async function Home() {
|
|||||||
<input type="email" name="email" id="form-email" placeholder="Your Email" required />
|
<input type="email" name="email" id="form-email" placeholder="Your Email" required />
|
||||||
<input type="text" name="subject" id="form-subject" placeholder="Subject" required />
|
<input type="text" name="subject" id="form-subject" placeholder="Subject" required />
|
||||||
<textarea name="message" id="form-message" rows={5} placeholder="Message" required></textarea>
|
<textarea name="message" id="form-message" rows={5} placeholder="Message" required></textarea>
|
||||||
<div id="recaptcha-container" style={{ display: 'flex', justifyContent: 'center', margin: '10px 0' }}></div>
|
|
||||||
|
|
||||||
|
|
||||||
{/* reCAPTCHA v3 Enterprise — invisible */}
|
|
||||||
<Script src={`https://www.google.com/recaptcha/enterprise.js?render=${recaptchaSiteKey}`} strategy="afterInteractive" />
|
|
||||||
<Script id="recaptcha-inline-script" strategy="afterInteractive">
|
|
||||||
{`
|
|
||||||
(function() {
|
|
||||||
function showToast(type, title, msg) {
|
|
||||||
var container = document.getElementById('toast-container');
|
|
||||||
var backdrop = document.getElementById('toast-backdrop');
|
|
||||||
var box = document.getElementById('toast-box');
|
|
||||||
var icon = document.getElementById('toast-icon');
|
|
||||||
var titleEl = document.getElementById('toast-title');
|
|
||||||
var msgEl = document.getElementById('toast-msg');
|
|
||||||
var closeBtn = document.getElementById('toast-close');
|
|
||||||
|
|
||||||
box.className = 'toast toast-' + type;
|
|
||||||
icon.textContent = type === 'success' ? '✓' : '✕';
|
|
||||||
titleEl.textContent = title;
|
|
||||||
msgEl.textContent = msg || '';
|
|
||||||
|
|
||||||
container.classList.add('toast-visible');
|
|
||||||
backdrop.classList.add('toast-visible');
|
|
||||||
|
|
||||||
var autoClose;
|
|
||||||
function dismiss() {
|
|
||||||
container.classList.remove('toast-visible');
|
|
||||||
backdrop.classList.remove('toast-visible');
|
|
||||||
clearTimeout(autoClose);
|
|
||||||
}
|
|
||||||
|
|
||||||
closeBtn.onclick = dismiss;
|
|
||||||
backdrop.onclick = dismiss;
|
|
||||||
autoClose = setTimeout(dismiss, type === 'success' ? 4000 : 6000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check URL params for status on load
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Move recaptcha badge inline
|
|
||||||
var moveBadge = setInterval(function() {
|
|
||||||
var badge = document.querySelector('.grecaptcha-badge');
|
|
||||||
var container = document.getElementById('recaptcha-container');
|
|
||||||
if (badge && container) {
|
|
||||||
container.appendChild(badge);
|
|
||||||
clearInterval(moveBadge);
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
var params = new URLSearchParams(window.location.search);
|
|
||||||
var status = params.get('status');
|
|
||||||
var msg = params.get('msg');
|
|
||||||
if (status === 'success') {
|
|
||||||
showToast('success', 'Message Sent!', 'Thank you for reaching out. I will get back to you soon.');
|
|
||||||
history.replaceState(null, '', window.location.pathname + window.location.hash);
|
|
||||||
} else if (status === 'error') {
|
|
||||||
showToast('error', 'Something went wrong', msg || 'Please try again later.');
|
|
||||||
history.replaceState(null, '', window.location.pathname + window.location.hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
var form = document.getElementById('contact-form');
|
|
||||||
if (!form) return;
|
|
||||||
|
|
||||||
form.addEventListener('submit', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
var btn = document.getElementById('btn-send-message');
|
|
||||||
var originalText = btn.innerText;
|
|
||||||
btn.innerText = 'Verifying...';
|
|
||||||
btn.disabled = true;
|
|
||||||
|
|
||||||
grecaptcha.enterprise.ready(async function() {
|
|
||||||
try {
|
|
||||||
const token = await grecaptcha.enterprise.execute('${recaptchaSiteKey}', {action: 'submit'});
|
|
||||||
|
|
||||||
const verifyRes = await fetch('/api/contact', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ token: token })
|
|
||||||
});
|
|
||||||
|
|
||||||
const verifyData = await verifyRes.json();
|
|
||||||
if (!verifyData.success) {
|
|
||||||
btn.innerText = originalText;
|
|
||||||
btn.disabled = false;
|
|
||||||
showToast('error', 'Verification Failed', verifyData.msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
btn.innerText = 'Sending...';
|
|
||||||
const formData = new FormData(form);
|
|
||||||
formData.append('access_key', '${process.env.WEB3FORMS_ACCESS_KEY}');
|
|
||||||
formData.append('from_name', 'Portfolio Contact Form');
|
|
||||||
|
|
||||||
const web3Res = await fetch('https://api.web3forms.com/submit', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
const web3Data = await web3Res.json();
|
|
||||||
btn.innerText = originalText;
|
|
||||||
btn.disabled = false;
|
|
||||||
|
|
||||||
if (web3Data.success) {
|
|
||||||
form.reset();
|
|
||||||
showToast('success', 'Message Sent!', 'Thank you for reaching out. I will get back to you soon.');
|
|
||||||
} else {
|
|
||||||
showToast('error', 'Something went wrong', 'The email service rejected the request. Please try again.');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch(err) {
|
|
||||||
btn.innerText = originalText;
|
|
||||||
btn.disabled = false;
|
|
||||||
showToast('error', 'Unexpected Error', 'reCAPTCHA failed to load. Please refresh and try again.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
`}
|
|
||||||
</Script>
|
|
||||||
|
|
||||||
<button type="submit" className="btn primary full-width" id="btn-send-message">Send Message</button>
|
<button type="submit" className="btn primary full-width" id="btn-send-message">Send Message</button>
|
||||||
</form>
|
</form>
|
||||||
|
<div id="recaptcha-container" style={{ display: 'flex', justifyContent: 'center', marginTop: '20px' }}></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
|
{/* reCAPTCHA v3 Enterprise — invisible */}
|
||||||
|
<Script src={`https://www.google.com/recaptcha/enterprise.js?render=explicit&onload=onloadRecaptcha`} strategy="afterInteractive" />
|
||||||
|
<Script id="recaptcha-inline-script" strategy="afterInteractive">
|
||||||
|
{`
|
||||||
|
(function() {
|
||||||
|
function showToast(type, title, msg) {
|
||||||
|
var container = document.getElementById('toast-container');
|
||||||
|
var backdrop = document.getElementById('toast-backdrop');
|
||||||
|
var box = document.getElementById('toast-box');
|
||||||
|
var icon = document.getElementById('toast-icon');
|
||||||
|
var titleEl = document.getElementById('toast-title');
|
||||||
|
var msgEl = document.getElementById('toast-msg');
|
||||||
|
var closeBtn = document.getElementById('toast-close');
|
||||||
|
|
||||||
|
box.className = 'toast toast-' + type;
|
||||||
|
icon.textContent = type === 'success' ? '✓' : '✕';
|
||||||
|
titleEl.textContent = title;
|
||||||
|
msgEl.textContent = msg || '';
|
||||||
|
|
||||||
|
container.classList.add('toast-visible');
|
||||||
|
backdrop.classList.add('toast-visible');
|
||||||
|
|
||||||
|
var autoClose;
|
||||||
|
function dismiss() {
|
||||||
|
container.classList.remove('toast-visible');
|
||||||
|
backdrop.classList.remove('toast-visible');
|
||||||
|
clearTimeout(autoClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeBtn.onclick = dismiss;
|
||||||
|
backdrop.onclick = dismiss;
|
||||||
|
autoClose = setTimeout(dismiss, type === 'success' ? 4000 : 6000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check URL params for status on load
|
||||||
|
window.onloadRecaptcha = function() {
|
||||||
|
grecaptcha.enterprise.ready(function() {
|
||||||
|
window.recaptchaWidgetId = grecaptcha.enterprise.render('recaptcha-container', {
|
||||||
|
sitekey: '${recaptchaSiteKey}',
|
||||||
|
badge: 'inline',
|
||||||
|
size: 'invisible'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var params = new URLSearchParams(window.location.search);
|
||||||
|
var status = params.get('status');
|
||||||
|
var msg = params.get('msg');
|
||||||
|
if (status === 'success') {
|
||||||
|
showToast('success', 'Message Sent!', 'Thank you for reaching out. I will get back to you soon.');
|
||||||
|
history.replaceState(null, '', window.location.pathname + window.location.hash);
|
||||||
|
} else if (status === 'error') {
|
||||||
|
showToast('error', 'Something went wrong', msg || 'Please try again later.');
|
||||||
|
history.replaceState(null, '', window.location.pathname + window.location.hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
var form = document.getElementById('contact-form');
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
form.addEventListener('submit', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
var btn = document.getElementById('btn-send-message');
|
||||||
|
var originalText = btn.innerText;
|
||||||
|
btn.innerText = 'Verifying...';
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
grecaptcha.enterprise.ready(async function() {
|
||||||
|
try {
|
||||||
|
const token = await grecaptcha.enterprise.execute(window.recaptchaWidgetId, {action: 'submit'});
|
||||||
|
|
||||||
|
const verifyRes = await fetch('/api/contact', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ token: token })
|
||||||
|
});
|
||||||
|
|
||||||
|
const verifyData = await verifyRes.json();
|
||||||
|
if (!verifyData.success) {
|
||||||
|
btn.innerText = originalText;
|
||||||
|
btn.disabled = false;
|
||||||
|
showToast('error', 'Verification Failed', verifyData.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.innerText = 'Sending...';
|
||||||
|
const formData = new FormData(form);
|
||||||
|
formData.append('access_key', '${process.env.WEB3FORMS_ACCESS_KEY}');
|
||||||
|
formData.append('from_name', 'Portfolio Contact Form');
|
||||||
|
|
||||||
|
const web3Res = await fetch('https://api.web3forms.com/submit', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const web3Data = await web3Res.json();
|
||||||
|
btn.innerText = originalText;
|
||||||
|
btn.disabled = false;
|
||||||
|
|
||||||
|
if (web3Data.success) {
|
||||||
|
form.reset();
|
||||||
|
showToast('success', 'Message Sent!', 'Thank you for reaching out. I will get back to you soon.');
|
||||||
|
} else {
|
||||||
|
showToast('error', 'Something went wrong', 'The email service rejected the request. Please try again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch(err) {
|
||||||
|
btn.innerText = originalText;
|
||||||
|
btn.disabled = false;
|
||||||
|
showToast('error', 'Unexpected Error', 'reCAPTCHA failed to load. Please refresh and try again.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
`}
|
||||||
|
</Script>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -107,12 +107,4 @@
|
|||||||
.btn.full-width {
|
.btn.full-width {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Custom reCAPTCHA Badge Styling (Inline) --- */
|
|
||||||
.grecaptcha-badge {
|
|
||||||
position: static !important;
|
|
||||||
margin-top: 15px !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
display: block !important;
|
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import { MetadataRoute } from 'next';
|
import { MetadataRoute } from 'next';
|
||||||
|
|
||||||
export default function sitemap(): MetadataRoute.Sitemap {
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
// const baseUrl = 'https://crischarles.vercel.app';
|
const baseUrl = 'https://crischarles.vercel.app';
|
||||||
const baseUrl = 'http://localhost:3000';
|
// const baseUrl = 'http://localhost:3000';
|
||||||
const lastMod = '2026-06-11T07:44:07+00:00';
|
const lastMod = '2026-06-11T07:44:07+00:00';
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
Reference in new issue
Block a user