From 5f5f3e9271753089122c87d63b9d4c25679b19f7 Mon Sep 17 00:00:00 2001 From: rene Date: Thu, 14 May 2026 09:09:59 +0200 Subject: [PATCH 1/8] =?UTF-8?q?UX:=20Z=C3=BCchter-Karte=20Icon=20heller=20?= =?UTF-8?q?=E2=80=94=20rgba=20.35=20+=20#f5c07a=20f=C3=BCr=20besseren=20Ko?= =?UTF-8?q?ntrast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/static/landing.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/static/landing.html b/backend/static/landing.html index bfa2af9..a4999d6 100644 --- a/backend/static/landing.html +++ b/backend/static/landing.html @@ -555,8 +555,8 @@
-
- +
+

Für Züchter

From 3da4a1b6d7df3dd12e47ff63ac9b96ed8d540bf6 Mon Sep 17 00:00:00 2001 From: rene Date: Thu, 14 May 2026 09:19:05 +0200 Subject: [PATCH 2/8] =?UTF-8?q?Fix:=20Z=C3=BCchter=20Icon=20=E2=80=94=20vo?= =?UTF-8?q?lles=20Orange=20#C4843A=20+=20wei=C3=9Fes=20Icon=20f=C3=BCr=20m?= =?UTF-8?q?aximalen=20Kontrast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/static/landing.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/static/landing.html b/backend/static/landing.html index a4999d6..8ce9912 100644 --- a/backend/static/landing.html +++ b/backend/static/landing.html @@ -555,8 +555,8 @@
-
- +
+

Für Züchter

From 509d4eda2b197cebeff5fc0c4e90e77e92b2d582 Mon Sep 17 00:00:00 2001 From: rene Date: Thu, 14 May 2026 09:24:01 +0200 Subject: [PATCH 3/8] =?UTF-8?q?Fix:=20UMAMI=5FURL=20in=20docker-compose.ym?= =?UTF-8?q?l=20erg=C3=A4nzt=20=E2=80=94=20Analytics-Fehler=20auf=20Admin-S?= =?UTF-8?q?eite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index a3d6772..772bb22 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: - VAPID_PUBLIC_KEY=BMKbFAmpsqJ-eFef_4XJcYpuxPWqBNAoy9buMNnMSa6ijcPzltboHi_YccPKJrUD0isBez-vJIzAgjnLTWkzcC0 - VAPID_PRIVATE_KEY=8PWa9vvwMqtqsJEJGcwmiLhR0_Yl7duVX3wmWiKS878 - VAPID_CONTACT=mailto:admin@banyaro.app + - UMAMI_URL=https://umami.motocamp.de healthcheck: test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/')"] interval: 30s From c5d4e730d9a3cbb9f653b70241cb976346c7844c Mon Sep 17 00:00:00 2001 From: rene Date: Thu, 14 May 2026 09:31:49 +0200 Subject: [PATCH 4/8] =?UTF-8?q?Feature:=20Preise=20live=20=E2=80=94=20Pro?= =?UTF-8?q?=2029=E2=82=AC/Jahr,=20Z=C3=BCchter=2049=E2=82=AC/Jahr=20(Gr?= =?UTF-8?q?=C3=BCnder=2039=E2=82=AC)=20auf=20Landing=20+=20Z=C3=BCchter-Se?= =?UTF-8?q?ite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/static/landing.html | 49 +++++++++++++++++++++++++++--------- backend/static/zuechter.html | 11 ++++++-- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/backend/static/landing.html b/backend/static/landing.html index 8ce9912..768eade 100644 --- a/backend/static/landing.html +++ b/backend/static/landing.html @@ -951,8 +951,10 @@
diff --git a/backend/static/zuechter.html b/backend/static/zuechter.html index 88f5b74..c3ac3b8 100644 --- a/backend/static/zuechter.html +++ b/backend/static/zuechter.html @@ -548,8 +548,15 @@
-

Jetzt kostenlos starten.

-

Züchter-Antrag stellen, Zuchthunde anlegen, ersten Wurf veröffentlichen — alles kostenlos, kein App Store, keine Kreditkarte.

+
+ 🎉 Gründer-Preis: 39 €/Jahr für die ersten 20 Züchter +
+

Jetzt starten.

+

Züchter-Antrag stellen, Zuchthunde anlegen, ersten Wurf veröffentlichen.

+

+ 39 €/Jahr für die ersten 20 Gründer-Züchter · danach 49 €/Jahr · kein App Store · keine Kreditkarte zum Start +

Als Züchter registrieren

Fragen? hallo@banyaro.app

From eaa2e02e888babb9d855c2eafec8ff843eb6f0e5 Mon Sep 17 00:00:00 2001 From: rene Date: Thu, 14 May 2026 09:43:21 +0200 Subject: [PATCH 5/8] =?UTF-8?q?SEO:=20llms.txt=20v1.5.1=20+=20sitemap=20/z?= =?UTF-8?q?uechter=20+=20JSON-LD=20Pricing=20(Pro=2029=E2=82=AC/Z=C3=BCcht?= =?UTF-8?q?er=2049=E2=82=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - llms.txt: Dual-Audience-Positionierung, echte Preise, neue Züchter-Features (Warteliste, Läufigkeit, Wurf-Buchstabe/-Name, Privater Header, Profilfotos), neue URL /zuechter, SW by-v918, Datum 2026-05-14 - landing.html JSON-LD: 3 Offers (kostenlos/Pro 29€/Züchter 49€), 7 neue featureList-Einträge, dateModified 2026-05-14, Beschreibung mit Preisen - zuechter.html JSON-LD: 2 Offers (49€/39€ Gründer), 5 neue Features, dateModified + softwareVersion - sitemap.xml: neue statische Datei (Backup-Referenz, dynamic route in main.py) - main.py sitemap: /zuechter mit priority 0.9 hinzugefügt --- backend/main.py | 7 ++-- backend/static/landing.html | 43 +++++++++++++++++++------ backend/static/llms.txt | 62 +++++++++++++++++++++++++----------- backend/static/sitemap.xml | 46 ++++++++++++++++++++++++++ backend/static/zuechter.html | 27 ++++++++++++++-- 5 files changed, 153 insertions(+), 32 deletions(-) create mode 100644 backend/static/sitemap.xml diff --git a/backend/main.py b/backend/main.py index 097edd7..93009ed 100644 --- a/backend/main.py +++ b/backend/main.py @@ -465,10 +465,11 @@ async def sitemap(): today = date.today().isoformat() urls = [ ("https://banyaro.app/", "weekly", "1.0"), - ("https://banyaro.app/info", "monthly", "0.9"), - ("https://banyaro.app/presse", "monthly", "0.8"), + ("https://banyaro.app/zuechter", "weekly", "0.9"), + ("https://banyaro.app/info", "monthly", "0.8"), + ("https://banyaro.app/presse", "monthly", "0.7"), ("https://banyaro.app/wiki/rassen", "weekly", "0.8"), - ("https://banyaro.app/knigge", "monthly", "0.8"), + ("https://banyaro.app/knigge", "monthly", "0.7"), ("https://banyaro.app/wurfboerse", "daily", "0.8"), ] diff --git a/backend/static/landing.html b/backend/static/landing.html index 768eade..68f7607 100644 --- a/backend/static/landing.html +++ b/backend/static/landing.html @@ -31,19 +31,38 @@ "@type": "MobileApplication", "name": "Ban Yaro", "alternateName": "Ban Yaro — Die Hunde-Plattform", - "description": "Ban Yaro ist die kostenlose, deutschsprachige All-in-One Hunde-App für Hundebesitzer und Züchter. Tagebuch, Impfpass, Wurfbörse, Stammbaum, Inzucht-Koeffizient, Tierschutz-Check, Giftköder-Alarm, Gassi-Community — DSGVO-konform, ohne App Store.", + "description": "Ban Yaro ist die deutschsprachige All-in-One Hunde-Plattform für Hundebesitzer und Züchter. Kostenlos: Tagebuch, Impfpass, Gassi-Community, Giftköder-Alarm. Pro (29 €/Jahr): mehrere Hunde, Ernährung. Züchter (49 €/Jahr): Warteliste, Läufigkeit, Wurfverwaltung, Stammbaum, Inzucht-Koeffizient, KI-Assistent — DSGVO-konform, ohne App Store.", "url": "https://banyaro.app", "applicationCategory": "LifestyleApplication", "applicationSubCategory": "PetApplication", "operatingSystem": "iOS, Android, Web", "inLanguage": "de", "availableOnDevice": "Smartphone, Tablet", - "offers": { - "@type": "Offer", - "price": "0", - "priceCurrency": "EUR", - "availability": "https://schema.org/InStock" - }, + "offers": [ + { + "@type": "Offer", + "name": "Kostenlos", + "price": "0", + "priceCurrency": "EUR", + "availability": "https://schema.org/InStock" + }, + { + "@type": "Offer", + "name": "Ban Yaro Pro", + "price": "29", + "priceCurrency": "EUR", + "availability": "https://schema.org/InStock", + "description": "Mehrere Hunde, Ernährung, erweiterte Karten-Layer" + }, + { + "@type": "Offer", + "name": "Züchter", + "price": "49", + "priceCurrency": "EUR", + "availability": "https://schema.org/InStock", + "description": "Vollständige Züchter-Plattform: Warteliste, Läufigkeit, Wurfverwaltung, Stammbaum, IK-Rechner, KI-Assistent" + } + ], "publisher": { "@type": "Organization", "name": "Ban Yaro", @@ -108,12 +127,18 @@ "DSGVO Datenexport (Art. 20): vollständiger JSON-Download aller eigenen Daten", "Hunde-Persönlichkeitstest mit Trainingstipps", "Reise-Checkliste und EU-Länder-Einreiseregeln", - "Integrierte Hilfe und FAQ ohne App Store" + "Integrierte Hilfe und FAQ ohne App Store", + "Warteliste: Interessenten mit Präferenzen pro Zuchthündin verwalten", + "Läufigkeit und Trächtigkeit: Zykluskalender, Progesterontests, Deckdaten, Meilensteine", + "Wurf-Buchstabe und Wurf-Name für jeden Wurf", + "Privater Züchter-Bereich mit Logo und Zwingername im Header", + "Züchter-Profilfotos und Galerie auf der öffentlichen Visitenkarte", + "Züchter-Kacheln in HUND-Welt mit Z-Badge erkennbar" ], "screenshot": "https://banyaro.app/icons/icon-512.png", "softwareVersion": "1.5.1", "datePublished": "2026-05-01", - "dateModified": "2026-05-12", + "dateModified": "2026-05-14", "areaServed": ["DE", "AT", "CH"], "audience": { "@type": "Audience", diff --git a/backend/static/llms.txt b/backend/static/llms.txt index 65c7a1c..bbc78bd 100644 --- a/backend/static/llms.txt +++ b/backend/static/llms.txt @@ -1,6 +1,6 @@ -# Ban Yaro — Die deutschsprachige Hunde-Plattform +# Ban Yaro — Die deutschsprachige Hunde-Plattform für Hundebesitzer und Züchter # https://banyaro.app -# Letzte Aktualisierung: 2026-05-12 +# Letzte Aktualisierung: 2026-05-14 ## Was ist Ban Yaro? @@ -10,7 +10,11 @@ Kein App Store, kein US-Konzern, DSGVO-konform, selbst gehostet in Deutschland. Das Kern-Versprechen: "Alles rund um deinen Hund — von Welpe bis Opa." -Ban Yaro ist kostenlos nutzbar (Freemium-Modell). Die App ist auf allen Smartphones installierbar +Ban Yaro richtet sich an zwei Zielgruppen, die nahtlos verzahnt sind: +- **Hundebesitzer**: vollständige Alltags-App (Tagebuch, Gesundheit, Training, Gassi-Community) +- **Züchter**: professionelles Zucht-Management direkt in derselben App (Warteliste, Läufigkeit, Wurf, Stammbaum) + +Ban Yaro ist im Freemium-Modell nutzbar. Die App ist auf allen Smartphones installierbar (iOS und Android) direkt über den Browser — ohne App Store. ## Der Name „Ban Yaro" @@ -33,12 +37,12 @@ gegründet, mit eigenem Schutzrecht auf den Namen. - Keine Werbung, keine Datenweitergabe an Dritte, kein Tracking (Umami, cookieless) - Kontakt: hallo@banyaro.app - Keine App-Store-Abhängigkeit: Als PWA direkt installierbar, keine Gatekeeper -- Aktuelle Version: v1.5.1 (Mai 2026), SW by-v885 +- Aktuelle Version: v1.5.1 (Mai 2026), SW by-v918 ## Zielgruppe - Deutschsprachige Hundebesitzer (Deutschland, Österreich, Schweiz) -- Verantwortungsvolle Hundezüchter (VDH und andere Verbände) +- Verantwortungsvolle Hundezüchter (VDH und andere Verbände) — dedizierte Landing Page: https://banyaro.app/zuechter - Welpen-Interessenten und Käufer - Hundeschulen und Hundetrainer - Tierärzte und Praxen @@ -182,21 +186,10 @@ Die Startseite für eingeloggte Nutzer zeigt: - KI lokal: LM Studio (Gemma-4-31B) - KI Cloud: Claude API (claude-sonnet-4-6, Anthropic) -## Monetarisierung - -**Kostenlos:** -- Alle Basis-Features inkl. Züchter-Antrag, Wurfverwaltung, Stammbaum, Tierschutz-Check - -**Züchter-Provision** (geplant): Wurfbörse bleibt für Käufer kostenlos - -**Ban Yaro Plus** (ca. 4,99 €/Monat, in Entwicklung): -- KI-Trainingsplan, erweiterte Statistiken - -**Hundesitting**: 8% Provision - ## Öffentliche Seiten (ohne Login) -- https://banyaro.app — Landing Page +- https://banyaro.app — Landing Page (Hundebesitzer + Züchter) +- https://banyaro.app/zuechter — Dedizierte Landing Page für Züchter - https://banyaro.app/info — Landing Page (Alias) - https://banyaro.app/wiki/rassen — Alle Hunderassen - https://banyaro.app/wiki/rasse/{slug} — Rassen-Detail @@ -250,6 +243,39 @@ Die Startseite für eingeloggte Nutzer zeigt: - **Auth-geschützte Medien**: Tagebuch-, Gesundheits- und Gassi-Fotos sind nur für eingeloggte Nutzer abrufbar — kein öffentlicher Zugriff über URL möglich. - **Datenschutzerklärung v2**: Vollständige Transparenz über KI-Datenübertragungen (Gesundheitsdaten im Cloud-Prompt, Fotos bei Rassenerkennung), OpenWeatherMap und Nominatim ergänzt, Datenexport konkret beschrieben. +## Features ab v1.5.1 — Züchter-Plattform Vollausbau (Mai 2026) + +- **Warteliste**: Interessenten mit Präferenzen (Geschlecht, Farbe, Verwendungszweck) pro Zuchthündin verwalten — mit Status (Interessent / Reserviert / Abgesagt) und Kontaktdaten. +- **Läufigkeit & Trächtigkeit**: Vollständiger Zykluskalender mit Progesterontests (Datum, ng/mL, Labormethode), Deckdaten (Rüde, Methode, Datum) und automatischer Meilensteinberechnung (Geburt, Absetzen, 8-Wochen-Abgabe). +- **Wurf-Buchstabe und -Name**: Jeder Wurf hat einen Rang-Buchstaben (A-Wurf, B-Wurf…) und optional einen freien Namen (z.B. "Vatertags-Wurf"). +- **Privater Züchter-Bereich**: Wurfverwaltung und Zuchtkartei zeigen Züchter-Logo und Zwingername im Header — professionelle, vertrauliche Arbeitsatmosphäre statt generischer App-Ansicht. +- **Züchter-Profilfotos**: Galerie direkt im Züchter-Profil — Fotos hochladen und Reihenfolge verwalten, öffentlich sichtbar auf der Profil-Visitenkarte. +- **Züchter-Profil als Visitenkarte**: Hero-Bereich mit Hintergrund, Hunde-Cards mit HD/ED-Ergebnis-Badges, Gesundheitsstatistik, Fotogalerie — öffentlich abrufbar unter banyaro.app/breeder/{zwingername}. +- **Dedizierte Züchter-Landing-Page**: https://banyaro.app/zuechter mit Erklärung aller Züchter-Features und Pricing. +- **Züchter-Kacheln in HUND-Welt**: Läufigkeit, Wurfverwaltung und Zuchtkartei sind als eigene Kacheln in der HUND-Navigation eingebunden — erkennbar am Z-Badge für Züchter-Features. + +## Monetarisierung + +**Kostenlos (dauerhaft):** +- Alle Basis-Features für Hundebesitzer: Tagebuch, Gesundheit, Gassi, Community, Forum, Wissen +- Züchter-Antrag, Wurfbörse, Stammbaum-Ansicht, Tierschutz-Check, Symptom-Checker +- 1 Hund + +**Ban Yaro Pro — 29 €/Jahr:** +- Mehrere Hunde +- Ernährungsbereich (KI-Berater, BARF-Guide) +- Erweiterte Karten-Layer +- Alle künftigen Pro-Features + +**Züchter-Abo — 49 €/Jahr** (Gründer-Preis: **39 €/Jahr** für die ersten 20 Züchter): +- Gesamte Züchter-Plattform: Wurfverwaltung, Zuchtkartei, Stammbaum, IK-Rechner +- Warteliste, Läufigkeit & Trächtigkeit, Kaufvertrag-Generator +- KI-Züchter-Assistenz (Wurfankündigungen, Paarungsanalyse, Jahresbericht) +- Datenexport (HTML + ODS) +- Verifiziertes Züchter-Profil mit öffentlicher Seite + +**Hundesitting**: 8% Provision (im Vergleich: Rover/Pawshake 20%) + ## Domains - https://banyaro.app (primäre Domain) diff --git a/backend/static/sitemap.xml b/backend/static/sitemap.xml new file mode 100644 index 0000000..b1cdd7a --- /dev/null +++ b/backend/static/sitemap.xml @@ -0,0 +1,46 @@ + + + + + https://banyaro.app/ + 2026-05-14 + weekly + 1.0 + + + + https://banyaro.app/zuechter + 2026-05-14 + weekly + 0.9 + + + + https://banyaro.app/wiki/rassen + 2026-05-14 + monthly + 0.8 + + + + https://banyaro.app/wurfboerse + 2026-05-14 + daily + 0.8 + + + + https://banyaro.app/knigge + 2026-05-01 + monthly + 0.6 + + + + https://banyaro.app/presse + 2026-05-14 + monthly + 0.6 + + + diff --git a/backend/static/zuechter.html b/backend/static/zuechter.html index c3ac3b8..e7e6b39 100644 --- a/backend/static/zuechter.html +++ b/backend/static/zuechter.html @@ -21,14 +21,37 @@ "@context": "https://schema.org", "@type": "SoftwareApplication", "name": "Ban Yaro — Züchter-Tool", - "description": "Digitales Zucht-Management für seriöse Hundezüchter: Stammbaum, Inzuchtkoeffizient nach Wright, Gesundheitstests, Gentests, Wurfverwaltung, Kaufvertrag-Generator, KI-Assistent.", + "description": "Professionelles Zucht-Management für seriöse Hundezüchter direkt in der Ban Yaro App: Warteliste, Läufigkeit und Trächtigkeit, Wurfverwaltung, Stammbaum, Inzuchtkoeffizient, Gesundheitstests, Gentests, KI-Assistent. Ab 49 €/Jahr, Gründer-Preis 39 €/Jahr.", "url": "https://banyaro.app/zuechter", "applicationCategory": "BusinessApplication", "operatingSystem": "iOS, Android, Web", "inLanguage": "de", - "offers": { "@type": "Offer", "price": "0", "priceCurrency": "EUR" }, + "offers": [ + { + "@type": "Offer", + "name": "Züchter-Abo", + "price": "49", + "priceCurrency": "EUR", + "availability": "https://schema.org/InStock", + "description": "Vollständige Züchter-Plattform" + }, + { + "@type": "Offer", + "name": "Gründer-Preis (erste 20 Züchter)", + "price": "39", + "priceCurrency": "EUR", + "availability": "https://schema.org/LimitedAvailability" + } + ], "audience": { "@type": "Audience", "audienceType": "Hundezüchter, VDH-Züchter, Rassehundzüchter" }, + "softwareVersion": "1.5.1", + "dateModified": "2026-05-14", "featureList": [ + "Warteliste: Interessenten mit Präferenzen pro Zuchthündin verwalten", + "Läufigkeit und Trächtigkeit: Zykluskalender, Progesterontests, Deckdaten, automatische Meilensteine", + "Wurf-Buchstabe und Wurf-Name für jeden Wurf", + "Privater Bereich mit Züchter-Logo und Zwingername im Header", + "Züchter-Profilfotos und öffentliche Visitenkarte", "Stammbaum bis 4 Generationen grafisch", "Inzuchtkoeffizient nach Wright mit Ampel-Bewertung", "Probeverpaarung mit IK-Simulation", From d61fd155c5d15e2b4b00e6c036dc1150970f5a08 Mon Sep 17 00:00:00 2001 From: rene Date: Thu, 14 May 2026 09:48:01 +0200 Subject: [PATCH 6/8] =?UTF-8?q?Feature:=20Abo=20&=20Tarif=20in=20Einstellu?= =?UTF-8?q?ngen=20=E2=80=94=20Upgrade-UI=20f=C3=BCr=20Pro=20+=20Z=C3=BCcht?= =?UTF-8?q?er=20(SW=20by-v919)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /api/me gibt subscription_tier jetzt zurück (fehlte im SELECT) - settings.js: "Pro kommt bald" durch echte Abo-Karte ersetzt - Zeigt aktuellen Tarif mit farbigem Badge (Kostenlos/Pro/Züchter/Admin) - Standard-Nutzer: zwei Upgrade-Buttons (Pro 29€/Jahr, Züchter 49€/Jahr) - Pro-Nutzer: Pro-Badge + optionaler Züchter-Upgrade - Züchter/Admin: Status-Badge, keine Upgrade-Buttons - Upgrade-Modal: Features-Liste + ehrlicher Hinweis auf manuelle Freischaltung + mailto-Button mit vorausgefülltem Betreff und Account-E-Mail - SW by-v919, APP_VER 919 --- backend/main.py | 2 +- backend/routes/auth.py | 2 +- backend/static/index.html | 8 +- backend/static/js/app.js | 2 +- backend/static/js/pages/settings.js | 135 ++++++++++++++++++++++++++-- backend/static/sw.js | 2 +- 6 files changed, 136 insertions(+), 15 deletions(-) diff --git a/backend/main.py b/backend/main.py index 93009ed..2588f0c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -406,7 +406,7 @@ async def serve_media(path: str, request: _Request): raise _HE(404, "Nicht gefunden.") return _media_response(filepath) -APP_VER = "918" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "919" # muss mit APP_VER in app.js übereinstimmen @app.get("/.well-known/assetlinks.json") async def assetlinks(): diff --git a/backend/routes/auth.py b/backend/routes/auth.py index a0174e6..9ad43fe 100644 --- a/backend/routes/auth.py +++ b/backend/routes/auth.py @@ -240,7 +240,7 @@ async def me(user=Depends(get_current_user)): profil_sichtbarkeit, avatar_url, created_at, is_founder, is_partner, founder_number, is_founder_pending, notes_ki_enabled, gassi_stunde_push, - preferred_theme + preferred_theme, subscription_tier FROM users WHERE id=?""", (user["id"],) ).fetchone() diff --git a/backend/static/index.html b/backend/static/index.html index 0e13659..d079f75 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -599,10 +599,10 @@ - - - - + + + + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index c6d2189..3e3529d 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 = '918'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '919'; // ← 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 diff --git a/backend/static/js/pages/settings.js b/backend/static/js/pages/settings.js index 8767cc6..f95e235 100644 --- a/backend/static/js/pages/settings.js +++ b/backend/static/js/pages/settings.js @@ -77,6 +77,125 @@ window.Page_settings = (() => { } } + // ---------------------------------------------------------- + // ABO & TARIF + // ---------------------------------------------------------- + function _tierCard(u) { + const tier = u.subscription_tier || 'standard'; + const rolle = u.rolle || 'user'; + const isAdmin = rolle === 'admin' || rolle === 'moderator'; + const isPro = ['pro','pro_test'].includes(tier); + const isBreeder = ['breeder','breeder_test'].includes(tier) || rolle === 'breeder'; + const isStandard = !isAdmin && !isPro && !isBreeder; + + const _badge = (label, color) => + `${label}`; + + const _upgradeBtn = (id, label, price, color) => + ``; + + let statusHtml = ''; + let actionsHtml = ''; + + if (isAdmin) { + statusHtml = _badge('Admin', '#6366f1'); + } else if (isBreeder) { + statusHtml = _badge('Züchter aktiv', '#C4843A'); + } else if (isPro) { + statusHtml = _badge('Pro aktiv', '#16a34a'); + actionsHtml = ` +
+ ${_upgradeBtn('settings-upgrade-breeder-btn','Züchter werden','49 €/Jahr','#C4843A')} +
`; + } else { + statusHtml = _badge('Kostenlos', '#888'); + actionsHtml = ` +
+ ${_upgradeBtn('settings-upgrade-pro-btn','Ban Yaro Pro','29 €/Jahr','#16a34a')} + ${_upgradeBtn('settings-upgrade-breeder-btn','Züchter','49 €/Jahr','#C4843A')} +
`; + } + + return ` +
+
Abo & Tarif
+
+
+ Aktueller Tarif: + ${statusHtml} +
+ ${actionsHtml} +
+
`; + } + + function _showUpgradeModal(tier) { + const isPro = tier === 'pro'; + const label = isPro ? 'Ban Yaro Pro' : 'Züchter'; + const price = isPro ? '29 €/Jahr' : '49 €/Jahr'; + 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']; + + const featureList = features.map(f => + `
  • ✓ ${f}
  • ` + ).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: ` +
    +
    +
    ${price}
    +
    + Einmaliger Jahresbeitrag
    Kündigung jederzeit möglich +
    +
    +
      + ${featureList} +
    +
    + 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. +
    +
    `, + footer: ` + + + E-Mail senden + ` + }); + } + // ---------------------------------------------------------- // RENDER // ---------------------------------------------------------- @@ -276,13 +395,6 @@ window.Page_settings = (() => { Feedback geben
    - ${!_appState.user?.subscription_tier || _appState.user.subscription_tier === 'standard' || _appState.user.subscription_tier === 'standard_test' ? ` -
    - ⭐ Ban Yaro Pro kommt bald — mehr Features, mehrere Hunde. -
    - ` : ''}
    ` + : `✓ ${r.fulfilled_at?.slice(0,10)}`} + + `; + + const thead = ` + ${['Nutzer','Tarif','Nachricht','Datum','Aktion'].map(h => + `${h}` + ).join('')}`; + + el.innerHTML = ` +
    +
    Offene Anfragen (${pending.length})
    +
    + + ${thead} + + ${pending.length + ? pending.map(r => _row(r, true)).join('') + : ``} + +
    + Keine offenen Anfragen +
    +
    +
    + ${done.length ? ` +
    +
    Erledigt (${done.length})
    +
    + + ${thead} + ${done.map(r => _row(r, false)).join('')} +
    +
    +
    ` : ''}`; + + el.querySelectorAll('.adm-fulfill-btn').forEach(btn => { + btn.addEventListener('click', async () => { + const { id, name, tier } = btn.dataset; + const tierLabel = { pro: 'Pro', breeder: 'Züchter' }[tier] || tier; + const ok = await UI.modal.confirm({ + title: `${name} auf ${tierLabel} freischalten?`, + body: `

    + Der Account wird auf ${tierLabel} gesetzt und + eine Bestätigungsmail gesendet. +

    `, + confirmLabel: 'Freischalten', + danger: false, + }); + if (!ok) return; + btn.disabled = true; + btn.textContent = '…'; + try { + const res = await API.post(`/admin/upgrade-requests/${id}/fulfill`); + UI.toast.success(`${res.user} wurde auf ${tierLabel} freigeschaltet.`); + _renderTab(); + } catch (e) { + UI.toast.error(e.message); + btn.disabled = false; + btn.textContent = 'Freischalten'; + } + }); + }); + } + return { init, refresh, onDogChange }; })(); diff --git a/backend/static/js/pages/settings.js b/backend/static/js/pages/settings.js index f95e235..96ee9f7 100644 --- a/backend/static/js/pages/settings.js +++ b/backend/static/js/pages/settings.js @@ -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 = (() => { `
  • ✓ ${f}
  • ` ).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: `
    -
    ${price}
    +
    ${price}
    Einmaliger Jahresbeitrag
    Kündigung jederzeit möglich
    @@ -173,9 +168,8 @@ window.Page_settings = (() => {
    - 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.
    `, footer: ` @@ -185,14 +179,32 @@ window.Page_settings = (() => { color:var(--c-text);font-size:var(--text-sm);cursor:pointer"> Abbrechen - - E-Mail senden - ` + ` + }); + + 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.'); + } }); } diff --git a/backend/static/sw.js b/backend/static/sw.js index dfad2b4..7fe3d87 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v919'; +const CACHE_VERSION = 'by-v920'; 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 From 52160e4dc02df517e57a11474c9133a2001a6487 Mon Sep 17 00:00:00 2001 From: rene Date: Thu, 14 May 2026 10:06:48 +0200 Subject: [PATCH 8/8] =?UTF-8?q?Fix:=20Admin=20Z=C3=BCchter-Tab=20=E2=80=94?= =?UTF-8?q?=20Alle=20Z=C3=BCchter=20Liste=20+=20Antraege-Section=20(SW=20b?= =?UTF-8?q?y-v921)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GET /api/admin/breeders: neuer Endpunkt listet alle aktiven Züchter mit Zwingername, Rasse, Stadt, Würfe/Zuchthunde-Zähler, subscription_tier - _renderZuechter: zwei Sektionen parallel geladen - "Offene Anträge" (wie vorher, aber mit Section-Header auch wenn leer) - "Alle Züchter": Tabelle analog Nutzer-Tab mit Abo-Button → _changeTier - api.js: API.breeder.allList() hinzugefügt - SW by-v921, APP_VER 921 --- backend/main.py | 2 +- backend/routes/breeder.py | 21 +++++++ backend/static/js/api.js | 1 + backend/static/js/app.js | 2 +- backend/static/js/pages/admin.js | 95 +++++++++++++++++++++++++++++--- backend/static/sw.js | 2 +- 6 files changed, 113 insertions(+), 10 deletions(-) diff --git a/backend/main.py b/backend/main.py index 8e75ede..9f5a2c4 100644 --- a/backend/main.py +++ b/backend/main.py @@ -406,7 +406,7 @@ async def serve_media(path: str, request: _Request): raise _HE(404, "Nicht gefunden.") return _media_response(filepath) -APP_VER = "920" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "921" # muss mit APP_VER in app.js übereinstimmen @app.get("/.well-known/assetlinks.json") async def assetlinks(): diff --git a/backend/routes/breeder.py b/backend/routes/breeder.py index 1afe535..3728277 100644 --- a/backend/routes/breeder.py +++ b/backend/routes/breeder.py @@ -183,6 +183,27 @@ async def admin_pending_breeders(admin=Depends(require_admin)): return [dict(r) for r in rows] +# ------------------------------------------------------------------ +# GET /api/admin/breeders — alle aktiven Züchter +# ------------------------------------------------------------------ +@router.get("/admin/breeders") +async def admin_all_breeders(admin=Depends(require_admin)): + with db() as conn: + rows = conn.execute(""" + SELECT u.id, u.name, u.email, u.created_at, u.subscription_tier, + u.breeder_status, u.last_login, + bp.zwingername, bp.rasse_text, bp.verein, bp.vdh_mitglied, + bp.stadt, bp.website, bp.verified_at, + (SELECT COUNT(*) FROM litters WHERE user_id=u.id) AS wuerfe_count, + (SELECT COUNT(*) FROM dogs WHERE user_id=u.id AND is_zucht_hund=1) AS zuchthunde_count + FROM users u + LEFT JOIN breeder_profiles bp ON bp.user_id = u.id + WHERE u.rolle = 'breeder' OR u.breeder_status = 'approved' + ORDER BY bp.verified_at DESC NULLS LAST, u.created_at DESC + """).fetchall() + return [dict(r) for r in rows] + + # ------------------------------------------------------------------ # GET /api/admin/breeder/{user_id}/documents — Dokumente eines Antrags # ------------------------------------------------------------------ diff --git a/backend/static/js/api.js b/backend/static/js/api.js index 62b0056..f75b2cf 100644 --- a/backend/static/js/api.js +++ b/backend/static/js/api.js @@ -691,6 +691,7 @@ const API = (() => { updateProfile(data) { return put('/breeder/profile', data); }, adminCreateProfile() { return post('/admin/breeder/create-profile', {}); }, pendingList() { return get('/admin/breeders/pending'); }, + allList() { return get('/admin/breeders'); }, documents(userId) { return get(`/admin/breeder/${userId}/documents`); }, documentUrl(userId, docId) { return `/api/admin/breeder/${userId}/document/${docId}`; }, approve(userId) { return post(`/admin/breeder/${userId}/approve`, {}); }, diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 2860ab3..1aa2c36 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 = '920'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '921'; // ← 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 diff --git a/backend/static/js/pages/admin.js b/backend/static/js/pages/admin.js index e95aa34..0f03bb4 100644 --- a/backend/static/js/pages/admin.js +++ b/backend/static/js/pages/admin.js @@ -1893,12 +1893,17 @@ window.Page_admin = (() => { ${UI.icon('arrows-clockwise')} Aktualisieren
    -
    Lade…
    +
    Lade…
    +
    Lade…
    `; - el.querySelector('#adm-zuchter-refresh').addEventListener('click', () => - _loadZuechterAntraege(el.querySelector('#adm-zuchter-list')) - ); - await _loadZuechterAntraege(el.querySelector('#adm-zuchter-list')); + el.querySelector('#adm-zuchter-refresh').addEventListener('click', () => { + _loadZuechterAntraege(el.querySelector('#adm-zuchter-antraege')); + _loadZuechterListe(el.querySelector('#adm-zuchter-liste')); + }); + await Promise.all([ + _loadZuechterAntraege(el.querySelector('#adm-zuchter-antraege')), + _loadZuechterListe(el.querySelector('#adm-zuchter-liste')), + ]); } async function _loadZuechterAntraege(el) { @@ -1912,12 +1917,20 @@ window.Page_admin = (() => { } if (!antraege.length) { - el.innerHTML = _emptyState('certificate', 'Keine offenen Anträge', 'Aktuell liegen keine Züchter-Anträge zur Prüfung vor.'); + el.innerHTML = `
    +
    Offene Anträge
    +
    + ${UI.icon('check-circle')} Keine offenen Anträge +
    +
    `; return; } el.innerHTML = ` -
    +
    +
    Offene Anträge (${antraege.length})
    +
    +
    ${antraege.map(a => `
    @@ -2072,6 +2085,74 @@ window.Page_admin = (() => { }); } + async function _loadZuechterListe(el) { + el.innerHTML = `
    Lade…
    `; + let breeders; + try { + breeders = await API.breeder.allList(); + } catch (e) { + el.innerHTML = _emptyState('warning', 'Fehler', e.message); + return; + } + + const tierBadge = t => { + if (t === 'breeder') return `Züchter-Abo`; + if (t === 'breeder_test') return `Test`; + return `Standard`; + }; + + const rows = breeders.map(b => ` + + +
    ${_esc(b.name)}
    +
    ${_esc(b.email)}
    + + ${_esc(b.zwingername || '—')} + ${_esc(b.rasse_text || '—')} + ${_esc(b.stadt || '—')} + + ${b.wuerfe_count || 0} Würfe
    + ${b.zuchthunde_count || 0} Zuchthunde + + ${tierBadge(b.subscription_tier)} + + ${b.verified_at ? new Date(b.verified_at).toLocaleDateString('de-DE') : '—'} + + + + + `).join(''); + + el.innerHTML = ` +
    +
    Alle Züchter (${breeders.length})
    +
    + + + ${['Nutzer','Zwingername','Rasse','Stadt','Aktivität','Abo','Seit',''].map(h => + `` + ).join('')} + + + ${rows || ``} + +
    ${h}
    Noch keine Züchter
    +
    +
    `; + + el.querySelectorAll('.adm-breeder-tier-btn').forEach(btn => { + btn.addEventListener('click', () => + _changeTier(btn.dataset.uid, btn.dataset.name, btn.dataset.tier) + ); + }); + } + // ------------------------------------------------------------------ async function _renderJobs(el) { el.innerHTML = ` diff --git a/backend/static/sw.js b/backend/static/sw.js index 7fe3d87..a33205f 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v920'; +const CACHE_VERSION = 'by-v921'; 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