Commits vergleichen

...

3 Commits

Autor SHA1 Nachricht Datum
Claude Dev
6b4af4cf2a fix: justify-content: center überall wiederhergestellt + Quellen-Duplikatprüfung
- CSS: 24x fälschliches flex-start zurück auf center (Login, Buttons, Modals, Badges, Map etc.)
- Sources: Domain-Duplikatprüfung bei manuellem Hinzufügen (web_source 1x pro Domain, Domain aus URL extrahieren)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:13:36 +01:00
Claude Dev
17088e588f fix: Credits-Dropdown linksbündig, Balken-Track sichtbar, Prozentzahl rechts, kein Fettdruck, mehr Abstand 2026-03-18 00:08:20 +01:00
Claude Dev
97997724de fix: Credits-Anzeige linksbündig, Balken-Hintergrund sichtbar 2026-03-18 00:03:18 +01:00
4 geänderte Dateien mit 49 neuen und 17 gelöschten Zeilen

Datei anzeigen

@@ -415,12 +415,14 @@ async def create_source(
"""Neue Quelle hinzufuegen (org-spezifisch).""" """Neue Quelle hinzufuegen (org-spezifisch)."""
tenant_id = current_user.get("tenant_id") tenant_id = current_user.get("tenant_id")
# Domain normalisieren (Subdomain-Aliase auflösen) # Domain normalisieren (Subdomain-Aliase auflösen, aus URL extrahieren)
domain = data.domain domain = data.domain
if not domain and data.url:
domain = _extract_domain(data.url)
if domain: if domain:
domain = _DOMAIN_ALIASES.get(domain.lower(), domain.lower()) domain = _DOMAIN_ALIASES.get(domain.lower(), domain.lower())
# Duplikat-Prüfung: gleiche URL bereits vorhanden? # Duplikat-Prüfung 1: gleiche URL bereits vorhanden? (tenant-übergreifend)
if data.url: if data.url:
cursor = await db.execute( cursor = await db.execute(
"SELECT id, name FROM sources WHERE url = ? AND status = 'active'", "SELECT id, name FROM sources WHERE url = ? AND status = 'active'",
@@ -433,6 +435,25 @@ async def create_source(
detail=f"Feed-URL bereits vorhanden: {existing['name']} (ID {existing['id']})", detail=f"Feed-URL bereits vorhanden: {existing['name']} (ID {existing['id']})",
) )
# Duplikat-Prüfung 2: Domain bereits vorhanden? (tenant-übergreifend)
if domain:
cursor = await db.execute(
"SELECT id, name, source_type FROM sources WHERE LOWER(domain) = ? AND status = 'active' AND (tenant_id IS NULL OR tenant_id = ?) LIMIT 1",
(domain.lower(), tenant_id),
)
domain_existing = await cursor.fetchone()
if domain_existing:
if data.source_type == "web_source":
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Web-Quelle für '{domain}' bereits vorhanden: {domain_existing['name']}",
)
if not data.url:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Domain '{domain}' bereits als Quelle vorhanden: {domain_existing['name']}. Für einen neuen RSS-Feed bitte die Feed-URL angeben.",
)
cursor = await db.execute( cursor = await db.execute(
"""INSERT INTO sources (name, url, domain, source_type, category, status, notes, added_by, tenant_id) """INSERT INTO sources (name, url, domain, source_type, category, status, notes, added_by, tenant_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",

Datei anzeigen

@@ -5338,13 +5338,14 @@ body.tutorial-active .tutorial-cursor {
/* ===== Credits-Anzeige im User-Dropdown ===== */ /* ===== Credits-Anzeige im User-Dropdown ===== */
.credits-section { .credits-section {
padding: 8px 16px 12px; padding: 0;
text-align: left;
} }
.credits-divider { .credits-divider {
height: 1px; height: 1px;
background: var(--border); background: var(--border);
margin-bottom: 10px; margin: 8px 0;
} }
.credits-label { .credits-label {
@@ -5352,22 +5353,24 @@ body.tutorial-active .tutorial-cursor {
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
color: var(--text-secondary); color: var(--text-tertiary);
margin-bottom: 6px; margin-bottom: 8px;
text-align: left;
} }
.credits-bar-container { .credits-bar-container {
width: 100%; width: 100%;
height: 6px; height: 8px;
background: var(--bg-tertiary); background: rgba(255,255,255,0.08);
border-radius: 3px; border: 1px solid rgba(255,255,255,0.12);
border-radius: 4px;
overflow: hidden; overflow: hidden;
margin-bottom: 4px; margin-bottom: 10px;
} }
.credits-bar { .credits-bar {
height: 100%; height: 100%;
border-radius: 3px; border-radius: 4px;
background: var(--accent); background: var(--accent);
transition: width 0.6s ease, background-color 0.3s ease; transition: width 0.6s ease, background-color 0.3s ease;
min-width: 2px; min-width: 2px;
@@ -5383,13 +5386,18 @@ body.tutorial-active .tutorial-cursor {
.credits-info { .credits-info {
font-size: 12px; font-size: 12px;
color: var(--text-secondary); color: var(--text-tertiary);
display: flex; display: flex;
justify-content: center; justify-content: space-between;
gap: 4px; align-items: center;
} }
.credits-info span { .credits-info span {
font-weight: 600; font-weight: 400;
color: var(--text-primary); color: var(--text-secondary);
}
.credits-percent {
font-size: 11px;
color: var(--text-tertiary);
} }

Datei anzeigen

@@ -57,7 +57,8 @@
<div id="credits-bar" class="credits-bar"></div> <div id="credits-bar" class="credits-bar"></div>
</div> </div>
<div class="credits-info"> <div class="credits-info">
<span id="credits-remaining">0</span> von <span id="credits-total">0</span> <span><span id="credits-remaining">0</span> von <span id="credits-total">0</span></span>
<span class="credits-percent" id="credits-percent"></span>
</div> </div>
</div> </div>
</div> </div>

Datei anzeigen

@@ -490,6 +490,8 @@ const App = {
} else if (percentUsed > 50) { } else if (percentUsed > 50) {
bar.classList.add('warning'); bar.classList.add('warning');
} }
const percentEl = document.getElementById("credits-percent");
if (percentEl) percentEl.textContent = percentRemaining.toFixed(0) + "% verbleibend";
} }
// Dropdown Toggle // Dropdown Toggle