Rewrite hero with email RSVP

This commit is contained in:
Gus Ruben 2025-07-28 20:03:18 -04:00
parent 9187c5d9e9
commit a55e66c05d
5 changed files with 162 additions and 57 deletions

View file

@ -1,4 +1,5 @@
AIRTABLE_API_KEY=your_airtable_api_key_here
AIRTABLE_BASE_ID=your_airtable_base_id_here
AIRTABLE_EMAILS_TABLE=your_airtable_emails_table_here
AIRTABLE_RSVPS_TABLE=your_airtable_rsvps_table_here
GEOCODER_API_KEY=your_geocoder_api_key_here

View file

@ -0,0 +1,107 @@
<script lang="ts">
let submitted = false;
let fadeOut = false;
function handleFormSubmit(event: Event) {
event.preventDefault();
const form = event.target as HTMLFormElement;
const emailInput = form.querySelector('input[name="email"]') as HTMLInputElement;
const email = emailInput.value;
fetch('/api/rsvp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email })
}).catch(error => {
console.warn('Failed to save email:', error);
});
submitted = true;
// Clear the input box while the green overlay is showing
emailInput.value = '';
setTimeout(() => {
fadeOut = true;
}, 1500);
// Reset to original position after fade out completes
setTimeout(() => {
submitted = false;
fadeOut = false;
}, 1500 + 500);
}
</script>
<style>
@keyframes slide-in {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
.animate-slide-in {
animation: slide-in 0.3s cubic-bezier(0, 0.55, 0.45, 1);
}
.animate-fade-out {
animation: fade-out 0.5s ease-out forwards;
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
</style>
<div class="mt-8 flex flex-col items-center gap-3 z-5 max-md:scale-90">
<div class="relative rounded-full overflow-hidden" style="padding: 2px 2px 5px 2px;">
<form on:submit={handleFormSubmit} class="rounded-full bg-white border-2 border-dark font-sans p-2 flex flex-row items-center gap-2 shadow-[0_3px_0_0_theme(colors.dark)] focus-within:border-pink focus-within:shadow-[0_3px_0_0_#E472AB] has-[button:active]:border-dark has-[button:active]:shadow-[0_3px_0_0_theme(colors.dark)] has-[button:focus]:border-dark has-[button:focus]:shadow-[0_3px_0_0_theme(colors.dark)]">
<input
type="email"
name="email"
placeholder="Enter email to RSVP"
class="w-80 px-3 py-1 text-dark focus:outline-none flex-1"
required
/>
<input type="hidden" name="mailingLists" value="cmd3c94kz0hvz0iwt7ps28cyd" />
<button type="submit" class="bg-light h-full px-5 py-[0.45rem] rounded-full border-b-2 border-[#B3866A] cursor-pointer hover:border-b-4 hover:transform active:border-b-0 active:transform active:translate-y-0.5 focus:outline-none transition-all duration-100 flex-shrink-0">
<img src="submit.svg" alt="Go">
</button>
</form>
<!-- Success overlay that slides in from left -->
{#if submitted}
<div class="absolute inset-0 -top-4 -bottom-4 bg-[#44DBC8] rounded-full flex items-center justify-center z-20 animate-slide-in {fadeOut ? 'animate-fade-out' : ''}">
<span class="text-white font-sans text-lg">RSVPed!</span>
</div>
{/if}
</div>
<a
href="https://forms.hackclub.com/daydream-stickers"
target="_blank"
class="w-max px-4 py-2 bg-pink border-b-2 border-b-pink-dark text-white rounded-full active:transform active:translate-y-0.5 transition-all duration-100 font-sans cursor-pointer mx-auto relative overflow-visible hover:shadow-[0_2px_0_0_theme(colors.pink.dark)] hover:-translate-y-[2px] active:border-transparent active:shadow-none active: mt-4 md:hidden"
>
Get free stickers
<img
src="button-clouds.svg"
alt=""
class="absolute bottom-0 left-1/2 -translate-x-1/2 w-auto object-contain pointer-events-none"
>
<img
src="rock-sticker.png"
alt=""
class="absolute bottom-2 right-3 translate-2/3 w-18 h-18 object-contain pointer-events-none"
style="transform: rotate(-15deg);"
>
</a>
</div>

View file

@ -0,0 +1,49 @@
import Airtable from 'airtable';
import { json } from '@sveltejs/kit';
import { AIRTABLE_API_KEY, AIRTABLE_BASE_ID, AIRTABLE_RSVPS_TABLE } from '$env/static/private';
if (!AIRTABLE_API_KEY || !AIRTABLE_BASE_ID) {
console.warn('Airtable environment variables not configured, email saving will be skipped');
}
const base = AIRTABLE_API_KEY && AIRTABLE_BASE_ID
? new Airtable({
apiKey: AIRTABLE_API_KEY
}).base(AIRTABLE_BASE_ID)
: null;
export async function POST({ request, getClientAddress }) {
try {
const { email } = await request.json();
if (!email) {
return json({ error: 'Email is required' }, { status: 400 });
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return json({ error: 'Invalid email format' }, { status: 400 });
}
// get IP address
const ip = request.headers.get('x-forwarded-for')?.split(',')[0] || getClientAddress();
if (base) {
await base(AIRTABLE_RSVPS_TABLE || 'participant_rsvps').create([
{
fields: {
email,
ip,
}
}
]);
}
return new Response(null, { status: 200 });
} catch (error) {
console.error('Error saving email to Airtable:', error);
return new Response(null, { status: 418 });
}
}

View file

@ -26,11 +26,10 @@ export async function POST({ request, getClientAddress }) {
}
// get IP address
const ip = getClientAddress();
const ip = request.headers.get('x-forwarded-for')?.split(',')[0] || getClientAddress();
let recordId = null;
if (base) {
const record = await base(AIRTABLE_EMAILS_TABLE || 'email_addresses').create([
await base(AIRTABLE_EMAILS_TABLE || 'email_addresses').create([
{
fields: {
email,
@ -38,7 +37,6 @@ export async function POST({ request, getClientAddress }) {
}
}
]);
recordId = record[0].id;
}
return new Response(null, { status: 200 });

View file

@ -2,6 +2,7 @@
import { onMount } from "svelte";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import ParticipantSignUp from "$lib/components/ParticipantSignUp.svelte";
/**
* This is the template site! Create a copy of this folder (src/routes/example)
@ -333,26 +334,7 @@ Mumbai`.split("\n")
showVideoPopup = false;
}
function handleFormSubmit(event: Event) {
event.preventDefault();
const form = event.target as HTMLFormElement;
const emailInput = form.querySelector('input[name="email"]') as HTMLInputElement;
const email = emailInput.value;
fetch('/api/submit-email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email })
}).catch(error => {
console.warn('Failed to save email:', error);
});
window.open(`https://forms.hackclub.com/daydream?email=${encodeURIComponent(email)}`, '_blank');
emailInput.value = '';
}
async function typeText(text: string) {
isTyping = true;
@ -797,39 +779,7 @@ Mumbai`.split("\n")
</h4>
</div>
<div class="mt-8 flex flex-col items-center gap-3 z-5 max-md:scale-90">
<form on:submit={handleFormSubmit} class="rounded-full bg-white border-2 border-dark font-sans p-2 flex flex-row items-center gap-2 shadow-[0_3px_0_0_theme(colors.dark)] focus-within:border-pink focus-within:shadow-[0_3px_0_0_#E472AB] has-[button:active]:border-dark has-[button:active]:shadow-[0_3px_0_0_theme(colors.dark)] has-[button:focus]:border-dark has-[button:focus]:shadow-[0_3px_0_0_theme(colors.dark)]">
<input
type="email"
name="email"
placeholder="Enter email to sign up"
class="w-80 px-3 py-1 text-dark focus:outline-none"
required
/>
<input type="hidden" name="mailingLists" value="cmd3c94kz0hvz0iwt7ps28cyd" />
<button type="submit" class="bg-light h-full px-5 rounded-full border-b-2 border-[#B3866A] cursor-pointer hover:border-b-4 hover:transform active:border-b-0 active:transform active:translate-y-0.5 focus:outline-none transition-all duration-100">
<img src="submit.svg" alt="Go">
</button>
</form>
<a
href="https://forms.hackclub.com/daydream-stickers"
target="_blank"
class="w-max px-4 py-2 bg-pink border-b-2 border-b-pink-dark text-white rounded-full active:transform active:translate-y-0.5 transition-all duration-100 font-sans cursor-pointer mx-auto relative overflow-visible hover:shadow-[0_2px_0_0_theme(colors.pink.dark)] hover:-translate-y-[2px] active:border-transparent active:shadow-none active: mt-4 md:hidden"
>
Get free stickers
<img
src="button-clouds.svg"
alt=""
class="absolute bottom-0 left-1/2 -translate-x-1/2 w-auto object-contain pointer-events-none"
>
<img
src="rock-sticker.png"
alt=""
class="absolute bottom-2 right-3 translate-2/3 w-18 h-18 object-contain pointer-events-none"
style="transform: rotate(-15deg);"
>
</a>
</div>
<ParticipantSignUp />
</div>
<!-- <img src="hot-air-balloon.png" alt="" class="absolute w-1/8 right-32 bottom-40 z-20"> -->