2026 Version + better Admin Panel
Some checks failed
Deploy to Firebase Hosting on merge / build_and_deploy (push) Has been cancelled
Some checks failed
Deploy to Firebase Hosting on merge / build_and_deploy (push) Has been cancelled
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"projects": {
|
||||
"default": "skatabend0"
|
||||
"default": "skatabend-e7c88"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"hosting": {
|
||||
"site": "skatturnier",
|
||||
"public": "build",
|
||||
"ignore": [
|
||||
"firebase.json",
|
||||
@@ -11,7 +12,6 @@
|
||||
"source": "**",
|
||||
"destination": "/index.html"
|
||||
}
|
||||
],
|
||||
"site": "skatabend"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
content="Skatabend 2026"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
@@ -24,7 +24,7 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Skatabend 2025</title>
|
||||
<title>Skatabend 2026</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
233
src/Admin.css
Normal file
233
src/Admin.css
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
121
src/Admin.jsx
Normal file
121
src/Admin.jsx
Normal file
@@ -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 (
|
||||
<div className="admin-container">
|
||||
<div className="admin-header">
|
||||
<h2>Verwaltung Dashboard</h2>
|
||||
<button type='button' onClick={() => signOut(auth)} className="logout-btn">Logout</button>
|
||||
</div>
|
||||
|
||||
<div className="admin-stats">
|
||||
<div className="stat-box">
|
||||
<h3>Gesamtzahl Anmeldungen</h3>
|
||||
<p className="stat-number">{data.length}</p>
|
||||
</div>
|
||||
<div className="stat-box">
|
||||
<h3>Gesamtzahl Personen</h3>
|
||||
<p className="stat-number">{getTotalPeople()}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="admin-table-container">
|
||||
<h3>Anmeldungen</h3>
|
||||
<table className="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>E-Mail</th>
|
||||
<th>Personen</th>
|
||||
<th>Anzahl</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((entry) => (
|
||||
<tr key={entry.id}>
|
||||
<td>{entry.email}</td>
|
||||
<td>
|
||||
<div className="names-list">
|
||||
{entry.names && entry.names.map((name, index) => (
|
||||
<div key={index} className="name-item">
|
||||
<span>{name}</span>
|
||||
<button
|
||||
className="delete-person-btn"
|
||||
onClick={() => deletePerson(entry.id, index)}
|
||||
title="Person löschen"
|
||||
>
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
<td className="count-cell">{entry.names ? entry.names.length : 0}</td>
|
||||
<td className="actions-cell">
|
||||
<button
|
||||
className="delete-registration-btn"
|
||||
onClick={() => deleteRegistration(entry.id)}
|
||||
title="Gesamte Anmeldung löschen"
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
194
src/App.jsx
194
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,48 +38,75 @@ 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,
|
||||
})
|
||||
} catch (error) {
|
||||
alert("Fehler beim Senden der Anmeldung. Bitte versuche es später erneut.");
|
||||
console.error(error);
|
||||
}
|
||||
alert("Anmeldung gesendet.")
|
||||
setEmail("")
|
||||
setCustomNumber(false)
|
||||
setNumber(null)
|
||||
window.location.reload(true)
|
||||
} catch (error) {
|
||||
console.error("Firestore Error:", error);
|
||||
alert("Fehler beim Senden der Anmeldung: " + error.message);
|
||||
}
|
||||
|
||||
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);
|
||||
setLoginPassword("");
|
||||
} else {
|
||||
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("");
|
||||
}
|
||||
}
|
||||
|
||||
const createPersonFields = () => {
|
||||
return names.map((name, i) => (
|
||||
<div key={i} style={{ display: 'flex', gap: '10px', marginBottom: '10px', alignItems: 'center' }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={"Name " + (i+1) + ". Person"}
|
||||
value={name}
|
||||
onChange={(e) => {
|
||||
let prev = [...names]
|
||||
prev[i] = e.target.value
|
||||
setNames(prev)
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setNames(names.filter((_, index) => index !== i))
|
||||
}}
|
||||
style={{ background: '#ff4444', color: 'white', border: 'none', padding: '5px 10px', cursor: 'pointer', borderRadius: '4px' }}
|
||||
>
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -111,35 +124,9 @@ function App() {
|
||||
autoFocus
|
||||
/>
|
||||
<button type="submit">Login</button>
|
||||
<button type="button" onClick={hideManagement} style={{background:'#888',marginTop:'10px'}}>Abbrechen</button>
|
||||
<button type="button" onClick={() => setShowManagement(false)} style={{background:'#888',marginTop:'10px'}}>Abbrechen</button>
|
||||
</form>
|
||||
) : (
|
||||
<>
|
||||
<h2>Verwaltung</h2>
|
||||
<button onClick={hideManagement}>Abmelden & Zurück</button>
|
||||
{/* Gesamtanzahl Personen */}
|
||||
<div className="gesamtanzahl">
|
||||
Gesamtanzahl Personen: <b>{ getNumberOfUsers() }</b>
|
||||
</div>
|
||||
{/* Tabelle mit Dummy-Daten */}
|
||||
<table className="management-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>E-Mail</th>
|
||||
<th>Anzahl Personen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ data.map(user => (
|
||||
<tr key={user.id}>
|
||||
<td>{ user.email }</td>
|
||||
<td>{ user.number }</td>
|
||||
</tr>
|
||||
)) }
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
)}
|
||||
) : <Admin />}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -156,7 +143,23 @@ function App() {
|
||||
</div>
|
||||
<div className="fact-row">
|
||||
<span className="fact-label">Datum:</span>
|
||||
<span className="fact-value">25.06.2025</span>
|
||||
<span className="fact-value">Fr. 27.03.2026</span>
|
||||
</div>
|
||||
<div className="fact-row">
|
||||
<span className="fact-label">Eintrittspreis:</span>
|
||||
<span className="fact-value">2€</span>
|
||||
</div>
|
||||
<div className="fact-row">
|
||||
<span className="fact-label">Anmeldung spätestens bis:</span>
|
||||
<span className="fact-value">17:30 (online oder vor Ort)</span>
|
||||
</div>
|
||||
<br />
|
||||
<div className="fact-row">
|
||||
{/* <span className='fact-label'></span> */}
|
||||
<span className="fact-value">Ausschließlich für Schüler, Lehrer und Familienmitglieder.</span>
|
||||
</div>
|
||||
<div className="fact-row">
|
||||
<span className='fact-value'>Für Verpflegung ist gesorgt.</span>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={scrollToForm}>Jetzt Anmelden</button>
|
||||
@@ -166,35 +169,9 @@ function App() {
|
||||
<label>E-Mail:</label>
|
||||
<input type="email" name="email" placeholder="Deine E-Mail-Adresse" onChange={(e => setEmail(e.target.value))} value={email}/>
|
||||
<br />
|
||||
<label>Anzahl der Personen</label>
|
||||
<div className="radio-group">
|
||||
{ radioGroupEntries.map((entry) => <label key={entry} style={ number === entry && !customNumber ? { backgroundColor: "rgba(0, 128, 0, 0.7)" } : null }>
|
||||
<input
|
||||
type="radio"
|
||||
name="number"
|
||||
onChange={() => { setNumber(entry); setCustomNumber(false) }}
|
||||
/>
|
||||
{ entry } Person{ entry > 1 ? "en" : null }
|
||||
</label> ) }
|
||||
<label style={ customNumber ? { backgroundColor: "rgba(0, 128, 0, 0.7)" } : null }>
|
||||
<input
|
||||
type="radio"
|
||||
name="number"
|
||||
onChange={() => setCustomNumber(true)}
|
||||
/>
|
||||
Andere Nummer
|
||||
</label>
|
||||
</div>
|
||||
{customNumber ? (
|
||||
<input
|
||||
type="number"
|
||||
name="customNumber"
|
||||
placeholder="Anzahl der Personen"
|
||||
min={1}
|
||||
onChange={(e) => setNumber(parseInt(e.target.value))}
|
||||
value={number}
|
||||
/>
|
||||
) : null}
|
||||
<label>Namen</label>
|
||||
<button type='button' onClick={() => setNames([...names, ""]) }>Neue Person hinzufügen</button>
|
||||
{ createPersonFields() }
|
||||
<br />
|
||||
<button type="submit" onClick={submit}>Anmelden</button>
|
||||
</form>
|
||||
@@ -203,7 +180,8 @@ function App() {
|
||||
)}
|
||||
<footer>
|
||||
<p>
|
||||
<a onClick={displayManagement} style={{ marginRight: '20px', cursor: 'pointer' }}>Verwaltung öffnen</a>
|
||||
|
||||
<a onClick={() => setShowManagement(true)} style={{ marginRight: '20px', cursor: 'pointer' }}>Verwaltung öffnen</a>
|
||||
Bildquelle Hintergrund: <a href="https://store-images.s-microsoft.com/image/apps.42739.13510798887965433.cf25521d-d390-432c-8007-cbc0aa3ebe97.53ed75d0-4a6a-4c11-a3ae-fd528ef6444a?h=1280" target="_blank" rel="noopener noreferrer">hier klicken</a>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
@@ -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 }
|
||||
export { db, auth }
|
||||
Reference in New Issue
Block a user