diff options
| author | Leo Goetz <dev@leogtz.de> | 2026-01-22 09:10:15 +0100 |
|---|---|---|
| committer | Leo Goetz <dev@leogtz.de> | 2026-01-22 09:10:15 +0100 |
| commit | 01c0f792484f8f52606eae0e58abe528acef3086 (patch) | |
| tree | e30894caf65b39aab1e050035f63450b5032123c /frontend/src/components | |
| parent | b6d422d33c3b647ab249a8cf3520bc986fa2c549 (diff) | |
Diffstat (limited to 'frontend/src/components')
| -rw-r--r-- | frontend/src/components/Events.ts (renamed from frontend/src/components/Events.js) | 72 | ||||
| -rw-r--r-- | frontend/src/components/Forms.js | 66 | ||||
| -rw-r--r-- | frontend/src/components/Forms.ts | 68 | ||||
| -rw-r--r-- | frontend/src/components/Header.js | 33 | ||||
| -rw-r--r-- | frontend/src/components/Header.ts | 40 | ||||
| -rw-r--r-- | frontend/src/components/Icons.ts (renamed from frontend/src/components/Icons.js) | 0 |
6 files changed, 156 insertions, 123 deletions
diff --git a/frontend/src/components/Events.js b/frontend/src/components/Events.ts index a84d291..a92cf83 100644 --- a/frontend/src/components/Events.js +++ b/frontend/src/components/Events.ts @@ -1,20 +1,39 @@ -import { Calendar } from './Icons.js'; +import { Calendar } from "./Icons"; const API_URL = import.meta.env.VITE_API_URL; -const loadEventsData = async () => { +interface Event { + id: number; + title: string; + description?: string; + date: Date; + host_id: number; + image_url?: string; + host: { + id: number; + name: string; + email: string; + }; + rsvps: { + id: number; + name: string; + email: string; + }[]; +} + +const loadEventsData = async (): Promise<Event[]> => { try { const response = await fetch(`${API_URL}/events`); return response.json(); } catch (e) { console.error(e); + return []; } -} - +}; -export const EventModal = (event) => { - const formId = `rsvp-form-${event.ID}`; - const modalId = `modal-event-${event.id}` +export const EventModal = (event: Event) => { + const formId = `rsvp-form-${event.id}`; + const modalId = `modal-event-${event.id}`; return `<dialog id="${modalId}"> <article> <header> @@ -49,10 +68,10 @@ export const EventModal = (event) => { </footer> </article> - </dialog>` -} + </dialog>`; +}; -export const EventCard = (e) => { +export const EventCard = (e: Event) => { const eventDate = new Date(e.date); const isPast = eventDate < new Date(); return ` @@ -65,41 +84,46 @@ export const EventCard = (e) => { <p>${Calendar} ${eventDate.toLocaleDateString()}</p> <p>Host: ${e.host?.name || `User ${e.host_id}`}</p> - ${e.description && `<p>${e.description}</p>`} + ${e.description ? `<p>${e.description}</p>` : ""} </main> <footer> <span> - ${e.rsvps?.length || 0} ${isPast ? 'went' : 'going'} + ${e.rsvps?.length || 0} ${isPast ? "went" : "going"} </span> - ${!isPast ? ` + ${ + !isPast + ? ` <button role="button" data-target="modal-event-${e.id}" class="toggle-modal" title="RSVP to ${e.title}" > RSVP - </button>`: ''} + </button>` + : "" + } </footer> ${EventModal(e)} </article> - ` -} + `; +}; -export const EventsSection = (title, events) => { +export const EventsSection = (title: string, events: Event[]) => { return ` <section class='events'> <h2>${title} events </h2> <div role = "group"> - ${events.map((e) => EventCard(e)).join('') || 'No events'} + ${events.map((e) => EventCard(e)).join("") || "No events"} </div> </section>`; -} +}; // IIFE to asynchronously load the Event data before exporting the component // https://developer.mozilla.org/en-US/docs/Glossary/IIFE export const Events = await (async () => { const all = await loadEventsData(); - const past = all.filter((e) => (new Date(e.date) < new Date())); - const upcoming = all.filter((e) => (new Date(e.date) > new Date())); + const past = all.filter((e) => new Date(e.date) < new Date()); + const upcoming = all.filter((e) => new Date(e.date) > new Date()); return ` - ${EventsSection('Upcoming', upcoming)} - ${EventsSection('Past', past)} -`})()
\ No newline at end of file + ${EventsSection("Upcoming", upcoming)} + ${EventsSection("Past", past)} +`; +})(); diff --git a/frontend/src/components/Forms.js b/frontend/src/components/Forms.js deleted file mode 100644 index 8e9160f..0000000 --- a/frontend/src/components/Forms.js +++ /dev/null @@ -1,66 +0,0 @@ -const API_URL = import.meta.env.VITE_API_URL; - -export const setupForm = (form) => { - if (!form) return; - - const eventId = form.id.replace('rsvp-form-', ''); - const url = `${API_URL}/events/${eventId}/rsvp`; - const btn = document.getElementById(`submit-${form.id}`); - - async function sendData(form) { - const formData = new FormData(form); - const encoded = new URLSearchParams(); - for (let [key, value] of formData.entries) { - encoded.append(key, value); - } - try { - const response = await fetch(url, { - method: "POST", - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: encoded, - }); - if (response.status === 201) { - form.dataset.submitted = 'success'; - if (btn) { - btn.textContent = "You're going!"; - btn.setAttribute('disabled', ''); - } - } else if (response.status === 200) { - form.dataset.submitted = 'duplicate'; - if (btn) { - btn.textContent = "You're already going!"; - btn.setAttribute('disabled', ''); - } - } - } catch (e) { - form.dataset.submitted = 'error'; - console.error(e); - } - } - - form.addEventListener("submit", (event) => { - event.preventDefault(); - sendData(form); - }); - - if (btn) { - const emailInput = form.querySelector('input.rsvp-email'); - emailInput.addEventListener('input', () => { - if (btn.textContent !== 'Submit RSVP') { - btn.removeAttribute('disabled'); - btn.textContent = 'Submit RSVP'; - } - }); - - } -}; - -export const setupForms = () => { - const rsvpForms = document.querySelectorAll('form'); - if (!rsvpForms) return; - for (let form of rsvpForms) { - setupForm(form); - } -};
\ No newline at end of file diff --git a/frontend/src/components/Forms.ts b/frontend/src/components/Forms.ts new file mode 100644 index 0000000..ff35065 --- /dev/null +++ b/frontend/src/components/Forms.ts @@ -0,0 +1,68 @@ +const API_URL = import.meta.env.VITE_API_URL; + +export const setupForm = (form: HTMLFormElement) => { + if (!form) return; + + const eventId = form.id.replace("rsvp-form-", ""); + const url = `${API_URL}/events/${eventId}/rsvp`; + const btn = document.getElementById(`submit-${form.id}`); + + async function sendData(form: HTMLFormElement) { + const formData = new FormData(form); + const encoded = new URLSearchParams(); + for (let [key, value] of formData.entries()) { + encoded.append(key, value.toString()); + } + try { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: encoded, + }); + if (response.status === 201) { + form.dataset.submitted = "success"; + if (btn) { + btn.textContent = "You're going!"; + btn.setAttribute("disabled", ""); + } + } else if (response.status === 200) { + form.dataset.submitted = "duplicate"; + if (btn) { + btn.textContent = "You're already going!"; + btn.setAttribute("disabled", ""); + } + } + } catch (e) { + form.dataset.submitted = "error"; + console.error(e); + } + } + + form.addEventListener("submit", (event) => { + event.preventDefault(); + sendData(form); + }); + + if (btn) { + const emailInput = form.querySelector("input.rsvp-email"); + if (emailInput) { + emailInput.addEventListener("input", () => { + if (btn.textContent !== "Submit RSVP") { + btn.removeAttribute("disabled"); + btn.textContent = "Submit RSVP"; + } + }); + } + } +}; + +export const setupForms = () => { + const rsvpForms = document.querySelectorAll("form"); + if (!rsvpForms) return; + for (let form of rsvpForms) { + setupForm(form); + } +}; + diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js deleted file mode 100644 index f28f8e5..0000000 --- a/frontend/src/components/Header.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Theme as ThemeIcon } from './Icons'; - -const themeToggleId = 'theme'; - -const Header = ` -<header> - <hgroup> - <h1 class=".parkinsans">event me</h1> - <p>All the events you never knew you needed to attend!</p> - </hgroup> - <a href="#" role="toggle" id="${themeToggleId}" title="Toggle color scheme" > - ${ThemeIcon} - </a> -</header> -`; - -const toggleDarkMode = () => { - const doc = document.documentElement; - const currentTheme = doc.getAttribute('data-theme'); - if (currentTheme === 'dark') { - doc.setAttribute('data-theme', 'lite'); - } else if (currentTheme === 'light') { - doc.setAttribute('data-theme', 'dark'); - } -} -export function setupThemeToggle() { - const themeToggle = document.getElementById(themeToggleId); - themeToggle.addEventListener('click', toggleDarkMode); - -} - - -export default Header diff --git a/frontend/src/components/Header.ts b/frontend/src/components/Header.ts new file mode 100644 index 0000000..11da46f --- /dev/null +++ b/frontend/src/components/Header.ts @@ -0,0 +1,40 @@ +import { Theme as ThemeIcon } from "./Icons"; + +const themeToggleId = "theme"; + +const Header = ` +<header> + <hgroup> + <h1 class=".parkinsans">event me</h1> + <p>All the events you never knew you needed to attend!</p> + </hgroup> + <a href="#" role="toggle" id="${themeToggleId}" title="Toggle color scheme" > + ${ThemeIcon} + </a> +</header> +`; + +type Theme = "dark" | "light"; + +const toggleDarkMode = () => { + const doc = document.documentElement; + const currentTheme = doc.getAttribute("data-theme"); + let newTheme: Theme; + if (currentTheme === "dark") { + newTheme = "light"; + doc.setAttribute("data-theme", newTheme); + } else if (currentTheme === "light") { + newTheme = "dark"; + doc.setAttribute("data-theme", newTheme); + } +}; +export function setupThemeToggle() { + const themeToggle = document.getElementById(themeToggleId); + if (!themeToggle) { + console.error("Missing Toggle"); + return; + } + themeToggle.addEventListener("click", toggleDarkMode); +} + +export default Header; diff --git a/frontend/src/components/Icons.js b/frontend/src/components/Icons.ts index c2eb993..c2eb993 100644 --- a/frontend/src/components/Icons.js +++ b/frontend/src/components/Icons.ts |
