feat(ui): Toast-Feedback ueberall, Job-Detail-Popup, System-Tab, Wizard-Media-Server, Favicon, Footer
- ToastContext: jede Aktion quittiert sichtbar ('Erfolgreich aktualisiert'
ploppt oben rechts rein) — Refresh-Knoepfe drehen jetzt wirklich
(Laufwerke, Logs, Worker, Speicherziele).
- JobDetailModal: Klick auf Titel in 'Neueste Jobs' -> Poster, Jahr,
Beschreibung, Genres, Ablagepfad, Fehler.
- Logs: Filter-Pills um Warnung + Fehler ergaenzt (vorher nur
Alle/Info/Erfolg).
- Speicherziele: Benutzer/Passwort VOR dem 'Freigaben auflisten'-Knopf
(Windows/NAS lehnen Gast-Abfragen ab — genau daher kam das
NT_STATUS_ACCESS_DENIED), Hinweistext, Toasts beim Einhaengen/Entfernen.
- Datei-Browser (Speicherziele + Rip-Ziel-Dialog) zeigt Dateien grau mit
Groesse — Ordner wirkten faelschlich leer.
- Einstellungen: neuer System-Tab (Werkzeug-Versionen je Worker, Key-Status,
Plattenplatz, MakeMKV-Beta-Key-Feld), Media-Server-Wahl im Ripping-Tab,
Arbeitsverzeichnis im Verarbeitung-Tab, Benachrichtigungs-Anleitung
(Discord/ntfy/Slack) + 'Test senden'.
- First-Run-Wizard: Media-Server-Schritt (Jellyfin/Emby/Kodi/Plex/Keins).
- Favicon (Disc im Logo-Verlauf, public/favicon.svg — /vite.svg war 404)
+ Footer 'Created with (Herz) by LucyAI, Claude and KrBrZ'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Disc, Cpu, Globe, CheckCircle, ArrowRight } from 'lucide-react'
|
||||
import { Disc, Cpu, Globe, Tv, CheckCircle, ArrowRight } from 'lucide-react'
|
||||
import { api } from '../lib/api'
|
||||
import { useDarkMode } from '../context/ThemeContext'
|
||||
import { MEDIA_SERVER_OPTIONEN } from '../lib/mediaServer'
|
||||
|
||||
// Erster Start: Rippy universell einrichten (Commander-Ziel: All-in-one,
|
||||
// weitergebbar — keine Handarbeit in .env-Dateien nötig).
|
||||
@@ -22,6 +23,7 @@ const ENCODER_LABELS: Record<string, string> = {
|
||||
export default function FirstRunWizard({ onDone }: { onDone: () => void }) {
|
||||
const [tmdbKey, setTmdbKey] = useState('')
|
||||
const [omdbKey, setOmdbKey] = useState('')
|
||||
const [mediaServer, setMediaServer] = useState('jellyfin')
|
||||
const [transcodeEnabled, setTranscodeEnabled] = useState(true)
|
||||
const [preset, setPreset] = useState('H.265 MKV 1080p30')
|
||||
const [workers, setWorkers] = useState<WorkerInfo[]>([])
|
||||
@@ -44,6 +46,7 @@ export default function FirstRunWizard({ onDone }: { onDone: () => void }) {
|
||||
...bestehende,
|
||||
...(tmdbKey ? { tmdbApiKey: tmdbKey } : {}),
|
||||
...(omdbKey ? { omdbApiKey: omdbKey } : {}),
|
||||
mediaServer,
|
||||
transcodeEnabled,
|
||||
transcodePreset: preset,
|
||||
})
|
||||
@@ -94,6 +97,41 @@ export default function FirstRunWizard({ onDone }: { onDone: () => void }) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Media-Server: worauf soll Rippy die Ablage vorbereiten? */}
|
||||
<section>
|
||||
<h2 className={`flex items-center gap-2 text-lg font-semibold mb-3 ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>
|
||||
<Tv size={18} className="text-indigo-500" /> Dein Media-Server
|
||||
</h2>
|
||||
<p className={`text-sm mb-3 ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>
|
||||
Rippy benennt fertige Rips als „Titel (Jahr)" und legt für Jellyfin/Emby/Kodi
|
||||
zusätzlich movie.nfo + poster.jpg dazu — dein Server erkennt alles ohne Nacharbeit.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
|
||||
{MEDIA_SERVER_OPTIONEN.map(o => (
|
||||
<button
|
||||
key={o.wert}
|
||||
type="button"
|
||||
onClick={() => setMediaServer(o.wert)}
|
||||
title={o.hinweis}
|
||||
className={`px-3 py-2 rounded-lg text-sm font-medium border transition-all ${
|
||||
mediaServer === o.wert
|
||||
? theme === 'dark'
|
||||
? 'bg-indigo-900/40 border-indigo-500 text-indigo-300'
|
||||
: 'bg-indigo-50 border-indigo-600 text-indigo-700'
|
||||
: theme === 'dark'
|
||||
? 'bg-slate-800 border-slate-700 text-slate-400 hover:border-slate-600'
|
||||
: 'bg-slate-50 border-slate-200 text-slate-600 hover:border-slate-300'
|
||||
}`}
|
||||
>
|
||||
{o.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p className={`text-xs mt-2 ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>
|
||||
{MEDIA_SERVER_OPTIONEN.find(o => o.wert === mediaServer)?.hinweis}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Verarbeitung + erkannte Hardware */}
|
||||
<section>
|
||||
<h2 className={`flex items-center gap-2 text-lg font-semibold mb-3 ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>
|
||||
|
||||
Reference in New Issue
Block a user