summaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/components/Events.ts (renamed from frontend/src/components/Events.js)72
-rw-r--r--frontend/src/components/Forms.js66
-rw-r--r--frontend/src/components/Forms.ts68
-rw-r--r--frontend/src/components/Header.js33
-rw-r--r--frontend/src/components/Header.ts40
-rw-r--r--frontend/src/components/Icons.ts (renamed from frontend/src/components/Icons.js)0
-rw-r--r--frontend/src/main.js21
7 files changed, 164 insertions, 136 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
diff --git a/frontend/src/main.js b/frontend/src/main.js
index cf91fa0..7e9d7c6 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -1,26 +1,21 @@
-import './style.css'
-import Header, { setupThemeToggle } from './components/Header';
-import Main from './components/Main';
-import Footer from './components/Footer';
-import { setupModals } from './components/Modal';
-import { setupForms } from './components/Forms';
-
+import "./style.css";
+import Header, { setupThemeToggle } from "./components/Header";
+import Main from "./components/Main";
+import Footer from "./components/Footer";
+import { setupModals } from "./components/Modal";
+import { setupForms } from "./components/Forms";
// Quick and dirty - not for production!
const render = (html) => {
- const app = document.querySelector('#app');
+ const app = document.querySelector("#app");
app.innerHTML = html;
setupThemeToggle();
setupModals();
setupForms();
-}
-
+};
render(`
${Header}
${Main}
${Footer}
`);
-
-
-