Refactor: Züchter-Antrag in Upgrade-Flow integriert (SW by-v925)
- /breeder/apply: Dokument jetzt optional (File(None)), kann per Mail nachgereicht werden
- _showUpgradeModal('breeder'): enthält jetzt Zwinger-Formular (Zwingername*, Rasse*,
Verein, Stadt, VDH-Checkbox, optionales Dokument)
→ sendet /breeder/apply + /auth/upgrade-request in einem Schritt
- Züchter-Profil-Karte in Settings: 'Züchter werden'-Button entfernt
→ für neue User ohne Antrag wird die Card vollständig ausgeblendet
→ 'Neu beantragen' bei Ablehnung öffnet jetzt _showUpgradeModal('breeder')
- Verifizierte Züchter: Card unverändert (Profil, Edit, KI-Settings)
This commit is contained in:
parent
a27b8ea5b4
commit
4332b1195e
5 changed files with 118 additions and 38 deletions
|
|
@ -406,7 +406,7 @@ async def serve_media(path: str, request: _Request):
|
|||
raise _HE(404, "Nicht gefunden.")
|
||||
return _media_response(filepath)
|
||||
|
||||
APP_VER = "924" # muss mit APP_VER in app.js übereinstimmen
|
||||
APP_VER = "925" # muss mit APP_VER in app.js übereinstimmen
|
||||
|
||||
@app.get("/.well-known/assetlinks.json")
|
||||
async def assetlinks():
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ async def breeder_apply(
|
|||
stadt: str = Form(...),
|
||||
website: str = Form(""),
|
||||
beschreibung: str = Form(""),
|
||||
dokument: UploadFile = File(...),
|
||||
dokument: UploadFile = File(None),
|
||||
user=Depends(get_current_user),
|
||||
):
|
||||
with db() as conn:
|
||||
|
|
@ -103,28 +103,27 @@ async def breeder_apply(
|
|||
if row["breeder_status"] == "pending":
|
||||
raise HTTPException(400, "Du hast bereits einen offenen Antrag.")
|
||||
|
||||
# Dokument validieren und speichern
|
||||
data = await dokument.read()
|
||||
if len(data) > 10 * 1024 * 1024:
|
||||
raise HTTPException(400, "Dokument zu groß (max. 10 MB).")
|
||||
ext = os.path.splitext(dokument.filename or "")[1].lower()
|
||||
if ext not in (".pdf", ".jpg", ".jpeg", ".png", ".webp"):
|
||||
raise HTTPException(400, "Nur PDF, JPG, PNG oder WebP erlaubt.")
|
||||
|
||||
user_doc_dir = os.path.join(BREEDER_DOCS_DIR, str(user["id"]))
|
||||
os.makedirs(user_doc_dir, exist_ok=True)
|
||||
|
||||
filename = f"antrag_{datetime.now(_TZ).strftime('%Y%m%d_%H%M%S')}{ext}"
|
||||
filepath = os.path.join(user_doc_dir, filename)
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(data)
|
||||
# Dokument optional speichern
|
||||
filepath = None
|
||||
if dokument and dokument.filename:
|
||||
data = await dokument.read()
|
||||
if len(data) > 10 * 1024 * 1024:
|
||||
raise HTTPException(400, "Dokument zu groß (max. 10 MB).")
|
||||
ext = os.path.splitext(dokument.filename)[1].lower()
|
||||
if ext not in (".pdf", ".jpg", ".jpeg", ".png", ".webp"):
|
||||
raise HTTPException(400, "Nur PDF, JPG, PNG oder WebP erlaubt.")
|
||||
user_doc_dir = os.path.join(BREEDER_DOCS_DIR, str(user["id"]))
|
||||
os.makedirs(user_doc_dir, exist_ok=True)
|
||||
filename = f"antrag_{datetime.now(_TZ).strftime('%Y%m%d_%H%M%S')}{ext}"
|
||||
filepath = os.path.join(user_doc_dir, filename)
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
with db() as conn:
|
||||
conn.execute(
|
||||
"UPDATE users SET breeder_status='pending' WHERE id=?",
|
||||
(user["id"],)
|
||||
)
|
||||
# Profil-Entwurf anlegen (oder überschreiben wenn rejected)
|
||||
conn.execute(
|
||||
"INSERT INTO breeder_profiles (user_id, zwingername, rasse_text, verein, vdh_mitglied, stadt, website, beschreibung) "
|
||||
"VALUES (?,?,?,?,?,?,?,?) "
|
||||
|
|
@ -135,10 +134,11 @@ async def breeder_apply(
|
|||
"verified_at=NULL",
|
||||
(user["id"], zwingername, rasse_text, verein, vdh_mitglied, stadt, website, beschreibung)
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT INTO breeder_documents (user_id, dokument_typ, file_path) VALUES (?,?,?)",
|
||||
(user["id"], "antrag", filepath)
|
||||
)
|
||||
if filepath:
|
||||
conn.execute(
|
||||
"INSERT INTO breeder_documents (user_id, dokument_typ, file_path) VALUES (?,?,?)",
|
||||
(user["id"], "antrag", filepath)
|
||||
)
|
||||
|
||||
# Admin benachrichtigen
|
||||
admin_body = f"""
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '924'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '925'; // ← 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
|
||||
|
|
|
|||
|
|
@ -152,6 +152,61 @@ window.Page_settings = (() => {
|
|||
`<li style="padding:var(--space-1) 0;font-size:var(--text-sm)">✓ ${f}</li>`
|
||||
).join('');
|
||||
|
||||
const inputStyle = `width:100%;box-sizing:border-box;padding:var(--space-2) var(--space-3);
|
||||
border:1.5px solid var(--c-border);border-radius:var(--radius-md);
|
||||
font-size:var(--text-sm);font-family:inherit;background:var(--c-surface);color:var(--c-text)`;
|
||||
|
||||
const breederForm = isPro ? '' : `
|
||||
<div style="margin-top:var(--space-4);padding-top:var(--space-4);border-top:1px solid var(--c-border)">
|
||||
<div style="font-size:var(--text-xs);font-weight:700;color:var(--c-text-muted);
|
||||
text-transform:uppercase;letter-spacing:.05em;margin-bottom:var(--space-3)">
|
||||
Dein Zwinger
|
||||
</div>
|
||||
<form id="breeder-upgrade-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:4px">
|
||||
Zwingername <span style="color:var(--c-danger)">*</span>
|
||||
</label>
|
||||
<input name="zwingername" type="text" maxlength="100" required
|
||||
placeholder="z. B. vom Sonnenfeld" style="${inputStyle}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:4px">
|
||||
Rasse <span style="color:var(--c-danger)">*</span>
|
||||
</label>
|
||||
<input name="rasse_text" type="text" maxlength="100" required
|
||||
placeholder="z. B. Labrador Retriever" style="${inputStyle}">
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2)">
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:4px">Zuchtverein</label>
|
||||
<input name="verein" type="text" maxlength="100"
|
||||
placeholder="z. B. VDH, BCD" style="${inputStyle}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:4px">Stadt</label>
|
||||
<input name="stadt" type="text" maxlength="80"
|
||||
placeholder="z. B. München" style="${inputStyle}">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2)">
|
||||
<input name="vdh_mitglied" type="checkbox" id="upg-breeder-vdh"
|
||||
style="width:16px;height:16px;cursor:pointer;flex-shrink:0">
|
||||
<label for="upg-breeder-vdh" style="font-size:var(--text-sm);cursor:pointer">VDH-Mitglied</label>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:4px">
|
||||
Dokument hochladen <span style="font-weight:400;color:var(--c-text-muted)">(optional)</span>
|
||||
</label>
|
||||
<input name="dokument" type="file" accept=".pdf,.jpg,.jpeg,.png,.webp"
|
||||
style="font-size:var(--text-sm);width:100%;box-sizing:border-box">
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:4px">
|
||||
Zuchtbuch, Vereinsausweis o.ä. — kann auch per E-Mail nachgereicht werden
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>`;
|
||||
|
||||
UI.modal.open({
|
||||
title: `${label} freischalten`,
|
||||
body: `
|
||||
|
|
@ -162,7 +217,7 @@ window.Page_settings = (() => {
|
|||
Einmaliger Jahresbeitrag<br>Kündigung jederzeit möglich
|
||||
</div>
|
||||
</div>
|
||||
<ul style="list-style:none;padding:0;margin:0 0 var(--space-4)">
|
||||
<ul style="list-style:none;padding:0;margin:0 0 var(--space-3)">
|
||||
${featureList}
|
||||
</ul>
|
||||
<div style="padding:var(--space-3);border-radius:var(--radius-md);
|
||||
|
|
@ -171,6 +226,7 @@ window.Page_settings = (() => {
|
|||
Wir schalten deinen Account manuell frei — innerhalb von 24 Stunden.
|
||||
Wir melden uns mit den Zahlungsdetails per E-Mail.
|
||||
</div>
|
||||
${breederForm}
|
||||
</div>`,
|
||||
footer: `
|
||||
<button data-modal-close
|
||||
|
|
@ -190,8 +246,35 @@ window.Page_settings = (() => {
|
|||
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…';
|
||||
|
||||
// Züchter: Formular validieren + als FormData senden
|
||||
if (!isPro) {
|
||||
const form = document.getElementById('breeder-upgrade-form');
|
||||
if (form && !form.reportValidity()) return;
|
||||
if (form) {
|
||||
const fd = new FormData(form);
|
||||
fd.set('vdh_mitglied', form.querySelector('[name="vdh_mitglied"]').checked ? '1' : '0');
|
||||
// Pflichtfelder aus Form übernehmen falls leer → leere Strings senden
|
||||
if (!fd.get('verein')) fd.set('verein', '');
|
||||
if (!fd.get('stadt')) fd.set('stadt', '');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird gesendet…';
|
||||
try {
|
||||
await API.breeder.apply(fd);
|
||||
} catch (e) {
|
||||
if (!e.message?.includes('bereits')) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Anfrage senden';
|
||||
UI.toast.error(e.message || 'Fehler beim Einreichen.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird gesendet…';
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await API.auth.upgradeRequest(tier);
|
||||
UI.modal.close();
|
||||
|
|
@ -200,6 +283,7 @@ window.Page_settings = (() => {
|
|||
} else {
|
||||
UI.toast.success('Anfrage gesendet! Wir melden uns per E-Mail.');
|
||||
}
|
||||
if (!isPro) _loadBreederCard();
|
||||
} catch (e) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Anfrage senden';
|
||||
|
|
@ -1300,17 +1384,17 @@ window.Page_settings = (() => {
|
|||
</span>`;
|
||||
actionBlock = `
|
||||
<div style="margin-top:var(--space-3)">
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0 0 var(--space-2)">
|
||||
Du kannst einen neuen Antrag stellen.
|
||||
</p>
|
||||
<button class="btn btn-secondary btn-sm" id="breeder-reapply-btn">
|
||||
${UI.icon('arrow-counter-clockwise')} Neu beantragen
|
||||
</button>
|
||||
</div>`;
|
||||
} else {
|
||||
actionBlock = `
|
||||
<div style="margin-top:var(--space-3)">
|
||||
<button class="btn btn-primary btn-sm" id="breeder-apply-btn">
|
||||
${UI.icon('certificate')} Züchter werden
|
||||
</button>
|
||||
</div>`;
|
||||
// Kein Antrag, kein Profil — Card ausblenden (Upgrade-Flow läuft über Abo & Tarif)
|
||||
slot.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
slot.innerHTML = `
|
||||
|
|
@ -1323,11 +1407,7 @@ window.Page_settings = (() => {
|
|||
</div>`;
|
||||
|
||||
// Button-Handler binden
|
||||
const applyBtn = slot.querySelector('#breeder-apply-btn');
|
||||
const reapplyBtn = slot.querySelector('#breeder-reapply-btn');
|
||||
if (applyBtn || reapplyBtn) {
|
||||
(applyBtn || reapplyBtn).addEventListener('click', () => _openBreederApplyModal());
|
||||
}
|
||||
slot.querySelector('#breeder-reapply-btn')?.addEventListener('click', () => _showUpgradeModal('breeder'));
|
||||
|
||||
slot.querySelector('#breeder-edit-profile-btn')?.addEventListener('click', () =>
|
||||
_openBreederEditModal(profile)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Offline-Cache + Push Notifications + Tile-Cache
|
||||
============================================================ */
|
||||
|
||||
const CACHE_VERSION = 'by-v924';
|
||||
const CACHE_VERSION = 'by-v925';
|
||||
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