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.")
|
raise _HE(404, "Nicht gefunden.")
|
||||||
return _media_response(filepath)
|
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")
|
@app.get("/.well-known/assetlinks.json")
|
||||||
async def assetlinks():
|
async def assetlinks():
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ async def breeder_apply(
|
||||||
stadt: str = Form(...),
|
stadt: str = Form(...),
|
||||||
website: str = Form(""),
|
website: str = Form(""),
|
||||||
beschreibung: str = Form(""),
|
beschreibung: str = Form(""),
|
||||||
dokument: UploadFile = File(...),
|
dokument: UploadFile = File(None),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
):
|
):
|
||||||
with db() as conn:
|
with db() as conn:
|
||||||
|
|
@ -103,28 +103,27 @@ async def breeder_apply(
|
||||||
if row["breeder_status"] == "pending":
|
if row["breeder_status"] == "pending":
|
||||||
raise HTTPException(400, "Du hast bereits einen offenen Antrag.")
|
raise HTTPException(400, "Du hast bereits einen offenen Antrag.")
|
||||||
|
|
||||||
# Dokument validieren und speichern
|
# Dokument optional speichern
|
||||||
data = await dokument.read()
|
filepath = None
|
||||||
if len(data) > 10 * 1024 * 1024:
|
if dokument and dokument.filename:
|
||||||
raise HTTPException(400, "Dokument zu groß (max. 10 MB).")
|
data = await dokument.read()
|
||||||
ext = os.path.splitext(dokument.filename or "")[1].lower()
|
if len(data) > 10 * 1024 * 1024:
|
||||||
if ext not in (".pdf", ".jpg", ".jpeg", ".png", ".webp"):
|
raise HTTPException(400, "Dokument zu groß (max. 10 MB).")
|
||||||
raise HTTPException(400, "Nur PDF, JPG, PNG oder WebP erlaubt.")
|
ext = os.path.splitext(dokument.filename)[1].lower()
|
||||||
|
if ext not in (".pdf", ".jpg", ".jpeg", ".png", ".webp"):
|
||||||
user_doc_dir = os.path.join(BREEDER_DOCS_DIR, str(user["id"]))
|
raise HTTPException(400, "Nur PDF, JPG, PNG oder WebP erlaubt.")
|
||||||
os.makedirs(user_doc_dir, exist_ok=True)
|
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}"
|
filename = f"antrag_{datetime.now(_TZ).strftime('%Y%m%d_%H%M%S')}{ext}"
|
||||||
filepath = os.path.join(user_doc_dir, filename)
|
filepath = os.path.join(user_doc_dir, filename)
|
||||||
with open(filepath, "wb") as f:
|
with open(filepath, "wb") as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
|
|
||||||
with db() as conn:
|
with db() as conn:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE users SET breeder_status='pending' WHERE id=?",
|
"UPDATE users SET breeder_status='pending' WHERE id=?",
|
||||||
(user["id"],)
|
(user["id"],)
|
||||||
)
|
)
|
||||||
# Profil-Entwurf anlegen (oder überschreiben wenn rejected)
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO breeder_profiles (user_id, zwingername, rasse_text, verein, vdh_mitglied, stadt, website, beschreibung) "
|
"INSERT INTO breeder_profiles (user_id, zwingername, rasse_text, verein, vdh_mitglied, stadt, website, beschreibung) "
|
||||||
"VALUES (?,?,?,?,?,?,?,?) "
|
"VALUES (?,?,?,?,?,?,?,?) "
|
||||||
|
|
@ -135,10 +134,11 @@ async def breeder_apply(
|
||||||
"verified_at=NULL",
|
"verified_at=NULL",
|
||||||
(user["id"], zwingername, rasse_text, verein, vdh_mitglied, stadt, website, beschreibung)
|
(user["id"], zwingername, rasse_text, verein, vdh_mitglied, stadt, website, beschreibung)
|
||||||
)
|
)
|
||||||
conn.execute(
|
if filepath:
|
||||||
"INSERT INTO breeder_documents (user_id, dokument_typ, file_path) VALUES (?,?,?)",
|
conn.execute(
|
||||||
(user["id"], "antrag", filepath)
|
"INSERT INTO breeder_documents (user_id, dokument_typ, file_path) VALUES (?,?,?)",
|
||||||
)
|
(user["id"], "antrag", filepath)
|
||||||
|
)
|
||||||
|
|
||||||
# Admin benachrichtigen
|
# Admin benachrichtigen
|
||||||
admin_body = f"""
|
admin_body = f"""
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Router, State-Management, Navigation, Initialisierung.
|
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 APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt
|
||||||
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
||||||
// Cache-Bust-Parameter nach Update-Reload sofort entfernen
|
// 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>`
|
`<li style="padding:var(--space-1) 0;font-size:var(--text-sm)">✓ ${f}</li>`
|
||||||
).join('');
|
).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({
|
UI.modal.open({
|
||||||
title: `${label} freischalten`,
|
title: `${label} freischalten`,
|
||||||
body: `
|
body: `
|
||||||
|
|
@ -162,7 +217,7 @@ window.Page_settings = (() => {
|
||||||
Einmaliger Jahresbeitrag<br>Kündigung jederzeit möglich
|
Einmaliger Jahresbeitrag<br>Kündigung jederzeit möglich
|
||||||
</div>
|
</div>
|
||||||
</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}
|
${featureList}
|
||||||
</ul>
|
</ul>
|
||||||
<div style="padding:var(--space-3);border-radius:var(--radius-md);
|
<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 schalten deinen Account manuell frei — innerhalb von 24 Stunden.
|
||||||
Wir melden uns mit den Zahlungsdetails per E-Mail.
|
Wir melden uns mit den Zahlungsdetails per E-Mail.
|
||||||
</div>
|
</div>
|
||||||
|
${breederForm}
|
||||||
</div>`,
|
</div>`,
|
||||||
footer: `
|
footer: `
|
||||||
<button data-modal-close
|
<button data-modal-close
|
||||||
|
|
@ -190,8 +246,35 @@ window.Page_settings = (() => {
|
||||||
document.getElementById('upgrade-request-send-btn')?.addEventListener('click', async () => {
|
document.getElementById('upgrade-request-send-btn')?.addEventListener('click', async () => {
|
||||||
const btn = document.getElementById('upgrade-request-send-btn');
|
const btn = document.getElementById('upgrade-request-send-btn');
|
||||||
if (!btn) return;
|
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 {
|
try {
|
||||||
const res = await API.auth.upgradeRequest(tier);
|
const res = await API.auth.upgradeRequest(tier);
|
||||||
UI.modal.close();
|
UI.modal.close();
|
||||||
|
|
@ -200,6 +283,7 @@ window.Page_settings = (() => {
|
||||||
} else {
|
} else {
|
||||||
UI.toast.success('Anfrage gesendet! Wir melden uns per E-Mail.');
|
UI.toast.success('Anfrage gesendet! Wir melden uns per E-Mail.');
|
||||||
}
|
}
|
||||||
|
if (!isPro) _loadBreederCard();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = 'Anfrage senden';
|
btn.textContent = 'Anfrage senden';
|
||||||
|
|
@ -1300,17 +1384,17 @@ window.Page_settings = (() => {
|
||||||
</span>`;
|
</span>`;
|
||||||
actionBlock = `
|
actionBlock = `
|
||||||
<div style="margin-top:var(--space-3)">
|
<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">
|
<button class="btn btn-secondary btn-sm" id="breeder-reapply-btn">
|
||||||
${UI.icon('arrow-counter-clockwise')} Neu beantragen
|
${UI.icon('arrow-counter-clockwise')} Neu beantragen
|
||||||
</button>
|
</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
} else {
|
} else {
|
||||||
actionBlock = `
|
// Kein Antrag, kein Profil — Card ausblenden (Upgrade-Flow läuft über Abo & Tarif)
|
||||||
<div style="margin-top:var(--space-3)">
|
slot.innerHTML = '';
|
||||||
<button class="btn btn-primary btn-sm" id="breeder-apply-btn">
|
return;
|
||||||
${UI.icon('certificate')} Züchter werden
|
|
||||||
</button>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
slot.innerHTML = `
|
slot.innerHTML = `
|
||||||
|
|
@ -1323,11 +1407,7 @@ window.Page_settings = (() => {
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
// Button-Handler binden
|
// Button-Handler binden
|
||||||
const applyBtn = slot.querySelector('#breeder-apply-btn');
|
slot.querySelector('#breeder-reapply-btn')?.addEventListener('click', () => _showUpgradeModal('breeder'));
|
||||||
const reapplyBtn = slot.querySelector('#breeder-reapply-btn');
|
|
||||||
if (applyBtn || reapplyBtn) {
|
|
||||||
(applyBtn || reapplyBtn).addEventListener('click', () => _openBreederApplyModal());
|
|
||||||
}
|
|
||||||
|
|
||||||
slot.querySelector('#breeder-edit-profile-btn')?.addEventListener('click', () =>
|
slot.querySelector('#breeder-edit-profile-btn')?.addEventListener('click', () =>
|
||||||
_openBreederEditModal(profile)
|
_openBreederEditModal(profile)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Offline-Cache + Push Notifications + Tile-Cache
|
Offline-Cache + Push Notifications + Tile-Cache
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const CACHE_VERSION = 'by-v924';
|
const CACHE_VERSION = 'by-v925';
|
||||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||||
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue