integrated onboarding tutorial for citizen portal

This commit is contained in:
2026-04-30 15:28:45 +02:00
parent ffc53f23e2
commit e1cf6f21f5
4 changed files with 434 additions and 8 deletions

View File

@@ -68,6 +68,10 @@ $news_items = $stmt->fetchAll();
<!-- Application Styles -->
<link rel="stylesheet" href="styles.css">
<!-- Shepherd.js Onboarding Tour -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/css/shepherd.css">
<!-- ============================================================= -->
<!-- Municipality Theme loaded from Database -->
<!-- ============================================================= -->
@@ -183,17 +187,30 @@ $news_items = $stmt->fetchAll();
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
</h2>
<div class="sidebar-body">
<h3><i class="fa-solid fa-book"></i> Interaktive Anleitung</h3>
<p>Klicken Sie unten auf Tutorial starten um Schritt für Schritt durch die Kernfunktionen der Mitmachkarte geführt zu werden.</p>
<p>
<button class="btn btn-primary" onclick="if(typeof restartOnboarding==='function'){sidebar.close();restartOnboarding()}" style="font-size:0.85rem;">
<i class="fa-solid fa-route"></i> Tutorial starten
</button>
</p>
<h3><i class="fa-solid fa-map-location-dot"></i> Karte bedienen</h3>
<p>Verschieben Sie die Karte per Mausklick und Ziehen. Zoomen Sie mit dem Mausrad oder den Zoom-Buttons.</p>
<h3><i class="fa-solid fa-plus"></i> Beitrag erstellen</h3>
<p>Verwenden Sie die Zeichenwerkzeuge rechts, um Beiträge als Punkte, Linien oder Flächen zu zeichnen. Anschließend können Sie Kategorie und Beschreibung hinzufügen.</p>
<h3><i class="fa-solid fa-location-dot"></i> Beitrag hinzufügen</h3>
<p>Verwenden Sie die Zeichenwerkzeuge rechts, um Hinweise, Anregungen und Vorschläge auf der Mitmachkarte als Punkte, Linien oder Flächen hinzuzufügen.</p>
<h3><i class="fa-solid fa-thumbs-up"></i> Abstimmen</h3>
<p>Klicken Sie auf bestehende Beiträge und nutzen Sie die Like/Dislike Funktion, um Ihre Meinung kundzugeben.</p>
<h3><i class="fa-solid fa-thumbs-up"></i> Bewerten</h3>
<p>Klicken Sie auf bestehende Beiträge und nutzen Sie die Bewertungsfunktion, um Ihre Meinung zu äußern.</p>
<h3><i class="fa-solid fa-comments"></i> Kommentieren</h3>
<p>Gerne können Sie Ihre Meinung zu bestehenden Beiträgen auch durch die Kommentarfunktion äußern.</p>
<h3><i class="fa-solid fa-magnifying-glass"></i> Suchen</h3>
<p>Verwenden Sie die Adresssuche rechts, um bestimmte Orte auf der Karte zu finden.</p>
<p>Verwenden Sie die Adresssuche rechts, um schnell den richtigen Ort auf der Mitmachkarte zu finden.</p>
</div>
</div>
@@ -246,7 +263,7 @@ $news_items = $stmt->fetchAll();
<!-- ============================================================= -->
<footer id="app-footer">
<span class="dev-warning">
<i class="fa-solid fa-triangle-exclamation"></i> Demoversion - nicht in Absprache mit der Stadt Lohne entwickelt! Alle Beitrage, Kommentare und Personen sind frei erfunden.
<i class="fa-solid fa-triangle-exclamation"></i> Demoversion - nicht in Rücksprache mit der Stadt Lohne entwickelt! Alle Beitrage, Kommentare und Personen sind frei erfunden.
</span>
<div class="footer-content">
<span class="footer-text">© <a href="https://endex-geodaten.de" target="_blank" style="color:inherit;">endex GmbH</a></span>
@@ -267,7 +284,7 @@ $news_items = $stmt->fetchAll();
<li>Bestehende Beiträge der Bürgerschaft betrachten und bewerten</li>
</ul>
<p style="background:#fff3cd;padding:10px;border-radius:6px;border:1px solid #ffc107;font-size:0.85rem;color:#856404;">
<i class="fa-solid fa-triangle-exclamation"></i> <strong>Hinweis:</strong> Dieses Bürgerbeteiligungsportal befindet sich noch in der Entwicklung und wurde nicht offiziell beauftragt.
<i class="fa-solid fa-triangle-exclamation"></i> <strong>Hinweis:</strong> Demoversion - nicht in Rücksprache mit der Stadt Lohne entwickelt! Alle Beitrage, Kommentare und Personen sind frei erfunden.
</p>
<p>Zum Hinzufügen von Beiträgen geben Sie bitte zunächst Ihren Namen ein.</p> <div class="modal-actions">
<button class="btn btn-primary" onclick="closeWelcomeAndShowLogin()">Loslegen</button>
@@ -366,6 +383,13 @@ $news_items = $stmt->fetchAll();
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.14.0/dist/sweetalert2.all.min.js"></script>
<!-- Shepherd.js Library -->
<script src="https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/js/shepherd.min.js"></script>
<!-- Onboarding Logic -->
<script src="js/onboarding.js"></script>
<!-- ============================================================= -->
<!-- Municipality Configuration passed to JavaScript -->
<!-- ============================================================= -->

View File

@@ -980,7 +980,15 @@ function showInfoModal() {
'<strong>' + MUNICIPALITY.name + '</strong> mitzuwirken.</p>' +
'<p style="text-align:left;line-height:1.6;">Bitte tragen Sie Hinweise, Anregungen und Vorschläge ' +
'mithilfe der Zeichenwerkzeuge auf der Karte ein.</p>',
confirmButtonColor: MUNICIPALITY.primaryColor
showDenyButton: true,
confirmButtonText: 'Schließen',
denyButtonText: '<i class="fa-solid fa-route"></i> Tutorial starten',
confirmButtonColor: MUNICIPALITY.primaryColor,
denyButtonColor: '#546E7A'
}).then(function (result) {
if (result.isDenied && typeof restartOnboarding === 'function') {
restartOnboarding();
}
});
}

277
public/js/onboarding.js Normal file
View File

@@ -0,0 +1,277 @@
// =====================================================================
// WebGIS Citizen Participation Portal — Onboarding Tour
// Guides Users through the Participation Portal
// =====================================================================
// =================================================================
// Block 1: Onboarding Configuration
// =================================================================
// ONBOARDING_MODE — Controls when the Tutorial is shown:
const ONBOARDING_MODE = 'once';
// 'once' — Shown on first Visit, stored in localStorage
// 'session' — Shown per Browser Session, stored in sessionStorage
// 'always' — Shows always, nothing stored
// Prevents double Initialization
let onboardingStarted = false;
// =================================================================
// Block 2: Tour Initialization
// =================================================================
function initOnboardingTour() {
// Checks if Tutorial should be shown based on Onboarding Mode
if (ONBOARDING_MODE === 'once' && localStorage.getItem('webgis_onboarding_done')) {
return;
}
if (ONBOARDING_MODE === 'session' && sessionStorage.getItem('webgis_onboarding_done')) {
return;
}
// Waits for Welcome and Login Modals to be closed
waitForModalsToClose(function () {
setTimeout(startTour, 600);
});
}
// =================================================================
// Block 3: Modal Watcher — Starts Tour other Welcome and Login Modals closed
// =================================================================
function waitForModalsToClose(callback) {
const welcomeModal = document.getElementById('welcome-modal');
const loginModal = document.getElementById('login-modal');
const checkInterval = setInterval(function () {
const welcomeHidden = !welcomeModal || welcomeModal.style.display === 'none' || welcomeModal.style.display === '';
const loginHidden = !loginModal || loginModal.style.display === 'none' || loginModal.style.display === '';
if (welcomeHidden && loginHidden) {
clearInterval(checkInterval);
callback();
}
}, 300);
// Safety Timeout
setTimeout(function () {
clearInterval(checkInterval);
callback();
}, 30000);
}
// =================================================================
// Block 4: Tour Definition
// =================================================================
function startTour() {
// Prevents double Start
if (onboardingStarted) return;
onboardingStarted = true;
const tour = new Shepherd.Tour({
useModalOverlay: true,
defaultStepOptions: {
cancelIcon: { enabled: true },
scrollTo: false,
classes: 'onboarding-step',
popperOptions: {
modifiers: [
{ name: 'offset', options: { offset: [0, 14] } }
]
}
}
});
// -----------------------------------------------------------------
// Step 1: Welcome
// -----------------------------------------------------------------
tour.addStep({
id: 'welcome',
title: '<i class="fa-solid fa-hand-wave"></i> Wilkommen bei der Mitmachkarte!',
text: 'Dieses interaktive Tutorial zeigt Ihnen die Kernfunktionen der Mitmachkarte.' +
'<br><br><span style="font-size:0.8rem;color:var(--color-text-secondary);">Sie können das Tutorial jederzeit durch den Hilfe-Tab der Seitenleiste wiederholen.</span>',
buttons: [
{
text: 'Überspringen',
action: tour.cancel,
classes: 'shepherd-button-secondary'
},
{
text: 'Los geht\'s <i class="fa-solid fa-arrow-right"></i>',
action: tour.next,
classes: 'shepherd-button-primary'
}
]
});
// -----------------------------------------------------------------
// Step 2: Drawing Tools
// -----------------------------------------------------------------
tour.addStep({
id: 'drawing-tools',
title: '<i class="fa-solid fa-pencil"></i> Beitrag hinzufügen',
text: 'Verwenden Sie die <strong>Zeichenwerkzeuge</strong>, um Hinweise, Anregungen und Vorschläge auf der Mitmachkarte als Punkte, Linien oder Flächen hinzuzufügen.',
attachTo: {
element: '.leaflet-pm-toolbar',
on: 'left'
},
beforeShowPromise: function () {
return new Promise(function (resolve) {
sidebar.close();
setTimeout(resolve, 300);
});
},
buttons: [
{
text: '<i class="fa-solid fa-arrow-left"></i> Zurück',
action: tour.back,
classes: 'shepherd-button-secondary'
},
{
text: 'Weiter <i class="fa-solid fa-arrow-right"></i>',
action: tour.next,
classes: 'shepherd-button-primary'
}
]
});
// -----------------------------------------------------------------
// Step 3: Address Search
// -----------------------------------------------------------------
tour.addStep({
id: 'address-search',
title: '<i class="fa-solid fa-magnifying-glass"></i> Adresssuche',
text: 'Verwenden Sie die <strong>Adresssuche</strong>, um schnell den richtigen Ort auf der Mitmachkarte zu finden.',
attachTo: {
element: '.leaflet-control-geocoder',
on: 'left'
},
buttons: [
{
text: '<i class="fa-solid fa-arrow-left"></i> Zurück',
action: tour.back,
classes: 'shepherd-button-secondary'
},
{
text: 'Weiter <i class="fa-solid fa-arrow-right"></i>',
action: tour.next,
classes: 'shepherd-button-primary'
}
]
});
// -----------------------------------------------------------------
// Step 4: Layer Control
// -----------------------------------------------------------------
tour.addStep({
id: 'layer-control',
title: '<i class="fa-solid fa-layer-group"></i> Kartenansicht',
text: 'Wechseln Sie zwischen verschiedenen <strong>Hintergrundkarten</strong> und <strong>Satellitenbildern</strong>.',
attachTo: {
element: '.leaflet-control-layers',
on: 'left'
},
buttons: [
{
text: '<i class="fa-solid fa-arrow-left"></i> Zurück',
action: tour.back,
classes: 'shepherd-button-secondary'
},
{
text: 'Weiter <i class="fa-solid fa-arrow-right"></i>',
action: tour.next,
classes: 'shepherd-button-primary'
}
]
});
// -----------------------------------------------------------------
// Step 5: Sidebar
// -----------------------------------------------------------------
tour.addStep({
id: 'sidebar',
title: '<i class="fa-solid fa-bars"></i> Seitenleiste',
text: 'In der Seitenleiste finden Sie <strong>Hilfestellungen</strong>, <strong>Listenansichten</strong> und <strong>Neuigkeiten</strong>.',
attachTo: {
element: '#sidebar',
on: 'right'
},
beforeShowPromise: function () {
return new Promise(function (resolve) {
sidebar.open('tab-help');
setTimeout(resolve, 400);
});
},
buttons: [
{
text: '<i class="fa-solid fa-arrow-left"></i> Zurück',
action: tour.back,
classes: 'shepherd-button-secondary'
},
{
text: 'Tutorial abschließen <i class="fa-solid fa-check"></i>',
action: tour.next,
classes: 'shepherd-button-primary'
}
]
});
// -----------------------------------------------------------------
// Completion and Cancellation
// -----------------------------------------------------------------
tour.on('complete', function () {
markOnboardingDone();
onboardingStarted = false;
});
tour.on('cancel', function () {
markOnboardingDone();
onboardingStarted = false;
});
tour.start();
}
// =================================================================
// Marks Onboarding as completed
// =================================================================
function markOnboardingDone() {
if (ONBOARDING_MODE === 'once') {
localStorage.setItem('webgis_onboarding_done', 'true');
} else if (ONBOARDING_MODE === 'session') {
sessionStorage.setItem('webgis_onboarding_done', 'true');
}
}
// =================================================================
// Manual Tour Restart
// =================================================================
function restartOnboarding() {
localStorage.removeItem('webgis_onboarding_done');
sessionStorage.removeItem('webgis_onboarding_done');
onboardingStarted = false;
startTour();
}
// =================================================================
// Auto-Start on Page Load
// =================================================================
initOnboardingTour();

View File

@@ -764,6 +764,123 @@ select.form-input { cursor: pointer; }
.popup-comment-submit:hover { filter: brightness(1.15); }
/* -----------------------------------------------------------------
4.9 Onboarding Tour (Shepherd.js Overrides)
----------------------------------------------------------------- */
/* Step Container */
.shepherd-element {
max-width: 340px;
border-radius: 12px !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2) !important;
font-family: var(--font-body) !important;
z-index: 2100 !important;
overflow: hidden;
}
.shepherd-element .shepherd-content {
border-radius: 12px !important;
padding: 0;
overflow: hidden;
}
/* Header */
.shepherd-element .shepherd-header {
background: var(--color-primary) !important;
padding: 14px 20px !important;
border-radius: 0 !important;
}
.shepherd-element .shepherd-title {
color: white !important;
font-size: 1rem !important;
font-weight: 600 !important;
font-family: var(--font-body) !important;
display: flex !important;
align-items: center !important;
gap: 6px !important;
}
.shepherd-element .shepherd-cancel-icon {
color: white !important;
opacity: 0.7;
font-size: 1.2rem;
}
.shepherd-element .shepherd-cancel-icon:hover { opacity: 1; }
/* Body Text */
.shepherd-element .shepherd-text {
padding: 16px 20px !important;
font-size: 0.88rem !important;
line-height: 1.6 !important;
color: var(--color-text) !important;
}
/* Footer Buttons */
.shepherd-element .shepherd-footer {
padding: 0 20px 16px 20px !important;
border-top: none !important;
display: flex !important;
justify-content: flex-end !important;
gap: 8px !important;
}
.shepherd-element .shepherd-button {
border: none !important;
border-radius: 6px !important;
padding: 8px 16px !important;
font-size: 0.85rem !important;
font-weight: 600 !important;
font-family: var(--font-body) !important;
cursor: pointer !important;
transition: filter 150ms ease !important;
}
.shepherd-element .shepherd-button:hover { filter: brightness(1.1); }
/* Primary Button */
.shepherd-button-primary {
background: var(--color-primary) !important;
color: white !important;
}
/* Secondary Button */
.shepherd-button-secondary {
background: var(--color-bg) !important;
color: var(--color-text) !important;
border: 1px solid var(--color-border) !important;
}
.shepherd-button-secondary:hover {
background: var(--color-border) !important;
}
/* Modal Overlay */
.shepherd-modal-overlay-container {
z-index: 2050 !important;
}
/* Arrow Colors */
.shepherd-arrow:before {
background: var(--color-primary) !important;
}
/* Welcome Step */
.shepherd-element:not([data-popper-placement]) {
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
}
/* Mobile */
@media (max-width: 768px) {
.shepherd-element { max-width: 300px !important; }
.shepherd-element .shepherd-text { font-size: 0.82rem !important; }
}
/* =================================================================
SECTION 5: Admin-specific Styles (admin.php)
================================================================= */