diff --git a/.env b/.env new file mode 100644 index 0000000..4307fdb --- /dev/null +++ b/.env @@ -0,0 +1 @@ +TM_API_KEY=0FLYFe9BnzGlk2OPHrWleCulNzHpWgtC \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3ec544c..40b878d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -node_modules/ -.env \ No newline at end of file +node_modules/ \ No newline at end of file diff --git a/index.html b/index.html index 755995f..2b9a63e 100644 --- a/index.html +++ b/index.html @@ -24,17 +24,37 @@ @@ -42,7 +62,7 @@
-
diff --git a/js/app.js b/js/app.js index 38e10cc..17c7c72 100644 --- a/js/app.js +++ b/js/app.js @@ -1,7 +1,112 @@ import { getEvents } from "./services/eventService.js"; import { renderEventList } from "./ui/eventList.js"; import { getFilters } from "./ui/filters.js"; +import { login, register } from "./auth.js"; +// ========================= +// STATE +// ========================= +let currentUser = null; +let currentPassword = null; + +window.currentUser = null; +window.currentPassword = null; + +// ========================= +// AUTH ELEMENTS +// ========================= +const loginBtn = document.querySelector("#login-btn"); +const registerBtn = document.querySelector("#register-btn"); + +// ========================= +// NAVIGATION ELEMENTS +// ========================= +const navEvents = document.querySelector("#nav-events"); +const navSaved = document.querySelector("#nav-my-events"); +const navInv = document.querySelector("#nav-invitations"); + +// ========================= +// SECTIONS +// ========================= +const searchSection = document.querySelector("#search-section"); +const eventsSection = document.querySelector("#events-section"); +const savedSection = document.querySelector("#saved-section"); +const invSection = document.querySelector("#invitations-section"); + +// ========================= +// AUTH UI +// ========================= +const authArea = document.querySelector("#auth-area"); +const userArea = document.querySelector("#user-area"); +const userNameEl = document.querySelector("#user-name"); +const logoutBtn = document.querySelector("#logout-btn"); + +// ========================= +// LOGIN +// ========================= +loginBtn.addEventListener("click", async () => { + + const username = document.querySelector("#username").value; + const password = document.querySelector("#password").value; + + const success = await login(username, password); + + if (success) { + currentUser = username; + currentPassword = password; + + window.currentUser = username; + window.currentPassword = password; + + // UI switch + authArea.classList.add("d-none"); + userArea.classList.remove("d-none"); + + userNameEl.textContent = `👤 ${username}`; + + navSaved.classList.remove("d-none"); + navInv.classList.remove("d-none"); + + } else { + alert("Login failed"); + } +}); + +// ========================= +// REGISTER +// ========================= +registerBtn.addEventListener("click", async () => { + + const username = document.querySelector("#username").value; + + const data = await register(username); + + alert(`User created. Password: ${data.password}`); +}); + +// ========================= +// LOGOUT +// ========================= +logoutBtn.addEventListener("click", () => { + + currentUser = null; + currentPassword = null; + + window.currentUser = null; + window.currentPassword = null; + + authArea.classList.remove("d-none"); + userArea.classList.add("d-none"); + + navSaved.classList.add("d-none"); + navInv.classList.add("d-none"); + + showSection("events"); +}); + +// ========================= +// SEARCH +// ========================= const button = document.querySelector("#load-events"); const container = document.querySelector("#event-list"); const cityInput = document.querySelector("#city-input"); @@ -30,6 +135,9 @@ async function handleSearch() { renderEventList(filteredEvents, container); } +// ========================= +// FILTERS +// ========================= function applyFilters(events, dateFrom, dateTo, category) { return events.filter(event => { @@ -43,4 +151,118 @@ function applyFilters(events, dateFrom, dateTo, category) { return matchDateFrom && matchDateTo && matchCategory; }); -} \ No newline at end of file +} + +// ========================= +// NAVIGATION LOGIC +// ========================= +navEvents.addEventListener("click", () => { + showSection("events"); +}); + +navSaved.addEventListener("click", () => { + showSection("saved"); + loadSavedEvents(); +}); + +navInv.addEventListener("click", () => { + showSection("invitations"); + loadInvitations(); +}); + +function showSection(section) { + + searchSection.classList.add("d-none"); + eventsSection.classList.add("d-none"); + savedSection.classList.add("d-none"); + invSection.classList.add("d-none"); + + if (section === "events") { + searchSection.classList.remove("d-none"); + eventsSection.classList.remove("d-none"); + } + + if (section === "saved") { + savedSection.classList.remove("d-none"); + } + + if (section === "invitations") { + invSection.classList.remove("d-none"); + } +} + +// ========================= +// SAVED EVENTS +// ========================= +function loadSavedEvents() { + + const saved = JSON.parse(localStorage.getItem("savedEvents") || "[]"); + const container = document.querySelector("#saved-list"); + + container.innerHTML = ""; + + if (saved.length === 0) { + container.innerHTML = "

No saved events yet.

"; + return; + } + + saved.forEach(event => { + const div = document.createElement("div"); + div.textContent = event.name; + container.appendChild(div); + }); +} + +// ========================= +// INVITATIONS +// ========================= +async function loadInvitations() { + + if (!currentUser) return; + + const res = await fetch("http://localhost:3000/api/invitation", { + headers: { + "X-Username": currentUser + } + }); + + const data = await res.json(); + const container = document.querySelector("#invitation-list"); + + container.innerHTML = ""; + + if (data.length === 0) { + container.innerHTML = "

No invitations.

"; + return; + } + + data.forEach(inv => { + + const div = document.createElement("div"); + + div.innerHTML = ` +

${inv.fromUser} invited you to ${inv.eventName}

+ + + `; + + container.appendChild(div); + }); +} + +// ========================= +// ACCEPT / DECLINE +// ========================= +window.accept = async (id) => { + await fetch(`http://localhost:3000/api/invitation/${id}/accept`, { + method: "POST" + }); + loadInvitations(); +}; + +window.decline = async (id) => { + await fetch(`http://localhost:3000/api/invitation/${id}/decline`, { + method: "POST" + }); + loadInvitations(); +}; \ No newline at end of file diff --git a/js/auth.js b/js/auth.js new file mode 100644 index 0000000..c7582ec --- /dev/null +++ b/js/auth.js @@ -0,0 +1,27 @@ +const BASE_URL = "http://localhost:3000/api"; + +export async function register(username) { + + const res = await fetch(`${BASE_URL}/user`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ username }) + }); + + return res.json(); +} + +export async function login(username, password) { + + const res = await fetch(`${BASE_URL}/user`, { + method: "GET", + headers: { + "X-Username": username, + "X-Password": password + } + }); + + return res.status === 201; +} \ No newline at end of file diff --git a/js/ui/eventCard.js b/js/ui/eventCard.js index 1e96673..c9f84fd 100644 --- a/js/ui/eventCard.js +++ b/js/ui/eventCard.js @@ -33,5 +33,82 @@ export function createEventCard(event) { article.append(title, date, venue); + // ========================= + // BUTTON CONTAINER + // ========================= + const buttonContainer = document.createElement("div"); + buttonContainer.className = "d-flex gap-2 mt-2"; + + // ========================= + // SAVE BUTTON + // ========================= + const saveBtn = document.createElement("button"); + saveBtn.textContent = "Save"; + saveBtn.className = "btn btn-outline-primary btn-sm"; + + saveBtn.addEventListener("click", () => { + + if (!window.currentUser) { + alert("Please login to save events"); + return; + } + + const saved = JSON.parse(localStorage.getItem("savedEvents") || "[]"); + + // prevent duplicates + if (saved.find(e => e.id === event.id)) { + alert("Event already saved"); + return; + } + + saved.push(event); + localStorage.setItem("savedEvents", JSON.stringify(saved)); + + alert("Event saved!"); + }); + + saveBtn.disabled = !window.currentUser; + + // ========================= + // INVITE BUTTON + // ========================= + const inviteBtn = document.createElement("button"); + inviteBtn.textContent = "Invite"; + inviteBtn.className = "btn btn-primary btn-sm"; + + inviteBtn.addEventListener("click", async () => { + + if (!window.currentUser) { + alert("Please login to invite users"); + return; + } + + const toUser = prompt("Enter username to invite:"); + if (!toUser) return; + + await fetch("http://localhost:3000/api/invitation", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Username": window.currentUser + }, + body: JSON.stringify({ + toUser, + eventId: event.id, + eventName: event.name + }) + }); + + alert("Invitation sent!"); + }); + + inviteBtn.disabled = !window.currentUser; + + // ========================= + // APPEND BUTTONS + // ========================= + buttonContainer.append(saveBtn, inviteBtn); + article.appendChild(buttonContainer); + return article; } \ No newline at end of file diff --git a/server/server.js b/server/server.js index 017c115..27c5743 100644 --- a/server/server.js +++ b/server/server.js @@ -10,7 +10,7 @@ app.use(cors()); app.use(express.json()); // ========================= -// ROOT (optional) +// ROOT // ========================= app.get("/", (req, res) => { res.send("Encore API is running"); @@ -121,6 +121,44 @@ app.post("/api/invitation/:id/decline", (req, res) => { res.json(inv); }); +let users = []; + +// CREATE USER +app.post("/api/user", (req, res) => { + + const { username } = req.body; + + if (!username) { + return res.status(400).json({ message: "Username required" }); + } + + if (users.find(u => u.username === username)) { + return res.status(400).json({ message: "User exists" }); + } + + const password = "1234"; + + const user = { username, password }; + users.push(user); + + res.json({ name: username, password }); +}); + +// LOGIN +app.get("/api/user", (req, res) => { + + const username = req.header("X-Username"); + const password = req.header("X-Password"); + + const user = users.find( + u => u.username === username && u.password === password + ); + + if (!user) return res.sendStatus(401); + + res.sendStatus(201); +}); + // ========================= // START SERVER // =========================