Feature+Fix: Referral-Admin, Pro-Gates, Karten-Layer, onDogChange, Staging-Media (SW by-v855)
Features: - Admin: Referral-Tab (Virality Factor, Top-Werber, letzte Einladungen) - Karte: Regenradar (RainViewer, zoom→7, color=4), Temperatur-Layer (OWM) mit Zahlen-Grid + Legende - Wetter-Chip: Umschwung-Warnung bei ≥40%-Sprung in Niederschlagswahrscheinlichkeit - Freundschaftsanfragen: Accept/Decline direkt in Notifications (kein Pro nötig) - Freunde-Seite für Standard-User freigeschaltet Pro-Gates: - KI-Trainer, Routenvorschläge, Regenradar, Temperatur-Layer jetzt Pro-Feature - Pro-Badge (P) auf Chips für Admins/Mods in allen Welten + Welten-einrichten - Oranger Banner auf Pro-Seiten für Admin/Mod/Manager Bugfixes: - onDogChange: uebungen.js (Cache leeren + _render), trainingsplaene.js (war leer) - robots.txt vereinfacht (nur Disallow, kein Allow-Durcheinander) - Hintergrund-Foto: Querformat-Filter korrigiert (kein Fallback auf Hochformat) - Staging Media: FileResponse mit korrektem MIME-Type, no-cache statt immutable - Staging Docker: MEDIA_DIR=/data/media + /prod-media:ro Fallback-Handler - Staging-Fix: Bild-Upload auf zweitem Hund (war Read-only file system)
This commit is contained in:
parent
2f021f54c2
commit
79fa5684b9
22 changed files with 570 additions and 58 deletions
|
|
@ -346,7 +346,8 @@ window.Worlds = (() => {
|
|||
<div class="w3-section-label">${worldLabels[w]}</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px">
|
||||
${chips.map(c => `
|
||||
<button class="all-chip-btn w3-chip-btn" data-page="${c.page}">
|
||||
<button class="all-chip-btn w3-chip-btn" data-page="${c.page}" style="position:relative">
|
||||
${c.pro && _isRoleBasedPro() ? `<span style="position:absolute;top:2px;left:3px;font-size:8px;font-weight:800;color:#fff;background:#92400e;border-radius:3px;padding:0 3px;line-height:14px">P</span>` : ''}
|
||||
<svg class="ph-icon" style="width:1.4rem;height:1.4rem;color:var(--c-primary)">
|
||||
<use href="/icons/phosphor.svg#${c.icon}"></use>
|
||||
</svg>
|
||||
|
|
@ -523,7 +524,7 @@ window.Worlds = (() => {
|
|||
fab:[{ icon:'map-pin', color:'#10B981', label:'Ort vorschlagen', sub:'Neuen POI auf der Karte', page:'map' }] },
|
||||
{ icon:'push-pin', label:'Forum', page:'forum',
|
||||
fab:[{ icon:'push-pin', color:'#8B5CF6', label:'Forum-Beitrag', sub:'Thema oder Frage erstellen', page:'forum', action:'openNew' }] },
|
||||
{ icon:'users', label:'Freunde', page:'friends', pro: true,
|
||||
{ icon:'users', label:'Freunde', page:'friends',
|
||||
fab:[{ icon:'users', color:'#3B82F6', label:'Freund einladen', sub:'Per Link einladen', page:'friends', action:'openNew' }] },
|
||||
{ icon:'paw-print', label:'Gassi', page:'walks', pro: true,
|
||||
fab:[{ icon:'paw-print', color:'#F59E0B', label:'Gassirunde', sub:'Neue Runde starten', page:'walks', action:'openNew' },
|
||||
|
|
@ -760,6 +761,7 @@ window.Worlds = (() => {
|
|||
<use href="/icons/phosphor.svg#lock-simple"></use>
|
||||
</svg>
|
||||
</div>`}
|
||||
${c.pro && _isRoleBasedPro() ? `<span style="position:absolute;top:3px;left:4px;font-size:8px;font-weight:800;color:#fff;background:#92400e;border-radius:3px;padding:0 3px;line-height:14px;z-index:2">P</span>` : ''}
|
||||
<svg class="ph-icon" style="width:1.4rem;height:1.4rem;color:white;flex-shrink:0">
|
||||
<use href="/icons/phosphor.svg#${c.icon}"></use>
|
||||
</svg>
|
||||
|
|
@ -969,10 +971,24 @@ window.Worlds = (() => {
|
|||
|
||||
// ── CHIP-HELPER ──────────────────────────────────────────────
|
||||
|
||||
function _chip(icon, label, page, locked = false) {
|
||||
function _isRoleBasedPro() {
|
||||
const u = _state?.user;
|
||||
if (!u) return false;
|
||||
const t = u.subscription_tier || 'standard';
|
||||
if (t.endsWith('_test') || ['pro','breeder'].includes(t)) return false;
|
||||
return u.rolle === 'admin' || u.rolle === 'moderator' || u.is_moderator || u.is_social_media;
|
||||
}
|
||||
|
||||
function _chip(icon, label, page, locked = false, proBadge = false) {
|
||||
const style = locked ? 'opacity:0.25;cursor:default;' : '';
|
||||
const badge = proBadge
|
||||
? `<span style="position:absolute;top:2px;left:3px;font-size:8px;font-weight:800;
|
||||
color:#fff;background:#92400e;border-radius:3px;padding:0 3px;line-height:14px">P</span>`
|
||||
: '';
|
||||
return `
|
||||
<div class="world-chip" ${locked ? '' : `data-wnav="${page}"`} style="${style}">
|
||||
<div class="world-chip" ${locked ? '' : `data-wnav="${page}"`}
|
||||
style="${style}position:relative">
|
||||
${badge}
|
||||
<svg class="ph-icon" style="width:1.4rem;height:1.4rem">
|
||||
<use href="/icons/phosphor.svg#${icon}"></use>
|
||||
</svg>
|
||||
|
|
@ -1097,9 +1113,9 @@ window.Worlds = (() => {
|
|||
<span style="font-size:1.25rem;font-weight:800;color:${gassiColor};line-height:1">${gassiScore ?? '—'}</span>
|
||||
${gassiScore ? `<span style="font-size:var(--text-xs);color:rgba(255,255,255,0.4);font-weight:600">/10</span>` : ''}
|
||||
</div>
|
||||
${w ? `<div style="font-size:9px;color:rgba(255,255,255,0.75);font-weight:500;margin-top:2px;line-height:1.5">
|
||||
<div>${Math.round(w.temp_c ?? 0)}° · ${w.precip_prob ?? 0}% Regen</div>
|
||||
${w.next_rain_time ? `<div style="color:rgba(255,255,255,0.5)">ab ${w.next_rain_time} Uhr</div>` : ''}
|
||||
${w ? `<div style="font-size:9px;font-weight:500;margin-top:2px;line-height:1.5">
|
||||
<div style="color:rgba(255,255,255,0.75)">${Math.round(w.temp_c ?? 0)}° · ${w.precip_prob ?? 0}% Regen</div>
|
||||
${w.rain_warning_time ? `<div style="color:#fbbf24;font-weight:700">⚠ Umschwung ab ${w.rain_warning_time}</div>` : w.next_rain_time ? `<div style="color:rgba(255,255,255,0.5)">ab ${w.next_rain_time} Uhr</div>` : ''}
|
||||
</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1122,7 +1138,7 @@ window.Worlds = (() => {
|
|||
<div class="world-bottom">
|
||||
<div class="world-section-label">Deine Bereiche</div>
|
||||
<div class="world-chips-grid">
|
||||
${features.map(f => _chip(f.icon, f.label, f.page)).join('')}
|
||||
${features.map(f => _chip(f.icon, f.label, f.page, false, f.pro && _isRoleBasedPro())).join('')}
|
||||
</div>
|
||||
<div class="world-footer-links">
|
||||
<span data-wnav="impressum">Impressum</span>
|
||||
|
|
@ -1413,7 +1429,7 @@ window.Worlds = (() => {
|
|||
` : ''}
|
||||
<div class="world-section-label">Alles über ${_esc(dog.name)}</div>
|
||||
<div class="world-chips-grid">
|
||||
${chips.map(c => _chip(c.icon, c.label, c.page)).join('')}
|
||||
${chips.map(c => _chip(c.icon, c.label, c.page, false, c.pro && _isRoleBasedPro())).join('')}
|
||||
</div>
|
||||
<div class="world-footer-links">
|
||||
<span data-wnav="gruender">Die 100 Gründer</span>
|
||||
|
|
@ -1583,7 +1599,7 @@ window.Worlds = (() => {
|
|||
<div class="world-bottom">
|
||||
<div class="world-section-label">Die Welt da draußen</div>
|
||||
<div class="world-chips-grid">
|
||||
${chips.map(c => _chip(c.icon, c.label, c.page)).join('')}
|
||||
${chips.map(c => _chip(c.icon, c.label, c.page, false, c.pro && _isRoleBasedPro())).join('')}
|
||||
</div>
|
||||
<div class="world-footer-links">
|
||||
<span data-wnav="datenschutz">Datenschutz</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue