Skip to content

Commit

Permalink
Merge pull request #85 from huskynz/dev
Browse files Browse the repository at this point in the history
Fix up the word counter on the form
  • Loading branch information
Husky-Devel authored Nov 7, 2024
2 parents d03092d + 065ba2e commit 0f3cddd
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 103 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

This site is built in AstroJS and is the new main site for HuskyNZ where I can share what I know and about who I am


## Built With

- [AstroJS](https://astro.build/)
Expand Down
5 changes: 1 addition & 4 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ Version 10 of HuskyNZ's main site are "Supported" this inculdes the docker conta

### Note on V6

Ver 6 is not fully supported but is still may be updated if needed.


Now why do we have this? Because why not

| Version | Supported |
| ------- | ------------------ |
| 10 | :white_check_mark: |
| 6 | See Note |
| < 3 | :x: |
| 6 and below | https://legacy.husky.nz |

## Reporting a Vulnerability
Send a email to github@husky.nz
Expand Down
2 changes: 1 addition & 1 deletion src/components/ContactForm.astro
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const turnsiteSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY;
data-theme="auto"
></div>
</div>

<button
type="submit"
class="w-full rounded-lg bg-black dark:bg-white text-white dark:text-black py-2 px-4 hover:opacity-75 blend transition-all"
Expand Down
194 changes: 99 additions & 95 deletions src/components/contactutils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,133 +27,137 @@ const nameMessages: Record<string, string> = {
'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');
}

export function initializeContactForm(turnsiteSiteKey: string): void {
const form = document.getElementById('contactForm') as HTMLFormElement;
const statusDiv = document.getElementById('formStatus') as HTMLDivElement;
const statusText = statusDiv.querySelector('p') as HTMLParagraphElement;

// Initialize form elements
const textarea = document.getElementById('message') as HTMLTextAreaElement;
const charCount = document.getElementById('charCount') as HTMLDivElement;
const emailInput = document.getElementById('email') as HTMLInputElement;
const nameInput = document.getElementById('name') as HTMLInputElement;
const emailMessage = document.getElementById('emailMessage') as HTMLDivElement;
const nameMessage = document.getElementById('nameMessage') as HTMLDivElement;

// Email check
emailInput?.addEventListener('input', () => {
const email = emailInput.value.toLowerCase();
const domain = email.split('@')[1];
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;

if (specificEmailMessages[email]) {
emailMessage.textContent = specificEmailMessages[email];
emailMessage.classList.remove('hidden');
emailMessage.classList.add('text-grey-500', 'dark:text-grey-400');
} else if (domain && domainMessages[domain]) {
emailMessage.textContent = domainMessages[domain];
emailMessage.classList.remove('hidden');
emailMessage.classList.add('text-grey-500', 'dark:text-grey-400');
// 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 {
emailMessage.classList.add('hidden');
charCount.innerHTML = `<span>${remaining} characters remaining</span>`;
}
});

// Name check
nameInput?.addEventListener('input', () => {
const name = nameInput.value.toLowerCase().trim();

if (name === '') {
nameMessage.classList.add('hidden');
} else if (nameMessages[name]) {
nameMessage.textContent = nameMessages[name];
nameMessage.classList.remove('hidden');
nameMessage.classList.add('text-grey-500', 'dark:text-grey-400');
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 {
nameMessage.classList.add('hidden');
charCount.classList.add('text-gray-500', 'dark:text-gray-400');
}
});

// Reset function
function resetEmailMessage(): void {
emailMessage.textContent = '';
emailMessage.classList.add('hidden');
nameMessage.textContent = '';
nameMessage.classList.add('hidden');
}

// Reset all form fields on page load
if (textarea && emailInput && nameInput) {
textarea.value = '';
emailInput.value = '';
nameInput.value = '';
resetEmailMessage();

if (charCount) {
charCount.textContent = '500 characters remaining';
charCount.className = 'text-sm text-gray-500 dark:text-gray-400 mt-1';
}
}
// Form submission
form.addEventListener('submit', async (e) => {
e.preventDefault();

const turnstileResponse = document.querySelector<HTMLInputElement>('[name="cf-turnstile-response"]')?.value;

if (!turnstileResponse) {
statusDiv.classList.remove('hidden');
statusText.textContent = 'Please complete the captcha';
statusText.className = 'text-sm text-red-600 dark:text-red-400';
export function initializeContactForm(turnsiteSiteKey: string): void {
const form = document.getElementById('contactForm') as HTMLFormElement;
const statusDiv = document.getElementById('formStatus') as HTMLDivElement;
const statusText = statusDiv.querySelector('p') as HTMLParagraphElement;
const textarea = document.getElementById('message') as HTMLTextAreaElement;
const charCount = document.getElementById('charCount') as HTMLElement;
const emailInput = document.querySelector('input[name="email"]') as HTMLInputElement;
const emailMessage = document.getElementById('emailMessage') as HTMLDivElement;
const nameInput = document.querySelector('input[name="name"]') as HTMLInputElement;

let turnstileWidget: string;

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

// 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 submitButton = form.querySelector('button[type="submit"]') as HTMLButtonElement;
const originalButtonText = submitButton.textContent;
submitButton.textContent = 'Sending...';
submitButton.disabled = true;
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 formData = new FormData(form);
const data = {
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
turnstileToken: turnstileResponse
};
// 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);
}
});

// Initialize Turnstile
turnstileWidget = (window as unknown as TurnstileWindow).turnstile.render(
'#turnstile-widget',
{
sitekey: turnsiteSiteKey,
theme: 'auto',
}
);

// Form submission
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);

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

const result = await response.json();

if (response.ok) {
statusDiv.classList.remove('hidden');
statusText.textContent = 'Email sent successfully, thank you I will get back to you shortly';
statusText.className = 'text-sm text-green-600 dark:text-green-400';
statusText.textContent = 'Message sent successfully!';
statusText.classList.add('text-green-500');
form.reset();
resetEmailMessage();
((window as unknown) as TurnstileWindow).turnstile.reset();
(window as unknown as TurnstileWindow).turnstile.reset(turnstileWidget);
} else {
throw new Error(result.error || 'Failed to send message');
throw new Error(result.message || 'Failed to send message');
}
} catch (error) {
statusDiv.classList.remove('hidden');
statusText.textContent = error instanceof Error ? error.message : 'Failed to send message';
statusText.className = 'text-sm text-red-600 dark:text-red-400';
} finally {
submitButton.textContent = originalButtonText;
submitButton.disabled = false;
statusText.textContent = error instanceof Error ? error.message : 'An error occurred';
statusText.classList.add('text-red-500');
}
});
}
7 changes: 4 additions & 3 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import PageLayout from "@layouts/PageLayout.astro"
import { SITE, SOCIALS } from "@consts"
import TwinklingStars from "@components/TwinklingStars.astro"
import MeteorShower from "@components/MeteorShower.astro"
// Renable with twitch embed at later point import Twitch from "@components/TwitchEmbed.astro"
// Renable with twitch embed at later point
import Twitch from "@components/TwitchEmbed.astro"
import GitHubActivity from "@components/GitHubActivity.astro"
import ContactForm from '@components/ContactForm.astro';
Expand Down Expand Up @@ -122,11 +123,11 @@ phClient.capture(
<TimelineSkills />
</section>
-->
<!-- Twitch Embed, Displays message when offline - Reanable later
<!-- Twitch Embed, Displays message when offline - Reanable later -->
<section id="contact" class="animate">
<Twitch />
</section>
-->

<!-- Contact Section -->
<section id="contact" class="animate scroll-mt-32">
<div>
Expand Down

0 comments on commit 0f3cddd

Please sign in to comment.