Secret Santa List Generator
Matches
- Kevin McCallister 🎅 is the Secret Santa of John McClane 🎁
- John McClane 🎅 is the Secret Santa of Clark Griswold 🎁
- Clark Griswold 🎅 is the Secret Santa of The Grinch 🎁
- The Grinch 🎅 is the Secret Santa of Marvin Merchants 🎁
- Marvin Merchants 🎅 is the Secret Santa of Kevin McCallister 🎁
Go back
<script lang="ts">
import type { ActionData, PageData } from './$types';
import { enhance } from '$app/forms';
import { flip } from 'svelte/animate';
import { slide, fade } from 'svelte/transition';
import { goto } from '$app/navigation';
export let data: PageData;
export let form: ActionData;
let name = form?.name ?? '';
let email = form?.email ?? '';
function prefill() {
name = data.fake.name;
email = data.fake.email;
}
</script>
<h1>Secret Santa List Generator</h1>
<p>
Use the form below to add Secret Santa participants to the list. Once you're done, <strong
>generate list</strong
> will match the participants.
</p>
<form
use:enhance={() => {
return async ({ update, result }) => {
if (result.type === 'redirect') {
goto(result.location, { invalidateAll: true, noScroll: true, keepFocus: true });
return;
}
await update();
};
}}
action="?/add"
method="post"
class="name-form"
>
<div class="input">
<label for="name">Name</label>
<input id="name" name="name" type="text" bind:value={name} />
</div>
<div class="input">
<label for="email">Email</label>
<input id="email" name="email" type="email" bind:value={email} />
</div>
<button type="button" on:click={prefill}>Pre-fill</button>
<button type="submit">Add to list</button>
<button formaction="?/reset" formnovalidate>Reset list</button>
</form>
{#if form?.error}
<p class="error" in:slide>{form?.error}</p>
{/if}
<a href="/day/14/match" class="button">Generate List</a>
<form
use:enhance={({ data: formData }) => {
let oldNames = data.names;
// optimistic UI: preemptively filter out deleted value
data.names = data.names.filter((n) => n.id.toString() !== formData.get('id'));
return async ({ update, result }) => {
if (result.type === 'failure') {
data.names = oldNames;
} else if (result.type === 'redirect') {
goto(result.location, { invalidateAll: true, noScroll: true });
return;
}
await update();
};
}}
action="?/delete"
method="post"
data-sveltekit-noscroll
>
<ul class="names">
{#each data.names as { name, email, id } (id)}
<li animate:flip transition:fade|local>
<span
><span class="name">{name}</span>
<span class="email">{email}</span></span
><button name="id" value={id}>Delete</button>
</li>
{/each}
</ul>
</form>
<style>
form {
width: 100%;
}
.name-form {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: end;
}
.name-form .input {
flex-grow: 5;
flex-basis: 250px;
}
input {
width: 100%;
}
.name-form button {
flex-grow: 1;
flex-basis: 120px;
}
.name-form button[type='submit'] {
flex-grow: 3;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
ul > * + * {
margin-top: 1rem;
}
li {
border: 3px solid var(--green-6);
padding: 0.5rem 1rem;
border-radius: var(--radius-round);
display: flex;
align-items: center;
justify-content: space-between;
}
.name {
font-weight: var(--font-weight-7);
}
.email {
color: var(--gray-6);
}
.error {
background-color: var(--red-7);
width: 100%;
color: var(--red-0);
padding: 0.5rem 1rem;
border-radius: var(--radius-2);
text-align: center;
}
button,
.button {
font-size: var(--font-size-2);
padding: 0.5rem 1rem;
color: white;
background: var(--color);
appearance: none;
border: none;
border-radius: var(--radius-2);
cursor: pointer;
text-decoration: none;
--color: var(--blue-8);
--hover: var(--blue-9);
}
button:hover,
.button:hover {
background-color: var(--hover);
}
.names button {
--color: var(--violet-8);
--hover: var(--violet-9);
}
.button {
--color: var(--red-8);
--hover: var(--red-9);
font-size: var(--font-size-3);
font-weight: 700;
}
</style>
<script lang="ts">
import type { PageData } from './$types';
import { invalidate } from '$app/navigation';
import { flip } from 'svelte/animate';
export let data: PageData;
</script>
<h1>Secret Santa List Generator</h1>
<h2>Matches</h2>
<ul>
{#each data.matches as { giver, receiver, id } (id)}
<li animate:flip>
<strong>{giver.name} 🎅</strong> is the Secret Santa of <strong>{receiver.name} 🎁</strong>
</li>
{/each}
</ul>
<form on:submit|preventDefault={() => invalidate(data.key)}>
<button>Shuffle 🎲</button>
</form>
<a href="/day/14">Go back</a>
<style>
ul {
width: 100%;
list-style: none;
margin: 0;
padding: 1rem;
border: 2px solid var(--gray-6);
border-radius: var(--radius-3);
}
ul > * + * {
margin-top: 0.5rem;
}
button,
a {
font-size: var(--font-size-2);
padding: 0.5rem 1rem;
color: white;
background: var(--color);
appearance: none;
border: none;
border-radius: var(--radius-2);
cursor: pointer;
text-decoration: none;
--color: var(--red-8);
--hover: var(--red-9);
}
button:hover {
background-color: var(--hover);
}
a {
--color: var(--green-8);
--hover: var(--green-9);
}
</style>
import { z } from 'zod';
import type { Cookies } from '@sveltejs/kit';
export function getNamesFromCookie(cookies: Cookies) {
const names = cookies.get('names');
if (names) {
try {
const parsed = Names.parse(JSON.parse(names));
return parsed;
} catch (e) {
console.error(e);
}
}
return [
{ name: 'Kevin McCallister', email: 'kevin@homealone.com', id: 1 },
{ name: 'John McClane', email: 'john@yippeekiyay.com', id: 2 },
{ name: 'Clark Griswold', email: 'clark@lastfamilyman.com', id: 3 },
{ name: 'The Grinch', email: 'grinch@mountcrumpit.com', id: 4 },
{ name: 'Marvin Merchants', email: 'marvin@stickybandits.com', id: 5 }
];
}
export const Name = z.object({
name: z.string().min(1),
email: z.string().email()
});
export const Names = z.array(
Name.extend({
id: z.number()
})
);
export function hasDuplicateNames(names: z.infer<typeof Names>) {
return new Set(names.map((n) => n.name)).size !== names.length;
}