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
|
robots.txt,1773230060143,2544ca049f223a42bff01f72ad930a5edba75bbb7199d0f8430a02ff5aca16ec
|
||||||
manifest.json,1747753184548,ee04fb47e525c67d8424ffe9b4d8a8a24e434504478afca4e0ca602146836d4c
|
manifest.json,1773230060143,0958a5e0c831126100c8c2d06a6bbaa665a3900f21aaff4130238a6f5a113aa1
|
||||||
logo512.png,1747753185347,212b102aa09e51b3b3e06647e81f7801a61333e171f6582e8124379aabccb41d
|
logo512.png,1773230060142,7779210d56c1f3741e2e487799fe3092def4fa6ac450a60532b807c3a8971205
|
||||||
logo192.png,1747753185281,79e2b749561016bc8af300ea19f48347ceed3cb1a54f48ae456172eca45e08f0
|
logo192.png,1773230060142,76c449ccb9cd117c2f2338f091b18f7050f3210e249b2228f5c81b23f34377cd
|
||||||
index.html,1747847326960,849e0ace18b79b41ecd4cf058617c8fe30d0260e9f780e7d6c5d9b0f4a393a08
|
index.html,1773230082934,bd194f2036618038faf2160756bebb04b2f1e83066a17bf2acc47ef40111a307
|
||||||
favicon.ico,1747753183459,27edce7be5922cf0bef7d4136f69b5bfbdd5bf8c13c7b026f71187d41a00aa7d
|
favicon.ico,1773230060141,c599b7a91ab3627e3538125d9f40adc2d4bf949046984262670545dc7738af06
|
||||||
asset-manifest.json,1747847326960,71f2b660bf82dcac89eca1a39967b8c1183022b8276ecf79af76141ae5267d1b
|
asset-manifest.json,1773230082934,7f4cf7a8d279a367d5ef87ec1b7e011ef95f754fe965d6cc8ab7c83f516422b2
|
||||||
static/media/image.68b1c4e66e36b61c1dd0.png,1747847326963,4c693c0814e4164a776deb6b47cbd2d1a3efd933c8a20b56d4859a09448cabe6
|
static/media/image.68b1c4e66e36b61c1dd0.png,1773230082958,4c15c35a390623a3f0336b6b0e8f485a2021e5056859a01cf4d3ac2f0811dc1a
|
||||||
static/js/main.fa819294.js.map,1747847326966,828b1179d7eed41ab38293e4b1b99783e76289b31fa7e7189a6aef7f641e0c9b
|
static/js/main.a4c2285e.js.map,1773230082958,6305b722062c4030817b783079dabc604bdeb4773e043d395b56426fc22eb2fc
|
||||||
static/js/main.fa819294.js.LICENSE.txt,1747847326961,a7ea19f57bfd4e3c0af924b53ff76690cd6095ac68e363fd14818efd5827d75f
|
static/js/main.a4c2285e.js.LICENSE.txt,1773230082958,0f20024d1acb99c151d60b35e6b4d4f4bc0306cc5146c2dc78faeeae7003b732
|
||||||
static/js/main.fa819294.js,1747847326965,4275a7875fa16aff74b58f53c91d869b9c645341db38a509231d4ba471f68fbf
|
static/js/main.a4c2285e.js,1773230082958,8e177703342bda82fe4a97629b6cd3c97a79ac0a0b5d7c173f780ab682e897f3
|
||||||
static/css/main.27c24027.css.map,1747847326965,033259ccb0f0ac7257a4783cdb717910d00da508d2342f0524eac6cce55fc742
|
static/css/main.6ccb1dfd.css.map,1773230082958,fb800f7cc089f3be092246a049b4646aa51b068dc5cd5e70916bc952f4d75d1e
|
||||||
static/css/main.27c24027.css,1747847326965,e4823df6aa4a5d438454fbcff833647c6b1f5418513454474837484107f75b87
|
static/css/main.6ccb1dfd.css,1773230082958,a1b343c74ba990507a2752e290e43a0857f2c68bbbdc1507a23e05e41a998def
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"projects": {
|
"projects": {
|
||||||
"default": "skatabend0"
|
"default": "skatabend-e7c88"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"hosting": {
|
"hosting": {
|
||||||
|
"site": "skatturnier",
|
||||||
"public": "build",
|
"public": "build",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"firebase.json",
|
"firebase.json",
|
||||||
@@ -11,7 +12,6 @@
|
|||||||
"source": "**",
|
"source": "**",
|
||||||
"destination": "/index.html"
|
"destination": "/index.html"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"site": "skatabend"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -6226,9 +6226,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001718",
|
"version": "1.0.30001777",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
|
||||||
"integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
|
"integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="Skatabend 2026"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<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.
|
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`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>Skatabend 2025</title>
|
<title>Skatabend 2026</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<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 { useEffect, useRef, useState } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { addDoc, collection, doc, getDoc, onSnapshot } from 'firebase/firestore';
|
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() {
|
function App() {
|
||||||
const radioGroupEntries = [1, 2, 3, 4]
|
|
||||||
const formSectionRef = useRef(null);
|
const formSectionRef = useRef(null);
|
||||||
const [customNumber, setCustomNumber] = useState(false);
|
const [names, setNames] = useState([""]);
|
||||||
const [number, setNumber] = useState(null)
|
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [showManagement, setShowManagement] = useState(false);
|
const [showManagement, setShowManagement] = useState(false);
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
|
||||||
const [loginPassword, setLoginPassword] = useState("");
|
const [loginPassword, setLoginPassword] = useState("");
|
||||||
const [data, setData] = useState([]);
|
const [isLoggedIn, setisLoggedIn] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let unsub = onSnapshot(collection(db, "forms"), (snapshot) => {
|
const unsub = onAuthStateChanged(auth, (user) => {
|
||||||
let data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
|
setisLoggedIn(user ? true : false)
|
||||||
for(let i = 0; i < data.length; i++){
|
|
||||||
let element = data[i]
|
|
||||||
if(element.id === "3GQrSzfPaZHZWleu6sq7") data.splice(i, 1)
|
|
||||||
}
|
|
||||||
setData(data)
|
|
||||||
})
|
})
|
||||||
return () => {
|
return () => {
|
||||||
unsub();
|
unsub()
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getNumberOfUsers = () => {
|
|
||||||
let number = 0
|
|
||||||
for(let user of data){
|
|
||||||
number += user.number
|
|
||||||
}
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollToForm = () => {
|
const scrollToForm = () => {
|
||||||
const formSection = formSectionRef.current;
|
const formSection = formSectionRef.current;
|
||||||
formSection.classList.add('visible', 'bounce'); // Füge Bounce-Klasse hinzu
|
formSection.classList.add('visible', 'bounce'); // Füge Bounce-Klasse hinzu
|
||||||
@@ -52,48 +38,75 @@ function App() {
|
|||||||
const submit = async (e) => {
|
const submit = async (e) => {
|
||||||
e.preventDefault(); // Verhindert das Standardverhalten des Formulars
|
e.preventDefault(); // Verhindert das Standardverhalten des Formulars
|
||||||
if (!emailIsValid(email)){
|
if (!emailIsValid(email)){
|
||||||
alert("Bitte eine gültige E-Mailadresse eintrgen");
|
alert("Bitte eine gültige E-Mailadresse eintragen");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!number){
|
if(names.length === 0){
|
||||||
alert("Bitte angeben wie viele Personen mitkommen");
|
alert("Bitte mindestens eine Person eintragen.")
|
||||||
return;
|
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
|
// send data
|
||||||
try {
|
try {
|
||||||
addDoc(collection(db, "forms"), {
|
await addDoc(collection(db, "forms"), {
|
||||||
email: email,
|
email,
|
||||||
number: number
|
names,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
|
||||||
alert("Fehler beim Senden der Anmeldung. Bitte versuche es später erneut.");
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
alert("Anmeldung gesendet.")
|
alert("Anmeldung gesendet.")
|
||||||
setEmail("")
|
window.location.reload(true)
|
||||||
setCustomNumber(false)
|
} catch (error) {
|
||||||
setNumber(null)
|
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) => {
|
const handleLogin = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let pw = await getDoc(doc(db, "forms", "3GQrSzfPaZHZWleu6sq7"))
|
try {
|
||||||
let pw2 = pw.data()
|
await signInWithEmailAndPassword(auth, "ex@luisk.de", loginPassword)
|
||||||
if (loginPassword === pw2.pw) {
|
setLoginPassword("")
|
||||||
setIsLoggedIn(true);
|
} catch (error) {
|
||||||
setLoginPassword("");
|
if (error.code === 'auth/wrong-password' || error.code === 'auth/invalid-credential') {
|
||||||
} else {
|
|
||||||
alert("Falsches Passwort!");
|
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 (
|
return (
|
||||||
@@ -111,35 +124,9 @@ function App() {
|
|||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<button type="submit">Login</button>
|
<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>
|
</form>
|
||||||
) : (
|
) : <Admin />}
|
||||||
<>
|
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -156,7 +143,23 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="fact-row">
|
<div className="fact-row">
|
||||||
<span className="fact-label">Datum:</span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={scrollToForm}>Jetzt Anmelden</button>
|
<button onClick={scrollToForm}>Jetzt Anmelden</button>
|
||||||
@@ -166,35 +169,9 @@ function App() {
|
|||||||
<label>E-Mail:</label>
|
<label>E-Mail:</label>
|
||||||
<input type="email" name="email" placeholder="Deine E-Mail-Adresse" onChange={(e => setEmail(e.target.value))} value={email}/>
|
<input type="email" name="email" placeholder="Deine E-Mail-Adresse" onChange={(e => setEmail(e.target.value))} value={email}/>
|
||||||
<br />
|
<br />
|
||||||
<label>Anzahl der Personen</label>
|
<label>Namen</label>
|
||||||
<div className="radio-group">
|
<button type='button' onClick={() => setNames([...names, ""]) }>Neue Person hinzufügen</button>
|
||||||
{ radioGroupEntries.map((entry) => <label key={entry} style={ number === entry && !customNumber ? { backgroundColor: "rgba(0, 128, 0, 0.7)" } : null }>
|
{ createPersonFields() }
|
||||||
<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}
|
|
||||||
<br />
|
<br />
|
||||||
<button type="submit" onClick={submit}>Anmelden</button>
|
<button type="submit" onClick={submit}>Anmelden</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -203,7 +180,8 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
<footer>
|
<footer>
|
||||||
<p>
|
<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>
|
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>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { initializeApp } from "firebase/app";
|
import { initializeApp } from "firebase/app";
|
||||||
import { getFirestore } from "firebase/firestore";
|
import { getFirestore } from "firebase/firestore";
|
||||||
|
import { getAuth } from "firebase/auth";
|
||||||
|
|
||||||
const firebaseConfig = {
|
const firebaseConfig = {
|
||||||
apiKey: "AIzaSyAMimOrp73SW_pffgJ_MXeIjrXr7a-IIPE",
|
apiKey: "AIzaSyB79cGYmKx_bpYFJ7H2AORfaksGsqZuFik",
|
||||||
authDomain: "skatabend0.firebaseapp.com",
|
authDomain: "skatabend-e7c88.firebaseapp.com",
|
||||||
projectId: "skatabend0",
|
projectId: "skatabend-e7c88",
|
||||||
storageBucket: "skatabend0.firebasestorage.app",
|
storageBucket: "skatabend-e7c88.firebasestorage.app",
|
||||||
messagingSenderId: "362332041327",
|
messagingSenderId: "931117483366",
|
||||||
appId: "1:362332041327:web:87779c1bcb2a70f8c8dd9a"
|
appId: "1:931117483366:web:071a84ce381dd2701734a7"
|
||||||
}
|
};
|
||||||
|
|
||||||
const app = initializeApp(firebaseConfig)
|
const app = initializeApp(firebaseConfig);
|
||||||
const db = getFirestore(app)
|
const db = getFirestore(app)
|
||||||
|
const auth = getAuth()
|
||||||
|
|
||||||
export { db }
|
export { db, auth }
|
||||||
Reference in New Issue
Block a user