Feature: Abo-Kündigung + Ablaufdatum + Dog-Auswahl nach Downgrade (SW by-v945)
This commit is contained in:
parent
44b3fba191
commit
3b666c545f
10 changed files with 341 additions and 11 deletions
|
|
@ -127,6 +127,12 @@ const API = (() => {
|
|||
upgradeRequest(tier, message) {
|
||||
return post('/auth/upgrade-request', { tier, message });
|
||||
},
|
||||
cancelSubscription() {
|
||||
return post('/auth/subscription/cancel', {});
|
||||
},
|
||||
selectPrimaryDog(dog_id) {
|
||||
return post('/auth/subscription/select-dog', { dog_id });
|
||||
},
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '944'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '945'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt
|
||||
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
||||
// Cache-Bust-Parameter nach Update-Reload sofort entfernen
|
||||
|
|
@ -567,6 +567,11 @@ const App = (() => {
|
|||
navigate('onboarding');
|
||||
}
|
||||
|
||||
// Abo abgelaufen mit mehreren Hunden → Haupthund auswählen
|
||||
if (state.user.needs_dog_selection && state.dogs.length > 1) {
|
||||
_showDogSelectionModal();
|
||||
}
|
||||
|
||||
// Theme aus DB-Profil übernehmen (überschreibt localStorage-Wert)
|
||||
_applyUserTheme(state.user);
|
||||
|
||||
|
|
@ -668,6 +673,57 @@ const App = (() => {
|
|||
document.getElementById('meta-theme-color')?.setAttribute('content', isDark ? '#0f1623' : '#C4843A');
|
||||
}
|
||||
|
||||
function _showDogSelectionModal() {
|
||||
const dogs = state.dogs;
|
||||
const optionHtml = dogs.map(d => `
|
||||
<label style="display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);
|
||||
border-radius:var(--radius-md);border:1.5px solid var(--c-border);
|
||||
cursor:pointer;margin-bottom:var(--space-2)">
|
||||
<input type="radio" name="select-dog" value="${d.id}" style="width:18px;height:18px">
|
||||
${d.foto_url
|
||||
? `<img src="${UI.escape(d.foto_url)}" style="width:40px;height:40px;border-radius:50%;object-fit:cover">`
|
||||
: `<div style="width:40px;height:40px;border-radius:50%;background:var(--c-border);display:flex;align-items:center;justify-content:center">🐕</div>`}
|
||||
<div>
|
||||
<div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(d.name)}</div>
|
||||
${d.rasse ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted)">${UI.escape(d.rasse)}</div>` : ''}
|
||||
</div>
|
||||
</label>`).join('');
|
||||
|
||||
UI.modal.open({
|
||||
title: 'Haupthund auswählen',
|
||||
body: `
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin-bottom:var(--space-4)">
|
||||
Dein Abo ist ausgelaufen. Wähle einen Haupthund für deinen kostenlosen Account.
|
||||
Alle anderen Hunde-Profile bleiben vollständig gespeichert — du kannst sie nach
|
||||
einem erneuten Upgrade wieder aktivieren.
|
||||
</p>
|
||||
<form id="dog-select-form">${optionHtml}</form>`,
|
||||
footer: `
|
||||
<button id="dog-select-confirm" class="btn btn-primary" style="width:100%">
|
||||
Auswahl bestätigen
|
||||
</button>`
|
||||
});
|
||||
|
||||
document.getElementById('dog-select-confirm')?.addEventListener('click', async () => {
|
||||
const chosen = document.querySelector('[name="select-dog"]:checked')?.value;
|
||||
if (!chosen) { UI.toast.warning('Bitte einen Hund auswählen.'); return; }
|
||||
const btn = document.getElementById('dog-select-confirm');
|
||||
btn.disabled = true; btn.textContent = '…';
|
||||
try {
|
||||
await API.auth.selectPrimaryDog(parseInt(chosen));
|
||||
state.user.needs_dog_selection = 0;
|
||||
state.activeDog = state.dogs.find(d => String(d.id) === chosen) || state.dogs[0];
|
||||
localStorage.setItem('by_active_dog', String(state.activeDog.id));
|
||||
UI.modal.close();
|
||||
UI.toast.success('Haupthund festgelegt.');
|
||||
_renderDogSwitcher();
|
||||
} catch (e) {
|
||||
btn.disabled = false; btn.textContent = 'Auswahl bestätigen';
|
||||
UI.toast.error(e.message || 'Fehler.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _showAndroidBetaBanner() {
|
||||
// Nur auf Android, nur einmalig, nur für eingeloggte Nutzer
|
||||
if (!/android/i.test(navigator.userAgent)) return;
|
||||
|
|
|
|||
|
|
@ -104,18 +104,43 @@ window.Page_settings = (() => {
|
|||
<span style="font-size:var(--text-xs);font-weight:400;opacity:.9">${price}</span>
|
||||
</button>`;
|
||||
|
||||
const expires = u.subscription_expires_at;
|
||||
const cancelled = u.subscription_cancelled_at;
|
||||
const expiresDate = expires ? new Date(expires).toLocaleDateString('de-DE', {day:'numeric',month:'long',year:'numeric'}) : null;
|
||||
const isPaid = (isPro || isBreeder) && !tier.endsWith('_test') && !isAdmin;
|
||||
|
||||
const _expiryInfo = () => {
|
||||
if (!isPaid || !expiresDate) return '';
|
||||
const color = cancelled ? '#e65100' : 'var(--c-text-secondary)';
|
||||
const text = cancelled
|
||||
? `Gekündigt — läuft bis ${expiresDate}`
|
||||
: `Aktiv bis ${expiresDate}`;
|
||||
return `<div style="font-size:var(--text-xs);color:${color};margin-top:var(--space-1)">${text}</div>`;
|
||||
};
|
||||
|
||||
const _cancelBtn = () => {
|
||||
if (!isPaid || cancelled) return '';
|
||||
return `<button id="settings-cancel-sub-btn"
|
||||
style="margin-top:var(--space-3);padding:var(--space-2) var(--space-3);
|
||||
border-radius:var(--radius-md);border:1px solid var(--c-border);
|
||||
background:transparent;color:var(--c-text-secondary);
|
||||
font-size:var(--text-xs);cursor:pointer">
|
||||
Abo kündigen
|
||||
</button>`;
|
||||
};
|
||||
|
||||
let statusHtml = '';
|
||||
let actionsHtml = '';
|
||||
|
||||
if (isAdmin) {
|
||||
statusHtml = _badge('Admin', '#6366f1');
|
||||
} else if (isBreeder) {
|
||||
statusHtml = _badge('Züchter aktiv', '#C4843A');
|
||||
statusHtml = _badge(cancelled ? 'Züchter (gekündigt)' : 'Züchter aktiv', '#C4843A');
|
||||
} else if (isPro) {
|
||||
statusHtml = _badge('Pro aktiv', '#16a34a');
|
||||
statusHtml = _badge(cancelled ? 'Pro (gekündigt)' : 'Pro aktiv', '#16a34a');
|
||||
actionsHtml = `
|
||||
<div style="margin-top:var(--space-3);display:flex;gap:var(--space-2);flex-wrap:wrap">
|
||||
${_upgradeBtn('settings-upgrade-breeder-btn','Züchter werden','49 €/Jahr','#C4843A')}
|
||||
${!cancelled ? _upgradeBtn('settings-upgrade-breeder-btn','Züchter werden','49 €/Jahr','#C4843A') : ''}
|
||||
</div>`;
|
||||
} else {
|
||||
statusHtml = _badge('Kostenlos', '#888');
|
||||
|
|
@ -134,7 +159,9 @@ window.Page_settings = (() => {
|
|||
<span style="font-size:var(--text-sm);color:var(--c-text-secondary)">Aktueller Tarif:</span>
|
||||
${statusHtml}
|
||||
</div>
|
||||
${_expiryInfo()}
|
||||
${actionsHtml}
|
||||
${_cancelBtn()}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -340,6 +367,71 @@ window.Page_settings = (() => {
|
|||
});
|
||||
}
|
||||
|
||||
function _showCancelModal() {
|
||||
const u = _appState.user;
|
||||
const tier = u?.subscription_tier || 'standard';
|
||||
const label = { pro: 'Ban Yaro Pro', breeder: 'Züchter' }[tier] || tier;
|
||||
const expires = u?.subscription_expires_at;
|
||||
const expiresDate = expires
|
||||
? new Date(expires).toLocaleDateString('de-DE', {day:'numeric',month:'long',year:'numeric'})
|
||||
: null;
|
||||
|
||||
UI.modal.open({
|
||||
title: `${label} kündigen`,
|
||||
body: `
|
||||
<div style="padding:var(--space-2) 0">
|
||||
${expiresDate ? `
|
||||
<div style="padding:var(--space-3);border-radius:var(--radius-md);
|
||||
background:rgba(234,88,12,.08);border:1px solid rgba(234,88,12,.2);
|
||||
margin-bottom:var(--space-4);font-size:var(--text-sm)">
|
||||
Dein Abo läuft noch bis <strong>${expiresDate}</strong> — du hast bis dahin vollen Zugriff.
|
||||
</div>` : ''}
|
||||
<div style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.6;
|
||||
display:flex;flex-direction:column;gap:var(--space-2)">
|
||||
<div>✓ Alle deine Daten (Tagebuch, Gesundheit, Notizen) bleiben vollständig erhalten</div>
|
||||
<div>✓ Deine Hunde-Profile bleiben gespeichert</div>
|
||||
<div>✓ Du kannst jederzeit wieder upgraden</div>
|
||||
${_appState.dogs?.length > 1
|
||||
? `<div style="color:var(--c-warning,#f59e0b)">⚠ Du hast mehrere Hunde — nach dem Ablauf wählst du einen als Haupthund</div>`
|
||||
: ''}
|
||||
</div>
|
||||
</div>`,
|
||||
footer: `
|
||||
<button data-modal-close
|
||||
style="padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);
|
||||
border:1.5px solid var(--c-border);background:transparent;
|
||||
color:var(--c-text);font-size:var(--text-sm);cursor:pointer">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button id="cancel-sub-confirm-btn"
|
||||
style="padding:var(--space-2) var(--space-4);border-radius:var(--radius-md);
|
||||
border:none;cursor:pointer;background:var(--c-danger);color:#fff;
|
||||
font-size:var(--text-sm);font-weight:600">
|
||||
Jetzt kündigen
|
||||
</button>`
|
||||
});
|
||||
|
||||
document.getElementById('cancel-sub-confirm-btn')?.addEventListener('click', async () => {
|
||||
const btn = document.getElementById('cancel-sub-confirm-btn');
|
||||
if (!btn) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '…';
|
||||
try {
|
||||
await API.auth.cancelSubscription();
|
||||
// User-State aktualisieren
|
||||
const fresh = await API.auth.me();
|
||||
Object.assign(_appState.user, fresh);
|
||||
UI.modal.close();
|
||||
UI.toast.success('Kündigung bestätigt. Eine Bestätigungsmail wurde gesendet.');
|
||||
_render();
|
||||
} catch (e) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Jetzt kündigen';
|
||||
UI.toast.error(e.message || 'Fehler beim Kündigen.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// RENDER
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -1102,6 +1194,9 @@ window.Page_settings = (() => {
|
|||
document.getElementById('settings-upgrade-breeder-btn')?.addEventListener('click', () => {
|
||||
_showUpgradeModal('breeder');
|
||||
});
|
||||
document.getElementById('settings-cancel-sub-btn')?.addEventListener('click', () => {
|
||||
_showCancelModal();
|
||||
});
|
||||
|
||||
document.getElementById('settings-worlds-btn')?.addEventListener('click', () => {
|
||||
if (window.Worlds?._openConfigModal) window.Worlds._openConfigModal();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Offline-Cache + Push Notifications + Tile-Cache
|
||||
============================================================ */
|
||||
|
||||
const CACHE_VERSION = 'by-v944';
|
||||
const CACHE_VERSION = 'by-v945';
|
||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue