Prepare upload package and DB sample config

This commit is contained in:
Kay Türtscher 2026-04-14 11:48:17 +02:00
parent e2b3ddcc60
commit 1f083fee3d
55 changed files with 665 additions and 6 deletions

11
Db+Conf/README.txt Normal file
View file

@ -0,0 +1,11 @@
Db+Conf Inhalt
- schema.sql: in MySQL/MariaDB importieren
- config.sample.php: Beispiel fuer api/config.php mit Platzhaltern
Empfohlener Ablauf:
1. Datenbank und User beim Hoster anlegen
2. schema.sql importieren
3. config.sample.php nach api/config.php auf dem Zielhost kopieren
4. Zugangsdaten und Kalender-Passwoerter anpassen
5. Anwendung testen

10
Db+Conf/config.sample.php Normal file
View file

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
const DB_HOST = 'localhost';
const DB_PORT = 3306;
const DB_NAME = 'deine_datenbank';
const DB_USER = 'dein_user';
const DB_PASSWORD = 'dein_passwort';
const SESSION_COOKIE_NAME = 'esv_bludenz_calendar_session';

24
Db+Conf/schema.sql Normal file
View file

@ -0,0 +1,24 @@
CREATE TABLE IF NOT EXISTS calendars (
id INT AUTO_INCREMENT PRIMARY KEY,
slug VARCHAR(100) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
description TEXT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS events (
id INT AUTO_INCREMENT PRIMARY KEY,
calendar_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT NULL,
location VARCHAR(255) NULL,
start_at DATETIME NOT NULL,
end_at DATETIME NOT NULL,
color VARCHAR(20) DEFAULT '#1a73e8',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_events_calendar FOREIGN KEY (calendar_id) REFERENCES calendars(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT IGNORE INTO calendars (slug, name, description) VALUES
('dittes', 'Buchungskalender Dittes Hütte', ''),
('kegeln', 'Buchungskalender ESV-Bludenz Sektion Kegeln', '');

11
Upload/README_UPLOAD.txt Normal file
View file

@ -0,0 +1,11 @@
Upload-Inhalt für den Zielhost
Hochladen:
- gesamter Inhalt dieses Upload-Ordners in das Webroot / Zielverzeichnis
- api/config.sample.php nach api/config.php kopieren und echte Zugangsdaten eintragen
- schema.sql einmal manuell in MySQL/MariaDB importieren
Wichtig:
- Die Datenbank wird aktuell NICHT automatisch beim ersten Start befüllt.
- Voraussetzung ist, dass die Tabellen aus api/schema.sql bereits existieren.
- Danach funktioniert die Anwendung, sofern PHP, PDO MySQL und Sessions beim Hoster verfügbar sind.

View file

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
const DB_HOST = 'localhost';
const DB_PORT = 3306;
const DB_NAME = 'deine_datenbank';
const DB_USER = 'dein_user';
const DB_PASSWORD = 'dein_passwort';
const SESSION_COOKIE_NAME = 'esv_bludenz_calendar_session';

View file

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
const DB_HOST = 'localhost';
const DB_PORT = 3306;
const DB_NAME = 'deine_datenbank';
const DB_USER = 'dein_user';
const DB_PASSWORD = 'dein_passwort';
const SESSION_COOKIE_NAME = 'esv_bludenz_calendar_session';

68
Upload/api/db.php Normal file
View file

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/config.php';
function calendar_db(): PDO
{
static $pdo = null;
if ($pdo instanceof PDO) {
return $pdo;
}
assert_db_config_present();
$dsn = sprintf('mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4', db_host(), db_port(), db_name());
$pdo = new PDO($dsn, db_user(), db_password(), [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
initialize_calendar_db($pdo);
return $pdo;
}
function initialize_calendar_db(PDO $pdo): void
{
$pdo->exec('CREATE TABLE IF NOT EXISTS calendars (
id INT AUTO_INCREMENT PRIMARY KEY,
slug VARCHAR(100) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
description TEXT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
$pdo->exec('CREATE TABLE IF NOT EXISTS events (
id INT AUTO_INCREMENT PRIMARY KEY,
calendar_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT NULL,
location VARCHAR(255) NULL,
start_at DATETIME NOT NULL,
end_at DATETIME NOT NULL,
color VARCHAR(20) DEFAULT "#1a73e8",
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_events_calendar FOREIGN KEY (calendar_id) REFERENCES calendars(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
$seedCalendars = [
['slug' => 'dittes', 'name' => 'Buchungskalender Dittes Hütte', 'description' => ''],
['slug' => 'kegeln', 'name' => 'Buchungskalender ESV-Bludenz Sektion Kegeln', 'description' => ''],
];
$insertCalendar = $pdo->prepare('INSERT IGNORE INTO calendars (slug, name, description) VALUES (:slug, :name, :description)');
foreach ($seedCalendars as $calendar) {
$insertCalendar->execute($calendar);
}
}
function find_calendar_by_slug(PDO $pdo, string $slug): ?array
{
$stmt = $pdo->prepare('SELECT * FROM calendars WHERE slug = :slug LIMIT 1');
$stmt->execute(['slug' => $slug]);
$row = $stmt->fetch();
return $row ?: null;
}

View file

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/helpers.php';
$body = read_json_body();
$id = (int)($body['id'] ?? ($_GET['id'] ?? 0));
if ($id <= 0) {
json_response(['error' => 'Termin fehlt'], 400);
}
$pdo = calendar_db();
$existing = find_event_by_id($pdo, $id);
if (!$existing) {
json_response(['error' => 'Termin nicht gefunden'], 404);
}
require_admin_for_slug($existing['calendar_slug']);
$stmt = $pdo->prepare('DELETE FROM events WHERE id = :id');
$stmt->execute(['id' => $id]);
json_response(['ok' => true]);

34
Upload/api/events.php Normal file
View file

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/db.php';
$pdo = calendar_db();
$slug = $_GET['calendar'] ?? '';
if ($slug === '') {
json_response(['error' => 'Kalender fehlt'], 400);
}
$calendar = find_calendar_by_slug($pdo, $slug);
if (!$calendar) {
json_response(['error' => 'Kalender nicht gefunden'], 404);
}
$stmt = $pdo->prepare('SELECT id, title, description, location, start_at AS start, end_at AS end, color
FROM events
WHERE calendar_id = :calendar_id
ORDER BY start_at ASC');
$stmt->execute(['calendar_id' => $calendar['id']]);
$events = $stmt->fetchAll();
json_response([
'calendar' => [
'slug' => $calendar['slug'],
'name' => $calendar['name'],
'description' => $calendar['description'],
],
'events' => $events,
'isAdmin' => current_admin_calendar_slug() === $calendar['slug'],
]);

37
Upload/api/helpers.php Normal file
View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/db.php';
function validate_event_payload(array $body): ?string
{
$title = trim((string)($body['title'] ?? ''));
$start = (string)($body['start'] ?? '');
$end = (string)($body['end'] ?? '');
if ($title === '') return 'Titel fehlt';
if ($start === '' || $end === '') return 'Start oder Ende fehlt';
$startTs = strtotime($start);
$endTs = strtotime($end);
if ($startTs === false || $endTs === false) return 'Ungültiges Datum';
if ($endTs < $startTs) return 'Ende liegt vor dem Start';
return null;
}
function require_admin_for_slug(string $slug): void
{
if (current_admin_calendar_slug() !== $slug) {
json_response(['error' => 'Nicht eingeloggt'], 401);
}
}
function find_event_by_id(PDO $pdo, int $id): ?array
{
$stmt = $pdo->prepare('SELECT events.*, calendars.slug AS calendar_slug FROM events JOIN calendars ON calendars.id = events.calendar_id WHERE events.id = :id LIMIT 1');
$stmt->execute(['id' => $id]);
$row = $stmt->fetch();
return $row ?: null;
}

23
Upload/api/login.php Normal file
View file

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/db.php';
$body = read_json_body();
$calendarSlug = (string)($body['calendarSlug'] ?? '');
$password = (string)($body['password'] ?? '');
$passwords = calendar_passwords();
if (!isset($passwords[$calendarSlug])) {
json_response(['error' => 'Unbekannter Kalender'], 400);
}
if ($passwords[$calendarSlug] !== $password) {
json_response(['error' => 'Passwort falsch'], 401);
}
start_calendar_session();
$_SESSION['admin_calendar_slug'] = $calendarSlug;
json_response(['ok' => true]);

10
Upload/api/logout.php Normal file
View file

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/config.php';
start_calendar_session();
session_destroy();
json_response(['ok' => true]);

48
Upload/api/save-event.php Normal file
View file

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/helpers.php';
$body = read_json_body();
$calendarSlug = (string)($body['calendarSlug'] ?? '');
if ($calendarSlug === '') {
json_response(['error' => 'Kalender fehlt'], 400);
}
require_admin_for_slug($calendarSlug);
$error = validate_event_payload($body);
if ($error) {
json_response(['error' => $error], 400);
}
$pdo = calendar_db();
$calendar = find_calendar_by_slug($pdo, $calendarSlug);
if (!$calendar) {
json_response(['error' => 'Kalender nicht gefunden'], 404);
}
$stmt = $pdo->prepare('INSERT INTO events (calendar_id, title, description, location, start_at, end_at, color) VALUES (:calendar_id, :title, :description, :location, :start_at, :end_at, :color)');
$stmt->execute([
'calendar_id' => $calendar['id'],
'title' => trim((string)$body['title']),
'description' => (string)($body['description'] ?? ''),
'location' => (string)($body['location'] ?? ''),
'start_at' => (string)$body['start'],
'end_at' => (string)$body['end'],
'color' => (string)($body['color'] ?? '#1a73e8'),
]);
$id = (int)$pdo->lastInsertId();
$created = find_event_by_id($pdo, $id);
json_response([
'id' => $created['id'],
'title' => $created['title'],
'description' => $created['description'],
'location' => $created['location'],
'start' => $created['start_at'],
'end' => $created['end_at'],
'color' => $created['color'],
], 201);

24
Upload/api/schema.sql Normal file
View file

@ -0,0 +1,24 @@
CREATE TABLE IF NOT EXISTS calendars (
id INT AUTO_INCREMENT PRIMARY KEY,
slug VARCHAR(100) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
description TEXT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS events (
id INT AUTO_INCREMENT PRIMARY KEY,
calendar_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT NULL,
location VARCHAR(255) NULL,
start_at DATETIME NOT NULL,
end_at DATETIME NOT NULL,
color VARCHAR(20) DEFAULT '#1a73e8',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_events_calendar FOREIGN KEY (calendar_id) REFERENCES calendars(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT IGNORE INTO calendars (slug, name, description) VALUES
('dittes', 'Buchungskalender Dittes Hütte', ''),
('kegeln', 'Buchungskalender ESV-Bludenz Sektion Kegeln', '');

View file

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/helpers.php';
$body = read_json_body();
$id = (int)($body['id'] ?? 0);
if ($id <= 0) {
json_response(['error' => 'Termin fehlt'], 400);
}
$error = validate_event_payload($body);
if ($error) {
json_response(['error' => $error], 400);
}
$pdo = calendar_db();
$existing = find_event_by_id($pdo, $id);
if (!$existing) {
json_response(['error' => 'Termin nicht gefunden'], 404);
}
require_admin_for_slug($existing['calendar_slug']);
$stmt = $pdo->prepare('UPDATE events SET title = :title, description = :description, location = :location, start_at = :start_at, end_at = :end_at, color = :color, updated_at = CURRENT_TIMESTAMP WHERE id = :id');
$stmt->execute([
'id' => $id,
'title' => trim((string)$body['title']),
'description' => (string)($body['description'] ?? ''),
'location' => (string)($body['location'] ?? ''),
'start_at' => (string)$body['start'],
'end_at' => (string)$body['end'],
'color' => (string)($body['color'] ?? '#1a73e8'),
]);
$updated = find_event_by_id($pdo, $id);
json_response([
'id' => $updated['id'],
'title' => $updated['title'],
'description' => $updated['description'],
'location' => $updated['location'],
'start' => $updated['start_at'],
'end' => $updated['end_at'],
'color' => $updated['color'],
]);

View file

@ -0,0 +1,13 @@
{
"files": {
"main.css": "./static/css/main.229d58bf.css",
"main.js": "./static/js/main.f77b3dd0.js",
"index.html": "./index.html",
"main.229d58bf.css.map": "./static/css/main.229d58bf.css.map",
"main.f77b3dd0.js.map": "./static/js/main.f77b3dd0.js.map"
},
"entrypoints": [
"static/css/main.229d58bf.css",
"static/js/main.f77b3dd0.js"
]
}

BIN
Upload/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
Upload/images/askoe.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 KiB

BIN
Upload/images/oees.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1 @@
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' width='27' height='32' viewBox='0 0 27 32' fill='rgb(255, 255, 255)'><title>screenshot</title><path d='M21.376 18.272h-1.952q-0.448 0-0.8-0.32t-0.352-0.8v-2.304q0-0.448 0.352-0.8t0.8-0.32h1.952q-0.576-1.952-2.016-3.392t-3.36-1.984v1.92q0 0.48-0.352 0.832t-0.8 0.32h-2.272q-0.48 0-0.8-0.32t-0.352-0.832v-1.92q-1.92 0.544-3.36 1.984t-2.016 3.392h1.952q0.48 0 0.8 0.32t0.352 0.8v2.304q0 0.448-0.352 0.8t-0.8 0.352h-1.952q0.576 1.92 2.016 3.36t3.36 1.984v-1.92q0-0.48 0.352-0.8t0.8-0.352h2.272q0.48 0 0.8 0.352t0.352 0.8v1.92q1.92-0.544 3.36-1.984t2.016-3.36zM27.424 14.848v2.304q0 0.448-0.32 0.8t-0.832 0.32h-2.528q-0.672 2.88-2.784 4.992t-4.96 2.752v2.56q0 0.448-0.352 0.8t-0.8 0.352h-2.272q-0.48 0-0.8-0.352t-0.352-0.8v-2.56q-2.88-0.672-4.96-2.752t-2.752-4.992h-2.56q-0.48 0-0.8-0.32t-0.352-0.8v-2.304q0-0.448 0.352-0.8t0.8-0.32h2.56q0.64-2.88 2.752-4.992t4.96-2.752v-2.56q0-0.448 0.352-0.8t0.8-0.352h2.272q0.48 0 0.8 0.352t0.352 0.8v2.56q2.88 0.672 4.96 2.752t2.784 4.992h2.528q0.48 0 0.832 0.32t0.32 0.8z'/></svg>

After

Width:  |  Height:  |  Size: 1 KiB

1
Upload/index.html Normal file
View file

@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>ESV-Bludenz</title><script defer="defer" src="./static/js/main.f77b3dd0.js"></script><link href="./static/css/main.229d58bf.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

25
Upload/manifest.json Normal file
View file

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
Upload/robots.txt Normal file
View file

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,55 @@
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/**
* @license React
* react-dom-client.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-dom.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

8
build/.htaccess Normal file
View file

@ -0,0 +1,8 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>

13
build/asset-manifest.json Normal file
View file

@ -0,0 +1,13 @@
{
"files": {
"main.css": "./static/css/main.229d58bf.css",
"main.js": "./static/js/main.f77b3dd0.js",
"index.html": "./index.html",
"main.229d58bf.css.map": "./static/css/main.229d58bf.css.map",
"main.f77b3dd0.js.map": "./static/js/main.f77b3dd0.js.map"
},
"entrypoints": [
"static/css/main.229d58bf.css",
"static/js/main.f77b3dd0.js"
]
}

BIN
build/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
build/images/askoe.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 KiB

BIN
build/images/oees.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1 @@
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' width='27' height='32' viewBox='0 0 27 32' fill='rgb(255, 255, 255)'><title>screenshot</title><path d='M21.376 18.272h-1.952q-0.448 0-0.8-0.32t-0.352-0.8v-2.304q0-0.448 0.352-0.8t0.8-0.32h1.952q-0.576-1.952-2.016-3.392t-3.36-1.984v1.92q0 0.48-0.352 0.832t-0.8 0.32h-2.272q-0.48 0-0.8-0.32t-0.352-0.832v-1.92q-1.92 0.544-3.36 1.984t-2.016 3.392h1.952q0.48 0 0.8 0.32t0.352 0.8v2.304q0 0.448-0.352 0.8t-0.8 0.352h-1.952q0.576 1.92 2.016 3.36t3.36 1.984v-1.92q0-0.48 0.352-0.8t0.8-0.352h2.272q0.48 0 0.8 0.352t0.352 0.8v1.92q1.92-0.544 3.36-1.984t2.016-3.36zM27.424 14.848v2.304q0 0.448-0.32 0.8t-0.832 0.32h-2.528q-0.672 2.88-2.784 4.992t-4.96 2.752v2.56q0 0.448-0.352 0.8t-0.8 0.352h-2.272q-0.48 0-0.8-0.352t-0.352-0.8v-2.56q-2.88-0.672-4.96-2.752t-2.752-4.992h-2.56q-0.48 0-0.8-0.32t-0.352-0.8v-2.304q0-0.448 0.352-0.8t0.8-0.32h2.56q0.64-2.88 2.752-4.992t4.96-2.752v-2.56q0-0.448 0.352-0.8t0.8-0.352h2.272q0.48 0 0.8 0.352t0.352 0.8v2.56q2.88 0.672 4.96 2.752t2.784 4.992h2.528q0.48 0 0.832 0.32t0.32 0.8z'/></svg>

After

Width:  |  Height:  |  Size: 1 KiB

1
build/index.html Normal file
View file

@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>ESV-Bludenz</title><script defer="defer" src="./static/js/main.f77b3dd0.js"></script><link href="./static/css/main.229d58bf.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

25
build/manifest.json Normal file
View file

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
build/robots.txt Normal file
View file

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,55 @@
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/**
* @license React
* react-dom-client.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-dom.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

View file

@ -2,7 +2,32 @@
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$root = __DIR__;
$public = $root . '/public';
$build = $root . '/build';
$staticBase = $build;
if ($path !== '/' && file_exists($staticBase . $path) && !is_dir($staticBase . $path)) {
$file = $staticBase . $path;
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
$types = [
'js' => 'application/javascript',
'css' => 'text/css',
'map' => 'application/json',
'json' => 'application/json',
'png' => 'image/png',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'svg' => 'image/svg+xml',
'ico' => 'image/x-icon',
'txt' => 'text/plain',
'webmanifest' => 'application/manifest+json',
];
if (isset($types[$ext])) {
header('Content-Type: ' . $types[$ext]);
}
readfile($file);
return true;
}
if ($path !== '/' && file_exists($public . $path) && !is_dir($public . $path)) {
return false;
}
@ -18,4 +43,4 @@ if (str_starts_with($path, '/api/')) {
return true;
}
require $public . '/index.html';
require $build . '/index.html';

View file

@ -3,9 +3,11 @@ import { Modal } from "react-bootstrap";
import { Link } from "react-router-dom";
import "./Dittes.css";
const configuredApiBase = process.env.REACT_APP_API_BASE;
const API_BASE =
process.env.REACT_APP_API_BASE ||
`${window.location.origin}/api`;
configuredApiBase && !configuredApiBase.includes("localhost:3002")
? configuredApiBase
: `${window.location.origin}/api`;
function formatDateTime(value) {
if (!value) return "";
@ -141,15 +143,31 @@ function Dittes({
const response = await fetch(`${API_BASE}/events.php?calendar=${encodeURIComponent(calendarSlug)}`, {
credentials: "include",
});
if (!response.ok) {
throw new Error(`API Fehler ${response.status}`);
}
const data = await response.json();
setEvents(data.events || []);
const normalizedEvents = Array.isArray(data.events)
? data.events.map((entry) => ({
...entry,
start: entry.start ? entry.start.replace(" ", "T") : entry.start,
end: entry.end ? entry.end.replace(" ", "T") : entry.end,
}))
: [];
setEvents(normalizedEvents);
setCalendarInfo(data.calendar || { name: fallbackName, description: "" });
setAdminMode(Boolean(data.isAdmin));
setStatus("");
}
useEffect(() => {
loadEvents().catch(() => setStatus("Kalender konnte nicht geladen werden. Läuft das Backend schon?"));
loadEvents().catch((error) => {
console.error("Kalender laden fehlgeschlagen", error);
setStatus(`Kalender konnte nicht geladen werden: ${error.message}`);
});
}, [calendarSlug, fallbackName]);
function changeMonth(direction) {

View file

@ -2,7 +2,7 @@
export const esvSections = [
{
name: "ESV Bludenz - Sektion Kegeln",
link: "/kegeln",
link: "https://esv-bludenz-kegeln.at/",
icon: "images/sektion_kegeln.png",
},
{