diff --git a/public/img/icon-192.png b/public/img/icon-192.png new file mode 100644 index 0000000..bd12ea3 Binary files /dev/null and b/public/img/icon-192.png differ diff --git a/public/img/icon-512.png b/public/img/icon-512.png new file mode 100644 index 0000000..2216b19 Binary files /dev/null and b/public/img/icon-512.png differ diff --git a/public/index.html b/public/index.html index 35cfe28..c5af9ed 100644 --- a/public/index.html +++ b/public/index.html @@ -5,14 +5,17 @@ + + + Wer zahlt? -
+

Who pays?

@@ -35,12 +38,29 @@
- + +
+ + + +
+ + + @@ -53,4 +73,4 @@ - + \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json index c5aa5e0..36706a5 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,20 +1,23 @@ { - "name": "WhoPays", + "name": "Who´s gonna pay this time?", "short_name": "WhoPays", "start_url": "/", "display": "standalone", - "background_color": "#ffffff", - "theme_color": "#2c3e50", + "background_color": "#fef3c7", + "theme_color": "#ec4899", + "orientation": "portrait", "icons": [ { - "src": "/icons/icon-192.png", + "src": "/img/icon-192.png", "sizes": "192x192", - "type": "image/png" + "type": "image/png", + "purpose": "any maskable" }, { - "src": "/icons/icon-512.png", + "src": "/img/icon-512.png", "sizes": "512x512", - "type": "image/png" + "type": "image/png", + "purpose": "any maskable" } ] } \ No newline at end of file diff --git a/public/script.js b/public/script.js index d760d52..731560e 100644 --- a/public/script.js +++ b/public/script.js @@ -1,11 +1,20 @@ const img = document.getElementById("personImg"); const nameEl = document.getElementById("personName"); const button = document.getElementById("toggleButton"); +const reasonInput = document.getElementById("reasonInput"); +const lastReasonBox = document.getElementById("lastReasonBox"); +const lastReasonText = document.getElementById("lastReasonText"); async function loadStatus() { const res = await fetch("/status"); const data = await res.json(); showStatus(data.current, false); + + // Show last reason if available + if (data.lastReason) { + lastReasonText.textContent = data.lastReason; + lastReasonBox.classList.remove("hidden"); + } } function showStatus(person, animate = true) { @@ -40,9 +49,34 @@ function showStatus(person, animate = true) { button.addEventListener("click", async () => { - const res = await fetch("/toggle", { method: "POST" }); + const reason = reasonInput.value.trim(); + + const res = await fetch("/toggle", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ reason: reason || undefined }) + }); + const data = await res.json(); showStatus(data.current); + + // Update last reason display + if (reason) { + lastReasonText.textContent = reason; + lastReasonBox.classList.remove("hidden"); + } + + // Clear input + reasonInput.value = ""; }); -loadStatus(); +// Allow Enter key to trigger switch +reasonInput.addEventListener("keypress", (e) => { + if (e.key === "Enter") { + button.click(); + } +}); + +loadStatus(); \ No newline at end of file diff --git a/public/serviceworker.js b/public/serviceworker.js index f882b39..310d5f8 100644 --- a/public/serviceworker.js +++ b/public/serviceworker.js @@ -1,23 +1,76 @@ -const CACHE_NAME = "whopays-cache-v1"; -const URLS_TO_CACHE = [ +const CACHE_NAME = "whopays-v1"; +const urlsToCache = [ "/", "/index.html", "/style.css", - "/script.js" + "/script.js", + "/img/a.png", + "/img/b.png", + "/img/bgA.jpg", + "/img/bgB.jpg", + "/manifest.json" ]; -self.addEventListener("install", event => { +// Install event - cache assets +self.addEventListener("install", (event) => { event.waitUntil( - caches.open(CACHE_NAME).then(cache => { - return cache.addAll(URLS_TO_CACHE); + caches.open(CACHE_NAME).then((cache) => { + console.log("Cache geöffnet"); + return cache.addAll(urlsToCache).catch(err => { + console.error("Cache fehler:", err); + }); }) ); + self.skipWaiting(); }); -self.addEventListener("fetch", event => { +// Activate event - clean old caches +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => { + if (cacheName !== CACHE_NAME) { + console.log("Alte Cache löschen:", cacheName); + return caches.delete(cacheName); + } + }) + ); + }) + ); + self.clients.claim(); +}); + +// Fetch event - serve from cache, fallback to network +self.addEventListener("fetch", (event) => { + // Network first for API calls + if (event.request.url.includes("/status") || + event.request.url.includes("/toggle")) { + event.respondWith( + fetch(event.request).catch(() => { + return new Response( + JSON.stringify({ error: "Offline" }), + { headers: { "Content-Type": "application/json" } } + ); + }) + ); + return; + } + + // Cache first for static assets event.respondWith( - caches.match(event.request).then(response => { - return response || fetch(event.request); + caches.match(event.request).then((response) => { + return response || fetch(event.request).then((fetchResponse) => { + return caches.open(CACHE_NAME).then((cache) => { + cache.put(event.request, fetchResponse.clone()); + return fetchResponse; + }); + }); + }).catch(() => { + // Fallback for navigation requests + if (event.request.mode === 'navigate') { + return caches.match('/index.html'); + } }) ); }); \ No newline at end of file diff --git a/server.js b/server.js index cb437b5..c2e3836 100644 --- a/server.js +++ b/server.js @@ -45,7 +45,7 @@ function getState() { if (!fs.existsSync(stateFile)) { fs.writeFileSync( stateFile, - JSON.stringify({ current: "A" }, null, 2) + JSON.stringify({ current: "A", lastReason: null }, null, 2) ); } return JSON.parse(fs.readFileSync(stateFile, "utf-8")); @@ -58,12 +58,34 @@ function setState(newState) { // -------------------- // Discord Webhook // -------------------- -async function sendDiscordEmbed(previousKey, currentKey) { +async function sendDiscordEmbed(previousKey, currentKey, reason) { const previous = CONFIG[previousKey]; const current = CONFIG[currentKey]; if (!DISCORD_WEBHOOK_URL) return; + const fields = [ + { + name: "Von", + value: previous.name, + inline: true + }, + { + name: "Zu", + value: current.name, + inline: true + } + ]; + + // Add reason field if provided + if (reason) { + fields.push({ + name: "📝 Grund", + value: reason, + inline: false + }); + } + const embed = { title: "💸 Zahl-Wechsel", description: `**${current.name}** ist jetzt dran zu zahlen`, @@ -71,18 +93,7 @@ async function sendDiscordEmbed(previousKey, currentKey) { thumbnail: { url: `${PUBLIC_BASE_URL}/img/${current.icon}` }, - fields: [ - { - name: "Von", - value: previous.name, - inline: true - }, - { - name: "Zu", - value: current.name, - inline: true - } - ], + fields: fields, footer: { text: new Date().toLocaleString("de-DE") } @@ -96,8 +107,9 @@ async function sendDiscordEmbed(previousKey, currentKey) { embeds: [embed] }) }); + console.log("✅ Discord Benachrichtigung gesendet"); } catch (err) { - console.error("Discord Webhook Fehler:", err.message); + console.error("❌ Discord Webhook Fehler:", err.message); } } @@ -115,19 +127,24 @@ app.get("/status", (req, res) => { app.post("/toggle", async (req, res) => { const state = getState(); const previousKey = state.current; + const reason = req.body?.reason; state.current = state.current === "A" ? "B" : "A"; + state.lastReason = reason || null; setState(state); - console.log( - `Zahl-Wechsel: ${CONFIG[previousKey].name} → ${CONFIG[state.current].name}` - ); + const logMessage = reason + ? `Zahl-Wechsel: ${CONFIG[previousKey].name} → ${CONFIG[state.current].name} (Grund: ${reason})` + : `Zahl-Wechsel: ${CONFIG[previousKey].name} → ${CONFIG[state.current].name}`; + + console.log(logMessage); - await sendDiscordEmbed(previousKey, state.current); + await sendDiscordEmbed(previousKey, state.current, reason); res.json({ current: state.current, - name: CONFIG[state.current].name + name: CONFIG[state.current].name, + lastReason: state.lastReason }); }); diff --git a/state.json b/state.json index 75c2092..4289e4e 100644 --- a/state.json +++ b/state.json @@ -1,3 +1,3 @@ { - "current": "A" + "current": "B" } \ No newline at end of file