diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache index 3ed5e50..5eb33fc 100644 --- a/.firebase/hosting.YnVpbGQ.cache +++ b/.firebase/hosting.YnVpbGQ.cache @@ -1,13 +1,13 @@ -robots.txt,1747753185483,bfe106a3fb878dc83461c86818bf74fc1bdc7f28538ba613cd3e775516ce8b49 -manifest.json,1747753184548,ee04fb47e525c67d8424ffe9b4d8a8a24e434504478afca4e0ca602146836d4c -logo512.png,1747753185347,212b102aa09e51b3b3e06647e81f7801a61333e171f6582e8124379aabccb41d -logo192.png,1747753185281,79e2b749561016bc8af300ea19f48347ceed3cb1a54f48ae456172eca45e08f0 -index.html,1747847326960,849e0ace18b79b41ecd4cf058617c8fe30d0260e9f780e7d6c5d9b0f4a393a08 -favicon.ico,1747753183459,27edce7be5922cf0bef7d4136f69b5bfbdd5bf8c13c7b026f71187d41a00aa7d -asset-manifest.json,1747847326960,71f2b660bf82dcac89eca1a39967b8c1183022b8276ecf79af76141ae5267d1b -static/media/image.68b1c4e66e36b61c1dd0.png,1747847326963,4c693c0814e4164a776deb6b47cbd2d1a3efd933c8a20b56d4859a09448cabe6 -static/js/main.fa819294.js.map,1747847326966,828b1179d7eed41ab38293e4b1b99783e76289b31fa7e7189a6aef7f641e0c9b -static/js/main.fa819294.js.LICENSE.txt,1747847326961,a7ea19f57bfd4e3c0af924b53ff76690cd6095ac68e363fd14818efd5827d75f -static/js/main.fa819294.js,1747847326965,4275a7875fa16aff74b58f53c91d869b9c645341db38a509231d4ba471f68fbf -static/css/main.27c24027.css.map,1747847326965,033259ccb0f0ac7257a4783cdb717910d00da508d2342f0524eac6cce55fc742 -static/css/main.27c24027.css,1747847326965,e4823df6aa4a5d438454fbcff833647c6b1f5418513454474837484107f75b87 +robots.txt,1773230060143,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec +manifest.json,1773230060143,0958a5e0c831126100c8c2d06a6bbaa665a3900f21aaff4130238a6f5a113aa1 +logo512.png,1773230060142,7779210d56c1f3741e2e487799fe3092def4fa6ac450a60532b807c3a8971205 +logo192.png,1773230060142,76c449ccb9cd117c2f2338f091b18f7050f3210e249b2228f5c81b23f34377cd +index.html,1773230082934,bd194f2036618038faf2160756bebb04b2f1e83066a17bf2acc47ef40111a307 +favicon.ico,1773230060141,c599b7a91ab3627e3538125d9f40adc2d4bf949046984262670545dc7738af06 +asset-manifest.json,1773230082934,7f4cf7a8d279a367d5ef87ec1b7e011ef95f754fe965d6cc8ab7c83f516422b2 +static/media/image.68b1c4e66e36b61c1dd0.png,1773230082958,4c15c35a390623a3f0336b6b0e8f485a2021e5056859a01cf4d3ac2f0811dc1a +static/js/main.a4c2285e.js.map,1773230082958,6305b722062c4030817b783079dabc604bdeb4773e043d395b56426fc22eb2fc +static/js/main.a4c2285e.js.LICENSE.txt,1773230082958,0f20024d1acb99c151d60b35e6b4d4f4bc0306cc5146c2dc78faeeae7003b732 +static/js/main.a4c2285e.js,1773230082958,8e177703342bda82fe4a97629b6cd3c97a79ac0a0b5d7c173f780ab682e897f3 +static/css/main.6ccb1dfd.css.map,1773230082958,fb800f7cc089f3be092246a049b4646aa51b068dc5cd5e70916bc952f4d75d1e +static/css/main.6ccb1dfd.css,1773230082958,a1b343c74ba990507a2752e290e43a0857f2c68bbbdc1507a23e05e41a998def diff --git a/.firebaserc b/.firebaserc index 339cd1a..0ef83ad 100644 --- a/.firebaserc +++ b/.firebaserc @@ -1,5 +1,5 @@ { "projects": { - "default": "skatabend0" + "default": "skatabend-e7c88" } } diff --git a/firebase.json b/firebase.json index 622e0e8..1af6dac 100644 --- a/firebase.json +++ b/firebase.json @@ -1,5 +1,6 @@ { "hosting": { + "site": "skatturnier", "public": "build", "ignore": [ "firebase.json", @@ -11,7 +12,6 @@ "source": "**", "destination": "/index.html" } - ], - "site": "skatabend" + ] } } diff --git a/package-lock.json b/package-lock.json index 9bdeeea..67c8544 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6226,9 +6226,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", - "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", "funding": [ { "type": "opencollective", diff --git a/public/index.html b/public/index.html index 7673174..2c2e9e1 100644 --- a/public/index.html +++ b/public/index.html @@ -7,7 +7,7 @@ - Skatabend 2025 + Skatabend 2026 diff --git a/src/Admin.css b/src/Admin.css new file mode 100644 index 0000000..d7d329a --- /dev/null +++ b/src/Admin.css @@ -0,0 +1,233 @@ +.admin-container { + min-height: 100vh; + padding: 30px 20px; + background: rgba(0, 0, 0, 0.4); + color: white; +} + +.admin-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 40px; + border-bottom: 2px solid #61dafb; + padding-bottom: 20px; + max-width: 1200px; + margin-left: auto; + margin-right: auto; +} + +.admin-header h2 { + margin: 0; + font-size: 28px; + color: #ffffff; +} + +.logout-btn { + background-color: #dc3545; + color: white; + border: none; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s ease; +} + +.logout-btn:hover { + background-color: #c82333; +} + +.logout-btn:active { + background-color: #a71d2a; + transform: scale(0.98); +} + +.admin-stats { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + margin-bottom: 40px; + max-width: 1200px; + margin-left: auto; + margin-right: auto; +} + +.stat-box { + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(10px); + color: white; + padding: 30px; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + text-align: center; + border: 1px solid rgba(97, 218, 251, 0.3); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.stat-box:hover { + transform: translateY(-5px); + box-shadow: 0 8px 16px rgba(97, 218, 251, 0.2); + border-color: #61dafb; +} + +.stat-box h3 { + margin: 0 0 15px 0; + font-size: 16px; + opacity: 0.8; + text-transform: uppercase; + letter-spacing: 1px; +} + +.stat-number { + margin: 0; + font-size: 48px; + font-weight: bold; + color: #61dafb; +} + +.admin-table-container { + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(10px); + border-radius: 8px; + padding: 25px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + max-width: 1200px; + margin: 0 auto; + border: 1px solid rgba(97, 218, 251, 0.3); +} + +.admin-table-container h3 { + margin-top: 0; + margin-bottom: 20px; + color: #ffffff; + font-size: 1.3em; +} + +.admin-table { + width: 100%; + border-collapse: collapse; + font-size: 14px; +} + +.admin-table thead { + background-color: rgba(97, 218, 251, 0.1); + border-bottom: 2px solid #61dafb; +} + +.admin-table th { + padding: 12px; + text-align: left; + font-weight: 600; + color: #61dafb; +} + +.admin-table th:last-child { + text-align: center; +} + +.admin-table td { + padding: 12px; + border-bottom: 1px solid rgba(97, 218, 251, 0.1); + color: #f0f0f0; +} + +.admin-table tbody tr:hover { + background-color: rgba(97, 218, 251, 0.05); +} + +.count-cell { + font-weight: 600; + color: #61dafb; + text-align: center; +} + +.names-list { + display: flex; + flex-direction: column; + gap: 8px; + align-items: flex-start; +} + +.name-item { + display: flex; + align-items: center; + gap: 10px; + padding: 5px 10px; + background-color: rgba(97, 218, 251, 0.1); + border-radius: 4px; + border: 1px solid rgba(97, 218, 251, 0.2); +} + +.name-item span { + color: #f0f0f0; +} + +.delete-person-btn { + background: none; + border: none; + color: #dc3545; + cursor: pointer; + font-size: 16px; + padding: 0; + transition: transform 0.2s ease; + display: flex; + align-items: center; +} + +.delete-person-btn:hover { + transform: scale(1.2); +} + +.actions-cell { + text-align: center; +} + +.delete-registration-btn { + background-color: #dc3545; + color: white; + border: none; + padding: 8px 12px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.3s ease, transform 0.2s ease; +} + +.delete-registration-btn:hover { + background-color: #c82333; +} + +.delete-registration-btn:active { + background-color: #a71d2a; + transform: scale(0.95); +} + +@media (max-width: 768px) { + .admin-header { + flex-direction: column; + gap: 15px; + align-items: flex-start; + } + + .admin-stats { + grid-template-columns: 1fr; + } + + .admin-table { + font-size: 12px; + } + + .admin-table th, + .admin-table td { + padding: 8px; + } + + .stat-number { + font-size: 36px; + } + + .admin-header h2 { + font-size: 24px; + } +} diff --git a/src/Admin.jsx b/src/Admin.jsx new file mode 100644 index 0000000..e0e536f --- /dev/null +++ b/src/Admin.jsx @@ -0,0 +1,121 @@ +import { collection, onSnapshot, deleteDoc, doc, updateDoc } from 'firebase/firestore'; +import React, { useEffect, useState } from 'react' +import { auth, db } from './firebase'; +import { signOut } from 'firebase/auth'; +import './Admin.css'; + +export default function Admin() { + const [data, setData] = useState([]); + + useEffect(() => { + let unsub = onSnapshot(collection(db, "forms"), (snapshot) => { + let data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); + setData(data) + }) + return () => { + unsub(); + }; + }, []); + + const getTotalPeople = () => { + return data.reduce((sum, entry) => sum + (entry.names ? entry.names.length : 0), 0); + } + + const deleteRegistration = async (id) => { + if (window.confirm("Möchtest du diese Anmeldung wirklich löschen?")) { + try { + await deleteDoc(doc(db, "forms", id)); + alert("Anmeldung gelöscht."); + } catch (error) { + alert("Fehler beim Löschen: " + error.message); + console.error(error); + } + } + } + + const deletePerson = async (id, index) => { + if (window.confirm("Möchtest du diese Person wirklich löschen?")) { + try { + const entry = data.find(item => item.id === id); + if (entry && entry.names) { + const updatedNames = entry.names.filter((_, i) => i !== index); + await updateDoc(doc(db, "forms", id), { + names: updatedNames, + number: updatedNames.length + }); + alert("Person gelöscht."); + } + } catch (error) { + alert("Fehler beim Löschen: " + error.message); + console.error(error); + } + } + } + + return ( +
+
+

Verwaltung Dashboard

+ +
+ +
+
+

Gesamtzahl Anmeldungen

+

{data.length}

+
+
+

Gesamtzahl Personen

+

{getTotalPeople()}

+
+
+ +
+

Anmeldungen

+ + + + + + + + + + + {data.map((entry) => ( + + + + + + + ))} + +
E-MailPersonenAnzahlAktionen
{entry.email} +
+ {entry.names && entry.names.map((name, index) => ( +
+ {name} + +
+ ))} +
+
{entry.names ? entry.names.length : 0} + +
+
+
+ ) +} diff --git a/src/App.jsx b/src/App.jsx index 216d645..adbac42 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,41 +1,27 @@ import { useEffect, useRef, useState } from 'react'; import './App.css'; import { addDoc, collection, doc, getDoc, onSnapshot } from 'firebase/firestore'; -import { db } from './firebase'; +import { auth, db } from './firebase'; +import Admin from './Admin'; +import { onAuthStateChanged, signInWithEmailAndPassword } from 'firebase/auth'; function App() { - const radioGroupEntries = [1, 2, 3, 4] const formSectionRef = useRef(null); - const [customNumber, setCustomNumber] = useState(false); - const [number, setNumber] = useState(null) + const [names, setNames] = useState([""]); const [email, setEmail] = useState(""); const [showManagement, setShowManagement] = useState(false); - const [isLoggedIn, setIsLoggedIn] = useState(false); const [loginPassword, setLoginPassword] = useState(""); - const [data, setData] = useState([]); + const [isLoggedIn, setisLoggedIn] = useState(false); useEffect(() => { - let unsub = onSnapshot(collection(db, "forms"), (snapshot) => { - let data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); - for(let i = 0; i < data.length; i++){ - let element = data[i] - if(element.id === "3GQrSzfPaZHZWleu6sq7") data.splice(i, 1) - } - setData(data) + const unsub = onAuthStateChanged(auth, (user) => { + setisLoggedIn(user ? true : false) }) return () => { - unsub(); + unsub() }; }, []); - const getNumberOfUsers = () => { - let number = 0 - for(let user of data){ - number += user.number - } - return number; - } - const scrollToForm = () => { const formSection = formSectionRef.current; formSection.classList.add('visible', 'bounce'); // Füge Bounce-Klasse hinzu @@ -52,50 +38,77 @@ function App() { const submit = async (e) => { e.preventDefault(); // Verhindert das Standardverhalten des Formulars if (!emailIsValid(email)){ - alert("Bitte eine gültige E-Mailadresse eintrgen"); + alert("Bitte eine gültige E-Mailadresse eintragen"); return; } - if(!number){ - alert("Bitte angeben wie viele Personen mitkommen"); + if(names.length === 0){ + alert("Bitte mindestens eine Person eintragen.") return; } + for (let i = 0; i < names.length; i++) { + if(names[i] === ""){ + alert("Bitte einen Name für Person " + (i+1) + " eintragen") + return; + } + } // send data try { - addDoc(collection(db, "forms"), { - email: email, - number: number + await addDoc(collection(db, "forms"), { + email, + names, }) + alert("Anmeldung gesendet.") + window.location.reload(true) } catch (error) { - alert("Fehler beim Senden der Anmeldung. Bitte versuche es später erneut."); - console.error(error); + console.error("Firestore Error:", error); + alert("Fehler beim Senden der Anmeldung: " + error.message); } - alert("Anmeldung gesendet.") - setEmail("") - setCustomNumber(false) - setNumber(null) - } - - const displayManagement = () => { - setShowManagement(true); - } - const hideManagement = () => { - setShowManagement(false); - setIsLoggedIn(false); - setLoginPassword(""); } const handleLogin = async (e) => { e.preventDefault(); - let pw = await getDoc(doc(db, "forms", "3GQrSzfPaZHZWleu6sq7")) - let pw2 = pw.data() - if (loginPassword === pw2.pw) { - setIsLoggedIn(true); + try { + await signInWithEmailAndPassword(auth, "ex@luisk.de", loginPassword) + setLoginPassword("") + } catch (error) { + if (error.code === 'auth/wrong-password' || error.code === 'auth/invalid-credential') { + alert("Falsches Passwort!"); + } else if (error.code === 'auth/user-not-found') { + alert("Benutzer nicht gefunden!"); + } else { + alert("Fehler beim Login. Bitte versuchen Sie es später erneut."); + console.error(error); + } setLoginPassword(""); - } else { - alert("Falsches Passwort!"); } } + const createPersonFields = () => { + return names.map((name, i) => ( +
+ { + let prev = [...names] + prev[i] = e.target.value + setNames(prev) + }} + /> + +
+ )) + } + return (
{showManagement ? ( @@ -111,35 +124,9 @@ function App() { autoFocus /> - + - ) : ( - <> -

Verwaltung

- - {/* Gesamtanzahl Personen */} -
- Gesamtanzahl Personen: { getNumberOfUsers() } -
- {/* Tabelle mit Dummy-Daten */} - - - - - - - - - { data.map(user => ( - - - - - )) } - -
E-MailAnzahl Personen
{ user.email }{ user.number }
- - )} + ) : }
) : ( <> @@ -156,7 +143,23 @@ function App() {
Datum: - 25.06.2025 + Fr. 27.03.2026 +
+
+ Eintrittspreis: + 2€ +
+
+ Anmeldung spätestens bis: + 17:30 (online oder vor Ort) +
+
+
+ {/* */} + Ausschließlich für Schüler, Lehrer und Familienmitglieder. +
+
+ Für Verpflegung ist gesorgt.
@@ -166,35 +169,9 @@ function App() { setEmail(e.target.value))} value={email}/>
- -
- { radioGroupEntries.map((entry) => ) } - -
- {customNumber ? ( - setNumber(parseInt(e.target.value))} - value={number} - /> - ) : null} + + + { createPersonFields() }
@@ -203,7 +180,8 @@ function App() { )} diff --git a/src/firebase.js b/src/firebase.js index bddd94b..e7e1797 100644 --- a/src/firebase.js +++ b/src/firebase.js @@ -1,16 +1,18 @@ import { initializeApp } from "firebase/app"; import { getFirestore } from "firebase/firestore"; +import { getAuth } from "firebase/auth"; const firebaseConfig = { - apiKey: "AIzaSyAMimOrp73SW_pffgJ_MXeIjrXr7a-IIPE", - authDomain: "skatabend0.firebaseapp.com", - projectId: "skatabend0", - storageBucket: "skatabend0.firebasestorage.app", - messagingSenderId: "362332041327", - appId: "1:362332041327:web:87779c1bcb2a70f8c8dd9a" -} + apiKey: "AIzaSyB79cGYmKx_bpYFJ7H2AORfaksGsqZuFik", + authDomain: "skatabend-e7c88.firebaseapp.com", + projectId: "skatabend-e7c88", + storageBucket: "skatabend-e7c88.firebasestorage.app", + messagingSenderId: "931117483366", + appId: "1:931117483366:web:071a84ce381dd2701734a7" +}; -const app = initializeApp(firebaseConfig) +const app = initializeApp(firebaseConfig); const db = getFirestore(app) +const auth = getAuth() -export { db } \ No newline at end of file +export { db, auth } \ No newline at end of file