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
+
+
+
+ | E-Mail |
+ Personen |
+ Anzahl |
+ Aktionen |
+
+
+
+ {data.map((entry) => (
+
+ | {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 */}
-
-
-
- | E-Mail |
- Anzahl Personen |
-
-
-
- { data.map(user => (
-
- | { 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