Redesign navbar mobile menu and rework site color palette

Swaps navbar layout (theme toggle left, nav links right), replaces the
mobile dropdown with a fullscreen overlay whose items fly in top-to-bottom,
and renames the site-wide "slate" gray to "neutral" to remove a blue
undertone that clashed with the brand green. Also converts the flat
saturated-green YouTube and stats sections into dark backgrounds with
green used only as an accent, matching typical premium car-audio branding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Maaxxs 2026-07-06 16:21:48 +02:00
parent 95764039c7
commit b6909e756c
43 changed files with 609 additions and 566 deletions

View file

@ -5,7 +5,7 @@ export default function Accordion({ items }) {
const [openIndex, setOpenIndex] = useState(null); const [openIndex, setOpenIndex] = useState(null);
return ( return (
<div className="divide-y divide-slate-200 rounded-xl border border-slate-200 bg-white dark:divide-slate-800 dark:border-slate-800 dark:bg-slate-900"> <div className="divide-y divide-neutral-200 rounded-xl border border-neutral-200 bg-white dark:divide-neutral-800 dark:border-neutral-800 dark:bg-neutral-900">
{items.map((item, i) => { {items.map((item, i) => {
const isOpen = openIndex === i; const isOpen = openIndex === i;
return ( return (
@ -15,7 +15,7 @@ export default function Accordion({ items }) {
className="flex w-full items-center justify-between gap-4 px-5 py-4 text-left" className="flex w-full items-center justify-between gap-4 px-5 py-4 text-left"
aria-expanded={isOpen} aria-expanded={isOpen}
> >
<span className="font-medium text-slate-900 dark:text-white">{item.question}</span> <span className="font-medium text-neutral-900 dark:text-white">{item.question}</span>
<motion.svg <motion.svg
animate={{ rotate: isOpen ? 180 : 0 }} animate={{ rotate: isOpen ? 180 : 0 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
@ -24,7 +24,7 @@ export default function Accordion({ items }) {
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
strokeWidth="2" strokeWidth="2"
className="h-5 w-5 shrink-0 text-slate-500" className="h-5 w-5 shrink-0 text-neutral-500"
> >
<path strokeLinecap="round" strokeLinejoin="round" d="m6 9 6 6 6-6" /> <path strokeLinecap="round" strokeLinejoin="round" d="m6 9 6 6 6-6" />
</motion.svg> </motion.svg>
@ -38,7 +38,7 @@ export default function Accordion({ items }) {
transition={{ duration: 0.25, ease: 'easeInOut' }} transition={{ duration: 0.25, ease: 'easeInOut' }}
className="overflow-hidden" className="overflow-hidden"
> >
<p className="px-5 pb-4 text-sm text-slate-600 dark:text-slate-300">{item.answer}</p> <p className="px-5 pb-4 text-sm text-neutral-600 dark:text-neutral-300">{item.answer}</p>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>

View file

@ -84,7 +84,7 @@ export default function AdminLayout() {
`flex items-center justify-between gap-2 rounded-md px-3 py-2 text-sm font-medium transition ${ `flex items-center justify-between gap-2 rounded-md px-3 py-2 text-sm font-medium transition ${
isActive isActive
? 'bg-brand-500 text-white' ? 'bg-brand-500 text-white'
: 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800' : 'text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800'
}`; }`;
const NavLinks = ({ onNavigate }) => ( const NavLinks = ({ onNavigate }) => (
@ -92,7 +92,7 @@ export default function AdminLayout() {
{navGroups.map((group, i) => ( {navGroups.map((group, i) => (
<div key={i}> <div key={i}>
{group.title && ( {group.title && (
<p className="mb-1.5 px-3 text-xs font-semibold uppercase tracking-wide text-slate-400 dark:text-slate-500"> <p className="mb-1.5 px-3 text-xs font-semibold uppercase tracking-wide text-neutral-400 dark:text-neutral-500">
{group.title} {group.title}
</p> </p>
)} )}
@ -115,25 +115,25 @@ export default function AdminLayout() {
); );
return ( return (
<div className="flex min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100"> <div className="flex min-h-screen bg-neutral-50 text-neutral-900 dark:bg-neutral-950 dark:text-neutral-100">
<aside className="hidden w-60 shrink-0 border-r border-slate-200 p-4 dark:border-slate-800 sm:block"> <aside className="hidden w-60 shrink-0 border-r border-neutral-200 p-4 dark:border-neutral-800 sm:block">
<div className="mb-6 flex items-center gap-2 px-3"> <div className="mb-6 flex items-center gap-2 px-3">
<span className="rounded-lg px-2 py-1 dark:bg-white"> <span className="rounded-lg px-2 py-1 dark:bg-white">
<img src={logo} alt="HifiPlanet" className="h-7 w-auto" /> <img src={logo} alt="HifiPlanet" className="h-7 w-auto" />
</span> </span>
<span className="text-sm font-semibold text-slate-500 dark:text-slate-400">Admin</span> <span className="text-sm font-semibold text-neutral-500 dark:text-neutral-400">Admin</span>
</div> </div>
<NavLinks /> <NavLinks />
</aside> </aside>
<div className="flex-1"> <div className="flex-1">
<header className="border-b border-slate-200 dark:border-slate-800"> <header className="border-b border-neutral-200 dark:border-neutral-800">
<div className="flex items-center justify-between px-4 py-3 sm:px-6"> <div className="flex items-center justify-between px-4 py-3 sm:px-6">
<div className="flex items-center gap-2 sm:hidden"> <div className="flex items-center gap-2 sm:hidden">
<button <button
onClick={() => setMobileOpen((o) => !o)} onClick={() => setMobileOpen((o) => !o)}
aria-label="Menü öffnen" aria-label="Menü öffnen"
className="rounded-md p-2 text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800" className="rounded-md p-2 text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800"
> >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-6 w-6"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-6 w-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" /> <path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
@ -143,21 +143,21 @@ export default function AdminLayout() {
<img src={logo} alt="HifiPlanet" className="h-6 w-auto" /> <img src={logo} alt="HifiPlanet" className="h-6 w-auto" />
</span> </span>
</div> </div>
<p className="hidden text-sm text-slate-500 dark:text-slate-400 sm:block"> <p className="hidden text-sm text-neutral-500 dark:text-neutral-400 sm:block">
Angemeldet als {user?.username}{user?.is_super_admin ? ' · Super-Admin' : ''} Angemeldet als {user?.username}{user?.is_super_admin ? ' · Super-Admin' : ''}
</p> </p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ThemeToggle /> <ThemeToggle />
<button <button
onClick={handleLogout} onClick={handleLogout}
className="rounded-md border border-slate-300 px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-slate-100 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800" className="rounded-md border border-neutral-300 px-3 py-1.5 text-sm font-medium text-neutral-700 hover:bg-neutral-100 dark:border-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-800"
> >
Abmelden Abmelden
</button> </button>
</div> </div>
</div> </div>
{mobileOpen && ( {mobileOpen && (
<div className="border-t border-slate-200 px-4 py-3 dark:border-slate-800 sm:hidden"> <div className="border-t border-neutral-200 px-4 py-3 dark:border-neutral-800 sm:hidden">
<NavLinks onNavigate={() => setMobileOpen(false)} /> <NavLinks onNavigate={() => setMobileOpen(false)} />
</div> </div>
)} )}

View file

@ -12,11 +12,11 @@ function SettingsModal({ onClose }) {
onClick={onClose} onClick={onClose}
> >
<div <div
className="w-full max-w-md rounded-xl bg-white p-6 dark:bg-slate-900" className="w-full max-w-md rounded-xl bg-white p-6 dark:bg-neutral-900"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<h2 className="mb-1 text-lg font-bold text-slate-900 dark:text-white">Cookie-Einstellungen</h2> <h2 className="mb-1 text-lg font-bold text-neutral-900 dark:text-white">Cookie-Einstellungen</h2>
<p className="mb-4 text-sm text-slate-600 dark:text-slate-300"> <p className="mb-4 text-sm text-neutral-600 dark:text-neutral-300">
Lege fest, welche Kategorien du zulassen möchtest. Mehr dazu in unserer{' '} Lege fest, welche Kategorien du zulassen möchtest. Mehr dazu in unserer{' '}
<Link to="/datenschutz" className="underline hover:text-brand-500" onClick={onClose}> <Link to="/datenschutz" className="underline hover:text-brand-500" onClick={onClose}>
Datenschutzerklärung Datenschutzerklärung
@ -24,20 +24,20 @@ function SettingsModal({ onClose }) {
. .
</p> </p>
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-start justify-between gap-3 rounded-lg border border-slate-200 p-3 dark:border-slate-700"> <div className="flex items-start justify-between gap-3 rounded-lg border border-neutral-200 p-3 dark:border-neutral-700">
<div> <div>
<p className="text-sm font-semibold text-slate-800 dark:text-slate-100">Notwendig</p> <p className="text-sm font-semibold text-neutral-800 dark:text-neutral-100">Notwendig</p>
<p className="text-xs text-slate-500 dark:text-slate-400"> <p className="text-xs text-neutral-500 dark:text-neutral-400">
Für den Betrieb der Website erforderlich (z. B. Login-Session, Theme- und Cookie-Einstellung). Für den Betrieb der Website erforderlich (z. B. Login-Session, Theme- und Cookie-Einstellung).
Kann nicht deaktiviert werden. Kann nicht deaktiviert werden.
</p> </p>
</div> </div>
<input type="checkbox" checked disabled className="mt-1 h-4 w-4 shrink-0" /> <input type="checkbox" checked disabled className="mt-1 h-4 w-4 shrink-0" />
</div> </div>
<div className="flex items-start justify-between gap-3 rounded-lg border border-slate-200 p-3 dark:border-slate-700"> <div className="flex items-start justify-between gap-3 rounded-lg border border-neutral-200 p-3 dark:border-neutral-700">
<div> <div>
<p className="text-sm font-semibold text-slate-800 dark:text-slate-100">Externe Medien</p> <p className="text-sm font-semibold text-neutral-800 dark:text-neutral-100">Externe Medien</p>
<p className="text-xs text-slate-500 dark:text-slate-400"> <p className="text-xs text-neutral-500 dark:text-neutral-400">
Google Maps und YouTube-Videos. Beim Laden werden Daten (u. a. deine IP-Adresse) an Google Google Maps und YouTube-Videos. Beim Laden werden Daten (u. a. deine IP-Adresse) an Google
übertragen. übertragen.
</p> </p>
@ -54,7 +54,7 @@ function SettingsModal({ onClose }) {
<button <button
type="button" type="button"
onClick={onClose} onClick={onClose}
className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700" className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700"
> >
Abbrechen Abbrechen
</button> </button>
@ -77,9 +77,9 @@ export default function CookieConsentBanner() {
return ( return (
<> <>
{!decided && ( {!decided && (
<div className="fixed inset-x-0 bottom-0 z-50 border-t border-slate-200 bg-white/95 p-4 shadow-lg backdrop-blur dark:border-slate-800 dark:bg-slate-900/95 sm:p-5"> <div className="fixed inset-x-0 bottom-0 z-50 border-t border-neutral-200 bg-white/95 p-4 shadow-lg backdrop-blur dark:border-neutral-800 dark:bg-neutral-900/95 sm:p-5">
<div className="mx-auto flex max-w-6xl flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div className="mx-auto flex max-w-6xl flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm text-slate-600 dark:text-slate-300"> <p className="text-sm text-neutral-600 dark:text-neutral-300">
Wir verwenden nur technisch notwendige Cookies. Für Google Maps und YouTube-Videos benötigen wir Wir verwenden nur technisch notwendige Cookies. Für Google Maps und YouTube-Videos benötigen wir
zusätzlich deine Zustimmung, da dabei Daten an Google übertragen werden. Mehr dazu in unserer{' '} zusätzlich deine Zustimmung, da dabei Daten an Google übertragen werden. Mehr dazu in unserer{' '}
<Link to="/datenschutz" className="underline hover:text-brand-500"> <Link to="/datenschutz" className="underline hover:text-brand-500">
@ -91,14 +91,14 @@ export default function CookieConsentBanner() {
<button <button
type="button" type="button"
onClick={openSettings} onClick={openSettings}
className="rounded-md border border-slate-300 px-3 py-2 text-sm dark:border-slate-700" className="rounded-md border border-neutral-300 px-3 py-2 text-sm dark:border-neutral-700"
> >
Einstellungen Einstellungen
</button> </button>
<button <button
type="button" type="button"
onClick={rejectNonEssential} onClick={rejectNonEssential}
className="rounded-md border border-slate-300 px-3 py-2 text-sm dark:border-slate-700" className="rounded-md border border-neutral-300 px-3 py-2 text-sm dark:border-neutral-700"
> >
Nur notwendige Nur notwendige
</button> </button>

View file

@ -9,9 +9,9 @@ export default function ExternalEmbed({ name, description, className = '', child
return ( return (
<div <div
className={`flex flex-col items-center justify-center gap-3 rounded-xl border border-slate-200 bg-slate-50 p-6 text-center dark:border-slate-800 dark:bg-slate-900/40 ${className}`} className={`flex flex-col items-center justify-center gap-3 rounded-xl border border-neutral-200 bg-neutral-50 p-6 text-center dark:border-neutral-800 dark:bg-neutral-900/40 ${className}`}
> >
<p className="text-sm text-slate-600 dark:text-slate-300"> <p className="text-sm text-neutral-600 dark:text-neutral-300">
{description || `${name} wird erst nach deiner Zustimmung geladen, da dabei Daten an Google übertragen werden.`} {description || `${name} wird erst nach deiner Zustimmung geladen, da dabei Daten an Google übertragen werden.`}
</p> </p>
<div className="flex flex-wrap justify-center gap-2"> <div className="flex flex-wrap justify-center gap-2">
@ -25,7 +25,7 @@ export default function ExternalEmbed({ name, description, className = '', child
<button <button
type="button" type="button"
onClick={openSettings} onClick={openSettings}
className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700" className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700"
> >
Cookie-Einstellungen Cookie-Einstellungen
</button> </button>

View file

@ -34,15 +34,15 @@ export default function Footer() {
return ( return (
<footer <footer
className={`border-t border-slate-200 bg-slate-50 py-10 text-sm text-slate-600 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-300 ${ className={`border-t border-neutral-200 bg-neutral-50 py-10 text-sm text-neutral-600 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-300 ${
isHome ? 'pb-24' : '' isHome ? 'pb-24' : ''
}`} }`}
> >
<div className="mx-auto grid max-w-6xl gap-4 px-4 sm:grid-cols-3 sm:px-6"> <div className="mx-auto grid max-w-6xl gap-4 px-4 sm:grid-cols-3 sm:px-6">
<div className="rounded-xl border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900"> <div className="rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900">
<div className="mb-3 flex items-center gap-2"> <div className="mb-3 flex items-center gap-2">
<DynamicIcon name="map-pin" className="h-4 w-4 text-brand-500" /> <DynamicIcon name="map-pin" className="h-4 w-4 text-brand-500" />
<p className="font-semibold text-slate-900 dark:text-white">HifiPlanet</p> <p className="font-semibold text-neutral-900 dark:text-white">HifiPlanet</p>
</div> </div>
<p>Boxbrunner Str. 20a</p> <p>Boxbrunner Str. 20a</p>
<p>63916 Amorbach</p> <p>63916 Amorbach</p>
@ -57,10 +57,10 @@ export default function Footer() {
</a> </a>
</div> </div>
<div className="rounded-xl border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900"> <div className="rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900">
<div className="mb-3 flex items-center gap-2"> <div className="mb-3 flex items-center gap-2">
<DynamicIcon name="phone" className="h-4 w-4 text-brand-500" /> <DynamicIcon name="phone" className="h-4 w-4 text-brand-500" />
<p className="font-semibold text-slate-900 dark:text-white">Kontakt</p> <p className="font-semibold text-neutral-900 dark:text-white">Kontakt</p>
</div> </div>
<p> <p>
<a href={`tel:${digitsOnly(phone)}`} className="hover:text-brand-500">{phone}</a> <a href={`tel:${digitsOnly(phone)}`} className="hover:text-brand-500">{phone}</a>
@ -70,40 +70,40 @@ export default function Footer() {
</p> </p>
<Link <Link
to="/kontakt" to="/kontakt"
className="mt-4 flex items-center justify-center gap-1.5 rounded-md border border-slate-300 px-3 py-2 text-xs font-semibold text-slate-700 hover:border-brand-500 hover:text-brand-600 dark:border-slate-700 dark:text-slate-200" className="mt-4 flex items-center justify-center gap-1.5 rounded-md border border-neutral-300 px-3 py-2 text-xs font-semibold text-neutral-700 hover:border-brand-500 hover:text-brand-600 dark:border-neutral-700 dark:text-neutral-200"
> >
Kontaktformular Kontaktformular
</Link> </Link>
</div> </div>
<div className="rounded-xl border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900"> <div className="rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900">
<div className="mb-3 flex items-center justify-between gap-2"> <div className="mb-3 flex items-center justify-between gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<DynamicIcon name="clock" className="h-4 w-4 text-brand-500" /> <DynamicIcon name="clock" className="h-4 w-4 text-brand-500" />
<p className="font-semibold text-slate-900 dark:text-white">Öffnungszeiten</p> <p className="font-semibold text-neutral-900 dark:text-white">Öffnungszeiten</p>
</div> </div>
<span <span
className={`flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold ${ className={`flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold ${
open open
? 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300' ? 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300'
: 'bg-slate-100 text-slate-500 dark:bg-slate-800 dark:text-slate-400' : 'bg-neutral-100 text-neutral-500 dark:bg-neutral-800 dark:text-neutral-400'
}`} }`}
> >
<span className={`h-1.5 w-1.5 rounded-full ${open ? 'bg-green-500' : 'bg-slate-400'}`} /> <span className={`h-1.5 w-1.5 rounded-full ${open ? 'bg-green-500' : 'bg-neutral-400'}`} />
{open ? 'Geöffnet' : 'Geschlossen'} {open ? 'Geöffnet' : 'Geschlossen'}
</span> </span>
</div> </div>
<dl className="space-y-1.5"> <dl className="space-y-1.5">
{HOURS.map((row) => ( {HOURS.map((row) => (
<div key={row.days} className="flex items-center justify-between gap-3"> <div key={row.days} className="flex items-center justify-between gap-3">
<dt className="text-slate-500 dark:text-slate-400">{row.days}</dt> <dt className="text-neutral-500 dark:text-neutral-400">{row.days}</dt>
<dd className="font-medium text-slate-700 dark:text-slate-200">{row.label}</dd> <dd className="font-medium text-neutral-700 dark:text-neutral-200">{row.label}</dd>
</div> </div>
))} ))}
</dl> </dl>
</div> </div>
</div> </div>
<div className="mx-auto mt-8 max-w-6xl border-t border-slate-200 px-4 pt-6 text-center text-xs text-slate-500 dark:border-slate-800 dark:text-slate-400 sm:px-6"> <div className="mx-auto mt-8 max-w-6xl border-t border-neutral-200 px-4 pt-6 text-center text-xs text-neutral-500 dark:border-neutral-800 dark:text-neutral-400 sm:px-6">
<p className="flex flex-wrap items-center justify-center gap-x-2 gap-y-1"> <p className="flex flex-wrap items-center justify-center gap-x-2 gap-y-1">
<span>© {new Date().getFullYear()} HifiPlanet. Alle Angaben ohne Gewähr.</span> <span>© {new Date().getFullYear()} HifiPlanet. Alle Angaben ohne Gewähr.</span>
<span>·</span> <span>·</span>

View file

@ -18,13 +18,13 @@ export default function IconPicker({ value, onChange }) {
return ( return (
<div> <div>
<div className="mb-2 flex items-center gap-3 rounded-md border border-slate-300 p-2 dark:border-slate-700"> <div className="mb-2 flex items-center gap-3 rounded-md border border-neutral-300 p-2 dark:border-neutral-700">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-brand-100 text-brand-600 dark:bg-brand-900/40 dark:text-brand-400"> <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-brand-100 text-brand-600 dark:bg-brand-900/40 dark:text-brand-400">
{value ? <DynamicIcon name={value} className="h-6 w-6" /> : <span className="text-xs text-slate-400">?</span>} {value ? <DynamicIcon name={value} className="h-6 w-6" /> : <span className="text-xs text-neutral-400">?</span>}
</div> </div>
<div className="text-sm"> <div className="text-sm">
<p className="font-medium text-slate-800 dark:text-slate-100">{value || 'Kein Icon gewählt'}</p> <p className="font-medium text-neutral-800 dark:text-neutral-100">{value || 'Kein Icon gewählt'}</p>
<p className="text-xs text-slate-400">{ALL_ICON_NAMES.length} Icons verfügbar</p> <p className="text-xs text-neutral-400">{ALL_ICON_NAMES.length} Icons verfügbar</p>
</div> </div>
</div> </div>
@ -32,10 +32,10 @@ export default function IconPicker({ value, onChange }) {
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
placeholder="Icon suchen (z. B. car, shield, zap)…" placeholder="Icon suchen (z. B. car, shield, zap)…"
className="mb-2 w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="mb-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
<div className="grid max-h-64 grid-cols-6 gap-1 overflow-y-auto rounded-md border border-slate-200 p-2 sm:grid-cols-8 dark:border-slate-700"> <div className="grid max-h-64 grid-cols-6 gap-1 overflow-y-auto rounded-md border border-neutral-200 p-2 sm:grid-cols-8 dark:border-neutral-700">
{results.map((name) => ( {results.map((name) => (
<button <button
type="button" type="button"
@ -45,18 +45,18 @@ export default function IconPicker({ value, onChange }) {
className={`flex h-10 w-10 items-center justify-center rounded-md transition ${ className={`flex h-10 w-10 items-center justify-center rounded-md transition ${
value === name value === name
? 'bg-brand-500 text-white' ? 'bg-brand-500 text-white'
: 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800' : 'text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800'
}`} }`}
> >
<DynamicIcon name={name} className="h-5 w-5" /> <DynamicIcon name={name} className="h-5 w-5" />
</button> </button>
))} ))}
{results.length === 0 && ( {results.length === 0 && (
<p className="col-span-full py-4 text-center text-sm text-slate-400">Keine Icons gefunden.</p> <p className="col-span-full py-4 text-center text-sm text-neutral-400">Keine Icons gefunden.</p>
)} )}
</div> </div>
{!search && ( {!search && (
<p className="mt-1 text-xs text-slate-400">Tipp: Tippe einen Suchbegriff ein, um alle {ALL_ICON_NAMES.length} Icons zu durchsuchen.</p> <p className="mt-1 text-xs text-neutral-400">Tipp: Tippe einen Suchbegriff ein, um alle {ALL_ICON_NAMES.length} Icons zu durchsuchen.</p>
)} )}
</div> </div>
); );

View file

@ -25,11 +25,11 @@ export default function ImageUploadField({ value, onChange, label = 'Bild' }) {
return ( return (
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">{label}</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">{label}</label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{value && <img src={value} alt="" className="h-12 w-12 rounded-md object-cover" />} {value && <img src={value} alt="" className="h-12 w-12 rounded-md object-cover" />}
<input type="file" accept="image/png,image/jpeg,image/webp" onChange={handleFile} className="text-sm" /> <input type="file" accept="image/png,image/jpeg,image/webp" onChange={handleFile} className="text-sm" />
{uploading && <span className="text-sm text-slate-500">Lädt hoch</span>} {uploading && <span className="text-sm text-neutral-500">Lädt hoch</span>}
</div> </div>
{error && <p className="mt-1 text-sm text-red-600">{error}</p>} {error && <p className="mt-1 text-sm text-red-600">{error}</p>}
</div> </div>

View file

@ -10,8 +10,8 @@ export default function MaintenanceNotice({ message, fullScreen = false }) {
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-brand-100 text-brand-600 dark:bg-brand-900/40 dark:text-brand-400"> <div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-brand-100 text-brand-600 dark:bg-brand-900/40 dark:text-brand-400">
<WrenchIcon /> <WrenchIcon />
</div> </div>
<h1 className="mb-2 text-xl font-bold text-slate-900 dark:text-white">Wartungsarbeiten</h1> <h1 className="mb-2 text-xl font-bold text-neutral-900 dark:text-white">Wartungsarbeiten</h1>
<p className="text-slate-600 dark:text-slate-300"> <p className="text-neutral-600 dark:text-neutral-300">
{message || 'Dieser Bereich wird gerade aktualisiert. Bitte schau in Kürze wieder vorbei.'} {message || 'Dieser Bereich wird gerade aktualisiert. Bitte schau in Kürze wieder vorbei.'}
</p> </p>
</div> </div>
@ -19,7 +19,7 @@ export default function MaintenanceNotice({ message, fullScreen = false }) {
if (fullScreen) { if (fullScreen) {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-white px-4 dark:bg-slate-950"> <div className="flex min-h-screen items-center justify-center bg-white px-4 dark:bg-neutral-950">
{content} {content}
</div> </div>
); );

View file

@ -1,4 +1,5 @@
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import ThemeToggle from './ThemeToggle.jsx'; import ThemeToggle from './ThemeToggle.jsx';
import DynamicIcon from './DynamicIcon.jsx'; import DynamicIcon from './DynamicIcon.jsx';
@ -12,25 +13,41 @@ const digitsOnly = (value) => (value || '').replace(/[^\d+]/g, '');
export default function Navbar() { export default function Navbar() {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [menuVisible, setMenuVisible] = useState(false);
const { user } = useAuth(); const { user } = useAuth();
const { phone, whatsapp } = useSiteSettings(); const { phone, whatsapp } = useSiteSettings();
useEffect(() => {
if (!open) {
setMenuVisible(false);
return;
}
const id = requestAnimationFrame(() => setMenuVisible(true));
return () => cancelAnimationFrame(id);
}, [open]);
const flyIn = () => `transition-all duration-500 ease-out ${menuVisible ? 'opacity-100' : 'opacity-0'}`;
const flyInStyle = (index) => ({
transitionDelay: `${index * 70}ms`,
transform: menuVisible ? 'translateY(0)' : 'translateY(-140px)',
});
const AdminIcon = user && ( const AdminIcon = user && (
<Link <Link
to="/admin" to="/admin"
aria-label="Zum Admin-Panel" aria-label="Zum Admin-Panel"
title="Zum Admin-Panel" title="Zum Admin-Panel"
className="rounded-full p-2 text-slate-600 transition hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800" className="rounded-full p-2 text-neutral-600 transition hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800"
> >
<DynamicIcon name="layout-dashboard" className="h-5 w-5" /> <DynamicIcon name="layout-dashboard" className="h-5 w-5" />
</Link> </Link>
); );
return ( return (
<header className="sticky top-0 z-30 border-b border-slate-200 bg-white/90 backdrop-blur dark:border-slate-800 dark:bg-slate-950/90"> <header className="sticky top-0 z-30 border-b border-neutral-200 bg-white/90 backdrop-blur dark:border-neutral-800 dark:bg-neutral-950/90">
{/* Schmale Kontakt-Leiste (Telefon/WhatsApp/Shop) - auf kleinen Screens ausgeblendet, {/* Schmale Kontakt-Leiste (Telefon/WhatsApp/Shop) - auf kleinen Screens ausgeblendet,
steht dort stattdessen im aufklappbaren mobilen Menü. */} steht dort stattdessen im aufklappbaren mobilen Menü. */}
<div className="hidden border-b border-slate-100 bg-slate-50 px-4 py-1.5 text-xs text-slate-600 dark:border-slate-900 dark:bg-slate-900/40 dark:text-slate-400 sm:px-6 md:block"> <div className="hidden border-b border-neutral-100 bg-neutral-50 px-4 py-1.5 text-xs text-neutral-600 dark:border-neutral-900 dark:bg-neutral-900/40 dark:text-neutral-400 sm:px-6 md:block">
<div className="mx-auto flex max-w-6xl items-center justify-end gap-5"> <div className="mx-auto flex max-w-6xl items-center justify-end gap-5">
{phone && ( {phone && (
<a href={`tel:${digitsOnly(phone)}`} className="flex items-center gap-1.5 hover:text-brand-500"> <a href={`tel:${digitsOnly(phone)}`} className="flex items-center gap-1.5 hover:text-brand-500">
@ -57,44 +74,67 @@ export default function Navbar() {
</div> </div>
<div className="mx-auto grid max-w-6xl grid-cols-[1fr_auto_1fr] items-center gap-3 px-4 py-3 sm:px-6"> <div className="mx-auto grid max-w-6xl grid-cols-[1fr_auto_1fr] items-center gap-3 px-4 py-3 sm:px-6">
<div className="flex items-center justify-self-start"> <div className="flex items-center justify-self-start gap-1">
<nav className="hidden items-center gap-6 text-sm font-medium text-slate-600 dark:text-slate-300 md:flex"> {AdminIcon}
<Link to="/fahrzeuge" className="hover:text-brand-500">Fahrzeuge</Link> <ThemeToggle />
<Link to="/leistungen" className="hover:text-brand-500">Leistungen</Link>
<Link to="/kontakt" className="hover:text-brand-500">Kontakt</Link>
</nav>
<button
onClick={() => setOpen((o) => !o)}
aria-label="Menü öffnen"
className="rounded-md p-2 text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800 md:hidden"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-6 w-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div> </div>
<Link to="/" className="flex items-center justify-self-center rounded-lg px-2 py-1 dark:bg-white"> <Link to="/" className="flex items-center justify-self-center rounded-lg px-2 py-1 dark:bg-white">
<img src={logo} alt="HifiPlanet" className="h-11 w-auto sm:h-14" /> <img src={logo} alt="HifiPlanet" className="h-11 w-auto sm:h-14" />
</Link> </Link>
<div className="flex items-center justify-self-end gap-1"> <div className="flex items-center justify-self-end">
{AdminIcon} <nav className="hidden items-center gap-6 text-sm font-medium text-neutral-600 dark:text-neutral-300 md:flex">
<ThemeToggle /> <Link to="/fahrzeuge" className="hover:text-brand-500">Fahrzeuge</Link>
<Link to="/leistungen" className="hover:text-brand-500">Leistungen</Link>
<Link to="/kontakt" className="hover:text-brand-500">Kontakt</Link>
</nav>
<button
onClick={() => setOpen(true)}
aria-label="Menü öffnen"
className="rounded-md p-2 text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800 md:hidden"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-6 w-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div> </div>
</div> </div>
{open && ( {open &&
<nav className="flex flex-col gap-1 border-t border-slate-200 px-4 py-3 text-sm font-medium text-slate-600 dark:border-slate-800 dark:text-slate-300 md:hidden"> createPortal(
<Link to="/fahrzeuge" onClick={() => setOpen(false)} className="rounded-md px-3 py-2 hover:bg-slate-100 dark:hover:bg-slate-800">Fahrzeuge</Link> <div
<Link to="/leistungen" onClick={() => setOpen(false)} className="rounded-md px-3 py-2 hover:bg-slate-100 dark:hover:bg-slate-800">Leistungen</Link> className="fixed inset-0 z-[100] flex flex-col items-end justify-center gap-5 overflow-y-auto px-8 py-24 text-right backdrop-blur-xl md:hidden"
<Link to="/kontakt" onClick={() => setOpen(false)} className="rounded-md px-3 py-2 hover:bg-slate-100 dark:hover:bg-slate-800">Kontakt</Link> style={{ background: 'linear-gradient(135deg, #f1f5f9 0%, #e3f0cf 50%, #f8fafc 100%)' }}
<div className="my-1 border-t border-slate-200 dark:border-slate-800" /> >
<button
onClick={() => setOpen(false)}
aria-label="Menü schließen"
className="absolute right-6 top-6 flex items-center gap-2 text-neutral-600 hover:text-brand-600"
>
<span className="text-xs font-semibold uppercase tracking-wide">Schließen</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-6 w-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M6 6l12 12M18 6 6 18" />
</svg>
</button>
<Link to="/fahrzeuge" onClick={() => setOpen(false)} className={`text-3xl font-extrabold tracking-tight text-neutral-900 hover:text-brand-600 ${flyIn()}`} style={flyInStyle(0)}>
Fahrzeuge
</Link>
<Link to="/leistungen" onClick={() => setOpen(false)} className={`text-3xl font-extrabold tracking-tight text-neutral-900 hover:text-brand-600 ${flyIn()}`} style={flyInStyle(1)}>
Leistungen
</Link>
<Link to="/kontakt" onClick={() => setOpen(false)} className={`text-3xl font-extrabold tracking-tight text-neutral-900 hover:text-brand-600 ${flyIn()}`} style={flyInStyle(2)}>
Kontakt
</Link>
<div className={`h-px w-16 bg-brand-500/60 ${flyIn()}`} style={flyInStyle(3)} />
{phone && ( {phone && (
<a href={`tel:${digitsOnly(phone)}`} className="flex items-center gap-2 rounded-md px-3 py-2 hover:bg-slate-100 dark:hover:bg-slate-800"> <a href={`tel:${digitsOnly(phone)}`} className={`flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-neutral-600 hover:text-brand-600 ${flyIn()}`} style={flyInStyle(4)}>
<DynamicIcon name="phone" className="h-4 w-4" />
{phone} {phone}
<DynamicIcon name="phone" className="h-4 w-4" />
</a> </a>
)} )}
{whatsapp && ( {whatsapp && (
@ -102,22 +142,25 @@ export default function Navbar() {
href={`https://wa.me/${digitsOnly(whatsapp).replace(/^0/, '49').replace('+', '')}`} href={`https://wa.me/${digitsOnly(whatsapp).replace(/^0/, '49').replace('+', '')}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-2 rounded-md px-3 py-2 hover:bg-slate-100 dark:hover:bg-slate-800" className={`flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-neutral-600 hover:text-brand-600 ${flyIn()}`}
style={flyInStyle(5)}
> >
<DynamicIcon name="message-circle" className="h-4 w-4" />
WhatsApp WhatsApp
<DynamicIcon name="message-circle" className="h-4 w-4" />
</a> </a>
)} )}
<a <a
href={SHOP_URL} href={SHOP_URL}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-2 rounded-md px-3 py-2 hover:bg-slate-100 dark:hover:bg-slate-800" className={`flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-neutral-600 hover:text-brand-600 ${flyIn()}`}
style={flyInStyle(whatsapp ? 6 : 5)}
> >
<DynamicIcon name="shopping-bag" className="h-4 w-4" />
Zum Shop Zum Shop
<DynamicIcon name="shopping-bag" className="h-4 w-4" />
</a> </a>
</nav> </div>,
document.body
)} )}
</header> </header>
); );

View file

@ -5,7 +5,7 @@ export default function ProtectedRoute() {
const { user, loading } = useAuth(); const { user, loading } = useAuth();
if (loading) { if (loading) {
return <div className="flex min-h-screen items-center justify-center text-slate-500">Lädt</div>; return <div className="flex min-h-screen items-center justify-center text-neutral-500">Lädt</div>;
} }
if (!user) { if (!user) {

View file

@ -42,7 +42,7 @@ export default function PublicLayout() {
}, []); }, []);
if (status === null || authLoading) { if (status === null || authLoading) {
return <div className="min-h-screen bg-white dark:bg-slate-950" />; return <div className="min-h-screen bg-white dark:bg-neutral-950" />;
} }
const bypass = hasPermission('maintenance.bypass'); const bypass = hasPermission('maintenance.bypass');
@ -55,7 +55,7 @@ export default function PublicLayout() {
return ( return (
<MaintenanceProvider value={{ ...status, bypass }}> <MaintenanceProvider value={{ ...status, bypass }}>
<SiteSettingsProvider value={siteSettings}> <SiteSettingsProvider value={siteSettings}>
<div className="flex min-h-screen flex-col bg-white text-slate-900 dark:bg-slate-950 dark:text-slate-100"> <div className="flex min-h-screen flex-col bg-white text-neutral-900 dark:bg-neutral-950 dark:text-neutral-100">
{bypass && status.global.enabled && <MaintenanceBypassBanner />} {bypass && status.global.enabled && <MaintenanceBypassBanner />}
<ScrollProgress /> <ScrollProgress />
<Navbar /> <Navbar />

View file

@ -8,9 +8,9 @@ export default function RequirePermission({ permission, children }) {
if (!allowed) { if (!allowed) {
return ( return (
<div className="rounded-xl border border-slate-200 bg-white p-8 text-center dark:border-slate-800 dark:bg-slate-900"> <div className="rounded-xl border border-neutral-200 bg-white p-8 text-center dark:border-neutral-800 dark:bg-neutral-900">
<h1 className="mb-2 text-lg font-bold text-slate-900 dark:text-white">Keine Berechtigung</h1> <h1 className="mb-2 text-lg font-bold text-neutral-900 dark:text-white">Keine Berechtigung</h1>
<p className="text-sm text-slate-500 dark:text-slate-400"> <p className="text-sm text-neutral-500 dark:text-neutral-400">
Du hast keinen Zugriff auf diesen Bereich. Bitte wende dich an einen Administrator, falls du hier Du hast keinen Zugriff auf diesen Bereich. Bitte wende dich an einen Administrator, falls du hier
Zugriff brauchst. Zugriff brauchst.
</p> </p>

View file

@ -33,7 +33,7 @@ export default function SetupGate({ children }) {
}, [location.pathname]); }, [location.pathname]);
if (state.pathname !== location.pathname || state.needsSetup === null) { if (state.pathname !== location.pathname || state.needsSetup === null) {
return <div className="min-h-screen bg-white dark:bg-slate-950" />; return <div className="min-h-screen bg-white dark:bg-neutral-950" />;
} }
if (state.needsSetup && location.pathname !== '/setup') { if (state.needsSetup && location.pathname !== '/setup') {

View file

@ -123,10 +123,10 @@ export default function TestimonialSlider({ testimonials }) {
<div ref={trackRef} className="marquee-track flex w-max gap-5"> <div ref={trackRef} className="marquee-track flex w-max gap-5">
{track.map((t, i) => ( {track.map((t, i) => (
<div key={`${t.name}-${i}`} className="w-64 shrink-0 sm:w-72 lg:w-80"> <div key={`${t.name}-${i}`} className="w-64 shrink-0 sm:w-72 lg:w-80">
<div className="flex h-full flex-col rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <div className="flex h-full flex-col rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<StarRating className="mb-3 h-4 w-4" /> <StarRating className="mb-3 h-4 w-4" />
<p className="mb-4 flex-1 text-sm text-slate-600 dark:text-slate-300">{t.text}"</p> <p className="mb-4 flex-1 text-sm text-neutral-600 dark:text-neutral-300">{t.text}"</p>
<p className="text-sm font-semibold text-slate-900 dark:text-white">{t.name}</p> <p className="text-sm font-semibold text-neutral-900 dark:text-white">{t.name}</p>
</div> </div>
</div> </div>
))} ))}

View file

@ -7,7 +7,7 @@ export default function ThemeToggle() {
<button <button
onClick={toggleTheme} onClick={toggleTheme}
aria-label="Theme umschalten" aria-label="Theme umschalten"
className="rounded-full p-2 text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800 transition" className="rounded-full p-2 text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800 transition"
> >
{theme === 'dark' ? ( {theme === 'dark' ? (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="h-5 w-5"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="h-5 w-5">

View file

@ -119,20 +119,20 @@ export default function AccountSettings() {
return ( return (
<div className="max-w-xl"> <div className="max-w-xl">
<h1 className="mb-1 text-xl font-bold text-slate-900 dark:text-white">Mein Konto</h1> <h1 className="mb-1 text-xl font-bold text-neutral-900 dark:text-white">Mein Konto</h1>
<p className="mb-6 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-6 text-sm text-neutral-500 dark:text-neutral-400">
Angemeldet als <strong>{user?.username}</strong>{user?.is_super_admin ? ' (Super-Admin)' : ''}. Angemeldet als <strong>{user?.username}</strong>{user?.is_super_admin ? ' (Super-Admin)' : ''}.
</p> </p>
<div className="mb-6 rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <div className="mb-6 rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-1 font-semibold text-slate-900 dark:text-white">Passwort ändern</h2> <h2 className="mb-1 font-semibold text-neutral-900 dark:text-white">Passwort ändern</h2>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Vergib ein neues Passwort für dein Konto. Vergib ein neues Passwort für dein Konto.
{user?.two_factor_enabled && ' Da 2FA aktiv ist, musst du die Änderung zusätzlich mit deinem Code bestätigen.'} {user?.two_factor_enabled && ' Da 2FA aktiv ist, musst du die Änderung zusätzlich mit deinem Code bestätigen.'}
</p> </p>
<form onSubmit={handleChangePassword}> <form onSubmit={handleChangePassword}>
<div className="mb-4"> <div className="mb-4">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
Aktuelles Passwort Aktuelles Passwort
</label> </label>
<input <input
@ -140,11 +140,11 @@ export default function AccountSettings() {
value={passwordForm.current_password} value={passwordForm.current_password}
onChange={(e) => setPasswordForm({ ...passwordForm, current_password: e.target.value })} onChange={(e) => setPasswordForm({ ...passwordForm, current_password: e.target.value })}
required required
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div className="mb-4"> <div className="mb-4">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
Neues Passwort Neues Passwort
</label> </label>
<input <input
@ -153,11 +153,11 @@ export default function AccountSettings() {
onChange={(e) => setPasswordForm({ ...passwordForm, new_password: e.target.value })} onChange={(e) => setPasswordForm({ ...passwordForm, new_password: e.target.value })}
required required
minLength={8} minLength={8}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div className="mb-4"> <div className="mb-4">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
Neues Passwort wiederholen Neues Passwort wiederholen
</label> </label>
<input <input
@ -166,12 +166,12 @@ export default function AccountSettings() {
onChange={(e) => setPasswordForm({ ...passwordForm, new_password_confirmation: e.target.value })} onChange={(e) => setPasswordForm({ ...passwordForm, new_password_confirmation: e.target.value })}
required required
minLength={8} minLength={8}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
{user?.two_factor_enabled && ( {user?.two_factor_enabled && (
<div className="mb-4"> <div className="mb-4">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
2FA-Code zur Bestätigung 2FA-Code zur Bestätigung
</label> </label>
<input <input
@ -180,7 +180,7 @@ export default function AccountSettings() {
required required
inputMode="numeric" inputMode="numeric"
placeholder="123456 oder Wiederherstellungscode" placeholder="123456 oder Wiederherstellungscode"
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
)} )}
@ -196,9 +196,9 @@ export default function AccountSettings() {
</form> </form>
</div> </div>
<div className="rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <div className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-1 font-semibold text-slate-900 dark:text-white">Zwei-Faktor-Authentifizierung</h2> <h2 className="mb-1 font-semibold text-neutral-900 dark:text-white">Zwei-Faktor-Authentifizierung</h2>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Schützt dein Konto zusätzlich mit einem Code aus der Google Authenticator App. Schützt dein Konto zusätzlich mit einem Code aus der Google Authenticator App.
</p> </p>
@ -209,14 +209,14 @@ export default function AccountSettings() {
{user?.two_factor_enabled ? ( {user?.two_factor_enabled ? (
<span className="font-semibold text-green-600 dark:text-green-400">Aktiv</span> <span className="font-semibold text-green-600 dark:text-green-400">Aktiv</span>
) : ( ) : (
<span className="font-semibold text-slate-500 dark:text-slate-400">Nicht aktiv</span> <span className="font-semibold text-neutral-500 dark:text-neutral-400">Nicht aktiv</span>
)} )}
</p> </p>
{user?.two_factor_enabled ? ( {user?.two_factor_enabled ? (
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<button <button
onClick={() => { setPhase('regenerate'); setError(''); }} onClick={() => { setPhase('regenerate'); setError(''); }}
className="rounded-md border border-slate-300 px-4 py-2 text-sm font-medium hover:bg-slate-50 dark:border-slate-700 dark:hover:bg-slate-800" className="rounded-md border border-neutral-300 px-4 py-2 text-sm font-medium hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800"
> >
Neue Wiederherstellungscodes erzeugen Neue Wiederherstellungscodes erzeugen
</button> </button>
@ -241,19 +241,19 @@ export default function AccountSettings() {
{phase === 'setup' && ( {phase === 'setup' && (
<form onSubmit={confirmSetup}> <form onSubmit={confirmSetup}>
<ol className="mb-4 list-decimal space-y-2 pl-5 text-sm text-slate-700 dark:text-slate-300"> <ol className="mb-4 list-decimal space-y-2 pl-5 text-sm text-neutral-700 dark:text-neutral-300">
<li>Öffne die Google Authenticator App (oder eine kompatible App).</li> <li>Öffne die Google Authenticator App (oder eine kompatible App).</li>
<li>Scanne den folgenden QR-Code:</li> <li>Scanne den folgenden QR-Code:</li>
</ol> </ol>
<div className="mb-4 flex justify-center"> <div className="mb-4 flex justify-center">
{qrDataUrl && <img src={qrDataUrl} alt="QR-Code für Zwei-Faktor-Authentifizierung" className="h-48 w-48 rounded-lg border border-slate-200 dark:border-slate-700" />} {qrDataUrl && <img src={qrDataUrl} alt="QR-Code für Zwei-Faktor-Authentifizierung" className="h-48 w-48 rounded-lg border border-neutral-200 dark:border-neutral-700" />}
</div> </div>
<p className="mb-4 text-center text-xs text-slate-500 dark:text-slate-400"> <p className="mb-4 text-center text-xs text-neutral-500 dark:text-neutral-400">
Geht das Scannen nicht? Gib diesen Code manuell ein: <br /> Geht das Scannen nicht? Gib diesen Code manuell ein: <br />
<code className="mt-1 inline-block rounded bg-slate-100 px-2 py-1 font-mono tracking-wider dark:bg-slate-800">{secret}</code> <code className="mt-1 inline-block rounded bg-neutral-100 px-2 py-1 font-mono tracking-wider dark:bg-neutral-800">{secret}</code>
</p> </p>
<div className="mb-4"> <div className="mb-4">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
Bestätigungscode aus der App Bestätigungscode aus der App
</label> </label>
<input <input
@ -263,7 +263,7 @@ export default function AccountSettings() {
autoFocus autoFocus
inputMode="numeric" inputMode="numeric"
placeholder="123456" placeholder="123456"
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-center text-lg tracking-widest dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-center text-lg tracking-widest dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
{error && <p className="mb-4 text-sm text-red-600">{error}</p>} {error && <p className="mb-4 text-sm text-red-600">{error}</p>}
@ -271,7 +271,7 @@ export default function AccountSettings() {
<button type="submit" disabled={busy} className="rounded-md bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600 disabled:opacity-60"> <button type="submit" disabled={busy} className="rounded-md bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600 disabled:opacity-60">
Bestätigen & aktivieren Bestätigen & aktivieren
</button> </button>
<button type="button" onClick={() => setPhase('idle')} className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700"> <button type="button" onClick={() => setPhase('idle')} className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700">
Abbrechen Abbrechen
</button> </button>
</div> </div>
@ -280,14 +280,14 @@ export default function AccountSettings() {
{phase === 'recovery-codes' && ( {phase === 'recovery-codes' && (
<div> <div>
<p className="mb-3 text-sm font-semibold text-slate-900 dark:text-white"> <p className="mb-3 text-sm font-semibold text-neutral-900 dark:text-white">
Wiederherstellungscodes jetzt sicher speichern! Wiederherstellungscodes jetzt sicher speichern!
</p> </p>
<p className="mb-3 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-3 text-sm text-neutral-500 dark:text-neutral-400">
Jeder Code funktioniert einmalig, falls du keinen Zugriff mehr auf die Authenticator-App hast. Jeder Code funktioniert einmalig, falls du keinen Zugriff mehr auf die Authenticator-App hast.
Diese Codes werden nur jetzt angezeigt. Diese Codes werden nur jetzt angezeigt.
</p> </p>
<div className="mb-4 grid grid-cols-2 gap-2 rounded-md bg-slate-100 p-4 font-mono text-sm dark:bg-slate-800"> <div className="mb-4 grid grid-cols-2 gap-2 rounded-md bg-neutral-100 p-4 font-mono text-sm dark:bg-neutral-800">
{recoveryCodes.map((c) => <span key={c}>{c}</span>)} {recoveryCodes.map((c) => <span key={c}>{c}</span>)}
</div> </div>
<button onClick={finishRecoveryCodes} className="rounded-md bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600"> <button onClick={finishRecoveryCodes} className="rounded-md bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600">
@ -299,7 +299,7 @@ export default function AccountSettings() {
{phase === 'disable' && ( {phase === 'disable' && (
<form onSubmit={handleDisable}> <form onSubmit={handleDisable}>
<div className="mb-4"> <div className="mb-4">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
Passwort zur Bestätigung Passwort zur Bestätigung
</label> </label>
<input <input
@ -308,7 +308,7 @@ export default function AccountSettings() {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
required required
autoFocus autoFocus
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
{error && <p className="mb-4 text-sm text-red-600">{error}</p>} {error && <p className="mb-4 text-sm text-red-600">{error}</p>}
@ -316,7 +316,7 @@ export default function AccountSettings() {
<button type="submit" disabled={busy} className="rounded-md bg-red-600 px-4 py-2 text-sm font-semibold text-white hover:bg-red-700 disabled:opacity-60"> <button type="submit" disabled={busy} className="rounded-md bg-red-600 px-4 py-2 text-sm font-semibold text-white hover:bg-red-700 disabled:opacity-60">
2FA jetzt deaktivieren 2FA jetzt deaktivieren
</button> </button>
<button type="button" onClick={() => { setPhase('idle'); setPassword(''); }} className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700"> <button type="button" onClick={() => { setPhase('idle'); setPassword(''); }} className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700">
Abbrechen Abbrechen
</button> </button>
</div> </div>
@ -325,11 +325,11 @@ export default function AccountSettings() {
{phase === 'regenerate' && ( {phase === 'regenerate' && (
<form onSubmit={handleRegenerate}> <form onSubmit={handleRegenerate}>
<p className="mb-3 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-3 text-sm text-neutral-500 dark:text-neutral-400">
Die bisherigen Wiederherstellungscodes werden ungültig. Die bisherigen Wiederherstellungscodes werden ungültig.
</p> </p>
<div className="mb-4"> <div className="mb-4">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
Passwort zur Bestätigung Passwort zur Bestätigung
</label> </label>
<input <input
@ -338,7 +338,7 @@ export default function AccountSettings() {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
required required
autoFocus autoFocus
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
{error && <p className="mb-4 text-sm text-red-600">{error}</p>} {error && <p className="mb-4 text-sm text-red-600">{error}</p>}
@ -346,7 +346,7 @@ export default function AccountSettings() {
<button type="submit" disabled={busy} className="rounded-md bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600 disabled:opacity-60"> <button type="submit" disabled={busy} className="rounded-md bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600 disabled:opacity-60">
Neue Codes erzeugen Neue Codes erzeugen
</button> </button>
<button type="button" onClick={() => { setPhase('idle'); setPassword(''); }} className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700"> <button type="button" onClick={() => { setPhase('idle'); setPassword(''); }} className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700">
Abbrechen Abbrechen
</button> </button>
</div> </div>

View file

@ -86,13 +86,13 @@ export default function AdminUsers() {
return ( return (
<div className="grid gap-6 lg:grid-cols-3"> <div className="grid gap-6 lg:grid-cols-3">
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<h1 className="mb-1 text-xl font-bold text-slate-900 dark:text-white">Benutzer</h1> <h1 className="mb-1 text-xl font-bold text-neutral-900 dark:text-white">Benutzer</h1>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Jeder Mitarbeiter bekommt ein eigenes Konto mit genau den Rechten, die er braucht. Jeder Mitarbeiter bekommt ein eigenes Konto mit genau den Rechten, die er braucht.
</p> </p>
<div className="overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800"> <div className="overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800">
<table className="w-full text-left text-sm"> <table className="w-full text-left text-sm">
<thead className="bg-slate-50 text-slate-500 dark:bg-slate-900 dark:text-slate-400"> <thead className="bg-neutral-50 text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
<tr> <tr>
<th className="px-4 py-2">Benutzername</th> <th className="px-4 py-2">Benutzername</th>
<th className="px-4 py-2">Rolle</th> <th className="px-4 py-2">Rolle</th>
@ -100,12 +100,12 @@ export default function AdminUsers() {
<th className="px-4 py-2 text-right">Aktionen</th> <th className="px-4 py-2 text-right">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-100 dark:divide-slate-800"> <tbody className="divide-y divide-neutral-100 dark:divide-neutral-800">
{users.map((u) => ( {users.map((u) => (
<tr key={u.id} className="bg-white dark:bg-slate-950"> <tr key={u.id} className="bg-white dark:bg-neutral-950">
<td className="px-4 py-2 font-medium text-slate-800 dark:text-slate-100"> <td className="px-4 py-2 font-medium text-neutral-800 dark:text-neutral-100">
{u.username} {u.username}
{u.id === currentUser?.id && <span className="ml-1 text-xs text-slate-400">(du)</span>} {u.id === currentUser?.id && <span className="ml-1 text-xs text-neutral-400">(du)</span>}
</td> </td>
<td className="px-4 py-2"> <td className="px-4 py-2">
{u.is_super_admin ? ( {u.is_super_admin ? (
@ -113,13 +113,13 @@ export default function AdminUsers() {
Super-Admin Super-Admin
</span> </span>
) : ( ) : (
<span className="text-slate-500 dark:text-slate-400">{u.permissions.length} Einzelrechte</span> <span className="text-neutral-500 dark:text-neutral-400">{u.permissions.length} Einzelrechte</span>
)} )}
</td> </td>
<td className="px-4 py-2"> <td className="px-4 py-2">
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{u.groups.map((g) => ( {u.groups.map((g) => (
<span key={g.id} className="rounded-full bg-slate-100 px-2 py-0.5 text-xs text-slate-600 dark:bg-slate-800 dark:text-slate-300"> <span key={g.id} className="rounded-full bg-neutral-100 px-2 py-0.5 text-xs text-neutral-600 dark:bg-neutral-800 dark:text-neutral-300">
{g.name} {g.name}
</span> </span>
))} ))}
@ -135,26 +135,26 @@ export default function AdminUsers() {
</tr> </tr>
))} ))}
{users.length === 0 && ( {users.length === 0 && (
<tr><td colSpan={4} className="px-4 py-6 text-center text-slate-400">Noch keine Benutzer angelegt.</td></tr> <tr><td colSpan={4} className="px-4 py-6 text-center text-neutral-400">Noch keine Benutzer angelegt.</td></tr>
)} )}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900"> <form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="font-bold text-slate-900 dark:text-white">{editingId ? 'Benutzer bearbeiten' : 'Neuer Benutzer'}</h2> <h2 className="font-bold text-neutral-900 dark:text-white">{editingId ? 'Benutzer bearbeiten' : 'Neuer Benutzer'}</h2>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Benutzername *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Benutzername *</label>
<input <input
required required
value={form.username} value={form.username}
onChange={(e) => setForm({ ...form, username: e.target.value })} onChange={(e) => setForm({ ...form, username: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
Passwort {editingId ? '(leer lassen = unverändert)' : '*'} Passwort {editingId ? '(leer lassen = unverändert)' : '*'}
</label> </label>
<input <input
@ -162,17 +162,17 @@ export default function AdminUsers() {
required={!editingId} required={!editingId}
value={form.password} value={form.password}
onChange={(e) => setForm({ ...form, password: e.target.value })} onChange={(e) => setForm({ ...form, password: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
{currentUser?.is_super_admin && ( {currentUser?.is_super_admin && (
<label className="flex items-center gap-2 text-sm text-slate-700 dark:text-slate-300"> <label className="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-300">
<input <input
type="checkbox" type="checkbox"
checked={form.is_super_admin} checked={form.is_super_admin}
onChange={(e) => setForm({ ...form, is_super_admin: e.target.checked })} onChange={(e) => setForm({ ...form, is_super_admin: e.target.checked })}
className="h-4 w-4 rounded border-slate-300 text-brand-600 focus:ring-brand-500" className="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500"
/> />
Super-Admin (hat automatisch alle Rechte) Super-Admin (hat automatisch alle Rechte)
</label> </label>
@ -181,15 +181,15 @@ export default function AdminUsers() {
{!form.is_super_admin && ( {!form.is_super_admin && (
<> <>
<div> <div>
<p className="mb-1 text-sm font-medium text-slate-700 dark:text-slate-300">Einzelne Berechtigungen</p> <p className="mb-1 text-sm font-medium text-neutral-700 dark:text-neutral-300">Einzelne Berechtigungen</p>
<div className="space-y-1.5 rounded-md border border-slate-200 p-3 dark:border-slate-700"> <div className="space-y-1.5 rounded-md border border-neutral-200 p-3 dark:border-neutral-700">
{catalog.map((p) => ( {catalog.map((p) => (
<label key={p.key} className="flex items-center gap-2 text-sm text-slate-700 dark:text-slate-300"> <label key={p.key} className="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-300">
<input <input
type="checkbox" type="checkbox"
checked={form.permissions.includes(p.key)} checked={form.permissions.includes(p.key)}
onChange={() => togglePermission(p.key)} onChange={() => togglePermission(p.key)}
className="h-4 w-4 rounded border-slate-300 text-brand-600 focus:ring-brand-500" className="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500"
/> />
{p.label} {p.label}
</label> </label>
@ -198,20 +198,20 @@ export default function AdminUsers() {
</div> </div>
<div> <div>
<p className="mb-1 text-sm font-medium text-slate-700 dark:text-slate-300">Berechtigungsgruppen</p> <p className="mb-1 text-sm font-medium text-neutral-700 dark:text-neutral-300">Berechtigungsgruppen</p>
<div className="space-y-1.5 rounded-md border border-slate-200 p-3 dark:border-slate-700"> <div className="space-y-1.5 rounded-md border border-neutral-200 p-3 dark:border-neutral-700">
{groups.map((g) => ( {groups.map((g) => (
<label key={g.id} className="flex items-center gap-2 text-sm text-slate-700 dark:text-slate-300"> <label key={g.id} className="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-300">
<input <input
type="checkbox" type="checkbox"
checked={form.group_ids.includes(g.id)} checked={form.group_ids.includes(g.id)}
onChange={() => toggleGroup(g.id)} onChange={() => toggleGroup(g.id)}
className="h-4 w-4 rounded border-slate-300 text-brand-600 focus:ring-brand-500" className="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500"
/> />
{g.name} {g.name}
</label> </label>
))} ))}
{groups.length === 0 && <p className="text-sm text-slate-400">Noch keine Berechtigungsgruppen angelegt.</p>} {groups.length === 0 && <p className="text-sm text-neutral-400">Noch keine Berechtigungsgruppen angelegt.</p>}
</div> </div>
</div> </div>
</> </>
@ -224,7 +224,7 @@ export default function AdminUsers() {
{editingId ? 'Speichern' : 'Anlegen'} {editingId ? 'Speichern' : 'Anlegen'}
</button> </button>
{editingId && ( {editingId && (
<button type="button" onClick={resetForm} className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700"> <button type="button" onClick={resetForm} className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700">
Abbrechen Abbrechen
</button> </button>
)} )}

View file

@ -51,20 +51,20 @@ export default function Brands() {
return ( return (
<div className="grid gap-6 lg:grid-cols-3"> <div className="grid gap-6 lg:grid-cols-3">
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<h1 className="mb-4 text-xl font-bold text-slate-900 dark:text-white">Marken</h1> <h1 className="mb-4 text-xl font-bold text-neutral-900 dark:text-white">Marken</h1>
<div className="overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800"> <div className="overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800">
<table className="w-full text-left text-sm"> <table className="w-full text-left text-sm">
<thead className="bg-slate-50 text-slate-500 dark:bg-slate-900 dark:text-slate-400"> <thead className="bg-neutral-50 text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
<tr> <tr>
<th className="px-4 py-2">Name</th> <th className="px-4 py-2">Name</th>
<th className="px-4 py-2">Sortierung</th> <th className="px-4 py-2">Sortierung</th>
<th className="px-4 py-2 text-right">Aktionen</th> <th className="px-4 py-2 text-right">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-100 dark:divide-slate-800"> <tbody className="divide-y divide-neutral-100 dark:divide-neutral-800">
{brands.map((brand) => ( {brands.map((brand) => (
<tr key={brand.id} className="bg-white dark:bg-slate-950"> <tr key={brand.id} className="bg-white dark:bg-neutral-950">
<td className="px-4 py-2 font-medium text-slate-800 dark:text-slate-100"> <td className="px-4 py-2 font-medium text-neutral-800 dark:text-neutral-100">
<Link to={`/marken/${brand.slug}`} target="_blank" className="hover:text-brand-500"> <Link to={`/marken/${brand.slug}`} target="_blank" className="hover:text-brand-500">
{brand.name} {brand.name}
</Link> </Link>
@ -77,31 +77,31 @@ export default function Brands() {
</tr> </tr>
))} ))}
{brands.length === 0 && ( {brands.length === 0 && (
<tr><td colSpan={3} className="px-4 py-6 text-center text-slate-400">Noch keine Marken angelegt.</td></tr> <tr><td colSpan={3} className="px-4 py-6 text-center text-neutral-400">Noch keine Marken angelegt.</td></tr>
)} )}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900"> <form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="font-bold text-slate-900 dark:text-white">{editingId ? 'Marke bearbeiten' : 'Neue Marke'}</h2> <h2 className="font-bold text-neutral-900 dark:text-white">{editingId ? 'Marke bearbeiten' : 'Neue Marke'}</h2>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Name *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Name *</label>
<input <input
required required
value={form.name} value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })} onChange={(e) => setForm({ ...form, name: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Sortierung</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Sortierung</label>
<input <input
type="number" type="number"
value={form.sort_order} value={form.sort_order}
onChange={(e) => setForm({ ...form, sort_order: Number(e.target.value) })} onChange={(e) => setForm({ ...form, sort_order: Number(e.target.value) })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
@ -112,7 +112,7 @@ export default function Brands() {
{editingId ? 'Speichern' : 'Anlegen'} {editingId ? 'Speichern' : 'Anlegen'}
</button> </button>
{editingId && ( {editingId && (
<button type="button" onClick={resetForm} className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700"> <button type="button" onClick={resetForm} className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700">
Abbrechen Abbrechen
</button> </button>
)} )}

View file

@ -61,8 +61,8 @@ export default function ContactRequests() {
return ( return (
<div> <div>
<h1 className="mb-1 text-xl font-bold text-slate-900 dark:text-white">Kontaktanfragen</h1> <h1 className="mb-1 text-xl font-bold text-neutral-900 dark:text-white">Kontaktanfragen</h1>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Alle Anfragen deiner Kunden. Wähle oben einen Reiter, um nur bestimmte Anfragen zu sehen. Alle Anfragen deiner Kunden. Wähle oben einen Reiter, um nur bestimmte Anfragen zu sehen.
</p> </p>
@ -74,7 +74,7 @@ export default function ContactRequests() {
className={`rounded-full px-4 py-1.5 text-sm font-medium transition ${ className={`rounded-full px-4 py-1.5 text-sm font-medium transition ${
filter === tab.value filter === tab.value
? 'bg-brand-500 text-white' ? 'bg-brand-500 text-white'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700' : 'bg-neutral-100 text-neutral-600 hover:bg-neutral-200 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700'
}`} }`}
> >
{tab.label} ({counts[tab.value]}) {tab.label} ({counts[tab.value]})
@ -84,14 +84,14 @@ export default function ContactRequests() {
<div className="space-y-3"> <div className="space-y-3">
{filtered.map((r) => ( {filtered.map((r) => (
<div key={r.id} className="rounded-xl border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900"> <div key={r.id} className="rounded-xl border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-neutral-900">
<div className="mb-2 flex flex-wrap items-start justify-between gap-2"> <div className="mb-2 flex flex-wrap items-start justify-between gap-2">
<div> <div>
<p className="font-semibold text-slate-800 dark:text-slate-100">{r.name} · {r.email}{r.phone ? ` · ${r.phone}` : ''}</p> <p className="font-semibold text-neutral-800 dark:text-neutral-100">{r.name} · {r.email}{r.phone ? ` · ${r.phone}` : ''}</p>
{r.vin && ( {r.vin && (
<p className="text-xs text-slate-500 dark:text-slate-400">FIN: <span className="font-mono">{r.vin}</span></p> <p className="text-xs text-neutral-500 dark:text-neutral-400">FIN: <span className="font-mono">{r.vin}</span></p>
)} )}
<p className="text-xs text-slate-400">{new Date(r.created_at.replace(' ', 'T')).toLocaleString('de-DE')}</p> <p className="text-xs text-neutral-400">{new Date(r.created_at.replace(' ', 'T')).toLocaleString('de-DE')}</p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<select <select
@ -108,7 +108,7 @@ export default function ContactRequests() {
onClick={() => handleDelete(r.id)} onClick={() => handleDelete(r.id)}
aria-label="Anfrage löschen" aria-label="Anfrage löschen"
title="Anfrage löschen" title="Anfrage löschen"
className="rounded-md p-1.5 text-slate-400 hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20" className="rounded-md p-1.5 text-neutral-400 hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
> >
<TrashIcon /> <TrashIcon />
</button> </button>
@ -117,7 +117,7 @@ export default function ContactRequests() {
</div> </div>
{(r.brand_name || r.model_name || r.package_name || r.product_name) && ( {(r.brand_name || r.model_name || r.package_name || r.product_name) && (
<p className="mb-2 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-2 text-sm text-neutral-500 dark:text-neutral-400">
{[r.brand_name, r.model_name, r.package_name, r.product_name].filter(Boolean).join(' · ')} {[r.brand_name, r.model_name, r.package_name, r.product_name].filter(Boolean).join(' · ')}
</p> </p>
)} )}
@ -133,12 +133,12 @@ export default function ContactRequests() {
</div> </div>
)} )}
{r.message && <p className="text-sm text-slate-700 dark:text-slate-300">{r.message}</p>} {r.message && <p className="text-sm text-neutral-700 dark:text-neutral-300">{r.message}</p>}
</div> </div>
))} ))}
{filtered.length === 0 && ( {filtered.length === 0 && (
<p className="text-slate-400"> <p className="text-neutral-400">
{filter === 'all' ? 'Noch keine Kontaktanfragen eingegangen.' : 'Keine Anfragen mit diesem Status.'} {filter === 'all' ? 'Noch keine Kontaktanfragen eingegangen.' : 'Keine Anfragen mit diesem Status.'}
</p> </p>
)} )}

View file

@ -40,12 +40,12 @@ export default function Dashboard() {
return ( return (
<div> <div>
<h1 className="mb-1 text-xl font-bold text-slate-900 dark:text-white">Willkommen, {user?.username}!</h1> <h1 className="mb-1 text-xl font-bold text-neutral-900 dark:text-white">Willkommen, {user?.username}!</h1>
<p className="mb-6 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-6 text-sm text-neutral-500 dark:text-neutral-400">
Hier siehst du auf einen Blick, wie viel in deinem Shop hinterlegt ist. Klicke auf eine Kachel, um dorthin zu gelangen. Hier siehst du auf einen Blick, wie viel in deinem Shop hinterlegt ist. Klicke auf eine Kachel, um dorthin zu gelangen.
</p> </p>
{cards.length === 0 ? ( {cards.length === 0 ? (
<p className="text-slate-500 dark:text-slate-400"> <p className="text-neutral-500 dark:text-neutral-400">
Für deinen Account sind noch keine Bereiche freigeschaltet. Bitte wende dich an einen Administrator. Für deinen Account sind noch keine Bereiche freigeschaltet. Bitte wende dich an einen Administrator.
</p> </p>
) : ( ) : (
@ -57,15 +57,15 @@ export default function Dashboard() {
className={`rounded-xl border p-5 shadow-sm transition hover:-translate-y-0.5 hover:shadow-md ${ className={`rounded-xl border p-5 shadow-sm transition hover:-translate-y-0.5 hover:shadow-md ${
card.highlight && card.value > 0 card.highlight && card.value > 0
? 'border-brand-300 bg-brand-50 dark:border-brand-900 dark:bg-brand-900/20' ? 'border-brand-300 bg-brand-50 dark:border-brand-900 dark:bg-brand-900/20'
: 'border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900' : 'border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900'
}`} }`}
> >
<div className="mb-3 flex h-10 w-10 items-center justify-center rounded-full bg-brand-100 text-brand-600 dark:bg-brand-900/40 dark:text-brand-400"> <div className="mb-3 flex h-10 w-10 items-center justify-center rounded-full bg-brand-100 text-brand-600 dark:bg-brand-900/40 dark:text-brand-400">
<Icon path={ICONS[card.icon]} /> <Icon path={ICONS[card.icon]} />
</div> </div>
<p className="text-sm font-semibold text-slate-700 dark:text-slate-200">{card.label}</p> <p className="text-sm font-semibold text-neutral-700 dark:text-neutral-200">{card.label}</p>
<p className="mt-1 text-3xl font-extrabold text-brand-600 dark:text-brand-400">{card.value ?? '…'}</p> <p className="mt-1 text-3xl font-extrabold text-brand-600 dark:text-brand-400">{card.value ?? '…'}</p>
<p className="mt-2 text-xs text-slate-500 dark:text-slate-400">{card.hint}</p> <p className="mt-2 text-xs text-neutral-500 dark:text-neutral-400">{card.hint}</p>
</Link> </Link>
))} ))}
</div> </div>

View file

@ -50,30 +50,30 @@ export default function Login() {
}; };
return ( return (
<div className="flex min-h-screen flex-col items-center justify-center gap-6 bg-slate-50 px-4 dark:bg-slate-950"> <div className="flex min-h-screen flex-col items-center justify-center gap-6 bg-neutral-50 px-4 dark:bg-neutral-950">
<img src={logo} alt="HifiPlanet" className="h-16 w-auto sm:h-20" /> <img src={logo} alt="HifiPlanet" className="h-16 w-auto sm:h-20" />
{step === 'credentials' ? ( {step === 'credentials' ? (
<form onSubmit={handleCredentials} className="w-full max-w-sm rounded-xl border border-slate-200 bg-white p-8 shadow-sm dark:border-slate-800 dark:bg-slate-900"> <form onSubmit={handleCredentials} className="w-full max-w-sm rounded-xl border border-neutral-200 bg-white p-8 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
<h1 className="mb-6 text-xl font-bold text-slate-900 dark:text-white">Admin-Login</h1> <h1 className="mb-6 text-xl font-bold text-neutral-900 dark:text-white">Admin-Login</h1>
<div className="mb-4"> <div className="mb-4">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Benutzername</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Benutzername</label>
<input <input
value={username} value={username}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
required required
autoFocus autoFocus
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div className="mb-6"> <div className="mb-6">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Passwort</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Passwort</label>
<input <input
type="password" type="password"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
required required
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
@ -88,15 +88,15 @@ export default function Login() {
</button> </button>
</form> </form>
) : ( ) : (
<form onSubmit={handleCode} className="w-full max-w-sm rounded-xl border border-slate-200 bg-white p-8 shadow-sm dark:border-slate-800 dark:bg-slate-900"> <form onSubmit={handleCode} className="w-full max-w-sm rounded-xl border border-neutral-200 bg-white p-8 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
<h1 className="mb-2 text-xl font-bold text-slate-900 dark:text-white">Bestätigungscode</h1> <h1 className="mb-2 text-xl font-bold text-neutral-900 dark:text-white">Bestätigungscode</h1>
<p className="mb-6 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-6 text-sm text-neutral-500 dark:text-neutral-400">
Öffne die Google Authenticator App und gib den 6-stelligen Code ein. Alternativ kannst du auch Öffne die Google Authenticator App und gib den 6-stelligen Code ein. Alternativ kannst du auch
einen deiner Wiederherstellungscodes verwenden. einen deiner Wiederherstellungscodes verwenden.
</p> </p>
<div className="mb-6"> <div className="mb-6">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Code</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Code</label>
<input <input
value={code} value={code}
onChange={(e) => setCode(e.target.value)} onChange={(e) => setCode(e.target.value)}
@ -104,7 +104,7 @@ export default function Login() {
autoFocus autoFocus
inputMode="numeric" inputMode="numeric"
placeholder="123456" placeholder="123456"
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-center text-lg tracking-widest focus:border-brand-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-center text-lg tracking-widest focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
@ -120,7 +120,7 @@ export default function Login() {
<button <button
type="button" type="button"
onClick={() => { setStep('credentials'); setCode(''); setError(''); }} onClick={() => { setStep('credentials'); setCode(''); setError(''); }}
className="mt-3 w-full text-sm text-slate-500 hover:underline dark:text-slate-400" className="mt-3 w-full text-sm text-neutral-500 hover:underline dark:text-neutral-400"
> >
Zurück zum Login Zurück zum Login
</button> </button>

View file

@ -59,14 +59,14 @@ export default function Models() {
return ( return (
<div className="grid gap-6 lg:grid-cols-3"> <div className="grid gap-6 lg:grid-cols-3">
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<h1 className="mb-4 text-xl font-bold text-slate-900 dark:text-white">Modelle</h1> <h1 className="mb-4 text-xl font-bold text-neutral-900 dark:text-white">Modelle</h1>
<div className="mb-3 flex flex-wrap items-center gap-2"> <div className="mb-3 flex flex-wrap items-center gap-2">
<label className="text-sm font-medium text-slate-600 dark:text-slate-300">Nach Marke filtern:</label> <label className="text-sm font-medium text-neutral-600 dark:text-neutral-300">Nach Marke filtern:</label>
<select <select
value={brandFilter} value={brandFilter}
onChange={(e) => setBrandFilter(e.target.value)} onChange={(e) => setBrandFilter(e.target.value)}
className="rounded-md border border-slate-300 bg-white px-3 py-1.5 text-sm dark:border-slate-700 dark:bg-slate-900" className="rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm dark:border-neutral-700 dark:bg-neutral-900"
> >
<option value="all">Alle Marken</option> <option value="all">Alle Marken</option>
{brands.map((b) => <option key={b.id} value={String(b.id)}>{b.name}</option>)} {brands.map((b) => <option key={b.id} value={String(b.id)}>{b.name}</option>)}
@ -76,23 +76,23 @@ export default function Models() {
Filter zurücksetzen Filter zurücksetzen
</button> </button>
)} )}
<span className="text-sm text-slate-400">{filteredModels.length} von {models.length}</span> <span className="text-sm text-neutral-400">{filteredModels.length} von {models.length}</span>
</div> </div>
<div className="overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800"> <div className="overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800">
<table className="w-full text-left text-sm"> <table className="w-full text-left text-sm">
<thead className="bg-slate-50 text-slate-500 dark:bg-slate-900 dark:text-slate-400"> <thead className="bg-neutral-50 text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
<tr> <tr>
<th className="px-4 py-2">Marke</th> <th className="px-4 py-2">Marke</th>
<th className="px-4 py-2">Modell</th> <th className="px-4 py-2">Modell</th>
<th className="px-4 py-2 text-right">Aktionen</th> <th className="px-4 py-2 text-right">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-100 dark:divide-slate-800"> <tbody className="divide-y divide-neutral-100 dark:divide-neutral-800">
{filteredModels.map((model) => ( {filteredModels.map((model) => (
<tr key={model.id} className="bg-white dark:bg-slate-950"> <tr key={model.id} className="bg-white dark:bg-neutral-950">
<td className="px-4 py-2">{model.brand_name}</td> <td className="px-4 py-2">{model.brand_name}</td>
<td className="px-4 py-2 font-medium text-slate-800 dark:text-slate-100">{model.name}</td> <td className="px-4 py-2 font-medium text-neutral-800 dark:text-neutral-100">{model.name}</td>
<td className="px-4 py-2 text-right"> <td className="px-4 py-2 text-right">
<button onClick={() => startEdit(model)} className="mr-3 text-brand-600 hover:underline">Bearbeiten</button> <button onClick={() => startEdit(model)} className="mr-3 text-brand-600 hover:underline">Bearbeiten</button>
<button onClick={() => handleDelete(model.id)} className="text-red-600 hover:underline">Löschen</button> <button onClick={() => handleDelete(model.id)} className="text-red-600 hover:underline">Löschen</button>
@ -100,7 +100,7 @@ export default function Models() {
</tr> </tr>
))} ))}
{filteredModels.length === 0 && ( {filteredModels.length === 0 && (
<tr><td colSpan={3} className="px-4 py-6 text-center text-slate-400"> <tr><td colSpan={3} className="px-4 py-6 text-center text-neutral-400">
{models.length === 0 ? 'Noch keine Modelle angelegt.' : 'Keine Modelle für diese Marke.'} {models.length === 0 ? 'Noch keine Modelle angelegt.' : 'Keine Modelle für diese Marke.'}
</td></tr> </td></tr>
)} )}
@ -109,36 +109,36 @@ export default function Models() {
</div> </div>
</div> </div>
<form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900"> <form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="font-bold text-slate-900 dark:text-white">{editingId ? 'Modell bearbeiten' : 'Neues Modell'}</h2> <h2 className="font-bold text-neutral-900 dark:text-white">{editingId ? 'Modell bearbeiten' : 'Neues Modell'}</h2>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Marke *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Marke *</label>
<select <select
required required
value={form.brand_id} value={form.brand_id}
onChange={(e) => setForm({ ...form, brand_id: e.target.value })} onChange={(e) => setForm({ ...form, brand_id: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
> >
<option value="">Bitte wählen</option> <option value="">Bitte wählen</option>
{brands.map((b) => <option key={b.id} value={b.id}>{b.name}</option>)} {brands.map((b) => <option key={b.id} value={b.id}>{b.name}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Modellname *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Modellname *</label>
<input <input
required required
value={form.name} value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })} onChange={(e) => setForm({ ...form, name: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Sortierung</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Sortierung</label>
<input <input
type="number" type="number"
value={form.sort_order} value={form.sort_order}
onChange={(e) => setForm({ ...form, sort_order: Number(e.target.value) })} onChange={(e) => setForm({ ...form, sort_order: Number(e.target.value) })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
@ -149,7 +149,7 @@ export default function Models() {
{editingId ? 'Speichern' : 'Anlegen'} {editingId ? 'Speichern' : 'Anlegen'}
</button> </button>
{editingId && ( {editingId && (
<button type="button" onClick={resetForm} className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700"> <button type="button" onClick={resetForm} className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700">
Abbrechen Abbrechen
</button> </button>
)} )}

View file

@ -62,26 +62,26 @@ export default function PackageProducts() {
<p className="mb-2 text-sm"> <p className="mb-2 text-sm">
<Link to="/admin/packages" className="text-brand-600 hover:underline"> Zurück zu den Paketen</Link> <Link to="/admin/packages" className="text-brand-600 hover:underline"> Zurück zu den Paketen</Link>
</p> </p>
<h1 className="mb-4 text-xl font-bold text-slate-900 dark:text-white">Produkte im Paket</h1> <h1 className="mb-4 text-xl font-bold text-neutral-900 dark:text-white">Produkte im Paket</h1>
<form onSubmit={handleAdd} className="mb-6 flex flex-wrap items-end gap-3 rounded-xl border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900"> <form onSubmit={handleAdd} className="mb-6 flex flex-wrap items-end gap-3 rounded-xl border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-neutral-900">
<div className="flex-1 min-w-[240px]"> <div className="flex-1 min-w-[240px]">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">audio4cars.de Produkt-URL *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">audio4cars.de Produkt-URL *</label>
<input <input
required required
type="url" type="url"
placeholder="https://www.audio4cars.de/produkt/..." placeholder="https://www.audio4cars.de/produkt/..."
value={sourceUrl} value={sourceUrl}
onChange={(e) => setSourceUrl(e.target.value)} onChange={(e) => setSourceUrl(e.target.value)}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div className="w-56"> <div className="w-56">
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Name überschreiben (optional)</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Name überschreiben (optional)</label>
<input <input
value={nameOverride} value={nameOverride}
onChange={(e) => setNameOverride(e.target.value)} onChange={(e) => setNameOverride(e.target.value)}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<button disabled={busy} type="submit" className="rounded-md bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600 disabled:opacity-60"> <button disabled={busy} type="submit" className="rounded-md bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600 disabled:opacity-60">
@ -91,9 +91,9 @@ export default function PackageProducts() {
{error && <p className="mb-4 text-sm text-red-600">{error}</p>} {error && <p className="mb-4 text-sm text-red-600">{error}</p>}
<div className="overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800"> <div className="overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800">
<table className="w-full text-left text-sm"> <table className="w-full text-left text-sm">
<thead className="bg-slate-50 text-slate-500 dark:bg-slate-900 dark:text-slate-400"> <thead className="bg-neutral-50 text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
<tr> <tr>
<th className="px-4 py-2">Produkt</th> <th className="px-4 py-2">Produkt</th>
<th className="px-4 py-2">Preis</th> <th className="px-4 py-2">Preis</th>
@ -102,11 +102,11 @@ export default function PackageProducts() {
<th className="px-4 py-2 text-right">Aktionen</th> <th className="px-4 py-2 text-right">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-100 dark:divide-slate-800"> <tbody className="divide-y divide-neutral-100 dark:divide-neutral-800">
{products.map((p) => ( {products.map((p) => (
<tr key={p.id} className="bg-white dark:bg-slate-950"> <tr key={p.id} className="bg-white dark:bg-neutral-950">
<td className="px-4 py-2"> <td className="px-4 py-2">
<a href={p.source_url} target="_blank" rel="noreferrer" className="font-medium text-slate-800 hover:underline dark:text-slate-100"> <a href={p.source_url} target="_blank" rel="noreferrer" className="font-medium text-neutral-800 hover:underline dark:text-neutral-100">
{p.name_override || p.scraped_name || p.source_url} {p.name_override || p.scraped_name || p.source_url}
</a> </a>
{p.scrape_status === 'error' && <p className="text-xs text-red-500">{p.scrape_error}</p>} {p.scrape_status === 'error' && <p className="text-xs text-red-500">{p.scrape_error}</p>}
@ -117,7 +117,7 @@ export default function PackageProducts() {
{p.scrape_status} {p.scrape_status}
</span> </span>
</td> </td>
<td className="px-4 py-2 text-slate-500 dark:text-slate-400"> <td className="px-4 py-2 text-neutral-500 dark:text-neutral-400">
{p.price_updated_at ? new Date(p.price_updated_at.replace(' ', 'T')).toLocaleString('de-DE') : ''} {p.price_updated_at ? new Date(p.price_updated_at.replace(' ', 'T')).toLocaleString('de-DE') : ''}
</td> </td>
<td className="px-4 py-2 text-right"> <td className="px-4 py-2 text-right">
@ -129,7 +129,7 @@ export default function PackageProducts() {
</tr> </tr>
))} ))}
{products.length === 0 && ( {products.length === 0 && (
<tr><td colSpan={5} className="px-4 py-6 text-center text-slate-400">Noch keine Produkte im Paket.</td></tr> <tr><td colSpan={5} className="px-4 py-6 text-center text-neutral-400">Noch keine Produkte im Paket.</td></tr>
)} )}
</tbody> </tbody>
</table> </table>

View file

@ -59,25 +59,25 @@ export default function PackageUpgrades() {
<p className="mb-2 text-sm"> <p className="mb-2 text-sm">
<Link to="/admin/packages" className="text-brand-600 hover:underline"> Zurück zu den Paketen</Link> <Link to="/admin/packages" className="text-brand-600 hover:underline"> Zurück zu den Paketen</Link>
</p> </p>
<h1 className="mb-1 text-xl font-bold text-slate-900 dark:text-white">Optionale Upgrades</h1> <h1 className="mb-1 text-xl font-bold text-neutral-900 dark:text-white">Optionale Upgrades</h1>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Diese Upgrades erscheinen nicht in der öffentlichen Paketübersicht, sondern erst als Auswahl im Kontaktformular. Diese Upgrades erscheinen nicht in der öffentlichen Paketübersicht, sondern erst als Auswahl im Kontaktformular.
</p> </p>
<div className="overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800"> <div className="overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800">
<table className="w-full text-left text-sm"> <table className="w-full text-left text-sm">
<thead className="bg-slate-50 text-slate-500 dark:bg-slate-900 dark:text-slate-400"> <thead className="bg-neutral-50 text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
<tr> <tr>
<th className="px-4 py-2">Name</th> <th className="px-4 py-2">Name</th>
<th className="px-4 py-2">Preis</th> <th className="px-4 py-2">Preis</th>
<th className="px-4 py-2 text-right">Aktionen</th> <th className="px-4 py-2 text-right">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-100 dark:divide-slate-800"> <tbody className="divide-y divide-neutral-100 dark:divide-neutral-800">
{upgrades.map((u) => ( {upgrades.map((u) => (
<tr key={u.id} className="bg-white dark:bg-slate-950"> <tr key={u.id} className="bg-white dark:bg-neutral-950">
<td className="px-4 py-2"> <td className="px-4 py-2">
<p className="font-medium text-slate-800 dark:text-slate-100">{u.name}</p> <p className="font-medium text-neutral-800 dark:text-neutral-100">{u.name}</p>
{u.description && <p className="text-xs text-slate-500 dark:text-slate-400">{u.description}</p>} {u.description && <p className="text-xs text-neutral-500 dark:text-neutral-400">{u.description}</p>}
</td> </td>
<td className="px-4 py-2">{formatPrice(u.price)}</td> <td className="px-4 py-2">{formatPrice(u.price)}</td>
<td className="px-4 py-2 text-right"> <td className="px-4 py-2 text-right">
@ -87,35 +87,35 @@ export default function PackageUpgrades() {
</tr> </tr>
))} ))}
{upgrades.length === 0 && ( {upgrades.length === 0 && (
<tr><td colSpan={3} className="px-4 py-6 text-center text-slate-400">Noch keine Upgrades hinterlegt.</td></tr> <tr><td colSpan={3} className="px-4 py-6 text-center text-neutral-400">Noch keine Upgrades hinterlegt.</td></tr>
)} )}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900"> <form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="font-bold text-slate-900 dark:text-white">{editingId ? 'Upgrade bearbeiten' : 'Neues Upgrade'}</h2> <h2 className="font-bold text-neutral-900 dark:text-white">{editingId ? 'Upgrade bearbeiten' : 'Neues Upgrade'}</h2>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Name *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Name *</label>
<input <input
required required
value={form.name} value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })} onChange={(e) => setForm({ ...form, name: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Beschreibung</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Beschreibung</label>
<textarea <textarea
rows={2} rows={2}
value={form.description} value={form.description}
onChange={(e) => setForm({ ...form, description: e.target.value })} onChange={(e) => setForm({ ...form, description: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Preis () *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Preis () *</label>
<input <input
required required
type="number" type="number"
@ -123,16 +123,16 @@ export default function PackageUpgrades() {
min="0" min="0"
value={form.price} value={form.price}
onChange={(e) => setForm({ ...form, price: e.target.value })} onChange={(e) => setForm({ ...form, price: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Sortierung</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Sortierung</label>
<input <input
type="number" type="number"
value={form.sort_order} value={form.sort_order}
onChange={(e) => setForm({ ...form, sort_order: Number(e.target.value) })} onChange={(e) => setForm({ ...form, sort_order: Number(e.target.value) })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
@ -143,7 +143,7 @@ export default function PackageUpgrades() {
{editingId ? 'Speichern' : 'Anlegen'} {editingId ? 'Speichern' : 'Anlegen'}
</button> </button>
{editingId && ( {editingId && (
<button type="button" onClick={resetForm} className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700"> <button type="button" onClick={resetForm} className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700">
Abbrechen Abbrechen
</button> </button>
)} )}

View file

@ -97,14 +97,14 @@ export default function Packages() {
return ( return (
<div className="grid gap-6 lg:grid-cols-3"> <div className="grid gap-6 lg:grid-cols-3">
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<h1 className="mb-4 text-xl font-bold text-slate-900 dark:text-white">Pakete</h1> <h1 className="mb-4 text-xl font-bold text-neutral-900 dark:text-white">Pakete</h1>
<div className="mb-3 flex flex-wrap items-center gap-2"> <div className="mb-3 flex flex-wrap items-center gap-2">
<label className="text-sm font-medium text-slate-600 dark:text-slate-300">Filtern:</label> <label className="text-sm font-medium text-neutral-600 dark:text-neutral-300">Filtern:</label>
<select <select
value={brandFilter} value={brandFilter}
onChange={(e) => handleBrandFilterChange(e.target.value)} onChange={(e) => handleBrandFilterChange(e.target.value)}
className="rounded-md border border-slate-300 bg-white px-3 py-1.5 text-sm dark:border-slate-700 dark:bg-slate-900" className="rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm dark:border-neutral-700 dark:bg-neutral-900"
> >
<option value="all">Alle Marken</option> <option value="all">Alle Marken</option>
{brandOptions.map((b) => <option key={b.id} value={String(b.id)}>{b.name}</option>)} {brandOptions.map((b) => <option key={b.id} value={String(b.id)}>{b.name}</option>)}
@ -112,7 +112,7 @@ export default function Packages() {
<select <select
value={modelFilter} value={modelFilter}
onChange={(e) => setModelFilter(e.target.value)} onChange={(e) => setModelFilter(e.target.value)}
className="rounded-md border border-slate-300 bg-white px-3 py-1.5 text-sm dark:border-slate-700 dark:bg-slate-900" className="rounded-md border border-neutral-300 bg-white px-3 py-1.5 text-sm dark:border-neutral-700 dark:bg-neutral-900"
> >
<option value="all">Alle Modelle</option> <option value="all">Alle Modelle</option>
{modelOptions.map((m) => <option key={m.id} value={String(m.id)}>{m.brand_name} {m.name}</option>)} {modelOptions.map((m) => <option key={m.id} value={String(m.id)}>{m.brand_name} {m.name}</option>)}
@ -122,12 +122,12 @@ export default function Packages() {
Filter zurücksetzen Filter zurücksetzen
</button> </button>
)} )}
<span className="text-sm text-slate-400">{filteredPackages.length} von {packages.length}</span> <span className="text-sm text-neutral-400">{filteredPackages.length} von {packages.length}</span>
</div> </div>
<div className="overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800"> <div className="overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800">
<table className="w-full text-left text-sm"> <table className="w-full text-left text-sm">
<thead className="bg-slate-50 text-slate-500 dark:bg-slate-900 dark:text-slate-400"> <thead className="bg-neutral-50 text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
<tr> <tr>
<th className="px-4 py-2">Marke / Modell</th> <th className="px-4 py-2">Marke / Modell</th>
<th className="px-4 py-2">Paket</th> <th className="px-4 py-2">Paket</th>
@ -135,12 +135,12 @@ export default function Packages() {
<th className="px-4 py-2 text-right">Aktionen</th> <th className="px-4 py-2 text-right">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-100 dark:divide-slate-800"> <tbody className="divide-y divide-neutral-100 dark:divide-neutral-800">
{filteredPackages.map((pkg) => ( {filteredPackages.map((pkg) => (
<tr key={pkg.id} className="bg-white dark:bg-slate-950"> <tr key={pkg.id} className="bg-white dark:bg-neutral-950">
<td className="px-4 py-2">{pkg.brand_name} {pkg.model_name}</td> <td className="px-4 py-2">{pkg.brand_name} {pkg.model_name}</td>
<td className="px-4 py-2 font-medium text-slate-800 dark:text-slate-100">{pkg.name}</td> <td className="px-4 py-2 font-medium text-neutral-800 dark:text-neutral-100">{pkg.name}</td>
<td className="px-4 py-2 text-slate-500 dark:text-slate-400"> <td className="px-4 py-2 text-neutral-500 dark:text-neutral-400">
{pkg.markup_type === 'fixed' && `+${pkg.markup_value}`} {pkg.markup_type === 'fixed' && `+${pkg.markup_value}`}
{pkg.markup_type === 'percent' && `+${pkg.markup_value} %`} {pkg.markup_type === 'percent' && `+${pkg.markup_value} %`}
{(!pkg.markup_type || pkg.markup_type === 'none') && ''} {(!pkg.markup_type || pkg.markup_type === 'none') && ''}
@ -154,7 +154,7 @@ export default function Packages() {
</tr> </tr>
))} ))}
{filteredPackages.length === 0 && ( {filteredPackages.length === 0 && (
<tr><td colSpan={4} className="px-4 py-6 text-center text-slate-400"> <tr><td colSpan={4} className="px-4 py-6 text-center text-neutral-400">
{packages.length === 0 ? 'Noch keine Pakete angelegt.' : 'Keine Pakete für diesen Filter.'} {packages.length === 0 ? 'Noch keine Pakete angelegt.' : 'Keine Pakete für diesen Filter.'}
</td></tr> </td></tr>
)} )}
@ -163,46 +163,46 @@ export default function Packages() {
</div> </div>
</div> </div>
<form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900"> <form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="font-bold text-slate-900 dark:text-white">{editingId ? 'Paket bearbeiten' : 'Neues Paket'}</h2> <h2 className="font-bold text-neutral-900 dark:text-white">{editingId ? 'Paket bearbeiten' : 'Neues Paket'}</h2>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Modell *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Modell *</label>
<select <select
required required
value={form.car_model_id} value={form.car_model_id}
onChange={(e) => setForm({ ...form, car_model_id: e.target.value })} onChange={(e) => setForm({ ...form, car_model_id: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
> >
<option value="">Bitte wählen</option> <option value="">Bitte wählen</option>
{models.map((m) => <option key={m.id} value={m.id}>{m.brand_name} {m.name}</option>)} {models.map((m) => <option key={m.id} value={m.id}>{m.brand_name} {m.name}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Paketname *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Paketname *</label>
<input <input
required required
value={form.name} value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })} onChange={(e) => setForm({ ...form, name: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Beschreibung</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Beschreibung</label>
<p className="mb-1 text-xs text-slate-500 dark:text-slate-400">Jede Zeile erscheint als eigener Stichpunkt zusammen mit den Bauteilen.</p> <p className="mb-1 text-xs text-neutral-500 dark:text-neutral-400">Jede Zeile erscheint als eigener Stichpunkt zusammen mit den Bauteilen.</p>
<textarea <textarea
rows={3} rows={3}
value={form.description} value={form.description}
onChange={(e) => setForm({ ...form, description: e.target.value })} onChange={(e) => setForm({ ...form, description: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Aufschlag</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Aufschlag</label>
<div className="flex gap-2"> <div className="flex gap-2">
<select <select
value={form.markup_type} value={form.markup_type}
onChange={(e) => setForm({ ...form, markup_type: e.target.value })} onChange={(e) => setForm({ ...form, markup_type: e.target.value })}
className="w-40 rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-40 rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
> >
<option value="none">Kein Aufschlag</option> <option value="none">Kein Aufschlag</option>
<option value="fixed">Fester Betrag ()</option> <option value="fixed">Fester Betrag ()</option>
@ -215,18 +215,18 @@ export default function Packages() {
disabled={form.markup_type === 'none'} disabled={form.markup_type === 'none'}
value={form.markup_value} value={form.markup_value}
onChange={(e) => setForm({ ...form, markup_value: e.target.value })} onChange={(e) => setForm({ ...form, markup_value: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm disabled:opacity-50 dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<p className="mt-1 text-xs text-slate-500 dark:text-slate-400">Wird auf die Summe der Bauteilpreise aufgeschlagen und ergibt den Gesamtpreis für den Kunden.</p> <p className="mt-1 text-xs text-neutral-500 dark:text-neutral-400">Wird auf die Summe der Bauteilpreise aufgeschlagen und ergibt den Gesamtpreis für den Kunden.</p>
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Sortierung</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Sortierung</label>
<input <input
type="number" type="number"
value={form.sort_order} value={form.sort_order}
onChange={(e) => setForm({ ...form, sort_order: Number(e.target.value) })} onChange={(e) => setForm({ ...form, sort_order: Number(e.target.value) })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
@ -237,7 +237,7 @@ export default function Packages() {
{editingId ? 'Speichern' : 'Anlegen'} {editingId ? 'Speichern' : 'Anlegen'}
</button> </button>
{editingId && ( {editingId && (
<button type="button" onClick={resetForm} className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700"> <button type="button" onClick={resetForm} className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700">
Abbrechen Abbrechen
</button> </button>
)} )}

View file

@ -59,28 +59,28 @@ export default function PermissionGroups() {
return ( return (
<div className="grid gap-6 lg:grid-cols-3"> <div className="grid gap-6 lg:grid-cols-3">
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<h1 className="mb-1 text-xl font-bold text-slate-900 dark:text-white">Berechtigungsgruppen</h1> <h1 className="mb-1 text-xl font-bold text-neutral-900 dark:text-white">Berechtigungsgruppen</h1>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Fasse mehrere Berechtigungen zu einer Gruppe zusammen (z. B. Werkstatt"), die du dann bequem einem Fasse mehrere Berechtigungen zu einer Gruppe zusammen (z. B. Werkstatt"), die du dann bequem einem
oder mehreren Benutzern zuweisen kannst. oder mehreren Benutzern zuweisen kannst.
</p> </p>
<div className="overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800"> <div className="overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800">
<table className="w-full text-left text-sm"> <table className="w-full text-left text-sm">
<thead className="bg-slate-50 text-slate-500 dark:bg-slate-900 dark:text-slate-400"> <thead className="bg-neutral-50 text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
<tr> <tr>
<th className="px-4 py-2">Name</th> <th className="px-4 py-2">Name</th>
<th className="px-4 py-2">Berechtigungen</th> <th className="px-4 py-2">Berechtigungen</th>
<th className="px-4 py-2 text-right">Aktionen</th> <th className="px-4 py-2 text-right">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-100 dark:divide-slate-800"> <tbody className="divide-y divide-neutral-100 dark:divide-neutral-800">
{groups.map((g) => ( {groups.map((g) => (
<tr key={g.id} className="bg-white dark:bg-slate-950"> <tr key={g.id} className="bg-white dark:bg-neutral-950">
<td className="px-4 py-2"> <td className="px-4 py-2">
<p className="font-medium text-slate-800 dark:text-slate-100">{g.name}</p> <p className="font-medium text-neutral-800 dark:text-neutral-100">{g.name}</p>
{g.description && <p className="text-xs text-slate-500 dark:text-slate-400">{g.description}</p>} {g.description && <p className="text-xs text-neutral-500 dark:text-neutral-400">{g.description}</p>}
</td> </td>
<td className="px-4 py-2 text-slate-500 dark:text-slate-400">{g.permissions.length} von {catalog.length}</td> <td className="px-4 py-2 text-neutral-500 dark:text-neutral-400">{g.permissions.length} von {catalog.length}</td>
<td className="px-4 py-2 text-right"> <td className="px-4 py-2 text-right">
<button onClick={() => startEdit(g)} className="mr-3 text-brand-600 hover:underline">Bearbeiten</button> <button onClick={() => startEdit(g)} className="mr-3 text-brand-600 hover:underline">Bearbeiten</button>
<button onClick={() => handleDelete(g.id)} className="text-red-600 hover:underline">Löschen</button> <button onClick={() => handleDelete(g.id)} className="text-red-600 hover:underline">Löschen</button>
@ -88,43 +88,43 @@ export default function PermissionGroups() {
</tr> </tr>
))} ))}
{groups.length === 0 && ( {groups.length === 0 && (
<tr><td colSpan={3} className="px-4 py-6 text-center text-slate-400">Noch keine Berechtigungsgruppen angelegt.</td></tr> <tr><td colSpan={3} className="px-4 py-6 text-center text-neutral-400">Noch keine Berechtigungsgruppen angelegt.</td></tr>
)} )}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900"> <form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="font-bold text-slate-900 dark:text-white">{editingId ? 'Gruppe bearbeiten' : 'Neue Gruppe'}</h2> <h2 className="font-bold text-neutral-900 dark:text-white">{editingId ? 'Gruppe bearbeiten' : 'Neue Gruppe'}</h2>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Name *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Name *</label>
<input <input
required required
value={form.name} value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })} onChange={(e) => setForm({ ...form, name: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Beschreibung</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Beschreibung</label>
<textarea <textarea
rows={2} rows={2}
value={form.description} value={form.description}
onChange={(e) => setForm({ ...form, description: e.target.value })} onChange={(e) => setForm({ ...form, description: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<p className="mb-1 text-sm font-medium text-slate-700 dark:text-slate-300">Berechtigungen</p> <p className="mb-1 text-sm font-medium text-neutral-700 dark:text-neutral-300">Berechtigungen</p>
<div className="space-y-1.5 rounded-md border border-slate-200 p-3 dark:border-slate-700"> <div className="space-y-1.5 rounded-md border border-neutral-200 p-3 dark:border-neutral-700">
{catalog.map((p) => ( {catalog.map((p) => (
<label key={p.key} className="flex items-center gap-2 text-sm text-slate-700 dark:text-slate-300"> <label key={p.key} className="flex items-center gap-2 text-sm text-neutral-700 dark:text-neutral-300">
<input <input
type="checkbox" type="checkbox"
checked={form.permissions.includes(p.key)} checked={form.permissions.includes(p.key)}
onChange={() => togglePermission(p.key)} onChange={() => togglePermission(p.key)}
className="h-4 w-4 rounded border-slate-300 text-brand-600 focus:ring-brand-500" className="h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500"
/> />
{p.label} {p.label}
</label> </label>
@ -139,7 +139,7 @@ export default function PermissionGroups() {
{editingId ? 'Speichern' : 'Anlegen'} {editingId ? 'Speichern' : 'Anlegen'}
</button> </button>
{editingId && ( {editingId && (
<button type="button" onClick={resetForm} className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700"> <button type="button" onClick={resetForm} className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700">
Abbrechen Abbrechen
</button> </button>
)} )}

View file

@ -72,14 +72,14 @@ export default function Services() {
return ( return (
<div className="grid gap-6 lg:grid-cols-3"> <div className="grid gap-6 lg:grid-cols-3">
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<h1 className="mb-1 text-xl font-bold text-slate-900 dark:text-white">Leistungen</h1> <h1 className="mb-1 text-xl font-bold text-neutral-900 dark:text-white">Leistungen</h1>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Diese Karten erscheinen auf der öffentlichen Leistungen-Seite. Die 8 mitgelieferten Einträge sind die Diese Karten erscheinen auf der öffentlichen Leistungen-Seite. Die 8 mitgelieferten Einträge sind die
Standard-Einstellung du kannst sie bearbeiten, löschen und beliebig neue hinzufügen. Standard-Einstellung du kannst sie bearbeiten, löschen und beliebig neue hinzufügen.
</p> </p>
<div className="overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800"> <div className="overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800">
<table className="w-full text-left text-sm"> <table className="w-full text-left text-sm">
<thead className="bg-slate-50 text-slate-500 dark:bg-slate-900 dark:text-slate-400"> <thead className="bg-neutral-50 text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
<tr> <tr>
<th className="px-4 py-2">Icon</th> <th className="px-4 py-2">Icon</th>
<th className="px-4 py-2">Titel</th> <th className="px-4 py-2">Titel</th>
@ -87,15 +87,15 @@ export default function Services() {
<th className="px-4 py-2 text-right">Aktionen</th> <th className="px-4 py-2 text-right">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-100 dark:divide-slate-800"> <tbody className="divide-y divide-neutral-100 dark:divide-neutral-800">
{services.map((s) => ( {services.map((s) => (
<tr key={s.id} className="bg-white dark:bg-slate-950"> <tr key={s.id} className="bg-white dark:bg-neutral-950">
<td className="px-4 py-2"> <td className="px-4 py-2">
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-brand-100 text-brand-600 dark:bg-brand-900/40 dark:text-brand-400"> <div className="flex h-9 w-9 items-center justify-center rounded-full bg-brand-100 text-brand-600 dark:bg-brand-900/40 dark:text-brand-400">
<DynamicIcon name={s.icon_name} className="h-5 w-5" /> <DynamicIcon name={s.icon_name} className="h-5 w-5" />
</div> </div>
</td> </td>
<td className="px-4 py-2 font-medium text-slate-800 dark:text-slate-100">{s.title}</td> <td className="px-4 py-2 font-medium text-neutral-800 dark:text-neutral-100">{s.title}</td>
<td className="px-4 py-2">{s.sort_order}</td> <td className="px-4 py-2">{s.sort_order}</td>
<td className="px-4 py-2 text-right"> <td className="px-4 py-2 text-right">
<button onClick={() => startEdit(s)} className="mr-3 text-brand-600 hover:underline">Bearbeiten</button> <button onClick={() => startEdit(s)} className="mr-3 text-brand-600 hover:underline">Bearbeiten</button>
@ -104,70 +104,70 @@ export default function Services() {
</tr> </tr>
))} ))}
{services.length === 0 && ( {services.length === 0 && (
<tr><td colSpan={4} className="px-4 py-6 text-center text-slate-400">Noch keine Leistungen angelegt.</td></tr> <tr><td colSpan={4} className="px-4 py-6 text-center text-neutral-400">Noch keine Leistungen angelegt.</td></tr>
)} )}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900"> <form onSubmit={handleSubmit} className="h-fit space-y-4 rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="font-bold text-slate-900 dark:text-white">{editingId ? 'Leistung bearbeiten' : 'Neue Leistung'}</h2> <h2 className="font-bold text-neutral-900 dark:text-white">{editingId ? 'Leistung bearbeiten' : 'Neue Leistung'}</h2>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Icon *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Icon *</label>
<IconPicker value={form.icon_name} onChange={(name) => setForm({ ...form, icon_name: name })} /> <IconPicker value={form.icon_name} onChange={(name) => setForm({ ...form, icon_name: name })} />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Name *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Name *</label>
<input <input
required required
value={form.title} value={form.title}
onChange={(e) => setForm({ ...form, title: e.target.value })} onChange={(e) => setForm({ ...form, title: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Beschreibung *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Beschreibung *</label>
<textarea <textarea
required required
rows={4} rows={4}
value={form.description} value={form.description}
onChange={(e) => setForm({ ...form, description: e.target.value })} onChange={(e) => setForm({ ...form, description: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<ImageUploadField value={form.image_path} onChange={(path) => setForm({ ...form, image_path: path })} label="Bild" /> <ImageUploadField value={form.image_path} onChange={(path) => setForm({ ...form, image_path: path })} label="Bild" />
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Button-Text (optional)</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Button-Text (optional)</label>
<input <input
value={form.cta_label} value={form.cta_label}
onChange={(e) => setForm({ ...form, cta_label: e.target.value })} onChange={(e) => setForm({ ...form, cta_label: e.target.value })}
placeholder="z. B. Fahrzeug konfigurieren" placeholder="z. B. Fahrzeug konfigurieren"
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Button-Link (optional)</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Button-Link (optional)</label>
<input <input
value={form.cta_url} value={form.cta_url}
onChange={(e) => setForm({ ...form, cta_url: e.target.value })} onChange={(e) => setForm({ ...form, cta_url: e.target.value })}
placeholder="z. B. /fahrzeuge" placeholder="z. B. /fahrzeuge"
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Sortierung</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Sortierung</label>
<input <input
type="number" type="number"
value={form.sort_order} value={form.sort_order}
onChange={(e) => setForm({ ...form, sort_order: Number(e.target.value) })} onChange={(e) => setForm({ ...form, sort_order: Number(e.target.value) })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
@ -178,7 +178,7 @@ export default function Services() {
{editingId ? 'Speichern' : 'Anlegen'} {editingId ? 'Speichern' : 'Anlegen'}
</button> </button>
{editingId && ( {editingId && (
<button type="button" onClick={resetForm} className="rounded-md border border-slate-300 px-4 py-2 text-sm dark:border-slate-700"> <button type="button" onClick={resetForm} className="rounded-md border border-neutral-300 px-4 py-2 text-sm dark:border-neutral-700">
Abbrechen Abbrechen
</button> </button>
)} )}

View file

@ -70,46 +70,46 @@ export default function DatabaseSettings() {
return ( return (
<div className="max-w-2xl space-y-6"> <div className="max-w-2xl space-y-6">
<section className="rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <section className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-1 font-semibold text-slate-900 dark:text-white">Datenbank-Zugangsdaten</h2> <h2 className="mb-1 font-semibold text-neutral-900 dark:text-white">Datenbank-Zugangsdaten</h2>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Wichtig für einen Umzug auf einen anderen Server/Webspace: Trage hier die Zugangsdaten der dortigen Wichtig für einen Umzug auf einen anderen Server/Webspace: Trage hier die Zugangsdaten der dortigen
Datenbank ein. Die Verbindung wird vor dem Speichern getestet, damit du dich nicht aussperrst. Datenbank ein. Die Verbindung wird vor dem Speichern getestet, damit du dich nicht aussperrst.
</p> </p>
{loading ? ( {loading ? (
<p className="text-sm text-slate-400">Lädt</p> <p className="text-sm text-neutral-400">Lädt</p>
) : ( ) : (
<form onSubmit={handleSave} className="space-y-3"> <form onSubmit={handleSave} className="space-y-3">
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Host *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Host *</label>
<input <input
required required
value={form.host} value={form.host}
onChange={(e) => setForm({ ...form, host: e.target.value })} onChange={(e) => setForm({ ...form, host: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Datenbankname *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Datenbankname *</label>
<input <input
required required
value={form.name} value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })} onChange={(e) => setForm({ ...form, name: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Benutzer *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Benutzer *</label>
<input <input
required required
value={form.user} value={form.user}
onChange={(e) => setForm({ ...form, user: e.target.value })} onChange={(e) => setForm({ ...form, user: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
Passwort {hasPassword ? '(leer lassen = unverändert)' : ''} Passwort {hasPassword ? '(leer lassen = unverändert)' : ''}
</label> </label>
<div className="flex gap-2"> <div className="flex gap-2">
@ -118,23 +118,23 @@ export default function DatabaseSettings() {
value={form.password} value={form.password}
onChange={(e) => setForm({ ...form, password: e.target.value })} onChange={(e) => setForm({ ...form, password: e.target.value })}
placeholder={hasPassword ? '••••••••' : ''} placeholder={hasPassword ? '••••••••' : ''}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
<button <button
type="button" type="button"
onClick={() => setShowPassword((s) => !s)} onClick={() => setShowPassword((s) => !s)}
className="shrink-0 rounded-md border border-slate-300 px-3 text-sm dark:border-slate-700" className="shrink-0 rounded-md border border-neutral-300 px-3 text-sm dark:border-neutral-700"
> >
{showPassword ? 'Verbergen' : 'Anzeigen'} {showPassword ? 'Verbergen' : 'Anzeigen'}
</button> </button>
</div> </div>
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Zeichensatz</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Zeichensatz</label>
<input <input
value={form.charset} value={form.charset}
onChange={(e) => setForm({ ...form, charset: e.target.value })} onChange={(e) => setForm({ ...form, charset: e.target.value })}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
@ -152,9 +152,9 @@ export default function DatabaseSettings() {
)} )}
</section> </section>
<section className="rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <section className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<div className="mb-1 flex items-center justify-between gap-3"> <div className="mb-1 flex items-center justify-between gap-3">
<h2 className="font-semibold text-slate-900 dark:text-white">Datenbankstruktur</h2> <h2 className="font-semibold text-neutral-900 dark:text-white">Datenbankstruktur</h2>
{!schemaChecking && schemaReport && ( {!schemaChecking && schemaReport && (
<span <span
className={`rounded-full px-3 py-1 text-xs font-semibold ${ className={`rounded-full px-3 py-1 text-xs font-semibold ${
@ -167,12 +167,12 @@ export default function DatabaseSettings() {
</span> </span>
)} )}
</div> </div>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Prüft, ob diese Datenbank alle Tabellen und Spalten hat, die der aktuelle Code-Stand braucht wichtig, Prüft, ob diese Datenbank alle Tabellen und Spalten hat, die der aktuelle Code-Stand braucht wichtig,
wenn deine Live-Version auf einem anderen Server einen älteren Stand hat. wenn deine Live-Version auf einem anderen Server einen älteren Stand hat.
</p> </p>
{schemaChecking && <p className="text-sm text-slate-400">Prüfe</p>} {schemaChecking && <p className="text-sm text-neutral-400">Prüfe</p>}
{!schemaChecking && schemaReport && !schemaReport.ok && ( {!schemaChecking && schemaReport && !schemaReport.ok && (
<ul className="mb-4 space-y-1 rounded-md bg-red-50 p-3 text-sm text-red-800 dark:bg-red-900/20 dark:text-red-300"> <ul className="mb-4 space-y-1 rounded-md bg-red-50 p-3 text-sm text-red-800 dark:bg-red-900/20 dark:text-red-300">
@ -192,7 +192,7 @@ export default function DatabaseSettings() {
<button <button
onClick={checkSchema} onClick={checkSchema}
disabled={schemaChecking} disabled={schemaChecking}
className="rounded-md border border-slate-300 px-4 py-2 text-sm font-medium hover:bg-slate-50 disabled:opacity-50 dark:border-slate-700 dark:hover:bg-slate-800" className="rounded-md border border-neutral-300 px-4 py-2 text-sm font-medium hover:bg-neutral-50 disabled:opacity-50 dark:border-neutral-700 dark:hover:bg-neutral-800"
> >
Erneut prüfen Erneut prüfen
</button> </button>
@ -208,7 +208,7 @@ export default function DatabaseSettings() {
</div> </div>
{schemaActions && ( {schemaActions && (
<ul className="mt-3 space-y-1 rounded-md bg-slate-50 p-3 text-sm text-slate-700 dark:bg-slate-800 dark:text-slate-300"> <ul className="mt-3 space-y-1 rounded-md bg-neutral-50 p-3 text-sm text-neutral-700 dark:bg-neutral-800 dark:text-neutral-300">
{schemaActions.map((a, i) => <li key={i}>{a}</li>)} {schemaActions.map((a, i) => <li key={i}>{a}</li>)}
</ul> </ul>
)} )}

View file

@ -31,9 +31,9 @@ export default function ExportImportSettings() {
return ( return (
<div className="max-w-2xl space-y-6"> <div className="max-w-2xl space-y-6">
<section className="rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <section className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-1 font-semibold text-slate-900 dark:text-white">Daten exportieren</h2> <h2 className="mb-1 font-semibold text-neutral-900 dark:text-white">Daten exportieren</h2>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Lädt eine Datei mit allen Marken, Modellen, Paketen, Produkten, Upgrades und Leistungen (inkl. Bilder) Lädt eine Datei mit allen Marken, Modellen, Paketen, Produkten, Upgrades und Leistungen (inkl. Bilder)
herunter. Benutzerkonten und Kontaktanfragen sind bewusst <strong>nicht</strong> enthalten. herunter. Benutzerkonten und Kontaktanfragen sind bewusst <strong>nicht</strong> enthalten.
</p> </p>
@ -45,9 +45,9 @@ export default function ExportImportSettings() {
</a> </a>
</section> </section>
<section className="rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <section className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-1 font-semibold text-slate-900 dark:text-white">Daten importieren</h2> <h2 className="mb-1 font-semibold text-neutral-900 dark:text-white">Daten importieren</h2>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Lädt eine zuvor exportierte Datei auf diesen Server. <strong>Ersetzt dabei alle</strong> aktuellen Lädt eine zuvor exportierte Datei auf diesen Server. <strong>Ersetzt dabei alle</strong> aktuellen
Marken, Modelle, Pakete, Produkte, Upgrades und Leistungen auf diesem Server das kann nicht Marken, Modelle, Pakete, Produkte, Upgrades und Leistungen auf diesem Server das kann nicht
rückgängig gemacht werden. Kontaktanfragen und Benutzerkonten bleiben unangetastet. rückgängig gemacht werden. Kontaktanfragen und Benutzerkonten bleiben unangetastet.
@ -59,12 +59,12 @@ export default function ExportImportSettings() {
onChange={(e) => setImportFile(e.target.files?.[0] || null)} onChange={(e) => setImportFile(e.target.files?.[0] || null)}
className="text-sm" className="text-sm"
/> />
<label className="flex items-start gap-2 text-sm text-slate-700 dark:text-slate-300"> <label className="flex items-start gap-2 text-sm text-neutral-700 dark:text-neutral-300">
<input <input
type="checkbox" type="checkbox"
checked={importConfirmed} checked={importConfirmed}
onChange={(e) => setImportConfirmed(e.target.checked)} onChange={(e) => setImportConfirmed(e.target.checked)}
className="mt-0.5 h-4 w-4 rounded border-slate-300 text-brand-600 focus:ring-brand-500" className="mt-0.5 h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500"
/> />
Mir ist bewusst, dass dies alle aktuellen Marken, Modelle, Pakete, Produkte, Upgrades und Mir ist bewusst, dass dies alle aktuellen Marken, Modelle, Pakete, Produkte, Upgrades und
Leistungen auf diesem Server unwiderruflich ersetzt. Leistungen auf diesem Server unwiderruflich ersetzt.

View file

@ -68,19 +68,19 @@ export default function MaintenanceSettings() {
]; ];
if (loading) { if (loading) {
return <p className="text-sm text-slate-400">Lädt</p>; return <p className="text-sm text-neutral-400">Lädt</p>;
} }
return ( return (
<form onSubmit={handleSave} className="max-w-2xl space-y-6"> <form onSubmit={handleSave} className="max-w-2xl space-y-6">
<p className="text-sm text-slate-500 dark:text-slate-400"> <p className="text-sm text-neutral-500 dark:text-neutral-400">
Ist "Gesamte Seite" aktiv, gewinnt das immer die beiden anderen Schalter spielen dann keine Rolle mehr. Ist "Gesamte Seite" aktiv, gewinnt das immer die beiden anderen Schalter spielen dann keine Rolle mehr.
</p> </p>
{sections.map((section) => ( {sections.map((section) => (
<section key={section.key} className="rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <section key={section.key} className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<div className="mb-1 flex items-center justify-between gap-3"> <div className="mb-1 flex items-center justify-between gap-3">
<h2 className="font-semibold text-slate-900 dark:text-white">{section.title}</h2> <h2 className="font-semibold text-neutral-900 dark:text-white">{section.title}</h2>
<label className="relative inline-flex cursor-pointer items-center"> <label className="relative inline-flex cursor-pointer items-center">
<input <input
type="checkbox" type="checkbox"
@ -88,12 +88,12 @@ export default function MaintenanceSettings() {
onChange={(e) => update(section.key, 'enabled', e.target.checked)} onChange={(e) => update(section.key, 'enabled', e.target.checked)}
className="peer sr-only" className="peer sr-only"
/> />
<div className="h-6 w-11 rounded-full bg-slate-200 transition peer-checked:bg-brand-500 dark:bg-slate-700" /> <div className="h-6 w-11 rounded-full bg-neutral-200 transition peer-checked:bg-brand-500 dark:bg-neutral-700" />
<div className="absolute left-1 top-1 h-4 w-4 rounded-full bg-white transition peer-checked:translate-x-5" /> <div className="absolute left-1 top-1 h-4 w-4 rounded-full bg-white transition peer-checked:translate-x-5" />
</label> </label>
</div> </div>
<p className="mb-3 text-sm text-slate-500 dark:text-slate-400">{section.hint}</p> <p className="mb-3 text-sm text-neutral-500 dark:text-neutral-400">{section.hint}</p>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
Eigene Nachricht (optional) Eigene Nachricht (optional)
</label> </label>
<textarea <textarea
@ -101,7 +101,7 @@ export default function MaintenanceSettings() {
value={status[section.key].message} value={status[section.key].message}
onChange={(e) => update(section.key, 'message', e.target.value)} onChange={(e) => update(section.key, 'message', e.target.value)}
placeholder="z. B. Wir aktualisieren gerade unser Angebot. Schau bald wieder vorbei!" placeholder="z. B. Wir aktualisieren gerade unser Angebot. Schau bald wieder vorbei!"
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</section> </section>
))} ))}

View file

@ -55,9 +55,9 @@ export default function ResetSettings() {
return ( return (
<div className="max-w-2xl space-y-6"> <div className="max-w-2xl space-y-6">
<section className="rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <section className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-1 font-semibold text-slate-900 dark:text-white">Nur Leistungen zurücksetzen</h2> <h2 className="mb-1 font-semibold text-neutral-900 dark:text-white">Nur Leistungen zurücksetzen</h2>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Löscht alle aktuellen Leistungen-Karten und stellt die 8 mitgelieferten Standard-Leistungen wieder her. Löscht alle aktuellen Leistungen-Karten und stellt die 8 mitgelieferten Standard-Leistungen wieder her.
Marken, Modelle und Pakete bleiben unangetastet. Marken, Modelle und Pakete bleiben unangetastet.
</p> </p>
@ -68,12 +68,12 @@ export default function ResetSettings() {
> >
{servicesBusy ? 'Setze zurück…' : 'Leistungen auf Standard zurücksetzen'} {servicesBusy ? 'Setze zurück…' : 'Leistungen auf Standard zurücksetzen'}
</button> </button>
{servicesMessage && <p className="mt-3 text-sm text-slate-600 dark:text-slate-300">{servicesMessage}</p>} {servicesMessage && <p className="mt-3 text-sm text-neutral-600 dark:text-neutral-300">{servicesMessage}</p>}
</section> </section>
<section className="rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <section className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-1 font-semibold text-slate-900 dark:text-white">Nur Fahrzeugkatalog leeren</h2> <h2 className="mb-1 font-semibold text-neutral-900 dark:text-white">Nur Fahrzeugkatalog leeren</h2>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Löscht alle Marken, Modelle, Pakete, Produkte und Upgrades unwiderruflich. Leistungen, Kontaktanfragen Löscht alle Marken, Modelle, Pakete, Produkte und Upgrades unwiderruflich. Leistungen, Kontaktanfragen
und Benutzerkonten bleiben erhalten. und Benutzerkonten bleiben erhalten.
</p> </p>
@ -84,12 +84,12 @@ export default function ResetSettings() {
> >
{catalogBusy ? 'Leere…' : 'Fahrzeugkatalog leeren'} {catalogBusy ? 'Leere…' : 'Fahrzeugkatalog leeren'}
</button> </button>
{catalogMessage && <p className="mt-3 text-sm text-slate-600 dark:text-slate-300">{catalogMessage}</p>} {catalogMessage && <p className="mt-3 text-sm text-neutral-600 dark:text-neutral-300">{catalogMessage}</p>}
</section> </section>
<section className="rounded-xl border border-red-300 bg-red-50/40 p-6 dark:border-red-900 dark:bg-red-900/10"> <section className="rounded-xl border border-red-300 bg-red-50/40 p-6 dark:border-red-900 dark:bg-red-900/10">
<h2 className="mb-1 font-semibold text-red-700 dark:text-red-300">Alles zurücksetzen</h2> <h2 className="mb-1 font-semibold text-red-700 dark:text-red-300">Alles zurücksetzen</h2>
<p className="mb-4 text-sm text-slate-600 dark:text-slate-300"> <p className="mb-4 text-sm text-neutral-600 dark:text-neutral-300">
Kombiniert beides: Fahrzeugkatalog wird komplett geleert <strong>und</strong> Leistungen werden auf Kombiniert beides: Fahrzeugkatalog wird komplett geleert <strong>und</strong> Leistungen werden auf
Standard zurückgesetzt. Kontaktanfragen und Benutzerkonten bleiben erhalten. Nicht rückgängig zu machen. Standard zurückgesetzt. Kontaktanfragen und Benutzerkonten bleiben erhalten. Nicht rückgängig zu machen.
</p> </p>
@ -100,7 +100,7 @@ export default function ResetSettings() {
> >
{allBusy ? 'Setze zurück…' : 'Wirklich alles zurücksetzen'} {allBusy ? 'Setze zurück…' : 'Wirklich alles zurücksetzen'}
</button> </button>
{allMessage && <p className="mt-3 text-sm text-slate-700 dark:text-slate-300">{allMessage}</p>} {allMessage && <p className="mt-3 text-sm text-neutral-700 dark:text-neutral-300">{allMessage}</p>}
</section> </section>
</div> </div>
); );

View file

@ -13,13 +13,13 @@ export default function SettingsLayout() {
`rounded-md px-4 py-2 text-sm font-medium transition ${ `rounded-md px-4 py-2 text-sm font-medium transition ${
isActive isActive
? 'bg-brand-500 text-white' ? 'bg-brand-500 text-white'
: 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800' : 'text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800'
}`; }`;
return ( return (
<div> <div>
<h1 className="mb-1 text-xl font-bold text-slate-900 dark:text-white">Einstellungen</h1> <h1 className="mb-1 text-xl font-bold text-neutral-900 dark:text-white">Einstellungen</h1>
<p className="mb-5 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-5 text-sm text-neutral-500 dark:text-neutral-400">
Werkzeuge rund um Datenbank, Server-Umzug und Zurücksetzen dieser Seite. Werkzeuge rund um Datenbank, Server-Umzug und Zurücksetzen dieser Seite.
</p> </p>
<nav className="mb-6 flex flex-wrap gap-2"> <nav className="mb-6 flex flex-wrap gap-2">

View file

@ -46,14 +46,14 @@ export default function WebsiteSettings() {
}; };
if (loading) { if (loading) {
return <p className="text-sm text-slate-400">Lädt</p>; return <p className="text-sm text-neutral-400">Lädt</p>;
} }
return ( return (
<form onSubmit={handleSave} className="max-w-2xl space-y-6"> <form onSubmit={handleSave} className="max-w-2xl space-y-6">
<section className="rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <section className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-1 font-semibold text-slate-900 dark:text-white">Startseiten-Bild</h2> <h2 className="mb-1 font-semibold text-neutral-900 dark:text-white">Startseiten-Bild</h2>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Das große Hero-Bild ganz oben auf der Startseite. Ohne eigenes Bild wird das Standardbild verwendet. Das große Hero-Bild ganz oben auf der Startseite. Ohne eigenes Bild wird das Standardbild verwendet.
</p> </p>
<ImageUploadField <ImageUploadField
@ -63,41 +63,41 @@ export default function WebsiteSettings() {
/> />
</section> </section>
<section className="rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <section className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-1 font-semibold text-slate-900 dark:text-white">Kontaktdaten</h2> <h2 className="mb-1 font-semibold text-neutral-900 dark:text-white">Kontaktdaten</h2>
<p className="mb-4 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-4 text-sm text-neutral-500 dark:text-neutral-400">
Wird im Header, Footer und auf der Kontaktseite angezeigt. Wird im Header, Footer und auf der Kontaktseite angezeigt.
</p> </p>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Telefon</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Telefon</label>
<input <input
value={form.phone} value={form.phone}
onChange={(e) => update('phone', e.target.value)} onChange={(e) => update('phone', e.target.value)}
placeholder="09373 20 62 390" placeholder="09373 20 62 390"
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
WhatsApp Business Nummer WhatsApp Business Nummer
</label> </label>
<input <input
value={form.whatsapp} value={form.whatsapp}
onChange={(e) => update('whatsapp', e.target.value)} onChange={(e) => update('whatsapp', e.target.value)}
placeholder="z. B. 09373 20 62 390" placeholder="z. B. 09373 20 62 390"
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
<p className="mt-1 text-xs text-slate-400">Leer lassen, um den WhatsApp-Link im Header auszublenden.</p> <p className="mt-1 text-xs text-neutral-400">Leer lassen, um den WhatsApp-Link im Header auszublenden.</p>
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">E-Mail</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">E-Mail</label>
<input <input
type="email" type="email"
value={form.contact_email} value={form.contact_email}
onChange={(e) => update('contact_email', e.target.value)} onChange={(e) => update('contact_email', e.target.value)}
placeholder="info@hifi-planet-amorbach.de" placeholder="info@hifi-planet-amorbach.de"
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
</div> </div>

View file

@ -9,13 +9,13 @@ export default function AGB() {
return ( return (
<div className="mx-auto max-w-3xl px-4 py-12 sm:px-6"> <div className="mx-auto max-w-3xl px-4 py-12 sm:px-6">
<h1 className="mb-8 text-2xl font-bold text-slate-900 dark:text-white sm:text-3xl"> <h1 className="mb-8 text-2xl font-bold text-neutral-900 dark:text-white sm:text-3xl">
Allgemeine Geschäftsbedingungen Allgemeine Geschäftsbedingungen
</h1> </h1>
<div className="space-y-8 text-sm text-slate-600 dark:text-slate-300"> <div className="space-y-8 text-sm text-neutral-600 dark:text-neutral-300">
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">1. Geltungsbereich</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">1. Geltungsbereich</h2>
<p> <p>
Diese Allgemeinen Geschäftsbedingungen (AGB) gelten für alle Beratungs-, Werkstatt- und Diese Allgemeinen Geschäftsbedingungen (AGB) gelten für alle Beratungs-, Werkstatt- und
Umbauleistungen, die die Hifi Planet e.K., Boxbrunner Str. 20a, 63916 Amorbach (wir", „HifiPlanet") Umbauleistungen, die die Hifi Planet e.K., Boxbrunner Str. 20a, 63916 Amorbach (wir", „HifiPlanet")
@ -24,7 +24,7 @@ export default function AGB() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">2. Anfragen über die Website</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">2. Anfragen über die Website</h2>
<p> <p>
Über das Kontaktformular können Sie unverbindlich eine Beratung oder ein Angebot zu einem einzelnen Über das Kontaktformular können Sie unverbindlich eine Beratung oder ein Angebot zu einem einzelnen
Produkt oder einem ganzen Paket anfragen. Die auf der Website angezeigten Preise sind, soweit sie von Produkt oder einem ganzen Paket anfragen. Die auf der Website angezeigten Preise sind, soweit sie von
@ -35,7 +35,7 @@ export default function AGB() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">3. Vertragsschluss</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">3. Vertragsschluss</h2>
<p> <p>
Ein verbindlicher Vertrag über Beratung, Material und Einbauleistungen kommt erst zustande, wenn wir Ein verbindlicher Vertrag über Beratung, Material und Einbauleistungen kommt erst zustande, wenn wir
Ihnen nach persönlicher oder schriftlicher Abstimmung ein konkretes Angebot unterbreitet haben und Sie Ihnen nach persönlicher oder schriftlicher Abstimmung ein konkretes Angebot unterbreitet haben und Sie
@ -45,7 +45,7 @@ export default function AGB() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">4. Preise und Zahlung</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">4. Preise und Zahlung</h2>
<p> <p>
Es gelten die im jeweiligen Einzelangebot genannten Preise inklusive der zum Zeitpunkt der Es gelten die im jeweiligen Einzelangebot genannten Preise inklusive der zum Zeitpunkt der
Rechnungsstellung gesetzlich geltenden Umsatzsteuer. Sofern nichts anderes vereinbart ist, ist die Rechnungsstellung gesetzlich geltenden Umsatzsteuer. Sofern nichts anderes vereinbart ist, ist die
@ -54,7 +54,7 @@ export default function AGB() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">5. Termine und Fahrzeugübergabe</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">5. Termine und Fahrzeugübergabe</h2>
<p> <p>
Vereinbarte Termine zur Fahrzeugübergabe sind von beiden Seiten einzuhalten. Verschiebungen sind Vereinbarte Termine zur Fahrzeugübergabe sind von beiden Seiten einzuhalten. Verschiebungen sind
möglichst frühzeitig mitzuteilen. Für Gegenstände, die sich zum Zeitpunkt der Übergabe im Fahrzeug möglichst frühzeitig mitzuteilen. Für Gegenstände, die sich zum Zeitpunkt der Übergabe im Fahrzeug
@ -63,7 +63,7 @@ export default function AGB() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">6. Gewährleistung</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">6. Gewährleistung</h2>
<p> <p>
Es gelten die gesetzlichen Gewährleistungsrechte. Mängel sind uns unverzüglich nach Feststellung Es gelten die gesetzlichen Gewährleistungsrechte. Mängel sind uns unverzüglich nach Feststellung
anzuzeigen, damit wir Ihnen Nacherfüllung anbieten können. Für Verschleißteile sowie für Schäden, die anzuzeigen, damit wir Ihnen Nacherfüllung anbieten können. Für Verschleißteile sowie für Schäden, die
@ -73,14 +73,14 @@ export default function AGB() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">7. Eigentumsvorbehalt</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">7. Eigentumsvorbehalt</h2>
<p> <p>
Verbautes Material und gelieferte Ware bleiben bis zur vollständigen Bezahlung unser Eigentum. Verbautes Material und gelieferte Ware bleiben bis zur vollständigen Bezahlung unser Eigentum.
</p> </p>
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">8. Haftung</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">8. Haftung</h2>
<p> <p>
Wir haften unbeschränkt bei Vorsatz und grober Fahrlässigkeit sowie nach Maßgabe des Wir haften unbeschränkt bei Vorsatz und grober Fahrlässigkeit sowie nach Maßgabe des
Produkthaftungsgesetzes. Bei leichter Fahrlässigkeit haften wir nur bei Verletzung einer wesentlichen Produkthaftungsgesetzes. Bei leichter Fahrlässigkeit haften wir nur bei Verletzung einer wesentlichen
@ -91,7 +91,7 @@ export default function AGB() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">9. Widerrufsrecht</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">9. Widerrufsrecht</h2>
<p> <p>
Ein gesetzliches Widerrufsrecht für Verbraucher besteht bei Verträgen, die außerhalb von Ein gesetzliches Widerrufsrecht für Verbraucher besteht bei Verträgen, die außerhalb von
Geschäftsräumen oder im Fernabsatz geschlossen werden. Da der eigentliche Werkvertrag über Beratung und Geschäftsräumen oder im Fernabsatz geschlossen werden. Da der eigentliche Werkvertrag über Beratung und
@ -103,7 +103,7 @@ export default function AGB() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">10. Anwendbares Recht und Gerichtsstand</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">10. Anwendbares Recht und Gerichtsstand</h2>
<p> <p>
Es gilt das Recht der Bundesrepublik Deutschland unter Ausschluss des UN-Kaufrechts. Ist der Kunde Es gilt das Recht der Bundesrepublik Deutschland unter Ausschluss des UN-Kaufrechts. Ist der Kunde
Kaufmann, juristische Person des öffentlichen Rechts oder öffentlich-rechtliches Sondervermögen, ist Kaufmann, juristische Person des öffentlichen Rechts oder öffentlich-rechtliches Sondervermögen, ist
@ -112,7 +112,7 @@ export default function AGB() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">11. Schlussbestimmungen</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">11. Schlussbestimmungen</h2>
<p> <p>
Sollte eine Bestimmung dieser AGB unwirksam sein oder werden, bleibt die Wirksamkeit der übrigen Sollte eine Bestimmung dieser AGB unwirksam sein oder werden, bleibt die Wirksamkeit der übrigen
Bestimmungen hiervon unberührt. Bestimmungen hiervon unberührt.

View file

@ -37,21 +37,21 @@ export default function BrandPage() {
} }
if (!data) { if (!data) {
return <p className="mx-auto max-w-6xl px-4 py-12 text-slate-500 sm:px-6">Lädt</p>; return <p className="mx-auto max-w-6xl px-4 py-12 text-neutral-500 sm:px-6">Lädt</p>;
} }
return ( return (
<div className="mx-auto max-w-6xl px-4 py-12 sm:px-6"> <div className="mx-auto max-w-6xl px-4 py-12 sm:px-6">
{maintenance.vehicles.enabled && maintenance.bypass && <MaintenanceBypassBanner inline />} {maintenance.vehicles.enabled && maintenance.bypass && <MaintenanceBypassBanner inline />}
<p className="mb-1 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-1 text-sm text-neutral-500 dark:text-neutral-400">
<Link to="/fahrzeuge" className="hover:text-brand-500">Fahrzeuge</Link> / {data.brand.name} <Link to="/fahrzeuge" className="hover:text-brand-500">Fahrzeuge</Link> / {data.brand.name}
</p> </p>
<h1 className="mb-8 text-2xl font-bold text-slate-900 dark:text-white sm:text-3xl"> <h1 className="mb-8 text-2xl font-bold text-neutral-900 dark:text-white sm:text-3xl">
{data.brand.name} Modell wählen {data.brand.name} Modell wählen
</h1> </h1>
{data.models.length === 0 && ( {data.models.length === 0 && (
<p className="text-slate-500 dark:text-slate-400">Für diese Marke sind noch keine Modelle hinterlegt.</p> <p className="text-neutral-500 dark:text-neutral-400">Für diese Marke sind noch keine Modelle hinterlegt.</p>
)} )}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3"> <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
@ -59,7 +59,7 @@ export default function BrandPage() {
<Link <Link
key={model.id} key={model.id}
to={`/fahrzeuge/${brandSlug}/${model.slug}`} to={`/fahrzeuge/${brandSlug}/${model.slug}`}
className="flex items-center justify-center rounded-xl border border-slate-200 bg-white px-6 py-8 text-center font-semibold text-slate-800 shadow-sm transition hover:-translate-y-0.5 hover:border-brand-500 hover:shadow-md dark:border-slate-800 dark:bg-slate-900 dark:text-slate-100" className="flex items-center justify-center rounded-xl border border-neutral-200 bg-white px-6 py-8 text-center font-semibold text-neutral-800 shadow-sm transition hover:-translate-y-0.5 hover:border-brand-500 hover:shadow-md dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-100"
> >
{model.name} {model.name}
</Link> </Link>

View file

@ -81,14 +81,14 @@ export default function ContactPage() {
return ( return (
<div className="mx-auto max-w-5xl px-4 py-12 sm:px-6"> <div className="mx-auto max-w-5xl px-4 py-12 sm:px-6">
<h1 className="mb-8 text-2xl font-bold text-slate-900 dark:text-white sm:text-3xl">Kontakt aufnehmen</h1> <h1 className="mb-8 text-2xl font-bold text-neutral-900 dark:text-white sm:text-3xl">Kontakt aufnehmen</h1>
<div className="grid gap-10 md:grid-cols-5"> <div className="grid gap-10 md:grid-cols-5">
<div className="md:col-span-3"> <div className="md:col-span-3">
{status === 'sent' ? ( {status === 'sent' ? (
<div className="rounded-xl border border-slate-200 bg-white p-8 text-center dark:border-slate-800 dark:bg-slate-900"> <div className="rounded-xl border border-neutral-200 bg-white p-8 text-center dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-2 text-xl font-bold text-slate-900 dark:text-white">Danke für deine Anfrage!</h2> <h2 className="mb-2 text-xl font-bold text-neutral-900 dark:text-white">Danke für deine Anfrage!</h2>
<p className="text-slate-600 dark:text-slate-300">Wir melden uns so schnell wie möglich bei dir.</p> <p className="text-neutral-600 dark:text-neutral-300">Wir melden uns so schnell wie möglich bei dir.</p>
</div> </div>
) : ( ) : (
<> <>
@ -111,20 +111,20 @@ export default function ContactPage() {
)} )}
{upgrades.length > 0 && ( {upgrades.length > 0 && (
<div className="mb-6 rounded-lg border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900"> <div className="mb-6 rounded-lg border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-neutral-900">
<p className="mb-3 text-sm font-semibold text-slate-900 dark:text-white">Optionale Upgrades</p> <p className="mb-3 text-sm font-semibold text-neutral-900 dark:text-white">Optionale Upgrades</p>
<div className="space-y-2"> <div className="space-y-2">
{upgrades.map((u) => ( {upgrades.map((u) => (
<label key={u.id} className="flex cursor-pointer items-start gap-3 rounded-md border border-slate-200 p-3 text-sm hover:bg-slate-50 dark:border-slate-700 dark:hover:bg-slate-800"> <label key={u.id} className="flex cursor-pointer items-start gap-3 rounded-md border border-neutral-200 p-3 text-sm hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800">
<input <input
type="checkbox" type="checkbox"
checked={selectedUpgradeIds.includes(u.id)} checked={selectedUpgradeIds.includes(u.id)}
onChange={() => toggleUpgrade(u.id)} onChange={() => toggleUpgrade(u.id)}
className="mt-0.5 h-4 w-4 rounded border-slate-300 text-brand-600 focus:ring-brand-500" className="mt-0.5 h-4 w-4 rounded border-neutral-300 text-brand-600 focus:ring-brand-500"
/> />
<span className="flex-1"> <span className="flex-1">
<span className="block font-medium text-slate-800 dark:text-slate-100">{u.name}</span> <span className="block font-medium text-neutral-800 dark:text-neutral-100">{u.name}</span>
{u.description && <span className="block text-slate-500 dark:text-slate-400">{u.description}</span>} {u.description && <span className="block text-neutral-500 dark:text-neutral-400">{u.description}</span>}
</span> </span>
<span className="font-semibold text-brand-600 dark:text-brand-400">+{formatPrice(u.price)}</span> <span className="font-semibold text-brand-600 dark:text-brand-400">+{formatPrice(u.price)}</span>
</label> </label>
@ -135,37 +135,37 @@ export default function ContactPage() {
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Name *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Name *</label>
<input <input
name="name" name="name"
required required
value={form.name} value={form.name}
onChange={handleChange} onChange={handleChange}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">E-Mail *</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">E-Mail *</label>
<input <input
type="email" type="email"
name="email" name="email"
required required
value={form.email} value={form.email}
onChange={handleChange} onChange={handleChange}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Telefon</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Telefon</label>
<input <input
name="phone" name="phone"
value={form.phone} value={form.phone}
onChange={handleChange} onChange={handleChange}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">
Fahrgestellnummer (FIN) Fahrgestellnummer (FIN)
</label> </label>
<input <input
@ -173,17 +173,17 @@ export default function ContactPage() {
value={form.vin} value={form.vin}
onChange={handleChange} onChange={handleChange}
placeholder="Optional hilft uns bei der genauen Einschätzung deines Fahrzeugs" placeholder="Optional hilft uns bei der genauen Einschätzung deines Fahrzeugs"
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Nachricht</label> <label className="mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300">Nachricht</label>
<textarea <textarea
name="message" name="message"
rows={4} rows={4}
value={form.message} value={form.message}
onChange={handleChange} onChange={handleChange}
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900" className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
/> />
</div> </div>
@ -202,32 +202,32 @@ export default function ContactPage() {
</div> </div>
<div className="md:col-span-2"> <div className="md:col-span-2">
<div className="rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <div className="rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-4 font-semibold text-slate-900 dark:text-white">HifiPlanet Amorbach</h2> <h2 className="mb-4 font-semibold text-neutral-900 dark:text-white">HifiPlanet Amorbach</h2>
<dl className="space-y-3 text-sm text-slate-600 dark:text-slate-300"> <dl className="space-y-3 text-sm text-neutral-600 dark:text-neutral-300">
<div> <div>
<dt className="font-medium text-slate-800 dark:text-slate-100">Adresse</dt> <dt className="font-medium text-neutral-800 dark:text-neutral-100">Adresse</dt>
<dd>Boxbrunner Str. 20a, 63916 Amorbach</dd> <dd>Boxbrunner Str. 20a, 63916 Amorbach</dd>
</div> </div>
<div> <div>
<dt className="font-medium text-slate-800 dark:text-slate-100">Telefon</dt> <dt className="font-medium text-neutral-800 dark:text-neutral-100">Telefon</dt>
<dd><a href={`tel:${digitsOnly(phone)}`} className="hover:text-brand-500">{phone}</a></dd> <dd><a href={`tel:${digitsOnly(phone)}`} className="hover:text-brand-500">{phone}</a></dd>
</div> </div>
<div> <div>
<dt className="font-medium text-slate-800 dark:text-slate-100">E-Mail</dt> <dt className="font-medium text-neutral-800 dark:text-neutral-100">E-Mail</dt>
<dd><a href={`mailto:${contactEmail}`} className="hover:text-brand-500">{contactEmail}</a></dd> <dd><a href={`mailto:${contactEmail}`} className="hover:text-brand-500">{contactEmail}</a></dd>
</div> </div>
<div> <div>
<dt className="font-medium text-slate-800 dark:text-slate-100">Öffnungszeiten</dt> <dt className="font-medium text-neutral-800 dark:text-neutral-100">Öffnungszeiten</dt>
<dd>MoFr: 9:0018:00 Uhr</dd> <dd>MoFr: 9:0018:00 Uhr</dd>
<dd>Sa: 10:0013:00 Uhr</dd> <dd>Sa: 10:0013:00 Uhr</dd>
</div> </div>
</dl> </dl>
</div> </div>
<div className="mt-6 rounded-xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"> <div className="mt-6 rounded-xl border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900">
<h2 className="mb-4 font-semibold text-slate-900 dark:text-white">Anfahrt</h2> <h2 className="mb-4 font-semibold text-neutral-900 dark:text-white">Anfahrt</h2>
<div className="overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700"> <div className="overflow-hidden rounded-lg border border-neutral-200 dark:border-neutral-700">
<ExternalEmbed name="Die Google-Maps-Karte" className="h-[220px] w-full"> <ExternalEmbed name="Die Google-Maps-Karte" className="h-[220px] w-full">
<iframe <iframe
title="HifiPlanet Amorbach Standort" title="HifiPlanet Amorbach Standort"

View file

@ -10,11 +10,11 @@ export default function Datenschutz() {
return ( return (
<div className="mx-auto max-w-3xl px-4 py-12 sm:px-6"> <div className="mx-auto max-w-3xl px-4 py-12 sm:px-6">
<h1 className="mb-8 text-2xl font-bold text-slate-900 dark:text-white sm:text-3xl">Datenschutzerklärung</h1> <h1 className="mb-8 text-2xl font-bold text-neutral-900 dark:text-white sm:text-3xl">Datenschutzerklärung</h1>
<div className="space-y-8 text-sm text-slate-600 dark:text-slate-300"> <div className="space-y-8 text-sm text-neutral-600 dark:text-neutral-300">
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">1. Verantwortlicher</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">1. Verantwortlicher</h2>
<p> <p>
Verantwortlicher im Sinne der Datenschutz-Grundverordnung (DSGVO) ist: Verantwortlicher im Sinne der Datenschutz-Grundverordnung (DSGVO) ist:
<br /> <br />
@ -28,7 +28,7 @@ export default function Datenschutz() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">2. Ihre Rechte als betroffene Person</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">2. Ihre Rechte als betroffene Person</h2>
<p className="mb-2">Sie haben jederzeit das Recht,</p> <p className="mb-2">Sie haben jederzeit das Recht,</p>
<ul className="list-disc space-y-1 pl-5"> <ul className="list-disc space-y-1 pl-5">
<li>Auskunft über Ihre bei uns gespeicherten personenbezogenen Daten zu erhalten (Art. 15 DSGVO),</li> <li>Auskunft über Ihre bei uns gespeicherten personenbezogenen Daten zu erhalten (Art. 15 DSGVO),</li>
@ -47,7 +47,7 @@ export default function Datenschutz() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">3. Hosting und Server-Logfiles</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">3. Hosting und Server-Logfiles</h2>
<p> <p>
Beim Aufruf dieser Website erhebt der Webserver automatisch Daten in sogenannten Server-Logfiles, die Beim Aufruf dieser Website erhebt der Webserver automatisch Daten in sogenannten Server-Logfiles, die
Ihr Browser übermittelt. Dazu gehören: IP-Adresse, Datum und Uhrzeit der Anfrage, aufgerufene Seite, Ihr Browser übermittelt. Dazu gehören: IP-Adresse, Datum und Uhrzeit der Anfrage, aufgerufene Seite,
@ -60,7 +60,7 @@ export default function Datenschutz() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">4. Kontaktformular</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">4. Kontaktformular</h2>
<p> <p>
Wenn Sie uns über das Kontaktformular eine Anfrage zukommen lassen, werden Ihre Angaben aus dem Wenn Sie uns über das Kontaktformular eine Anfrage zukommen lassen, werden Ihre Angaben aus dem
Formular (Name, E-Mail-Adresse, optional Telefonnummer, Nachricht sowie ggf. der Kontext Ihrer Anfrage Formular (Name, E-Mail-Adresse, optional Telefonnummer, Nachricht sowie ggf. der Kontext Ihrer Anfrage
@ -75,7 +75,7 @@ export default function Datenschutz() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">5. Cookies und lokale Speicherung</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">5. Cookies und lokale Speicherung</h2>
<p className="mb-2"> <p className="mb-2">
Diese Website verwendet technisch notwendige Cookies bzw. Speichertechnologien, die für den Betrieb der Diese Website verwendet technisch notwendige Cookies bzw. Speichertechnologien, die für den Betrieb der
Seite erforderlich sind und ohne die einzelne Funktionen nicht funktionieren würden. Dazu gehören: Seite erforderlich sind und ohne die einzelne Funktionen nicht funktionieren würden. Dazu gehören:
@ -100,7 +100,7 @@ export default function Datenschutz() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">6. Google Maps</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">6. Google Maps</h2>
<p> <p>
Auf unserer Kontaktseite und im Footer binden wir Kartenmaterial des Dienstes Google Maps" ein, um Auf unserer Kontaktseite und im Footer binden wir Kartenmaterial des Dienstes Google Maps" ein, um
Ihnen die Anfahrt zu unserem Standort zu erleichtern. Anbieter ist Google Ireland Limited, Gordon Ihnen die Anfahrt zu unserem Standort zu erleichtern. Anbieter ist Google Ireland Limited, Gordon
@ -122,7 +122,7 @@ export default function Datenschutz() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">7. YouTube</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">7. YouTube</h2>
<p> <p>
Auf unserer Startseite binden wir ein Video des Anbieters YouTube (Google Ireland Limited, Gordon Auf unserer Startseite binden wir ein Video des Anbieters YouTube (Google Ireland Limited, Gordon
House, Barrow Street, Dublin 4, Irland) im erweiterten Datenschutzmodus" ein (Domain House, Barrow Street, Dublin 4, Irland) im erweiterten Datenschutzmodus" ein (Domain
@ -145,7 +145,7 @@ export default function Datenschutz() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">8. Empfänger der Daten</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">8. Empfänger der Daten</h2>
<p> <p>
Ihre Daten werden grundsätzlich nur innerhalb unseres Unternehmens verarbeitet. Eine Weitergabe an Ihre Daten werden grundsätzlich nur innerhalb unseres Unternehmens verarbeitet. Eine Weitergabe an
Dritte erfolgt nur, soweit dies zur Bearbeitung Ihrer Anfrage erforderlich ist (z. B. an unseren Dritte erfolgt nur, soweit dies zur Bearbeitung Ihrer Anfrage erforderlich ist (z. B. an unseren
@ -155,7 +155,7 @@ export default function Datenschutz() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">9. Speicherdauer</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">9. Speicherdauer</h2>
<p> <p>
Sofern in dieser Erklärung keine speziellere Speicherdauer genannt wurde, verbleiben Ihre Sofern in dieser Erklärung keine speziellere Speicherdauer genannt wurde, verbleiben Ihre
personenbezogenen Daten bei uns, bis der Zweck für die Datenspeicherung entfällt. Gesetzliche personenbezogenen Daten bei uns, bis der Zweck für die Datenspeicherung entfällt. Gesetzliche
@ -165,7 +165,7 @@ export default function Datenschutz() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">10. SSL-/TLS-Verschlüsselung</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">10. SSL-/TLS-Verschlüsselung</h2>
<p> <p>
Diese Seite nutzt aus Sicherheitsgründen eine SSL-/TLS-Verschlüsselung zur Übertragung Ihrer Daten. Eine Diese Seite nutzt aus Sicherheitsgründen eine SSL-/TLS-Verschlüsselung zur Übertragung Ihrer Daten. Eine
verschlüsselte Verbindung erkennen Sie an dem Schloss-Symbol in der Adresszeile Ihres Browsers. verschlüsselte Verbindung erkennen Sie an dem Schloss-Symbol in der Adresszeile Ihres Browsers.
@ -173,7 +173,7 @@ export default function Datenschutz() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">11. Änderung dieser Datenschutzerklärung</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">11. Änderung dieser Datenschutzerklärung</h2>
<p> <p>
Wir passen diese Datenschutzerklärung an, sobald sich die von uns eingesetzten Datenverarbeitungen Wir passen diese Datenschutzerklärung an, sobald sich die von uns eingesetzten Datenverarbeitungen
ändern. Es gilt jeweils die zum Zeitpunkt Ihres Besuchs aktuelle, auf dieser Seite veröffentlichte ändern. Es gilt jeweils die zum Zeitpunkt Ihres Besuchs aktuelle, auf dieser Seite veröffentlichte

View file

@ -106,10 +106,10 @@ export default function Home() {
<div> <div>
<motion.div <motion.div
style={{ opacity: ctaOpacity, pointerEvents: ctaPointerEvents, y: ctaY }} style={{ opacity: ctaOpacity, pointerEvents: ctaPointerEvents, y: ctaY }}
className="fixed inset-x-0 bottom-0 z-20 border-t border-slate-200 bg-white/95 px-4 py-3 shadow-[0_-8px_24px_rgba(0,0,0,0.08)] backdrop-blur dark:border-slate-800 dark:bg-slate-950/95 sm:px-6" className="fixed inset-x-0 bottom-0 z-20 border-t border-neutral-200 bg-white/95 px-4 py-3 shadow-[0_-8px_24px_rgba(0,0,0,0.08)] backdrop-blur dark:border-neutral-800 dark:bg-neutral-950/95 sm:px-6"
> >
<div className="mx-auto flex max-w-6xl items-center justify-between gap-3"> <div className="mx-auto flex max-w-6xl items-center justify-between gap-3">
<p className="hidden text-sm font-medium text-slate-600 dark:text-slate-300 sm:block"> <p className="hidden text-sm font-medium text-neutral-600 dark:text-neutral-300 sm:block">
Bereit für deinen Sound-Umbau? Bereit für deinen Sound-Umbau?
</p> </p>
<div className="flex w-full gap-3 sm:w-auto"> <div className="flex w-full gap-3 sm:w-auto">
@ -121,7 +121,7 @@ export default function Home() {
</Link> </Link>
<Link <Link
to="/kontakt" to="/kontakt"
className="flex-1 rounded-md border border-slate-300 px-5 py-2.5 text-center text-sm font-semibold text-slate-700 transition hover:-translate-y-0.5 hover:bg-slate-100 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800 sm:flex-none" className="flex-1 rounded-md border border-neutral-300 px-5 py-2.5 text-center text-sm font-semibold text-neutral-700 transition hover:-translate-y-0.5 hover:bg-neutral-100 dark:border-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-800 sm:flex-none"
> >
Kontakt aufnehmen Kontakt aufnehmen
</Link> </Link>
@ -136,7 +136,7 @@ export default function Home() {
alt="Individueller Subwoofer-Einbau mit LED-Beleuchtung im Kofferraum" alt="Individueller Subwoofer-Einbau mit LED-Beleuchtung im Kofferraum"
className="absolute inset-0 h-full w-full scale-110 object-cover" className="absolute inset-0 h-full w-full scale-110 object-cover"
/> />
<div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-slate-950/80 to-slate-950/40" /> <div className="absolute inset-0 bg-gradient-to-t from-neutral-950 via-neutral-950/80 to-neutral-950/40" />
<motion.div <motion.div
style={{ y: heroTextY, opacity: heroOpacity }} style={{ y: heroTextY, opacity: heroOpacity }}
@ -153,7 +153,7 @@ export default function Home() {
<h1 className="text-3xl font-extrabold tracking-tight text-white sm:text-5xl"> <h1 className="text-3xl font-extrabold tracking-tight text-white sm:text-5xl">
Audiophil aus Prinzip Audiophil aus Prinzip
</h1> </h1>
<p className="mx-auto mt-4 max-w-2xl text-base text-slate-200 sm:text-lg"> <p className="mx-auto mt-4 max-w-2xl text-base text-neutral-200 sm:text-lg">
Dein Car-Hifi Spezialist für individuelle Sound-Umbauten. Wähle dein Fahrzeug, Dein Car-Hifi Spezialist für individuelle Sound-Umbauten. Wähle dein Fahrzeug,
entdecke passende Pakete und frag unverbindlich an wir kümmern uns um den Rest. entdecke passende Pakete und frag unverbindlich an wir kümmern uns um den Rest.
</p> </p>
@ -175,25 +175,25 @@ export default function Home() {
</motion.div> </motion.div>
</section> </section>
<section className="border-b border-slate-200 bg-brand-500 py-8 dark:border-slate-800"> <section className="border-b border-neutral-200 bg-neutral-950 py-8 dark:border-neutral-800">
<div className="mx-auto grid max-w-6xl grid-cols-2 gap-6 px-4 text-center sm:grid-cols-4 sm:px-6"> <div className="mx-auto grid max-w-6xl grid-cols-2 gap-6 px-4 text-center sm:grid-cols-4 sm:px-6">
{stats.map((stat, i) => ( {stats.map((stat, i) => (
<Reveal key={stat.label} index={i}> <Reveal key={stat.label} index={i}>
<p className="text-2xl font-extrabold text-white sm:text-3xl">{stat.value}</p> <p className="text-2xl font-extrabold text-brand-400 sm:text-3xl">{stat.value}</p>
<p className="text-sm text-brand-50">{stat.label}</p> <p className="text-sm text-neutral-400">{stat.label}</p>
</Reveal> </Reveal>
))} ))}
</div> </div>
</section> </section>
<section className="border-b border-slate-200 py-8 dark:border-slate-800"> <section className="border-b border-neutral-200 py-8 dark:border-neutral-800">
<div className="mx-auto max-w-6xl px-4 sm:px-6"> <div className="mx-auto max-w-6xl px-4 sm:px-6">
<p className="mb-4 text-center text-xs font-semibold uppercase tracking-wide text-slate-400"> <p className="mb-4 text-center text-xs font-semibold uppercase tracking-wide text-neutral-400">
Marken, mit denen wir arbeiten Marken, mit denen wir arbeiten
</p> </p>
<div className="flex flex-wrap items-center justify-center gap-x-8 gap-y-3"> <div className="flex flex-wrap items-center justify-center gap-x-8 gap-y-3">
{brands.map((brand, i) => ( {brands.map((brand, i) => (
<Reveal key={brand} index={i} className="text-lg font-bold text-slate-400 dark:text-slate-600"> <Reveal key={brand} index={i} className="text-lg font-bold text-neutral-400 dark:text-neutral-600">
{brand} {brand}
</Reveal> </Reveal>
))} ))}
@ -203,24 +203,24 @@ export default function Home() {
<section className="mx-auto max-w-6xl px-4 py-16 sm:px-6"> <section className="mx-auto max-w-6xl px-4 py-16 sm:px-6">
<Reveal> <Reveal>
<h2 className="mb-8 text-center text-2xl font-bold text-slate-900 dark:text-white">So funktioniert's</h2> <h2 className="mb-8 text-center text-2xl font-bold text-neutral-900 dark:text-white">So funktioniert's</h2>
</Reveal> </Reveal>
<div className="grid gap-6 sm:grid-cols-3"> <div className="grid gap-6 sm:grid-cols-3">
{steps.map((step, i) => ( {steps.map((step, i) => (
<Reveal key={step.title} index={i} className="rounded-xl border border-slate-200 bg-white p-6 text-center dark:border-slate-800 dark:bg-slate-900"> <Reveal key={step.title} index={i} className="rounded-xl border border-neutral-200 bg-white p-6 text-center dark:border-neutral-800 dark:bg-neutral-900">
<div className="mx-auto mb-4 flex h-10 w-10 items-center justify-center rounded-full bg-brand-500 text-lg font-bold text-white"> <div className="mx-auto mb-4 flex h-10 w-10 items-center justify-center rounded-full bg-brand-500 text-lg font-bold text-white">
{i + 1} {i + 1}
</div> </div>
<h3 className="mb-1 font-semibold text-slate-900 dark:text-white">{step.title}</h3> <h3 className="mb-1 font-semibold text-neutral-900 dark:text-white">{step.title}</h3>
<p className="text-sm text-slate-600 dark:text-slate-300">{step.text}</p> <p className="text-sm text-neutral-600 dark:text-neutral-300">{step.text}</p>
</Reveal> </Reveal>
))} ))}
</div> </div>
</section> </section>
<section className="relative overflow-hidden bg-gradient-to-br from-red-700 via-red-800 to-slate-950 py-20 sm:py-28"> <section className="relative overflow-hidden bg-neutral-950 py-20 sm:py-28">
<div className="pointer-events-none absolute -right-24 -top-24 h-72 w-72 rounded-full bg-red-500/30 blur-3xl" /> <div className="pointer-events-none absolute -right-24 -top-24 h-72 w-72 rounded-full bg-brand-500/25 blur-3xl" />
<div className="pointer-events-none absolute -bottom-24 -left-24 h-72 w-72 rounded-full bg-red-500/20 blur-3xl" /> <div className="pointer-events-none absolute -bottom-24 -left-24 h-72 w-72 rounded-full bg-brand-500/15 blur-3xl" />
<div className="relative mx-auto grid max-w-6xl gap-12 px-4 sm:px-6 lg:grid-cols-2 lg:items-center"> <div className="relative mx-auto grid max-w-6xl gap-12 px-4 sm:px-6 lg:grid-cols-2 lg:items-center">
<Reveal direction="left" className="text-center lg:text-left"> <Reveal direction="left" className="text-center lg:text-left">
@ -231,10 +231,10 @@ export default function Home() {
HifiPlanet auf YouTube HifiPlanet auf YouTube
</div> </div>
<h2 className="text-3xl font-extrabold text-white sm:text-4xl">Sieh dir unsere Umbauten in Aktion an</h2> <h2 className="text-3xl font-extrabold text-white sm:text-4xl">Sieh dir unsere Umbauten in Aktion an</h2>
<p className="mx-auto mt-4 max-w-xl text-lg text-red-50/90 lg:mx-0"> <p className="mx-auto mt-4 max-w-xl text-lg text-neutral-300 lg:mx-0">
Umbauten, Soundchecks und Verstärker-Tests aus unserer Werkstatt Den besten Sound gibt es nicht ab Werk!" Umbauten, Soundchecks und Verstärker-Tests aus unserer Werkstatt Den besten Sound gibt es nicht ab Werk!"
</p> </p>
<ul className="mx-auto mt-6 flex max-w-xl flex-col items-center gap-2 text-sm text-red-50/90 sm:flex-row sm:flex-wrap sm:justify-center lg:mx-0 lg:justify-start"> <ul className="mx-auto mt-6 flex max-w-xl flex-col items-center gap-2 text-sm text-neutral-300 sm:flex-row sm:flex-wrap sm:justify-center lg:mx-0 lg:justify-start">
{['Umbauten Schritt für Schritt', 'Soundchecks & Vergleiche', 'Verstärker- & Komponenten-Tests'].map((item) => ( {['Umbauten Schritt für Schritt', 'Soundchecks & Vergleiche', 'Verstärker- & Komponenten-Tests'].map((item) => (
<li key={item} className="flex items-center gap-2 rounded-full bg-white/10 px-3 py-1"> <li key={item} className="flex items-center gap-2 rounded-full bg-white/10 px-3 py-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-3.5 w-3.5 flex-none"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-3.5 w-3.5 flex-none">
@ -248,7 +248,7 @@ export default function Home() {
href={YOUTUBE_URL} href={YOUTUBE_URL}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="mt-8 inline-flex items-center gap-2 rounded-md bg-white px-6 py-3 text-sm font-bold text-red-700 shadow-lg shadow-black/20 transition hover:-translate-y-0.5 hover:bg-red-50" className="mt-8 inline-flex items-center gap-2 rounded-md bg-brand-500 px-6 py-3 text-sm font-bold text-white shadow-lg shadow-black/40 transition hover:-translate-y-0.5 hover:bg-brand-400"
> >
Zum YouTube-Kanal Zum YouTube-Kanal
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-4 w-4"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-4 w-4">
@ -274,7 +274,7 @@ export default function Home() {
</div> </div>
</section> </section>
<section className="border-y border-slate-200 bg-slate-50 py-16 dark:border-slate-800 dark:bg-slate-900/40"> <section className="border-y border-neutral-200 bg-neutral-50 py-16 dark:border-neutral-800 dark:bg-neutral-900/40">
<div className="mx-auto max-w-6xl space-y-16 overflow-hidden px-4 sm:px-6"> <div className="mx-auto max-w-6xl space-y-16 overflow-hidden px-4 sm:px-6">
<div className="grid items-center gap-8 md:grid-cols-2"> <div className="grid items-center gap-8 md:grid-cols-2">
<Reveal direction="left" shine className="rounded-xl shadow-lg"> <Reveal direction="left" shine className="rounded-xl shadow-lg">
@ -282,8 +282,8 @@ export default function Home() {
</Reveal> </Reveal>
<Reveal direction="right" index={1}> <Reveal direction="right" index={1}>
<p className="mb-2 text-sm font-semibold uppercase tracking-wide text-brand-600 dark:text-brand-400">Handwerk</p> <p className="mb-2 text-sm font-semibold uppercase tracking-wide text-brand-600 dark:text-brand-400">Handwerk</p>
<h2 className="mb-3 text-2xl font-bold text-slate-900 dark:text-white">Präzisionsarbeit in jedem Detail</h2> <h2 className="mb-3 text-2xl font-bold text-neutral-900 dark:text-white">Präzisionsarbeit in jedem Detail</h2>
<p className="text-slate-600 dark:text-slate-300"> <p className="text-neutral-600 dark:text-neutral-300">
Ob eigens gefertigte Subwoofer-Gehäuse, integrierte Beleuchtung oder unsichtbar verlegte Kabelwege Ob eigens gefertigte Subwoofer-Gehäuse, integrierte Beleuchtung oder unsichtbar verlegte Kabelwege
dank eigener CNC-, Laser- und 3D-Druck-Fertigung entstehen bei uns Lösungen, die es von der Stange nicht gibt. dank eigener CNC-, Laser- und 3D-Druck-Fertigung entstehen bei uns Lösungen, die es von der Stange nicht gibt.
</p> </p>
@ -293,8 +293,8 @@ export default function Home() {
<div className="grid items-center gap-8 md:grid-cols-2"> <div className="grid items-center gap-8 md:grid-cols-2">
<Reveal direction="left" className="order-2 md:order-1"> <Reveal direction="left" className="order-2 md:order-1">
<p className="mb-2 text-sm font-semibold uppercase tracking-wide text-brand-600 dark:text-brand-400">Beratung</p> <p className="mb-2 text-sm font-semibold uppercase tracking-wide text-brand-600 dark:text-brand-400">Beratung</p>
<h2 className="mb-3 text-2xl font-bold text-slate-900 dark:text-white">Von der ersten Idee bis zum letzten Schliff</h2> <h2 className="mb-3 text-2xl font-bold text-neutral-900 dark:text-white">Von der ersten Idee bis zum letzten Schliff</h2>
<p className="text-slate-600 dark:text-slate-300"> <p className="text-neutral-600 dark:text-neutral-300">
Jedes Fahrzeug und jeder Anspruch ist anders. Deshalb beraten wir dich persönlich und unverbindlich Jedes Fahrzeug und jeder Anspruch ist anders. Deshalb beraten wir dich persönlich und unverbindlich
vom dezenten Upgrade bis zum kompromisslosen High-End-System. vom dezenten Upgrade bis zum kompromisslosen High-End-System.
</p> </p>
@ -308,28 +308,28 @@ export default function Home() {
<section className="mx-auto max-w-6xl px-4 py-16 sm:px-6"> <section className="mx-auto max-w-6xl px-4 py-16 sm:px-6">
<Reveal className="mb-8 text-center"> <Reveal className="mb-8 text-center">
<h2 className="text-2xl font-bold text-slate-900 dark:text-white">Einblicke in unsere Umbauten</h2> <h2 className="text-2xl font-bold text-neutral-900 dark:text-white">Einblicke in unsere Umbauten</h2>
<p className="mt-2 text-slate-600 dark:text-slate-300"> <p className="mt-2 text-neutral-600 dark:text-neutral-300">
Vom unauffälligen Sound-Upgrade bis zum aufwendigen Komplettumbau Handarbeit aus unserer Werkstatt in Amorbach. Vom unauffälligen Sound-Upgrade bis zum aufwendigen Komplettumbau Handarbeit aus unserer Werkstatt in Amorbach.
</p> </p>
</Reveal> </Reveal>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3"> <div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
{gallery.map((img, i) => ( {gallery.map((img, i) => (
<Reveal key={img.src} index={i % 3} shine className="aspect-square overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800"> <Reveal key={img.src} index={i % 3} shine className="aspect-square overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800">
<img src={img.src} alt={img.alt} loading="lazy" className="h-full w-full object-cover transition duration-300 hover:scale-105" /> <img src={img.src} alt={img.alt} loading="lazy" className="h-full w-full object-cover transition duration-300 hover:scale-105" />
</Reveal> </Reveal>
))} ))}
</div> </div>
</section> </section>
<section className="border-y border-slate-200 bg-slate-50 py-16 dark:border-slate-800 dark:bg-slate-900/40"> <section className="border-y border-neutral-200 bg-neutral-50 py-16 dark:border-neutral-800 dark:bg-neutral-900/40">
<div className="mx-auto max-w-6xl px-4 sm:px-6"> <div className="mx-auto max-w-6xl px-4 sm:px-6">
<Reveal className="mb-10 text-center"> <Reveal className="mb-10 text-center">
<div className="mb-2 flex items-center justify-center gap-2"> <div className="mb-2 flex items-center justify-center gap-2">
<StarRating className="h-6 w-6" /> <StarRating className="h-6 w-6" />
</div> </div>
<h2 className="text-2xl font-bold text-slate-900 dark:text-white">5,0 von 181 Kunden bewertet</h2> <h2 className="text-2xl font-bold text-neutral-900 dark:text-white">5,0 von 181 Kunden bewertet</h2>
<p className="mt-1 text-slate-600 dark:text-slate-300">Das sagen unsere Kunden über uns.</p> <p className="mt-1 text-neutral-600 dark:text-neutral-300">Das sagen unsere Kunden über uns.</p>
<a <a
href="https://www.google.com/maps/search/?api=1&query=Hifi+Planet+Amorbach+Boxbrunner+Stra%C3%9Fe+20a" href="https://www.google.com/maps/search/?api=1&query=Hifi+Planet+Amorbach+Boxbrunner+Stra%C3%9Fe+20a"
target="_blank" target="_blank"
@ -351,14 +351,14 @@ export default function Home() {
<img src={heroHighlight} alt="Hochwertige Endstufe und Subwoofer, Studioaufnahme" className="h-full w-full object-cover" loading="lazy" /> <img src={heroHighlight} alt="Hochwertige Endstufe und Subwoofer, Studioaufnahme" className="h-full w-full object-cover" loading="lazy" />
</Reveal> </Reveal>
<Reveal direction="right" index={1} className="text-center md:text-left"> <Reveal direction="right" index={1} className="text-center md:text-left">
<h2 className="mb-3 text-2xl font-bold text-slate-900 dark:text-white">Mehr als nur Car-Hifi</h2> <h2 className="mb-3 text-2xl font-bold text-neutral-900 dark:text-white">Mehr als nur Car-Hifi</h2>
<p className="mx-auto mb-8 max-w-2xl text-slate-600 dark:text-slate-300 md:mx-0"> <p className="mx-auto mb-8 max-w-2xl text-neutral-600 dark:text-neutral-300 md:mx-0">
Neben individuellen Sound-Umbauten bieten wir CNC-Zerspanung, Lasertechnik, 3D-Druck und mehr Neben individuellen Sound-Umbauten bieten wir CNC-Zerspanung, Lasertechnik, 3D-Druck und mehr
alles aus einer Hand in unserer eigenen Werkstatt. alles aus einer Hand in unserer eigenen Werkstatt.
</p> </p>
<Link <Link
to="/leistungen" to="/leistungen"
className="inline-block rounded-md border border-slate-300 px-6 py-3 text-sm font-semibold text-slate-700 transition hover:-translate-y-0.5 hover:bg-slate-100 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800" className="inline-block rounded-md border border-neutral-300 px-6 py-3 text-sm font-semibold text-neutral-700 transition hover:-translate-y-0.5 hover:bg-neutral-100 dark:border-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-800"
> >
Alle Leistungen entdecken Alle Leistungen entdecken
</Link> </Link>
@ -366,10 +366,10 @@ export default function Home() {
</div> </div>
</section> </section>
<section className="border-t border-slate-200 py-16 dark:border-slate-800"> <section className="border-t border-neutral-200 py-16 dark:border-neutral-800">
<div className="mx-auto max-w-3xl px-4 sm:px-6"> <div className="mx-auto max-w-3xl px-4 sm:px-6">
<Reveal className="mb-8 text-center"> <Reveal className="mb-8 text-center">
<h2 className="text-2xl font-bold text-slate-900 dark:text-white">Häufig gestellte Fragen</h2> <h2 className="text-2xl font-bold text-neutral-900 dark:text-white">Häufig gestellte Fragen</h2>
</Reveal> </Reveal>
<Reveal index={1}> <Reveal index={1}>
<Accordion items={faqs} /> <Accordion items={faqs} />

View file

@ -9,11 +9,11 @@ export default function Impressum() {
return ( return (
<div className="mx-auto max-w-3xl px-4 py-12 sm:px-6"> <div className="mx-auto max-w-3xl px-4 py-12 sm:px-6">
<h1 className="mb-8 text-2xl font-bold text-slate-900 dark:text-white sm:text-3xl">Impressum</h1> <h1 className="mb-8 text-2xl font-bold text-neutral-900 dark:text-white sm:text-3xl">Impressum</h1>
<div className="space-y-8 text-sm text-slate-600 dark:text-slate-300"> <div className="space-y-8 text-sm text-neutral-600 dark:text-neutral-300">
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">Angaben gemäß § 5 DDG</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">Angaben gemäß § 5 DDG</h2>
<p> <p>
Hifi Planet e.K. Hifi Planet e.K.
<br /> <br />
@ -26,7 +26,7 @@ export default function Impressum() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">Kontakt</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">Kontakt</h2>
<p> <p>
Telefon: <a href="tel:+4993732062390" className="hover:text-brand-500">09373 20 62 390</a> Telefon: <a href="tel:+4993732062390" className="hover:text-brand-500">09373 20 62 390</a>
<br /> <br />
@ -35,19 +35,19 @@ export default function Impressum() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">Umsatzsteuer-ID</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">Umsatzsteuer-ID</h2>
<p> <p>
Umsatzsteuer-Identifikationsnummer gemäß § 27a Umsatzsteuergesetz: DE 275084766 Umsatzsteuer-Identifikationsnummer gemäß § 27a Umsatzsteuergesetz: DE 275084766
</p> </p>
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">Zuständige Kammer</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">Zuständige Kammer</h2>
<p>IHK Aschaffenburg</p> <p>IHK Aschaffenburg</p>
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV</h2>
<p> <p>
Christopher Horndasch Christopher Horndasch
<br /> <br />
@ -56,7 +56,7 @@ export default function Impressum() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">Verbraucherstreitbeilegung</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">Verbraucherstreitbeilegung</h2>
<p> <p>
Wir sind nicht bereit und nicht verpflichtet, an Streitbeilegungsverfahren vor einer Wir sind nicht bereit und nicht verpflichtet, an Streitbeilegungsverfahren vor einer
Verbraucherschlichtungsstelle im Sinne des Verbraucherstreitbeilegungsgesetzes (VSBG) teilzunehmen. Verbraucherschlichtungsstelle im Sinne des Verbraucherstreitbeilegungsgesetzes (VSBG) teilzunehmen.
@ -64,7 +64,7 @@ export default function Impressum() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">Haftung für Inhalte</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">Haftung für Inhalte</h2>
<p> <p>
Als Diensteanbieter sind wir gemäß § 7 Abs. 1 DDG für eigene Inhalte auf diesen Seiten nach den Als Diensteanbieter sind wir gemäß § 7 Abs. 1 DDG für eigene Inhalte auf diesen Seiten nach den
allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 DDG sind wir als Diensteanbieter jedoch nicht allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 DDG sind wir als Diensteanbieter jedoch nicht
@ -78,7 +78,7 @@ export default function Impressum() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">Haftung für Links</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">Haftung für Links</h2>
<p> <p>
Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen Einfluss haben. Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen Einfluss haben.
Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der
@ -90,7 +90,7 @@ export default function Impressum() {
</section> </section>
<section> <section>
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">Urheberrecht</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">Urheberrecht</h2>
<p> <p>
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen
Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der

View file

@ -30,8 +30,8 @@ export default function Leistungen() {
return ( return (
<div className="mx-auto max-w-6xl px-4 py-12 sm:px-6"> <div className="mx-auto max-w-6xl px-4 py-12 sm:px-6">
{maintenance.services.enabled && maintenance.bypass && <MaintenanceBypassBanner inline />} {maintenance.services.enabled && maintenance.bypass && <MaintenanceBypassBanner inline />}
<h1 className="mb-2 text-2xl font-bold text-slate-900 dark:text-white sm:text-3xl">Unsere Leistungen</h1> <h1 className="mb-2 text-2xl font-bold text-neutral-900 dark:text-white sm:text-3xl">Unsere Leistungen</h1>
<p className="mb-10 max-w-2xl text-slate-600 dark:text-slate-300"> <p className="mb-10 max-w-2xl text-neutral-600 dark:text-neutral-300">
Von individuellen Sound-Umbauten bis zur eigenen CNC- und 3D-Druck-Fertigung alles aus einer Hand Von individuellen Sound-Umbauten bis zur eigenen CNC- und 3D-Druck-Fertigung alles aus einer Hand
in unserer Werkstatt in Amorbach. in unserer Werkstatt in Amorbach.
</p> </p>
@ -42,19 +42,19 @@ export default function Leistungen() {
key={service.id} key={service.id}
index={i % 3} index={i % 3}
shine shine
className="overflow-hidden rounded-xl border border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900" className="overflow-hidden rounded-xl border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900"
> >
<div className="relative aspect-[4/3]"> <div className="relative aspect-[4/3]">
{service.image_path && ( {service.image_path && (
<img src={service.image_path} alt={service.title} className="h-full w-full object-cover" loading="lazy" /> <img src={service.image_path} alt={service.title} className="h-full w-full object-cover" loading="lazy" />
)} )}
<div className="absolute -bottom-5 left-5 flex h-12 w-12 items-center justify-center rounded-full bg-brand-500 text-white shadow-lg ring-4 ring-white dark:ring-slate-900"> <div className="absolute -bottom-5 left-5 flex h-12 w-12 items-center justify-center rounded-full bg-brand-500 text-white shadow-lg ring-4 ring-white dark:ring-neutral-900">
<DynamicIcon name={service.icon_name} className="h-6 w-6" /> <DynamicIcon name={service.icon_name} className="h-6 w-6" />
</div> </div>
</div> </div>
<div className="p-6 pt-8"> <div className="p-6 pt-8">
<h2 className="mb-2 font-semibold text-slate-900 dark:text-white">{service.title}</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">{service.title}</h2>
<p className="mb-3 text-sm text-slate-600 dark:text-slate-300">{service.description}</p> <p className="mb-3 text-sm text-neutral-600 dark:text-neutral-300">{service.description}</p>
{service.cta_label && service.cta_url && ( {service.cta_label && service.cta_url && (
<Link to={service.cta_url} className="text-sm font-semibold text-brand-600 hover:underline dark:text-brand-400"> <Link to={service.cta_url} className="text-sm font-semibold text-brand-600 hover:underline dark:text-brand-400">
{service.cta_label} {service.cta_label}
@ -65,9 +65,9 @@ export default function Leistungen() {
))} ))}
</div> </div>
<div className="mt-12 rounded-xl border border-slate-200 bg-slate-50 p-8 text-center dark:border-slate-800 dark:bg-slate-900/40"> <div className="mt-12 rounded-xl border border-neutral-200 bg-neutral-50 p-8 text-center dark:border-neutral-800 dark:bg-neutral-900/40">
<h2 className="mb-2 text-xl font-bold text-slate-900 dark:text-white">Dein Projekt ist nicht dabei?</h2> <h2 className="mb-2 text-xl font-bold text-neutral-900 dark:text-white">Dein Projekt ist nicht dabei?</h2>
<p className="mb-5 text-slate-600 dark:text-slate-300">Sprich uns einfach an wir finden gemeinsam die passende Lösung.</p> <p className="mb-5 text-neutral-600 dark:text-neutral-300">Sprich uns einfach an wir finden gemeinsam die passende Lösung.</p>
<Link to="/kontakt" className="inline-block rounded-md bg-brand-500 px-6 py-3 text-sm font-semibold text-white hover:bg-brand-600"> <Link to="/kontakt" className="inline-block rounded-md bg-brand-500 px-6 py-3 text-sm font-semibold text-white hover:bg-brand-600">
Kontakt aufnehmen Kontakt aufnehmen
</Link> </Link>

View file

@ -40,7 +40,7 @@ export default function ModelPage() {
} }
if (!data) { if (!data) {
return <p className="mx-auto max-w-6xl px-4 py-12 text-slate-500 sm:px-6">Lädt</p>; return <p className="mx-auto max-w-6xl px-4 py-12 text-neutral-500 sm:px-6">Lädt</p>;
} }
const { model, packages } = data; const { model, packages } = data;
@ -59,30 +59,30 @@ export default function ModelPage() {
return ( return (
<div className="mx-auto max-w-6xl px-4 py-12 sm:px-6"> <div className="mx-auto max-w-6xl px-4 py-12 sm:px-6">
{maintenance.vehicles.enabled && maintenance.bypass && <MaintenanceBypassBanner inline />} {maintenance.vehicles.enabled && maintenance.bypass && <MaintenanceBypassBanner inline />}
<p className="mb-1 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-1 text-sm text-neutral-500 dark:text-neutral-400">
<Link to="/fahrzeuge" className="hover:text-brand-500">Fahrzeuge</Link> /{' '} <Link to="/fahrzeuge" className="hover:text-brand-500">Fahrzeuge</Link> /{' '}
<Link to={`/fahrzeuge/${brandSlug}`} className="hover:text-brand-500">{model.brand_name}</Link> / {model.name} <Link to={`/fahrzeuge/${brandSlug}`} className="hover:text-brand-500">{model.brand_name}</Link> / {model.name}
</p> </p>
<h1 className="mb-8 text-2xl font-bold text-slate-900 dark:text-white sm:text-3xl"> <h1 className="mb-8 text-2xl font-bold text-neutral-900 dark:text-white sm:text-3xl">
{model.brand_name} {model.name} Sound-Pakete {model.brand_name} {model.name} Sound-Pakete
</h1> </h1>
{packages.length === 0 && ( {packages.length === 0 && (
<p className="text-slate-500 dark:text-slate-400">Für dieses Modell sind noch keine Pakete hinterlegt.</p> <p className="text-neutral-500 dark:text-neutral-400">Für dieses Modell sind noch keine Pakete hinterlegt.</p>
)} )}
<div className="space-y-6"> <div className="space-y-6">
{packages.map((pkg) => ( {packages.map((pkg) => (
<div key={pkg.id} className="rounded-xl border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-900"> <div key={pkg.id} className="rounded-xl border border-neutral-200 bg-white p-6 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
<div className="mb-4 flex flex-wrap items-start justify-between gap-4"> <div className="mb-4 flex flex-wrap items-start justify-between gap-4">
<h2 className="text-lg font-bold text-slate-900 dark:text-white">{pkg.name}</h2> <h2 className="text-lg font-bold text-neutral-900 dark:text-white">{pkg.name}</h2>
<div className="text-right"> <div className="text-right">
<p className="text-xs uppercase tracking-wide text-slate-400">Gesamtpreis (ca.)</p> <p className="text-xs uppercase tracking-wide text-neutral-400">Gesamtpreis (ca.)</p>
<p className="text-xl font-extrabold text-brand-600 dark:text-brand-400">{formatPrice(pkg.total_price)}</p> <p className="text-xl font-extrabold text-brand-600 dark:text-brand-400">{formatPrice(pkg.total_price)}</p>
</div> </div>
</div> </div>
<ul className="mb-5 list-disc space-y-1 pl-5 text-sm text-slate-700 dark:text-slate-300"> <ul className="mb-5 list-disc space-y-1 pl-5 text-sm text-neutral-700 dark:text-neutral-300">
{pkg.products.map((product) => ( {pkg.products.map((product) => (
<li key={product.id}> <li key={product.id}>
{product.name_override || product.scraped_name || 'Produkt wird geladen…'} {product.name_override || product.scraped_name || 'Produkt wird geladen…'}

View file

@ -107,8 +107,8 @@ export default function SetupWizard() {
}; };
const inputClass = const inputClass =
'w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900'; 'w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm focus:border-brand-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900';
const labelClass = 'mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300'; const labelClass = 'mb-1 block text-sm font-medium text-neutral-700 dark:text-neutral-300';
const steps = [ const steps = [
{ key: 'database', label: 'Datenbank' }, { key: 'database', label: 'Datenbank' },
@ -118,16 +118,16 @@ export default function SetupWizard() {
const currentStepIndex = steps.findIndex((s) => s.key === phase); const currentStepIndex = steps.findIndex((s) => s.key === phase);
return ( return (
<div className="flex min-h-screen flex-col items-center justify-center gap-6 bg-slate-50 px-4 py-12 dark:bg-slate-950"> <div className="flex min-h-screen flex-col items-center justify-center gap-6 bg-neutral-50 px-4 py-12 dark:bg-neutral-950">
<img src={logo} alt="HifiPlanet" className="h-16 w-auto sm:h-20" /> <img src={logo} alt="HifiPlanet" className="h-16 w-auto sm:h-20" />
<div className="w-full max-w-md rounded-xl border border-slate-200 bg-white p-8 shadow-sm dark:border-slate-800 dark:bg-slate-900"> <div className="w-full max-w-md rounded-xl border border-neutral-200 bg-white p-8 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
{phase === 'loading' && <p className="text-center text-sm text-slate-400">Lädt</p>} {phase === 'loading' && <p className="text-center text-sm text-neutral-400">Lädt</p>}
{phase === 'locked' && ( {phase === 'locked' && (
<div className="text-center"> <div className="text-center">
<h1 className="mb-2 text-xl font-bold text-slate-900 dark:text-white">Bereits eingerichtet</h1> <h1 className="mb-2 text-xl font-bold text-neutral-900 dark:text-white">Bereits eingerichtet</h1>
<p className="mb-6 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-6 text-sm text-neutral-500 dark:text-neutral-400">
Diese Seite wurde bereits vollständig eingerichtet. Der Assistent ist deshalb gesperrt. Diese Seite wurde bereits vollständig eingerichtet. Der Assistent ist deshalb gesperrt.
</p> </p>
<Link to="/admin/login" className="text-sm font-medium text-brand-600 hover:underline dark:text-brand-400"> <Link to="/admin/login" className="text-sm font-medium text-brand-600 hover:underline dark:text-brand-400">
@ -146,12 +146,12 @@ export default function SetupWizard() {
? 'bg-green-500 text-white' ? 'bg-green-500 text-white'
: i === currentStepIndex : i === currentStepIndex
? 'bg-brand-500 text-white' ? 'bg-brand-500 text-white'
: 'bg-slate-200 text-slate-500 dark:bg-slate-700 dark:text-slate-400' : 'bg-neutral-200 text-neutral-500 dark:bg-neutral-700 dark:text-neutral-400'
}`} }`}
> >
{i < currentStepIndex ? '✓' : i + 1} {i < currentStepIndex ? '✓' : i + 1}
</span> </span>
{i < steps.length - 1 && <span className="h-px w-6 bg-slate-300 dark:bg-slate-700" />} {i < steps.length - 1 && <span className="h-px w-6 bg-neutral-300 dark:bg-neutral-700" />}
</div> </div>
))} ))}
</div> </div>
@ -159,8 +159,8 @@ export default function SetupWizard() {
{phase === 'password' && ( {phase === 'password' && (
<form onSubmit={handlePassword}> <form onSubmit={handlePassword}>
<h1 className="mb-2 text-xl font-bold text-slate-900 dark:text-white">Ersteinrichtung</h1> <h1 className="mb-2 text-xl font-bold text-neutral-900 dark:text-white">Ersteinrichtung</h1>
<p className="mb-6 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-6 text-sm text-neutral-500 dark:text-neutral-400">
Gib das Einmalpasswort ein, das in <code>config/setup.php</code> hinterlegt ist. Gib das Einmalpasswort ein, das in <code>config/setup.php</code> hinterlegt ist.
</p> </p>
<div className="mb-4"> <div className="mb-4">
@ -187,8 +187,8 @@ export default function SetupWizard() {
{phase === 'database' && ( {phase === 'database' && (
<form onSubmit={handleDatabase}> <form onSubmit={handleDatabase}>
<h1 className="mb-2 text-xl font-bold text-slate-900 dark:text-white">Datenbank-Zugangsdaten</h1> <h1 className="mb-2 text-xl font-bold text-neutral-900 dark:text-white">Datenbank-Zugangsdaten</h1>
<p className="mb-6 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-6 text-sm text-neutral-500 dark:text-neutral-400">
Trage die Zugangsdaten der Datenbank auf diesem Server ein. Die Verbindung wird vor dem Speichern Trage die Zugangsdaten der Datenbank auf diesem Server ein. Die Verbindung wird vor dem Speichern
getestet. getestet.
</p> </p>
@ -249,12 +249,12 @@ export default function SetupWizard() {
{phase === 'schema' && ( {phase === 'schema' && (
<div> <div>
<h1 className="mb-2 text-xl font-bold text-slate-900 dark:text-white">Datenbankstruktur</h1> <h1 className="mb-2 text-xl font-bold text-neutral-900 dark:text-white">Datenbankstruktur</h1>
<p className="mb-6 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-6 text-sm text-neutral-500 dark:text-neutral-400">
Die Datenbankverbindung funktioniert. Jetzt legen wir die benötigten Tabellen an. Die Datenbankverbindung funktioniert. Jetzt legen wir die benötigten Tabellen an.
</p> </p>
{migrateActions && ( {migrateActions && (
<ul className="mb-4 space-y-1 rounded-md bg-slate-50 p-3 text-sm text-slate-700 dark:bg-slate-800 dark:text-slate-300"> <ul className="mb-4 space-y-1 rounded-md bg-neutral-50 p-3 text-sm text-neutral-700 dark:bg-neutral-800 dark:text-neutral-300">
{migrateActions.map((a, i) => ( {migrateActions.map((a, i) => (
<li key={i}>{a}</li> <li key={i}>{a}</li>
))} ))}
@ -274,8 +274,8 @@ export default function SetupWizard() {
{phase === 'admin' && ( {phase === 'admin' && (
<form onSubmit={handleAdmin}> <form onSubmit={handleAdmin}>
<h1 className="mb-2 text-xl font-bold text-slate-900 dark:text-white">Erster Admin-Account</h1> <h1 className="mb-2 text-xl font-bold text-neutral-900 dark:text-white">Erster Admin-Account</h1>
<p className="mb-6 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-6 text-sm text-neutral-500 dark:text-neutral-400">
Lege den ersten Super-Admin-Account an, mit dem du dich anschließend einloggen kannst. Lege den ersten Super-Admin-Account an, mit dem du dich anschließend einloggen kannst.
</p> </p>
<div className="mb-3"> <div className="mb-3">
@ -323,8 +323,8 @@ export default function SetupWizard() {
{phase === 'done' && ( {phase === 'done' && (
<div className="text-center"> <div className="text-center">
<h1 className="mb-2 text-xl font-bold text-slate-900 dark:text-white">Fertig!</h1> <h1 className="mb-2 text-xl font-bold text-neutral-900 dark:text-white">Fertig!</h1>
<p className="mb-6 text-sm text-slate-500 dark:text-slate-400"> <p className="mb-6 text-sm text-neutral-500 dark:text-neutral-400">
Die Ersteinrichtung ist abgeschlossen. Du kannst dich jetzt mit deinem neuen Admin-Account anmelden. Die Ersteinrichtung ist abgeschlossen. Du kannst dich jetzt mit deinem neuen Admin-Account anmelden.
</p> </p>
<Link <Link

View file

@ -29,15 +29,15 @@ export default function VehicleSelect() {
return ( return (
<div className="mx-auto max-w-6xl px-4 py-12 sm:px-6"> <div className="mx-auto max-w-6xl px-4 py-12 sm:px-6">
{maintenance.vehicles.enabled && maintenance.bypass && <MaintenanceBypassBanner inline />} {maintenance.vehicles.enabled && maintenance.bypass && <MaintenanceBypassBanner inline />}
<h1 className="mb-2 text-2xl font-bold text-slate-900 dark:text-white sm:text-3xl">Fahrzeug auswählen</h1> <h1 className="mb-2 text-2xl font-bold text-neutral-900 dark:text-white sm:text-3xl">Fahrzeug auswählen</h1>
<p className="mb-8 text-slate-600 dark:text-slate-300"> <p className="mb-8 text-neutral-600 dark:text-neutral-300">
Wähle zuerst deine Marke, dann dein Modell wir zeigen dir direkt die passenden Sound-Pakete. Wähle zuerst deine Marke, dann dein Modell wir zeigen dir direkt die passenden Sound-Pakete.
</p> </p>
{error && <p className="text-red-600">{error}</p>} {error && <p className="text-red-600">{error}</p>}
{brands.length === 0 && !error && ( {brands.length === 0 && !error && (
<p className="text-slate-500 dark:text-slate-400">Es sind noch keine Marken hinterlegt.</p> <p className="text-neutral-500 dark:text-neutral-400">Es sind noch keine Marken hinterlegt.</p>
)} )}
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4"> <div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4">
@ -45,7 +45,7 @@ export default function VehicleSelect() {
<Link <Link
key={brand.id} key={brand.id}
to={`/fahrzeuge/${brand.slug}`} to={`/fahrzeuge/${brand.slug}`}
className="flex items-center justify-center rounded-xl border border-slate-200 bg-white px-6 py-8 text-center font-semibold text-slate-800 shadow-sm transition hover:-translate-y-0.5 hover:border-brand-500 hover:shadow-md dark:border-slate-800 dark:bg-slate-900 dark:text-slate-100" className="flex items-center justify-center rounded-xl border border-neutral-200 bg-white px-6 py-8 text-center font-semibold text-neutral-800 shadow-sm transition hover:-translate-y-0.5 hover:border-brand-500 hover:shadow-md dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-100"
> >
{brand.name} {brand.name}
</Link> </Link>