diff --git a/MARKETING.md b/MARKETING.md
index 72078eb..d232fe2 100644
--- a/MARKETING.md
+++ b/MARKETING.md
@@ -14,8 +14,7 @@ _Stand: 2026-06-03_
| Lokal (Ebersberg) | ⬜ offen | Tierärzte, Hundeschulen, Futterläden, Tierheim |
| Online-Communities | ⬜ offen | FB-Gruppen Landkreis EBE + nebenan.de |
| Empfehlung / Referral | 🟡 Infra da (`referral_code`) | Empfehlungs-QR + Tracking sichtbar machen |
-| Partner-Programm | 🟢 Infra komplett (v1265, 07.06.) | Partner einladen! Showcase `#partner`, Pro gratis, Partner-Dashboard, QR-Kontingente (Druck-PDF) mit Einzel-Code-Tracking, Dank-Mails mit Statistik, Pause-Notbremse für geleakte Codes. Onboarding: Admin → Code anlegen → Partner-Badge → Besitzer zuordnen |
-| Influencer | 🟡 2 Runden (Mai), kaum Resonanz | Runde 3 erst ab ~50 aktiven Usern — jetzt mit Partner-Paket als konkretem Angebot |
+| Influencer | 🟡 2 Runden (Mai), kaum Resonanz | Runde 3 erst ab ~50 aktiven Usern |
| Presse / Blogs | 🟡 1 Runde, kaum Resonanz | keine Massenwelle; Nische zuerst |
| Verzeichnisse / Listings | ⬜ offen | Product Hunt, PWA-Dirs, Google Business EBE |
| SEO / KI-Auffindbarkeit | 🟡 technisch optimiert | Backlinks (Blog-Testberichte) |
diff --git a/VERSION b/VERSION
index 4c8735e..3420149 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1266
\ No newline at end of file
+1265
\ No newline at end of file
diff --git a/backend/routes/partner.py b/backend/routes/partner.py
index 7690352..115517b 100644
--- a/backend/routes/partner.py
+++ b/backend/routes/partner.py
@@ -48,28 +48,6 @@ def list_partner_codes(user=Depends(require_admin)):
return [dict(r) for r in rows]
-@router.get("/admin/partner/codes/{code_id}/registrations")
-def code_registrations(code_id: int, user=Depends(require_admin)):
- """ALLE Einlösungen eines Partner-Codes — mit Kanal (QR-Sticker vs. Link/manuell).
- Admin-only (personenbezogene Daten)."""
- with db() as conn:
- if not conn.execute(
- "SELECT id FROM partner_codes WHERE id=?", (code_id,)
- ).fetchone():
- raise HTTPException(404, "Partner-Code nicht gefunden.")
- rows = conn.execute(
- """SELECT u.id, u.name, u.email, u.email_verified, u.created_at,
- q.seq AS qr_seq, b.label AS qr_batch_label
- FROM users u
- LEFT JOIN partner_qr_codes q ON q.token = u.referred_qr
- LEFT JOIN partner_qr_batches b ON b.id = q.batch_id
- WHERE u.referred_by = ?
- ORDER BY u.created_at DESC""",
- (-code_id,)
- ).fetchall()
- return [dict(r) for r in rows]
-
-
@router.post("/admin/partner/codes/{code_id}/toggle")
def toggle_partner_code(code_id: int, user=Depends(require_admin)):
"""Notbremse: Code pausieren/reaktivieren (z. B. wenn er im Internet kursiert).
diff --git a/backend/static/index.html b/backend/static/index.html
index 80ab2df..4d30aec 100644
--- a/backend/static/index.html
+++ b/backend/static/index.html
@@ -86,14 +86,14 @@
Ban Yaro
-
+
-
-
-
-
-
+
+
+
+
+
@@ -511,10 +511,6 @@
-
-
@@ -620,11 +616,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -634,7 +630,7 @@
-
+
diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index 9b433b3..1903e4a 100644
--- a/backend/static/js/app.js
+++ b/backend/static/js/app.js
@@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
-const APP_VER = '1266'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '1265'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
window.APP_VERSION = APP_VERSION;
@@ -73,7 +73,6 @@ const App = (() => {
notifications: { title: 'Aktuelles', module: null, requiresAuth: true },
breeder: { title: 'Züchter-Profil', module: null },
'breeder-editor': { title: 'Profil bearbeiten', module: null, requiresAuth: true },
- 'breeder-dashboard': { title: 'Züchter-Bereich', module: null, requiresAuth: true },
litters: { title: 'Wurfverwaltung', module: null, requiresAuth: true },
wurfboerse: { title: 'Wurfbörse', module: null },
zuchthunde: { title: 'Zuchtkartei', module: null, requiresAuth: true },
diff --git a/backend/static/js/pages/admin.js b/backend/static/js/pages/admin.js
index 21120e7..b07b51f 100644
--- a/backend/static/js/pages/admin.js
+++ b/backend/static/js/pages/admin.js
@@ -2381,10 +2381,6 @@ window.Page_admin = (() => {
${c.grants_founder ? '✓' : '—'}
- ${c.uses > 0 ? `
- ` : ''}
|
-
- |
- Lädt…
- |
-
`).join('')}
`
@@ -2580,38 +2571,6 @@ window.Page_admin = (() => {
`;
- // Alle Einlösungen eines Codes (lazy, .hidden via classList) — mit Kanal-Spalte
- el.querySelectorAll('.adm-code-regs').forEach(btn => {
- btn.addEventListener('click', async () => {
- const row = el.querySelector(`#adm-code-regs-${btn.dataset.id}`);
- if (!row) return;
- row.classList.toggle('hidden');
- if (row.classList.contains('hidden') || row.dataset.loaded === '1') return;
- try {
- const regs = await API.get(`/admin/partner/codes/${btn.dataset.id}/registrations`);
- row.dataset.loaded = '1';
- const cell = row.querySelector('td');
- cell.innerHTML = !regs.length
- ? `Keine Accounts.
`
- : regs.map(u => `
-
-
- ${UI.escape(u.name)}
- · ${UI.escape(u.email)}
-
-
- ${u.qr_seq ? `QR #${u.qr_seq}` : 'Link/manuell'}
-
-
${(u.created_at || '').slice(0, 16).replace(' ', ' · ')}
- ${u.email_verified
- ? `
✓ bestätigt`
- : `
⏳ unbestätigt`}
-
`).join('');
- } catch (err) { UI.toast.error(err.message); }
- });
- });
-
// Code pausieren/aktivieren (Notbremse bei geleakten Codes)
el.querySelectorAll('.adm-toggle-code').forEach(btn => {
btn.addEventListener('click', async () => {
diff --git a/backend/static/js/pages/breeder-dashboard.js b/backend/static/js/pages/breeder-dashboard.js
deleted file mode 100644
index 4fde179..0000000
--- a/backend/static/js/pages/breeder-dashboard.js
+++ /dev/null
@@ -1,129 +0,0 @@
-/* ============================================================
- BAN YARO — Züchter-Bereich
- Hub für Züchter: Profil-Status, Wurfverwaltung, Zuchtkartei.
- (Läufigkeit bleibt bewusst als eigener Chip in der HUND-Welt.)
- ============================================================ */
-
-window.Page_breeder_dashboard = (() => {
-
- let _container = null;
-
- async function init(container) {
- _container = container;
- _render();
- await _load();
- }
-
- function refresh() { _load(); }
- function onDogChange() {}
-
- function _render() {
- _container.innerHTML = `
-
-
-
- ${UI.icon('certificate')} Züchter-Bereich
-
-
- Dein Zwinger, deine Würfe, deine Zuchthunde.
-
-
-
-
- `;
- }
-
- async function _load() {
- const el = _container.querySelector('#bd-content');
- try {
- const [status, litters, hunde] = await Promise.all([
- API.breeder.status().catch(() => null),
- API.litters.myList().catch(() => []),
- API.zuchthunde.list().catch(() => []),
- ]);
- el.innerHTML = _renderHub(status, litters || [], hunde || []);
- _bindEvents(el);
- } catch (e) {
- el.innerHTML = `${UI.escape(e.message || 'Fehler beim Laden.')}
`;
- }
- }
-
- function _renderHub(status, litters, hunde) {
- const profile = status?.profile;
- const isBreeder = status?.rolle === 'breeder' || status?.rolle === 'admin';
- if (!isBreeder) {
- return `
-
-
- Der Züchter-Bereich ist für verifizierte Züchter.
- Den Antrag findest du in den Einstellungen.
-
-
`;
- }
-
- return `
-
-
-
-
-
Mein Zwinger
-
${UI.escape(profile?.zwingername || 'Noch kein Profil angelegt')}
- ${profile?.rasse_text ? `
${UI.escape(profile.rasse_text)}
` : ''}
-
- ${UI.icon('check-circle')} Verifizierter Züchter
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Wurfverwaltung
-
${litters.length} ${litters.length === 1 ? 'Wurf' : 'Würfe'} · Welpen, Gewichte, Kaufverträge
-
-
-
-
-
-
-
-
-
-
-
-
-
Zuchtkartei
-
${hunde.length} ${hunde.length === 1 ? 'Zuchthund' : 'Zuchthunde'} · Stammbaum, Genetik, Titel
-
-
-
-
-
-
- ${UI.icon('info')} Läufigkeit & Trächtigkeit findest du wie gewohnt in der HUND-Welt.
-
- `;
- }
-
- function _bindEvents(el) {
- el.querySelectorAll('[data-bd-nav]').forEach(btn => {
- btn.addEventListener('click', () => App.navigate(btn.dataset.bdNav));
- });
- }
-
- return { init, refresh, onDogChange };
-
-})();
diff --git a/backend/static/js/pages/settings.js b/backend/static/js/pages/settings.js
index f4bc01d..66d9af0 100644
--- a/backend/static/js/pages/settings.js
+++ b/backend/static/js/pages/settings.js
@@ -665,6 +665,25 @@ window.Page_settings = (() => {
+ ${u.is_partner ? `
+
+
+
+
+
+ Als Partner hast du vollen Pro-Zugang und eine öffentliche Karte auf der
+ Partner-Seite. Deine Zahlen und QR-Codes findest du im Partner-Bereich.
+
+
+
+
+
+
+
` : ''}
@@ -1662,6 +1681,10 @@ window.Page_settings = (() => {
_loadReferral();
_loadBreederCard();
+ document.getElementById('settings-partner-dashboard-btn')
+ ?.addEventListener('click', () => App.navigate('partner-dashboard'));
+ document.getElementById('settings-partner-profile-btn')
+ ?.addEventListener('click', () => App.navigate('partner-profil'));
}
// ----------------------------------------------------------
diff --git a/backend/static/js/worlds.js b/backend/static/js/worlds.js
index 936b348..e54f867 100644
--- a/backend/static/js/worlds.js
+++ b/backend/static/js/worlds.js
@@ -570,9 +570,10 @@ window.Worlds = (() => {
{ icon:'sparkle', label:'Jobs', page:'jobs' },
{ icon:'book-open', label:'Knigge', page:'knigge' },
{ icon:'film-slate', label:'Filme', page:'movies' },
- { icon:'certificate', label:'Züchter', page:'breeder-dashboard', role:'breeder',
- fab:[{ icon:'notebook', color:'#10B981', label:'Wurf anlegen', sub:'Neuen Wurf eintragen', page:'litters', action:'openNew' },
- { icon:'tree-structure', color:'#8B5CF6', label:'Zuchthund eintragen', sub:'Neuen Hund anlegen', page:'zuchthunde', action:'openNew' }] },
+ { icon:'tree-structure', label:'Zuchtkartei', page:'zuchthunde', role:'breeder',
+ fab:[{ icon:'tree-structure', color:'#8B5CF6', label:'Zuchthund eintragen', sub:'Neuen Hund anlegen', page:'zuchthunde', action:'openNew' }] },
+ { icon:'notebook', label:'Wurfverw.', page:'litters', role:'breeder',
+ fab:[{ icon:'notebook', color:'#10B981', label:'Wurf anlegen', sub:'Neuen Wurf eintragen', page:'litters', action:'openNew' }] },
{ icon:'thermometer', label:'Läufigkeit', page:'laeufi', role:'breeder' },
{ icon:'sparkle', label:'Social', page:'social', role:'social',
fab:[{ icon:'sparkle', color:'#EC4899', label:'Social-Post', sub:'Beitrag erstellen', page:'social', action:'openNew' }] },
@@ -589,7 +590,7 @@ window.Worlds = (() => {
const _DEFAULT_CONFIG = {
jetzt: ['notes','expenses','erste-hilfe','playdate','chat','wetter','social','moderation','partner-dashboard','admin'],
hund: ['diary','health','uebungen','trainingsplaene','adoption','sitting','wiki','wurfboerse',
- 'breeder-dashboard','laeufi','ernaehrung','personality'],
+ 'litters','zuchthunde','laeufi','ernaehrung','personality'],
welt: ['map','forum','friends','walks','poison','recalls','lost','routes','events',
'jobs','knigge','movies','reise'],
};
diff --git a/backend/static/landing.html b/backend/static/landing.html
index 9ead496..88ec792 100644
--- a/backend/static/landing.html
+++ b/backend/static/landing.html
@@ -4,7 +4,7 @@
-
+
Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz
diff --git a/backend/static/sw.js b/backend/static/sw.js
index 46913ce..3059fa7 100644
--- a/backend/static/sw.js
+++ b/backend/static/sw.js
@@ -4,7 +4,7 @@
============================================================ */
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
-const VER = '1266';
+const VER = '1265';
const CACHE_VERSION = `by-v${VER}`;
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
diff --git a/tests/test_partner_qr.py b/tests/test_partner_qr.py
index 6dfbac7..73c2994 100644
--- a/tests/test_partner_qr.py
+++ b/tests/test_partner_qr.py
@@ -125,30 +125,6 @@ def test_registration_with_qr_only(client, admin):
assert row["referred_qr"] == token
-def test_code_registrations_with_channel(client, admin):
- """Admin-Liste aller Code-Einloesungen unterscheidet QR-Sticker und Link/manuell."""
- code = _create_code(client, admin)
- batch = _create_batch(client, admin, code["id"], quantity=1)
- token = _batch_tokens(batch["id"])[0]
-
- # 1x via QR, 1x via Code direkt
- client.post("/api/auth/register", json={
- "email": f"ch1-{secrets.token_hex(4)}@example.com", "password": "QrTest1234!",
- "name": f"ch1{secrets.token_hex(3)}", "ref_code": code["code"], "qr_token": token,
- })
- client.post("/api/auth/register", json={
- "email": f"ch2-{secrets.token_hex(4)}@example.com", "password": "QrTest1234!",
- "name": f"ch2{secrets.token_hex(3)}", "ref_code": code["code"],
- })
-
- r = client.get(f"/api/admin/partner/codes/{code['id']}/registrations", headers=admin["headers"])
- assert r.status_code == 200
- regs = r.json()
- assert len(regs) == 2
- channels = {(x["qr_seq"] or 0) for x in regs}
- assert channels == {0, 1} # einer ohne QR (None), einer über Sticker #1
-
-
def test_paused_code_not_redeemable(client, admin):
"""Pausierter Code (Notbremse) -> keine Einloesung, Info-Endpoint 404; reaktivierbar."""
code = _create_code(client, admin)