Skip to content

Commit

Permalink
Fix contact form with small regestion by removeing word count and spi…
Browse files Browse the repository at this point in the history
…cle email messages
  • Loading branch information
Husky-Devel committed Dec 8, 2024
1 parent dbfa5b1 commit 404bcdb
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 196 deletions.
99 changes: 73 additions & 26 deletions src/components/ContactForm.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
const turnsiteSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY;
---

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>

<form id="contactForm" class="space-y-4">
<div>
<label for="name" class="block text-sm font-medium text-black dark:text-white">
Expand All @@ -16,43 +14,37 @@ const turnsiteSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY;
required
class="mt-1 w-full rounded-lg border border-black/25 dark:border-white/25 bg-transparent p-2 text-black dark:text-white focus:ring-2 focus:ring-black/25 dark:focus:ring-white/25 blend"
/>
<div id="nameMessage" class="text-sm mt-1 hidden italic"></div>
</div>

<div>
<label for="email" class="block text-sm font-medium text-black dark:text-white">
Email
</label>
<input
type="email"
type="email"
id="email"
name="email"
required
class="mt-1 w-full rounded-lg border border-black/25 dark:border-white/25 bg-transparent p-2 text-black dark:text-white focus:ring-2 focus:ring-black/25 dark:focus:ring-white/25 blend"
/>
<div id="emailMessage" class="text-sm mt-1 hidden italic"></div>
</div>

<div>
<label for="message" class="block text-sm font-medium text-black dark:text-white">
Message
</label>
<textarea
id="message"
name="message"
required
maxlength="500"
class="mt-1 w-full h-[170px] resize-none rounded-lg border border-black/25 dark:border-white/25 bg-transparent p-2 text-black dark:text-white focus:ring-2 focus:ring-black/25 dark:focus:ring-white/25 blend"
></textarea>
<div id="charCount" class="text-sm text-gray-500 dark:text-gray-400 mt-1">
500 characters remaining
</div>
id="message"
name="message"
rows="4"
required
class="mt-1 w-full rounded-lg border border-black/25 dark:border-white/25 bg-transparent p-2 text-black dark:text-white focus:ring-2 focus:ring-black/25 dark:focus:ring-white/25 blend"
></textarea>
</div>

<!-- Turnstile Widget -->
<div class="flex justify-center">
<div
id="turnstile-widget"
class="cf-turnstile"
data-sitekey={turnsiteSiteKey}
data-theme="auto"
Expand All @@ -71,16 +63,71 @@ const turnsiteSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY;
</div>
</form>

<script define:vars={{ turnsiteSiteKey }}>
import { initializeContactForm } from './contactutils';

window.onload = function() {
if (typeof window.turnstile !== 'undefined') {
initializeContactForm(turnsiteSiteKey);
} else {
window.addEventListener('turnstile-loaded', function() {
initializeContactForm(turnsiteSiteKey);
<!-- Turnstile Script -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<script>
const form = document.getElementById('contactForm') as HTMLFormElement;
const statusDiv = document.getElementById('formStatus') as HTMLDivElement;
const statusText = statusDiv.querySelector('p') as HTMLParagraphElement;

form.addEventListener('submit', async (e) => {
e.preventDefault();

// Get turnstile token
const turnstileResponse = document.querySelector<HTMLElement>('[name="cf-turnstile-response"]')?.getAttribute('value');

if (!turnstileResponse) {
statusDiv.classList.remove('hidden');
statusText.textContent = 'Please complete the captcha';
statusText.className = 'text-sm text-red-600 dark:text-red-400';
return;
}

// Show loading state
const submitButton = form.querySelector('button[type="submit"]') as HTMLButtonElement;
const originalButtonText = submitButton.textContent;
submitButton.textContent = 'Sending...';
submitButton.disabled = true;

const formData = new FormData(form);
const data = {
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
turnstileToken: turnstileResponse
};

try {
const response = await fetch('/api/send-email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});

const result = await response.json();

if (response.ok) {
// Success
statusDiv.classList.remove('hidden');
statusText.textContent = 'Message sent successfully!';
statusText.className = 'text-sm text-green-600 dark:text-green-400';
form.reset();
// Reset turnstile
window.turnstile.reset();
} else {
// Error
throw new Error(result.error || 'Failed to send message');
}
} catch (error) {
statusDiv.classList.remove('hidden');
statusText.textContent = error.message;
statusText.className = 'text-sm text-red-600 dark:text-red-400';
} finally {
submitButton.textContent = originalButtonText;
submitButton.disabled = false;
}
};
});
</script>
225 changes: 57 additions & 168 deletions src/components/contactutils.ts
Original file line number Diff line number Diff line change
@@ -1,176 +1,65 @@
interface TurnstileWindow extends Window {
turnstile: {
render: (container: string | HTMLElement, options: any) => string;
reset: (widgetId?: string) => void;
getResponse: (widgetId?: string) => string | null;
};
}

const domainMessages: Record<string, string> = {
'husky.nz': 'Woof! Always great to hear from a fellow Husky! 🐕',
'inde.nz': 'Why Hello there! Always awesome to see someone from Inde! 🚀',
'rollestoncollege.nz': 'Hello what are you doing here? Testing out my form I see 🤔',
};

const specificEmailMessages: Record<string, string> = {
'peter@husky.nz': 'Hey, this is your own contact form silly! 😄',
'mike.blair@inde.nz': 'Hi Mike! Nice to see you checking out my site! 👋',
'royden@inde.nz': 'Hey Royden! Thanks for checking out my site! 👋',
'preston.gallwas@inde.nz': 'Hey Preston! Thanks for checking out my site! 👋',
};

const nameMessages: Record<string, string> = {
'peter': 'Hey, that\'s my name too! 😄',
'mike': 'Mike! Is that really you? 🤔',
'royden': 'The legend himself! 🚀',
'preston': 'Preston in the house! 💻',
'bob': 'Bob the builder, can we fix it? Yes we can! 🏗️',
'alice': 'Following any white rabbits lately? 🐰',
};

function resetEmailMessage(emailMessage: HTMLDivElement): void {
emailMessage.textContent = '';
emailMessage.classList.add('hidden');
emailMessage.classList.remove('text-blue-500', 'dark:text-blue-400');
}

function updateCharacterCount(textarea: HTMLTextAreaElement, charCount: HTMLElement): void {
const MAX_LENGTH = 500;
const WARNING_THRESHOLD = 200;
const CRITICAL_THRESHOLD = 50;
const remaining = MAX_LENGTH - textarea.value.length;

// Update counter text and message
if (remaining <= 0) {
charCount.innerHTML = `
<div class="text-l mt-1 text-red-500 dark:text-red-400">
You have run out of characters, please try and keep it short and sweet!!!!
</div>
`;
} else {
charCount.innerHTML = `<span>${remaining} characters remaining</span>`;
}

charCount.classList.remove(
'text-gray-500', 'dark:text-gray-400',
'text-yellow-500', 'dark:text-yellow-400',
'text-red-500', 'dark:text-red-400'
);

if (remaining < 0) {
charCount.classList.add('text-red-500', 'dark:text-red-400');
} else if (remaining <= CRITICAL_THRESHOLD) {
charCount.classList.add('text-red-500', 'dark:text-red-400');
} else if (remaining <= WARNING_THRESHOLD) {
charCount.classList.add('text-yellow-500', 'dark:text-yellow-400');
} else {
charCount.classList.add('text-gray-500', 'dark:text-gray-400');
}
}

export function initializeContactForm(turnsiteSiteKey: string): void {
const form = document.getElementById('contactForm') as HTMLFormElement;
const nameInput = document.getElementById('name') as HTMLInputElement;
const emailMessage = document.getElementById('emailMessage') as HTMLDivElement;
const statusDiv = document.getElementById('statusDiv') as HTMLDivElement;
const statusText = document.getElementById('statusText') as HTMLParagraphElement;
let turnstileWidget: string;

// Initialize Turnstile only when it's available
if (window.turnstile) {
turnstileWidget = window.turnstile.render('#turnstile-widget', {
sitekey: turnsiteSiteKey,
theme: 'auto',
});
}

// Reset form on page load/refresh
window.addEventListener('load', () => {
form.reset();
resetEmailMessage(emailMessage);
updateCharacterCount(textarea, charCount);
statusDiv.classList.add('hidden');
import { Resend } from 'resend';
import type { APIRoute } from 'astro';

const resend = new Resend(import.meta.env.RESEND_API_KEY);

async function verifyTurnstileToken(token: string) {
const response = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
secret: import.meta.env.TURNSTILE_SECRET_KEY,
response: token,
}),
});

// Initialize character counter
textarea.addEventListener('input', () => updateCharacterCount(textarea, charCount));
updateCharacterCount(textarea, charCount);

// Email validation and custom messages
emailInput.addEventListener('input', () => {
const email = emailInput.value.toLowerCase();
resetEmailMessage(emailMessage);

if (specificEmailMessages[email]) {
emailMessage.textContent = specificEmailMessages[email];
emailMessage.classList.remove('hidden');
emailMessage.classList.add('text-blue-500', 'dark:text-blue-400');
return;
}

const domain = email.split('@')[1];
if (domain && domainMessages[domain]) {
emailMessage.textContent = domainMessages[domain];
emailMessage.classList.remove('hidden');
emailMessage.classList.add('text-blue-500', 'dark:text-blue-400');
}
});
const data = await response.json();
return data.success;
}

// Name validation and custom messages
nameInput.addEventListener('input', () => {
const name = nameInput.value.toLowerCase();
if (nameMessages[name]) {
emailMessage.textContent = nameMessages[name];
emailMessage.classList.remove('hidden');
emailMessage.classList.add('text-blue-500', 'dark:text-blue-400');
} else {
resetEmailMessage(emailMessage);
export const POST: APIRoute = async ({ request }) => {
try {
const formData = await request.json();
const { name, email, message, turnstileToken } = formData;

// Verify turnstile token
const isValid = await verifyTurnstileToken(turnstileToken);
if (!isValid) {
return new Response(
JSON.stringify({ error: 'Invalid captcha' }),
{ status: 400 }
);
}
});

// Form submission
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const turnstileToken = (window as unknown as TurnstileWindow).turnstile.getResponse(turnstileWidget);

if (!turnstileToken) {
statusDiv.classList.remove('hidden');
statusText.textContent = 'Please complete the captcha';
statusText.classList.add('text-red-500');
return;
}
const { data, error } = await resend.emails.send({
from: import.meta.env.CONTACT_SEND_EMAIL,
to: import.meta.env.CONTACT_EMAIL,
subject: `New Contact Form Submission from ${name}`,
html: `
<h2>New Contact Form Submission</h2>
<p><strong>Name:</strong> ${name}</p>
<p><strong>Email:</strong> ${email}</p>
<p><strong>Message:</strong></p>
<p>${message}</p>
`
});

try {
const response = await fetch('/api/send-email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
turnstileToken
}),
if (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 400,
});

const result = await response.json();

if (response.ok) {
statusDiv.classList.remove('hidden');
statusText.textContent = 'Message sent successfully!';
statusText.classList.add('text-green-500');
form.reset();
(window as unknown as TurnstileWindow).turnstile.reset(turnstileWidget);
} else {
throw new Error(result.message || 'Failed to send message');
}
} catch (error) {
statusDiv.classList.remove('hidden');
statusText.textContent = error instanceof Error ? error.message : 'An error occurred';
statusText.classList.remove('text-green-500');
statusText.classList.add('text-red-500');
}
});
}

return new Response(
JSON.stringify({ message: 'Email sent successfully' }),
{ status: 200 }
);
} catch (e) {
return new Response(
JSON.stringify({ error: 'Failed to send email' }),
{ status: 500 }
);
}
};
4 changes: 2 additions & 2 deletions src/pages/api/send-email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const POST: APIRoute = async ({ request }) => {
const { data, error } = await resend.emails.send({
from: import.meta.env.CONTACT_SEND_EMAIL,
to: import.meta.env.CONTACT_EMAIL,
subject: `New Contact Form Submission from ${name} there email is ${email}`,
subject: `A new message from ${name} on the main site contact form`,
html: `
<h2>New Contact Form Submission</h2>
<p><strong>Name:</strong> ${name}</p>
Expand All @@ -53,7 +53,7 @@ export const POST: APIRoute = async ({ request }) => {
}

return new Response(
JSON.stringify({ message: 'Email sent successfully, thank you I will get back to you shortly' }),
JSON.stringify({ message: 'Email sent successfully' }),
{ status: 200 }
);
} catch (e) {
Expand Down

0 comments on commit 404bcdb

Please sign in to comment.