Feature: Upgrade-Anfragen-System — User-Flow + Admin-Panel (SW by-v920)

- DB: upgrade_requests-Tabelle (user_id, tier, message, fulfilled_at)
- POST /api/upgrade-request: Anfrage speichern + Admin-Benachrichtigungsmail
- GET/POST /api/admin/upgrade-requests[/{id}/fulfill]: Admin-Endpunkte
  — fulfill setzt subscription_tier + sendet Bestätigungsmail an User
- action-items: upgrades_pending zählt offene Anfragen → Badge im Admin
- Admin-Tab "Upgrades": Tabelle offener/erledigter Anfragen, Freischalten-Button
  mit Confirm-Modal, automatischer Tier-Setzung und Bestätigungsmail
- Settings: Upgrade-Modal sendet echte API-Anfrage statt nur mailto
  — doppelte Anfrage wird erkannt (already:true → Toast statt Fehler)
- api.js: API.auth.upgradeRequest(tier, message) hinzugefügt
- SW by-v920, APP_VER 920
This commit is contained in:
rene 2026-05-14 09:59:11 +02:00
parent d61fd155c5
commit f6b37717b4
9 changed files with 268 additions and 27 deletions

View file

@ -143,6 +143,7 @@ window.Page_settings = (() => {
const isPro = tier === 'pro';
const label = isPro ? 'Ban Yaro Pro' : 'Züchter';
const price = isPro ? '29 €/Jahr' : '49 €/Jahr';
const color = isPro ? '#16a34a' : '#C4843A';
const features = isPro
? ['Mehrere Hunde verwalten', 'Ernährungsbereich mit KI-Berater', 'Erweiterte Karten-Layer', 'Alle künftigen Pro-Features']
: ['Vollständige Züchter-Plattform', 'Warteliste, Läufigkeit & Trächtigkeit', 'Wurfverwaltung, Stammbaum, IK-Rechner', 'KI-Züchter-Assistent & Datenexport'];
@ -151,18 +152,12 @@ window.Page_settings = (() => {
`<li style="padding:var(--space-1) 0;font-size:var(--text-sm)">✓ ${f}</li>`
).join('');
const subject = encodeURIComponent(`Upgrade auf ${label} — Ban Yaro`);
const body = encodeURIComponent(
`Hallo,\n\nich möchte meinen Account auf ${label} upgraden.\n\nMein Account: ${_appState.user?.email || ''}\n\nBitte schickt mir die Zahlungsinformationen.\n\nViele Grüße`
);
const mailHref = `mailto:hallo@banyaro.app?subject=${subject}&body=${body}`;
UI.modal.open({
title: `${label} freischalten`,
body: `
<div style="padding:var(--space-2) 0">
<div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-4)">
<div style="font-size:2rem;font-weight:800;color:var(--c-primary)">${price}</div>
<div style="font-size:2rem;font-weight:800;color:${color}">${price}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.5">
Einmaliger Jahresbeitrag<br>Kündigung jederzeit möglich
</div>
@ -173,9 +168,8 @@ window.Page_settings = (() => {
<div style="padding:var(--space-3);border-radius:var(--radius-md);
background:var(--c-surface-raised,rgba(0,0,0,.04));
font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.6">
Aktuell läuft die Freischaltung noch manuell. Schreib uns kurz eine E-Mail
wir schalten deinen Account innerhalb von 24 Stunden frei und schicken
dir die Bankverbindung.
Wir schalten deinen Account manuell frei innerhalb von 24 Stunden.
Wir melden uns mit den Zahlungsdetails per E-Mail.
</div>
</div>`,
footer: `
@ -185,14 +179,32 @@ window.Page_settings = (() => {
color:var(--c-text);font-size:var(--text-sm);cursor:pointer">
Abbrechen
</button>
<a href="${mailHref}"
style="display:inline-flex;align-items:center;gap:var(--space-2);
padding:var(--space-2) var(--space-4);
border-radius:var(--radius-md);border:none;cursor:pointer;
background:var(--c-primary);color:#fff;
font-size:var(--text-sm);font-weight:600;text-decoration:none">
E-Mail senden
</a>`
<button id="upgrade-request-send-btn"
style="padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);
border:none;cursor:pointer;background:${color};color:#fff;
font-size:var(--text-sm);font-weight:600">
Anfrage senden
</button>`
});
document.getElementById('upgrade-request-send-btn')?.addEventListener('click', async () => {
const btn = document.getElementById('upgrade-request-send-btn');
if (!btn) return;
btn.disabled = true;
btn.textContent = 'Wird gesendet…';
try {
const res = await API.auth.upgradeRequest(tier);
UI.modal.close();
if (res.already) {
UI.toast.info('Deine Anfrage liegt bereits vor — wir melden uns bald.');
} else {
UI.toast.success('Anfrage gesendet! Wir melden uns per E-Mail.');
}
} catch (e) {
btn.disabled = false;
btn.textContent = 'Anfrage senden';
UI.toast.error(e.message || 'Fehler beim Senden.');
}
});
}