index.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. <?php
  2. declare(strict_types=1);
  3. use App\App\Bootstrap;
  4. use App\Form\FormSchema;
  5. use App\Security\Csrf;
  6. require __DIR__ . '/src/autoload.php';
  7. Bootstrap::init();
  8. $schema = new FormSchema();
  9. $steps = $schema->getSteps();
  10. $csrf = Csrf::token();
  11. $app = Bootstrap::config('app');
  12. $disclaimerConfigRaw = $app['disclaimer'] ?? [];
  13. if (is_string($disclaimerConfigRaw)) {
  14. $disclaimerConfig = ['text' => $disclaimerConfigRaw];
  15. } elseif (is_array($disclaimerConfigRaw)) {
  16. $disclaimerConfig = $disclaimerConfigRaw;
  17. } else {
  18. $disclaimerConfig = [];
  19. }
  20. $disclaimerTitle = (string) ($disclaimerConfig['title'] ?? 'Hinweis');
  21. $disclaimerText = (string) ($disclaimerConfig['text'] ?? '');
  22. $disclaimerAcceptLabel = (string) ($disclaimerConfig['accept_label'] ?? 'Hinweis gelesen, weiter');
  23. $baseUrl = Bootstrap::baseUrl();
  24. /** @param array<string, mixed> $field */
  25. function renderField(array $field): void
  26. {
  27. $key = htmlspecialchars((string) $field['key']);
  28. $label = htmlspecialchars((string) $field['label']);
  29. $type = (string) ($field['type'] ?? 'text');
  30. $required = ((bool) ($field['required'] ?? false)) ? 'required' : '';
  31. echo '<div class="field" data-field="' . $key . '">';
  32. if ($type === 'checkbox') {
  33. echo '<label class="checkbox-label"><input type="checkbox" name="form_data[' . $key . ']" value="1" ' . $required . '> ' . $label . '</label>';
  34. } else {
  35. echo '<label for="' . $key . '">' . $label . '</label>';
  36. if ($type === 'textarea') {
  37. echo '<textarea id="' . $key . '" name="form_data[' . $key . ']" ' . $required . '></textarea>';
  38. } elseif ($type === 'select') {
  39. echo '<select id="' . $key . '" name="form_data[' . $key . ']" ' . $required . '>';
  40. echo '<option value="">Bitte wählen</option>';
  41. foreach (($field['options'] ?? []) as $option) {
  42. if (!is_array($option)) {
  43. continue;
  44. }
  45. $value = htmlspecialchars((string) ($option['value'] ?? ''));
  46. $optLabel = htmlspecialchars((string) ($option['label'] ?? ''));
  47. echo '<option value="' . $value . '">' . $optLabel . '</option>';
  48. }
  49. echo '</select>';
  50. } elseif ($type === 'file') {
  51. $accept = htmlspecialchars((string) ($field['accept'] ?? ''));
  52. $fileInputId = $key . '_file';
  53. $cameraInputId = $key . '_camera';
  54. echo '<div class="upload-control" data-upload-key="' . $key . '">';
  55. echo '<div class="upload-actions">';
  56. echo '<label class="upload-action-btn" for="' . $fileInputId . '">Datei auswählen</label>';
  57. echo '<label class="upload-action-btn upload-action-btn-camera" for="' . $cameraInputId . '">Foto aufnehmen</label>';
  58. echo '</div>';
  59. echo '<input id="' . $fileInputId . '" class="upload-native-input" type="file" name="' . $key . '" accept="' . $accept . '">';
  60. echo '<input id="' . $cameraInputId . '" class="upload-native-input" type="file" name="' . $key . '__camera" accept="image/*" capture="environment">';
  61. echo '<p class="upload-selected" data-upload-selected="' . $key . '">Keine Datei gewählt</p>';
  62. echo '</div>';
  63. echo '<div class="upload-list" data-upload-list="' . $key . '"></div>';
  64. } else {
  65. $inputType = htmlspecialchars($type);
  66. echo '<input id="' . $key . '" type="' . $inputType . '" name="form_data[' . $key . ']" ' . $required . '>';
  67. }
  68. }
  69. if (isset($field['required_if']) && is_array($field['required_if'])) {
  70. $depField = htmlspecialchars((string) ($field['required_if']['field'] ?? ''));
  71. $depValue = htmlspecialchars((string) ($field['required_if']['equals'] ?? ''));
  72. echo '<small class="hint">Pflicht, wenn ' . $depField . ' = ' . $depValue . '.</small>';
  73. }
  74. echo '<div class="error" data-error-for="' . $key . '"></div>';
  75. echo '</div>';
  76. }
  77. ?><!doctype html>
  78. <html lang="de">
  79. <head>
  80. <meta charset="utf-8">
  81. <meta name="viewport" content="width=device-width, initial-scale=1">
  82. <title><?= htmlspecialchars((string) $app['project_name']) ?></title>
  83. <link rel="stylesheet" href="<?= htmlspecialchars(Bootstrap::url('assets/css/tokens.css')) ?>">
  84. <link rel="stylesheet" href="<?= htmlspecialchars(Bootstrap::url('assets/css/base.css')) ?>">
  85. </head>
  86. <body>
  87. <header class="site-header">
  88. <div class="container header-inner">
  89. <a class="brand" href="<?= htmlspecialchars(Bootstrap::url('index.php')) ?>">
  90. <img class="brand-logo" src="<?= htmlspecialchars(Bootstrap::url('assets/images/feuerwehr-Logo-invers.webp')) ?>" alt="Feuerwehr Logo">
  91. <div>
  92. <div class="brand-title"><?= htmlspecialchars((string) $app['project_name']) ?></div>
  93. <div class="brand-subtitle">Feuerwehr Freising</div>
  94. </div>
  95. </a>
  96. </div>
  97. </header>
  98. <main class="container">
  99. <h1>Digitaler Mitgliedsantrag Feuerwehrverein</h1>
  100. <section id="disclaimerSection" class="card">
  101. <h2><?= htmlspecialchars($disclaimerTitle) ?></h2>
  102. <p class="disclaimer-text"><?= nl2br(htmlspecialchars($disclaimerText)) ?></p>
  103. <button id="acceptDisclaimerBtn" type="button" class="btn"><?= htmlspecialchars($disclaimerAcceptLabel) ?></button>
  104. </section>
  105. <section id="startSection" class="card hidden">
  106. <h2>Start</h2>
  107. <p id="startIntroText">Bitte E-Mail eingeben. Bestehende Entwürfe werden automatisch geladen.</p>
  108. <form id="startForm" novalidate>
  109. <input type="hidden" name="csrf" value="<?= htmlspecialchars($csrf) ?>">
  110. <div class="hp-field" aria-hidden="true">
  111. <label for="website">Website</label>
  112. <input id="website" type="text" name="website" autocomplete="off" tabindex="-1">
  113. </div>
  114. <div class="field" id="startEmailField">
  115. <label for="startEmail">E-Mail</label>
  116. <input id="startEmail" type="email" name="email" required inputmode="email" autocomplete="email">
  117. </div>
  118. <div class="inline-actions" id="startActions">
  119. <button id="startSubmitBtn" type="submit" class="btn">Formular laden</button>
  120. </div>
  121. <div id="compactStatusBox" class="compact-status hidden">
  122. <p><strong>E-Mail:</strong> <span id="statusEmailValue">-</span></p>
  123. <p><strong>Speicherstatus:</strong> <span id="draftStatusValue">Noch nicht gespeichert</span></p>
  124. <button id="resetDataBtn" type="button" class="btn btn-secondary btn-small">Gespeicherte Daten löschen und neu starten</button>
  125. </div>
  126. <p id="feedbackMessage" class="status-text" role="status" aria-live="polite"></p>
  127. </form>
  128. </section>
  129. <section id="wizardSection" class="card hidden">
  130. <h2>Mitgliedsantrag</h2>
  131. <div id="progress" class="progress"></div>
  132. <form id="applicationForm" enctype="multipart/form-data" novalidate>
  133. <input type="hidden" name="csrf" value="<?= htmlspecialchars($csrf) ?>">
  134. <input type="hidden" id="applicationEmail" name="email" value="">
  135. <input type="hidden" id="applicationWebsite" name="website" value="">
  136. <?php foreach ($steps as $index => $step): ?>
  137. <section class="step hidden" data-step="<?= $index + 1 ?>">
  138. <h3>Schritt <?= $index + 1 ?>: <?= htmlspecialchars((string) ($step['title'] ?? '')) ?></h3>
  139. <p><?= htmlspecialchars((string) ($step['description'] ?? '')) ?></p>
  140. <?php foreach (($step['fields'] ?? []) as $field): ?>
  141. <?php if (is_array($field)) { renderField($field); } ?>
  142. <?php endforeach; ?>
  143. </section>
  144. <?php endforeach; ?>
  145. <div class="wizard-actions">
  146. <button type="button" id="prevBtn" class="btn btn-secondary">Zurück</button>
  147. <button type="button" id="nextBtn" class="btn">Weiter</button>
  148. <button type="button" id="submitBtn" class="btn hidden">Verbindlich absenden</button>
  149. </div>
  150. </form>
  151. </section>
  152. </main>
  153. <script>
  154. window.APP_BOOT = {
  155. steps: <?= json_encode($steps, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
  156. csrf: <?= json_encode($csrf, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
  157. contactEmail: <?= json_encode((string) ($app['contact_email'] ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
  158. baseUrl: <?= json_encode($baseUrl, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>
  159. };
  160. </script>
  161. <script src="<?= htmlspecialchars(Bootstrap::url('assets/js/form.js')) ?>"></script>
  162. </body>
  163. </html>