feat: Add Supabase configuration and migrations for MC Cars application

- Create Kong declarative configuration for routing and authentication.
- Implement initialization script to set up the database.
- Add SQL migration for initializing roles, schemas, and seeding vehicle data.
- Create leads and customers tables with appropriate policies and functions for CRM.
- Seed admin user and configure storage bucket with RLS policies.
This commit is contained in:
Lago
2026-04-17 17:50:57 +02:00
commit 61517879e1
23 changed files with 3673 additions and 0 deletions
+220
View File
@@ -0,0 +1,220 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Admin · MC Cars</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Playfair+Display:wght@500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="styles.css" />
<script src="config.js"></script>
</head>
<body>
<!-- Login -->
<section id="loginView" class="admin-login" style="display:none;">
<div class="logo" style="justify-content:center;margin-bottom:1.5rem;">
<span class="logo-mark">MC</span>
<span>MC Cars Admin</span>
</div>
<form id="loginForm" class="admin-form">
<label>
<span>E-Mail</span>
<input type="email" name="email" required autocomplete="username" />
</label>
<label>
<span>Passwort</span>
<input type="password" name="password" required autocomplete="current-password" />
</label>
<button class="btn" type="submit">Anmelden</button>
<p class="form-feedback error" id="loginError"></p>
<p style="color:var(--muted);font-size:0.82rem;text-align:center;">
Only admins. Self-registration is disabled.
</p>
</form>
</section>
<!-- Forced password rotation (first login OR user-triggered) -->
<section id="rotateView" class="admin-login" style="display:none;">
<div class="logo" style="justify-content:center;margin-bottom:1rem;">
<span class="logo-mark">MC</span>
<span>Passwort setzen</span>
</div>
<p style="color:var(--muted);font-size:0.9rem;text-align:center;max-width:38ch;margin:0 auto 1rem;">
Das Bootstrap-Passwort muss ersetzt werden. Das neue Passwort muss sich vom
Start-Passwort unterscheiden.
</p>
<form id="rotateForm" class="admin-form">
<label>
<span>Neues Passwort (mind. 10 Zeichen)</span>
<input type="password" name="pw1" minlength="10" required autocomplete="new-password" />
</label>
<label>
<span>Wiederholen</span>
<input type="password" name="pw2" minlength="10" required autocomplete="new-password" />
</label>
<button class="btn" type="submit">Speichern</button>
<p class="form-feedback error" id="rotateError"></p>
</form>
</section>
<!-- Admin -->
<section id="adminView" class="admin-page" style="display:none;">
<div class="admin-bar">
<h1>MC Cars · Admin</h1>
<div style="display:flex;gap:0.6rem;align-items:center;flex-wrap:wrap;">
<a href="index.html" class="btn ghost small">Website</a>
<a href="http://localhost:3000" target="_blank" rel="noopener" class="btn ghost small">Supabase Studio</a>
<span id="adminWho" style="color:var(--muted);font-size:0.85rem;"></span>
<button id="changePwBtn" class="btn ghost small">Passwort aendern</button>
<button id="logoutBtn" class="btn small">Logout</button>
</div>
</div>
<!-- Tabs -->
<div class="admin-tabs" role="tablist">
<button class="tab active" data-tab="leads" role="tab">Leads <span id="leadsBadge" class="tab-badge">0</span></button>
<button class="tab" data-tab="customers" role="tab">Kunden <span id="customersBadge" class="tab-badge">0</span></button>
<button class="tab" data-tab="vehicles" role="tab">Fahrzeuge</button>
</div>
<!-- LEADS -->
<div class="tab-panel" id="tab-leads">
<div class="panel">
<div style="display:flex;justify-content:space-between;align-items:center;gap:1rem;flex-wrap:wrap;margin-bottom:1rem;">
<h2 style="margin:0;">Leads</h2>
<div class="sub-tabs" role="tablist">
<button class="sub-tab active" data-lview="active">Aktive Leads</button>
<button class="sub-tab" data-lview="inactive">Abgeschlossen</button>
</div>
</div>
<table class="admin-table" id="leadsTable">
<thead>
<tr>
<th>Eingang</th>
<th>Name / E-Mail</th>
<th>Fahrzeug</th>
<th>Zeitraum</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>
<p id="leadsEmpty" class="muted" style="display:none;text-align:center;padding:2rem 0;">Keine Leads in dieser Ansicht.</p>
</div>
</div>
<!-- CUSTOMERS -->
<div class="tab-panel" id="tab-customers" style="display:none;">
<div class="panel">
<h2>Kunden</h2>
<p class="muted" style="margin-top:-0.4rem;">Entstehen automatisch, sobald ein Lead qualifiziert wird. Die Quelle bleibt als <code>lead_id</code> verknuepft.</p>
<table class="admin-table" id="customersTable">
<thead>
<tr>
<th>Erster Kontakt</th>
<th>Name / E-Mail</th>
<th>Telefon</th>
<th>Quelle (Lead)</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>
<p id="customersEmpty" class="muted" style="display:none;text-align:center;padding:2rem 0;">Noch keine Kunden.</p>
</div>
</div>
<!-- VEHICLES -->
<div class="tab-panel" id="tab-vehicles" style="display:none;">
<div class="admin-grid">
<div class="panel">
<h2 id="formTitle">Neues Fahrzeug</h2>
<form class="admin-form" id="vehicleForm">
<input type="hidden" name="id" />
<div class="admin-photo-preview" id="photoPreview"></div>
<label>
<span>Foto hochladen (JPG/PNG/WebP, max 50 MB)</span>
<input type="file" id="photoInput" accept="image/*" />
</label>
<label>
<span>Foto-URL (wird automatisch gesetzt nach Upload)</span>
<input type="url" name="photo_url" placeholder="https://..." />
</label>
<div class="row2">
<label><span>Marke</span><input name="brand" required /></label>
<label><span>Modell</span><input name="model" required /></label>
</div>
<div class="row3">
<label><span>PS</span><input type="number" name="power_hp" min="0" /></label>
<label><span>Top-Speed km/h</span><input type="number" name="top_speed_kmh" min="0" /></label>
<label><span>0-100</span><input name="acceleration" placeholder="3.2s" /></label>
</div>
<div class="row3">
<label><span>Sitze</span><input type="number" name="seats" min="1" value="2" /></label>
<label><span>Preis / Tag (€)</span><input type="number" name="daily_price_eur" min="0" required /></label>
<label><span>Reihenfolge</span><input type="number" name="sort_order" value="100" /></label>
</div>
<label>
<span>Standort</span>
<input name="location" value="Steiermark (TBD)" />
</label>
<label>
<span>Beschreibung (Deutsch)</span>
<textarea name="description_de" rows="3"></textarea>
</label>
<label>
<span>Description (English)</span>
<textarea name="description_en" rows="3"></textarea>
</label>
<label style="flex-direction:row;align-items:center;gap:0.5rem;">
<input type="checkbox" name="is_active" checked style="width:auto;" />
<span>Aktiv / auf Website sichtbar</span>
</label>
<div style="display:flex;gap:0.5rem;">
<button class="btn" type="submit" id="saveBtn">Speichern</button>
<button class="btn ghost" type="button" id="resetBtn">Neu</button>
</div>
<p class="form-feedback" id="formFeedback"></p>
</form>
</div>
<div class="panel">
<h2>Alle Fahrzeuge</h2>
<table class="admin-table" id="adminTable">
<thead>
<tr>
<th>Foto</th>
<th>Marke / Modell</th>
<th>€ / Tag</th>
<th>Aktiv</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</section>
<!-- Lead detail / qualify dialog -->
<dialog id="leadDialog">
<div class="dialog-head">
<h3 id="leadDialogTitle" style="margin:0;">Lead</h3>
<button class="dialog-close" id="leadDialogClose" aria-label="Close">×</button>
</div>
<div class="dialog-body" id="leadDialogBody"></div>
</dialog>
<script type="module" src="admin.js"></script>
</body>
</html>