Industrial Dark UI Overhaul: Updated colors, typography, navigation, and component styling across the application
This commit is contained in:
142
.aiideas
142
.aiideas
@@ -1,117 +1,103 @@
|
|||||||
Rolle: Du bist ein Senior Frontend Engineer(React / Tailwind) und UI / UX Designer.Ziel: Wir machen ein umfassendes UI - Overhaul einer bestehenden Whisky - App("WhiskyVault") und benennen sie um in "DramLog".Das Ziel ist eine Premium - Mobile - Experience(Dark Mode, Gold Akzente, Serif Fonts).
|
Rolle: Du bist ein UI - Designer mit Fokus auf "Modern Minimalist" Design(Stilrichtung: Linear, Teenage Engineering, Vercel).Ziel: Redesign der Whisky - App "DramLog".Wir verabschieden uns vom klassischen "Luxus-Look"(Gold / Serifen) und nutzen einen "Industrial Dark" Stil.
|
||||||
|
|
||||||
Tech Stack: React, Tailwind CSS, Lucide - React Icons.
|
Design Rules & Tokens:
|
||||||
1. Globales Design - System(Anwenden auf alle Views)
|
|
||||||
|
|
||||||
Branding: App Name ist "DramLog".
|
Color Palette(Tailwind):
|
||||||
|
|
||||||
Farben:
|
Bg - App: bg - zinc - 950(Ein sehr dunkles, warmes Grau, kein hartes Schwarz).
|
||||||
|
|
||||||
Background: #0F1014(Deep Rich Black)
|
Bg - Card: bg - zinc - 900(Deutlich abgesetzt vom Hintergrund).
|
||||||
|
|
||||||
Surface: #1A1B20(Card Backgrounds)
|
Text - Primary: text - zinc - 50(Fast Weiß, hoher Kontrast).
|
||||||
|
|
||||||
Primary Accent: #C89D46(Whisky Amber / Gold)
|
Text - Secondary: text - zinc - 400(Mittelgrau für Labels).
|
||||||
|
|
||||||
Text - Secondary: #8F9096
|
Accent: text - orange - 500(Ein sattes, mattes Orange für Highlights) und bg - orange - 600 für Primary Buttons.Keine Verläufe / Gradients, sondern flache("flat") Farben.
|
||||||
|
|
||||||
Typografie:
|
Typography:
|
||||||
|
|
||||||
Importiere und nutze Playfair Display(Serif) für: Überschriften(h1 - h3), Whisky - Namen auf Cards.
|
Nutze Inter oder DM Sans für alles.
|
||||||
|
|
||||||
Nutze Inter(Sans) für: UI - Elemente, Labels, Fließtext, Daten.
|
Headlines: font - bold tracking - tight(Enger Buchstabenabstand, wirkt kompakt und modern).
|
||||||
|
|
||||||
Shape: rounded - 2xl für Cards, rounded - full für Buttons.
|
Labels: uppercase text - xs tracking - widest font - semibold(Technischer Look).
|
||||||
|
|
||||||
2. Spezifische Component Refactorings
|
Component: Whisky Card(Clean Split):
|
||||||
|
|
||||||
A.Dashboard(Home View):
|
Kein Text mehr über dem Bild!
|
||||||
|
|
||||||
Entferne die grauen Hintergründe der 4 oberen Statistik - Boxen.Zeige die Werte(Zahlen) groß in Playfair Display(Weiß) und die Labels klein darunter(Grau).Ordne sie in einem Grid oder einer flex - row mit justify - between an.
|
Top: Bild(Aspect Ratio 4: 3 oder 16: 9), rounded - t - xl.
|
||||||
|
|
||||||
Benenne "Dein Bestand" um in "Collection".
|
Bottom: Info - Block(p - 4 bg - zinc - 900 rounded - b - xl).
|
||||||
|
|
||||||
Mache die Listen - Einträge unter "Tasting Sessions" interaktiv.Entferne die sichtbaren Trash / Edit Buttons und nutze ein sauberes Listen - Layout.
|
Inhalt: Whisky Name(Bold, White), darunter Destillerie(Orange, Small).
|
||||||
|
|
||||||
B.Whisky Card(Collection Grid):
|
Tags: Kleine "Pills" mit bg - zinc - 800 text - zinc - 300.
|
||||||
|
|
||||||
Redesign der Card - Komponente.
|
Component: Dashboard Stats:
|
||||||
|
|
||||||
Bild: Muss bis an den Rand gehen(w - full, kein Padding).
|
Minimalistisch.Nur die Zahl(riesig, z.B.text - 4xl font - bold text - white) und darunter das Label(text - zinc - 500).
|
||||||
|
|
||||||
Overlay: Lege einen bg - gradient - to - t from - black via - black / 80 to - transparent über das untere Drittel des Bildes.
|
Keine Boxen, keine Rahmen. "Data Ink Ratio" optimieren.
|
||||||
|
|
||||||
Text: Platziere Name(Serif) und Destillerie(Sans, Uppercase, Gold) weiß auf dem Bild im unteren Bereich(über dem Gradient).
|
Component: Floating Navigation(The Capsule):
|
||||||
|
|
||||||
Tags: Mache Tags minimalistisch(bg - white / 10 backdrop - blur - sm text - xs border border - white / 10).
|
Statt einer durchgehenden Leiste unten, nutze eine "Floating Capsule".
|
||||||
|
|
||||||
3. Die "Floating" Navigation(Core Feature)
|
Eine abgerundete "Insel"(rounded - full) die ca. 20px über dem unteren Bildschirmrand schwebt.
|
||||||
|
|
||||||
Erstelle eine neue Komponente BottomNavigation.Sie ersetzt alle bisherigen Menüs.Sie muss am unteren Bildschirmrand fixiert sein.WICHTIG: Implementiere exakt dieses Layout - Pattern für den schwebenden Button:
|
Breite: ca. 90 % des Screens oder max - w - md.
|
||||||
|
|
||||||
|
Farbe: bg - zinc - 800 / 90 backdrop - blur - md border border - zinc - 700.
|
||||||
|
|
||||||
|
Scan Button: In der Mitte der Kapsel, Kreis in bg - orange - 600(Flat, kein Schatten), weißes Icon.
|
||||||
|
|
||||||
|
Code - Snippet für die "Industrial Capsule Navigation":
|
||||||
JavaScript
|
JavaScript
|
||||||
|
|
||||||
// Reference Implementation for BottomNavigation.jsx
|
import { Home, Grid, Scan, User } from 'lucide-react';
|
||||||
import { Home, Grid, Scan, User, Search } from 'lucide-react';
|
|
||||||
|
|
||||||
export const BottomNavigation = () => {
|
export const NavigationCapsule = () => {
|
||||||
return (
|
return (
|
||||||
<div className= "fixed bottom-0 left-0 w-full z-50" >
|
<div className= "fixed bottom-6 left-1/2 -translate-x-1/2 w-[90%] max-w-sm z-50" >
|
||||||
{/* Background Container mit Glassmorphism */ }
|
<div className="flex items-center justify-between px-2 py-2 bg-zinc-900/90 backdrop-blur-lg border border-zinc-800 rounded-full shadow-2xl" >
|
||||||
< div className = "relative bg-[#0F1014]/90 backdrop-blur-xl border-t border-white/10 pb-safe pt-2" >
|
|
||||||
|
|
||||||
<div className="flex justify-between items-end px-6 h-16" >
|
{/* Left Item */ }
|
||||||
|
< button className = "p-3 text-zinc-400 hover:text-white transition-colors" >
|
||||||
{/* Left Actions */ }
|
<Home size={ 22 } strokeWidth = { 2.5} />
|
||||||
< button className = "flex flex-col items-center gap-1 text-[#C89D46] w-12" >
|
|
||||||
<Home size={ 24 } />
|
|
||||||
< span className = "text-[10px] font-medium" > Home </span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
< button className = "flex flex-col items-center gap-1 text-gray-500 hover:text-white w-12 transition-colors" >
|
|
||||||
<Grid size={ 24 } />
|
|
||||||
< span className = "text-[10px] font-medium" > Shelf </span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Spacer für den Center Button */ }
|
|
||||||
<div className="w-16" />
|
|
||||||
|
|
||||||
{/* Right Actions */ }
|
|
||||||
< button className = "flex flex-col items-center gap-1 text-gray-500 hover:text-white w-12 transition-colors" >
|
|
||||||
<Search size={ 24 } />
|
|
||||||
< span className = "text-[10px] font-medium" > Search </span>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
< button className = "flex flex-col items-center gap-1 text-gray-500 hover:text-white w-12 transition-colors" >
|
{/* Left Item */ }
|
||||||
<User size={ 24 } />
|
<button className="p-3 text-zinc-400 hover:text-white transition-colors" >
|
||||||
< span className = "text-[10px] font-medium" > Profile </span>
|
<Grid size={ 22 } strokeWidth = { 2.5} />
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* THE FLOATING MAGIC BUTTON */ }
|
|
||||||
<div className="absolute -top-8 left-1/2 -translate-x-1/2" >
|
|
||||||
<button
|
|
||||||
className="flex items-center justify-center w-16 h-16 rounded-full
|
|
||||||
bg - gradient - to - tr from - [#C89D46] to - [#E0B456]
|
|
||||||
shadow - [0_0_20px_rgba(200, 157, 70, 0.4)]
|
|
||||||
border - 4 border - [#0F1014]
|
|
||||||
active: scale - 95 transition - transform duration - 200"
|
|
||||||
aria - label="Scan Bottle"
|
|
||||||
>
|
|
||||||
<Scan color="#0F1014" size = { 32} strokeWidth = { 2} />
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
{/* PRIMARY ACTION - The "Industrial Button" */ }
|
||||||
</div>
|
<button className="flex items-center justify-center w-14 h-14 rounded-full bg-orange-600 text-white hover:bg-orange-500 active:scale-95 transition-all mx-2" >
|
||||||
|
<Scan size={ 26 } strokeWidth = { 2.5} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Right Item */ }
|
||||||
|
<button className="p-3 text-zinc-400 hover:text-white transition-colors" >
|
||||||
|
<User size={ 22 } strokeWidth = { 2.5} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Right Item (Settings/More) */ }
|
||||||
|
<button className="p-3 text-zinc-400 hover:text-white transition-colors" >
|
||||||
|
<div className="w-1 h-1 bg-current rounded-full mb-1" />
|
||||||
|
<div className="w-1 h-1 bg-current rounded-full" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Aufgabe:
|
Aufgabe: Setze die bestehenden Views(Dashboard und Liste) mit diesen neuen "Industrial Dark" Regeln um.Sorge für klare Kontraste durch zinc - 950 vs zinc - 900 Flächen.
|
||||||
|
Was dieser Look ändert:
|
||||||
|
|
||||||
Implementiere das globale Styling(Fonts / Colors).
|
Lesbarkeit: Durch den zinc - 900 Hintergrund der Karten hebt sich der weiße Text extrem gut ab.Kein "Text auf unruhigem Foto" - Problem mehr.
|
||||||
|
|
||||||
Baue die BottomNavigation ein(ersetze alte Navs).
|
Modernität: Das "Pill" - Menü(Capsule) sieht aus wie bei modernen iOS Apps(Dynamic Island Ästhetik).
|
||||||
|
|
||||||
Passe die Dashboard View an den neuen "Clean Look" an.
|
Ehrlichkeit: Es versucht nicht, "altes Geld"(Gold / Serifen) zu imitieren, sondern wirkt wie ein modernes Werkzeug für Enthusiasten.
|
||||||
|
|
||||||
Passe die WhiskyCard an den neuen "Editorial Look" an.
|
|
||||||
@@ -12,10 +12,10 @@ export default function GlobalError({
|
|||||||
return (
|
return (
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<body>
|
<body>
|
||||||
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center">
|
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-black text-center">
|
||||||
<div className="bg-white dark:bg-zinc-900 p-8 rounded-3xl border border-zinc-200 dark:border-zinc-800 shadow-xl max-w-md w-full space-y-6">
|
<div className="bg-zinc-900 p-8 rounded-[32px] border border-zinc-800 shadow-xl max-w-md w-full space-y-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h2 className="text-2xl font-black text-zinc-900 dark:text-white">Kritischer Fehler</h2>
|
<h2 className="text-2xl font-bold text-white uppercase tracking-tighter">Kritischer Fehler</h2>
|
||||||
<p className="text-zinc-500 text-sm">
|
<p className="text-zinc-500 text-sm">
|
||||||
Ein schwerwiegender Fehler ist aufgetreten. Bitte versuche die Seite neu zu laden.
|
Ein schwerwiegender Fehler ist aufgetreten. Bitte versuche die Seite neu zu laden.
|
||||||
</p>
|
</p>
|
||||||
@@ -23,7 +23,7 @@ export default function GlobalError({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => reset()}
|
onClick={() => reset()}
|
||||||
className="w-full py-4 bg-amber-600 hover:bg-amber-700 text-white rounded-xl font-bold flex items-center justify-center gap-2 transition-all"
|
className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl font-bold flex items-center justify-center gap-2 transition-all shadow-lg shadow-orange-950/20"
|
||||||
>
|
>
|
||||||
<RefreshCcw size={18} />
|
<RefreshCcw size={18} />
|
||||||
Erneut versuchen
|
Erneut versuchen
|
||||||
|
|||||||
@@ -4,16 +4,24 @@
|
|||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: #0F1014;
|
--background: #09090b;
|
||||||
--surface: #1A1B20;
|
/* zinc-950 */
|
||||||
--primary: #C89D46;
|
--surface: #18181b;
|
||||||
--text-secondary: #8F9096;
|
/* zinc-900 */
|
||||||
--border: rgba(255, 255, 255, 0.1);
|
--primary: #ea580c;
|
||||||
|
/* orange-600 */
|
||||||
|
--secondary: #f97316;
|
||||||
|
/* orange-500 */
|
||||||
|
--text-primary: #fafafa;
|
||||||
|
--text-secondary: #a1a1aa;
|
||||||
|
--border: #27272a;
|
||||||
|
/* zinc-800 */
|
||||||
|
--ring: #f97316;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-[#0F1014] text-white antialiased;
|
@apply bg-[#09090b] text-[#fafafa] antialiased;
|
||||||
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
|
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,16 +30,17 @@ h2,
|
|||||||
h3,
|
h3,
|
||||||
h4,
|
h4,
|
||||||
.font-display {
|
.font-display {
|
||||||
font-family: var(--font-playfair), serif;
|
font-family: var(--font-inter), system-ui, sans-serif;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
.glass {
|
.glass {
|
||||||
@apply backdrop-blur-md bg-white/5 border border-white/10;
|
@apply backdrop-blur-md bg-zinc-900/50 border border-zinc-800/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.glass-dark {
|
.glass-dark {
|
||||||
@apply backdrop-blur-md bg-black/40 border border-white/5;
|
@apply backdrop-blur-md bg-zinc-950/80 border border-zinc-900/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar-hide::-webkit-scrollbar {
|
.scrollbar-hide::-webkit-scrollbar {
|
||||||
|
|||||||
@@ -12,16 +12,13 @@ import AuthListener from "@/components/AuthListener";
|
|||||||
import SyncHandler from "@/components/SyncHandler";
|
import SyncHandler from "@/components/SyncHandler";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"], variable: '--font-inter' });
|
const inter = Inter({ subsets: ["latin"], variable: '--font-inter' });
|
||||||
const playfair = Playfair_Display({ subsets: ["latin"], variable: '--font-playfair' });
|
|
||||||
|
|
||||||
import { Playfair_Display } from "next/font/google";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
default: "DramLog",
|
default: "DramLog",
|
||||||
template: "%s | DramLog"
|
template: "%s | DramLog"
|
||||||
},
|
},
|
||||||
description: "Premium Digitaler Tasting Begleiter für Genießer.",
|
description: "Modern Whisky Enthusiast Tool",
|
||||||
manifest: "/manifest.webmanifest",
|
manifest: "/manifest.webmanifest",
|
||||||
appleWebApp: {
|
appleWebApp: {
|
||||||
capable: true,
|
capable: true,
|
||||||
@@ -48,7 +45,7 @@ export default function RootLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<body className={`${inter.variable} ${playfair.variable} font-sans`}>
|
<body className={`${inter.variable} font-sans`}>
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<AuthListener />
|
<AuthListener />
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { Loader2 } from 'lucide-react';
|
|||||||
|
|
||||||
export default function Loading() {
|
export default function Loading() {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center">
|
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-black text-center">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<Loader2 size={40} className="animate-spin text-amber-600" />
|
<Loader2 size={40} className="animate-spin text-orange-600" />
|
||||||
<p className="text-zinc-500 font-medium animate-pulse">Whisky Vault wird geladen...</p>
|
<p className="text-zinc-500 font-bold animate-pulse uppercase tracking-widest text-[10px]">Whisky Vault wird geladen...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { Home, MoveLeft } from 'lucide-react';
|
|||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center">
|
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-black text-center">
|
||||||
<div className="bg-white dark:bg-zinc-900 p-8 rounded-3xl border border-zinc-200 dark:border-zinc-800 shadow-xl max-w-md w-full space-y-6">
|
<div className="bg-zinc-900 p-8 rounded-[32px] border border-zinc-800 shadow-xl max-w-md w-full space-y-6">
|
||||||
<div className="text-8xl font-black text-amber-600/20">404</div>
|
<div className="text-8xl font-bold text-orange-600/10 tracking-tighter">404</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h2 className="text-2xl font-black text-zinc-900 dark:text-white">Seite nicht gefunden</h2>
|
<h2 className="text-2xl font-bold text-white uppercase tracking-tighter">Seite nicht gefunden</h2>
|
||||||
<p className="text-zinc-500 text-sm">
|
<p className="text-zinc-500 text-sm">
|
||||||
Die gesuchte Seite existiert leider nicht oder wurde verschoben.
|
Die gesuchte Seite existiert leider nicht oder wurde verschoben.
|
||||||
</p>
|
</p>
|
||||||
@@ -17,7 +17,7 @@ export default function NotFound() {
|
|||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className="w-full py-4 bg-amber-600 hover:bg-amber-700 text-white rounded-xl font-bold flex items-center justify-center gap-2 transition-all shadow-lg shadow-amber-600/20"
|
className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl font-bold flex items-center justify-center gap-2 transition-all shadow-lg shadow-orange-950/20"
|
||||||
>
|
>
|
||||||
<Home size={18} />
|
<Home size={18} />
|
||||||
Zurück zum Vault
|
Zurück zum Vault
|
||||||
|
|||||||
@@ -151,13 +151,13 @@ export default function Home() {
|
|||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen flex-col items-center justify-center p-6 bg-[#0F1014]">
|
<main className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-950">
|
||||||
<div className="mb-12 text-center animate-in fade-in zoom-in duration-1000">
|
<div className="mb-12 text-center animate-in fade-in zoom-in duration-1000">
|
||||||
<h1 className="text-6xl font-display font-bold text-white tracking-tighter mb-4">
|
<h1 className="text-6xl font-bold text-zinc-50 tracking-tighter mb-4">
|
||||||
DRAM<span className="text-[#C89D46]">LOG</span>
|
DRAM<span className="text-orange-600">LOG</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-[#8F9096] max-w-sm mx-auto font-sans tracking-wide">
|
<p className="text-zinc-500 max-w-sm mx-auto font-bold tracking-wide">
|
||||||
Premium Digitaler Tasting Begleiter für Genießer.
|
Modern Minimalist Tasting Tool.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
@@ -169,20 +169,20 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen flex-col items-center gap-6 md:gap-12 p-4 md:p-24 bg-[#0F1014] pb-32">
|
<main className="flex min-h-screen flex-col items-center gap-6 md:gap-12 p-4 md:p-24 bg-zinc-950 pb-32">
|
||||||
<div className="z-10 max-w-5xl w-full flex flex-col items-center gap-12">
|
<div className="z-10 max-w-5xl w-full flex flex-col items-center gap-12">
|
||||||
<header className="w-full flex flex-col sm:flex-row justify-between items-center gap-4 sm:gap-0">
|
<header className="w-full flex flex-col sm:flex-row justify-between items-center gap-4 sm:gap-0">
|
||||||
<div className="flex flex-col items-center sm:items-start group">
|
<div className="flex flex-col items-center sm:items-start group">
|
||||||
<h1 className="text-4xl font-display font-bold text-white tracking-tighter">
|
<h1 className="text-4xl font-bold text-zinc-50 tracking-tighter">
|
||||||
DRAM<span className="text-[#C89D46]">LOG</span>
|
DRAM<span className="text-orange-600">LOG</span>
|
||||||
</h1>
|
</h1>
|
||||||
{activeSession && (
|
{activeSession && (
|
||||||
<div className="flex items-center gap-2 mt-1 animate-in fade-in slide-in-from-left-2 duration-700">
|
<div className="flex items-center gap-2 mt-1 animate-in fade-in slide-in-from-left-2 duration-700">
|
||||||
<div className="relative flex h-2 w-2">
|
<div className="relative flex h-2 w-2">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#C89D46] opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-orange-600 opacity-75"></span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-[#C89D46]"></span>
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-orange-600"></span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[9px] font-sans font-bold uppercase tracking-widest text-[#C89D46] flex items-center gap-1">
|
<span className="text-[9px] font-bold uppercase tracking-widest text-orange-600 flex items-center gap-1">
|
||||||
<Sparkles size={10} className="animate-pulse" />
|
<Sparkles size={10} className="animate-pulse" />
|
||||||
Live: {activeSession.name}
|
Live: {activeSession.name}
|
||||||
</span>
|
</span>
|
||||||
@@ -195,7 +195,7 @@ export default function Home() {
|
|||||||
<DramOfTheDay bottles={bottles} />
|
<DramOfTheDay bottles={bottles} />
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className="text-xs font-sans font-bold uppercase tracking-widest text-[#8F9096] hover:text-white transition-colors"
|
className="text-[10px] font-bold uppercase tracking-widest text-zinc-500 hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
{t('home.logout')}
|
{t('home.logout')}
|
||||||
</button>
|
</button>
|
||||||
@@ -206,7 +206,7 @@ export default function Home() {
|
|||||||
<StatsDashboard bottles={bottles} />
|
<StatsDashboard bottles={bottles} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 w-full max-w-5xl">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 w-full max-w-5xl">
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8">
|
||||||
<SessionList />
|
<SessionList />
|
||||||
</div>
|
</div>
|
||||||
@@ -215,27 +215,27 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full mt-4">
|
<div className="w-full mt-4" id="collection">
|
||||||
<div className="flex items-end justify-between mb-8">
|
<div className="flex items-end justify-between mb-8">
|
||||||
<h2 className="text-3xl font-display font-bold text-white">
|
<h2 className="text-3xl font-bold text-zinc-50 uppercase tracking-tight">
|
||||||
Collection
|
Collection
|
||||||
</h2>
|
</h2>
|
||||||
<span className="text-xs font-sans font-bold text-[#8F9096] uppercase tracking-widest pb-1">
|
<span className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest pb-1">
|
||||||
{bottles.length} Bottles
|
{bottles.length} Bottles
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex justify-center py-20">
|
<div className="flex justify-center py-20">
|
||||||
<div className="animate-spin rounded-full h-10 w-10 border-b-2 border-[#C89D46]"></div>
|
<div className="animate-spin rounded-full h-10 w-10 border-b-2 border-orange-600"></div>
|
||||||
</div>
|
</div>
|
||||||
) : fetchError ? (
|
) : fetchError ? (
|
||||||
<div className="p-12 bg-[#1A1B20] border border-white/10 rounded-3xl text-center">
|
<div className="p-12 bg-zinc-900 border border-zinc-800 rounded-3xl text-center">
|
||||||
<p className="text-white font-display text-xl mb-4">{t('common.error')}</p>
|
<p className="text-zinc-50 font-bold text-xl mb-2">{t('common.error')}</p>
|
||||||
<p className="text-[#8F9096] text-sm italic mb-8 mx-auto max-w-xs">{fetchError}</p>
|
<p className="text-zinc-500 text-xs italic mb-8 mx-auto max-w-xs">{fetchError}</p>
|
||||||
<button
|
<button
|
||||||
onClick={fetchCollection}
|
onClick={fetchCollection}
|
||||||
className="px-10 py-4 bg-[#C89D46] hover:bg-[#E0B456] text-[#0F1014] rounded-full text-xs font-sans font-bold uppercase tracking-widest transition-all"
|
className="px-10 py-4 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl text-[10px] font-bold uppercase tracking-widest transition-all shadow-lg shadow-orange-950/20"
|
||||||
>
|
>
|
||||||
{t('home.reTry')}
|
{t('home.reTry')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function ActiveSessionBanner() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-0 left-0 right-0 z-[100] animate-in slide-in-from-top duration-500">
|
<div className="fixed top-0 left-0 right-0 z-[100] animate-in slide-in-from-top duration-500">
|
||||||
<div className="bg-amber-600 text-white px-4 py-2 flex items-center justify-between shadow-lg">
|
<div className="bg-orange-600 text-white px-4 py-2 flex items-center justify-between shadow-lg">
|
||||||
<Link
|
<Link
|
||||||
href={`/sessions/${activeSession.id}`}
|
href={`/sessions/${activeSession.id}`}
|
||||||
className="flex items-center gap-3 flex-1 min-w-0"
|
className="flex items-center gap-3 flex-1 min-w-0"
|
||||||
@@ -23,7 +23,7 @@ export default function ActiveSessionBanner() {
|
|||||||
<div className="bg-white/20 p-1.5 rounded-lg">
|
<div className="bg-white/20 p-1.5 rounded-lg">
|
||||||
<Sparkles size={16} className="text-white animate-pulse" />
|
<Sparkles size={16} className="text-white animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute -top-1 -right-1 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-amber-600 animate-ping" />
|
<div className="absolute -top-1 -right-1 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-orange-600 animate-ping" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="flex items-center gap-2 mb-0.5">
|
<div className="flex items-center gap-2 mb-0.5">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default function AvatarStack({ names, limit = 3, size = 'sm' }: AvatarSta
|
|||||||
{visibleNames.map((name, i) => (
|
{visibleNames.map((name, i) => (
|
||||||
<div
|
<div
|
||||||
key={`${name}-${i}`}
|
key={`${name}-${i}`}
|
||||||
className={`${sizeClasses} rounded-full bg-amber-100 dark:bg-amber-900/30 border-2 border-white dark:border-zinc-900 flex items-center justify-center text-amber-700 dark:text-amber-400 font-black ring-1 ring-amber-500/10 shadow-sm relative group`}
|
className={`${sizeClasses} rounded-full bg-orange-900/30 border-2 border-zinc-950 flex items-center justify-center text-orange-400 font-bold ring-1 ring-orange-500/10 shadow-sm relative group`}
|
||||||
title={name}
|
title={name}
|
||||||
>
|
>
|
||||||
{getInitials(name)}
|
{getInitials(name)}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-[60vh] flex flex-col items-center justify-center gap-4">
|
<div className="min-h-[60vh] flex flex-col items-center justify-center gap-4">
|
||||||
<Loader2 size={48} className="animate-spin text-amber-600" />
|
<Loader2 size={48} className="animate-spin text-orange-600" />
|
||||||
<p className="text-zinc-500 font-bold animate-pulse uppercase tracking-widest text-xs">{t('common.loading')}</p>
|
<p className="text-zinc-500 font-bold animate-pulse uppercase tracking-widest text-xs">{t('common.loading')}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -42,7 +42,7 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
|
|||||||
Inhalte konnten nicht geladen werden. Bitte stelle eine Internetverbindung her, um diese Flasche zum ersten Mal zu laden.
|
Inhalte konnten nicht geladen werden. Bitte stelle eine Internetverbindung her, um diese Flasche zum ersten Mal zu laden.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/" className="px-6 py-3 bg-amber-600 text-white rounded-2xl text-sm font-black uppercase tracking-widest shadow-xl shadow-amber-600/20">
|
<Link href="/" className="px-6 py-3 bg-orange-600 text-white rounded-2xl text-sm font-black uppercase tracking-widest shadow-xl shadow-orange-950/20">
|
||||||
Zurück zum Vault
|
Zurück zum Vault
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,22 +56,22 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
|
|||||||
{/* Back Button */}
|
{/* Back Button */}
|
||||||
<Link
|
<Link
|
||||||
href={`/${sessionId ? `?session_id=${sessionId}` : ''}`}
|
href={`/${sessionId ? `?session_id=${sessionId}` : ''}`}
|
||||||
className="inline-flex items-center gap-2 text-zinc-500 hover:text-amber-600 transition-colors font-medium mb-4"
|
className="inline-flex items-center gap-2 text-zinc-500 hover:text-orange-600 transition-colors font-bold mb-4"
|
||||||
>
|
>
|
||||||
<ChevronLeft size={20} />
|
<ChevronLeft size={20} />
|
||||||
Zurück zur Sammlung
|
Zurück zur Sammlung
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{isOffline && (
|
{isOffline && (
|
||||||
<div className="bg-amber-600/10 border border-amber-600/20 p-3 rounded-2xl flex items-center gap-3 animate-in fade-in slide-in-from-top-2">
|
<div className="bg-orange-600/10 border border-orange-600/20 p-3 rounded-2xl flex items-center gap-3 animate-in fade-in slide-in-from-top-2">
|
||||||
<WifiOff size={16} className="text-amber-600" />
|
<WifiOff size={16} className="text-orange-600" />
|
||||||
<p className="text-[10px] font-black uppercase tracking-widest text-amber-700">Offline-Modus: Daten aus dem Cache</p>
|
<p className="text-[10px] font-bold uppercase tracking-widest text-orange-500">Offline-Modus: Daten aus dem Cache</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="grid grid-cols-1 md:grid-cols-2 gap-8 items-start">
|
<section className="grid grid-cols-1 md:grid-cols-2 gap-8 items-start">
|
||||||
<div className="aspect-[4/5] rounded-3xl overflow-hidden shadow-2xl border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900">
|
<div className="aspect-[4/5] rounded-3xl overflow-hidden shadow-2xl border border-zinc-800 bg-zinc-900">
|
||||||
<img
|
<img
|
||||||
src={getStorageUrl(bottle.image_url)}
|
src={getStorageUrl(bottle.image_url)}
|
||||||
alt={bottle.name}
|
alt={bottle.name}
|
||||||
@@ -81,10 +81,10 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
|
|||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl md:text-4xl font-black text-zinc-900 dark:text-white tracking-tighter leading-tight">
|
<h1 className="text-2xl md:text-4xl font-bold text-zinc-50 tracking-tighter leading-tight uppercase">
|
||||||
{bottle.name}
|
{bottle.name}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm md:text-xl text-amber-600 font-bold mt-1 uppercase tracking-widest">{bottle.distillery}</p>
|
<p className="text-sm md:text-xl text-orange-600 font-bold mt-1 uppercase tracking-widest">{bottle.distillery}</p>
|
||||||
|
|
||||||
{bottle.whiskybase_id && (
|
{bottle.whiskybase_id && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
@@ -92,9 +92,9 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
|
|||||||
href={`https://www.whiskybase.com/whiskies/whisky/${bottle.whiskybase_id}`}
|
href={`https://www.whiskybase.com/whiskies/whisky/${bottle.whiskybase_id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex items-center gap-2 px-4 py-2 bg-[#db0000] text-white rounded-xl text-sm font-bold shadow-lg shadow-red-600/20 hover:scale-[1.05] transition-transform"
|
className="inline-flex items-center gap-2 px-4 py-2 bg-zinc-900 text-zinc-400 border border-zinc-800 rounded-xl text-xs font-bold hover:text-orange-600 transition-colors"
|
||||||
>
|
>
|
||||||
<ExternalLink size={16} />
|
<ExternalLink size={14} />
|
||||||
Whiskybase ID: {bottle.whiskybase_id}
|
Whiskybase ID: {bottle.whiskybase_id}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,59 +102,59 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||||
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm flex flex-col justify-between">
|
<div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm flex flex-col justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1">
|
<div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
|
||||||
<Tag size={12} /> Kategorie
|
<Tag size={12} /> Kategorie
|
||||||
</div>
|
</div>
|
||||||
<div className="font-bold text-sm dark:text-zinc-200">{bottle.category || '-'}</div>
|
<div className="font-bold text-sm text-zinc-200">{bottle.category || '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm">
|
<div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm">
|
||||||
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1">
|
<div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
|
||||||
<Droplets size={12} /> Alkoholgehalt
|
<Droplets size={12} /> Alkoholgehalt
|
||||||
</div>
|
</div>
|
||||||
<div className="font-bold text-sm dark:text-zinc-200">{bottle.abv}% Vol.</div>
|
<div className="font-bold text-sm text-zinc-200">{bottle.abv}% Vol.</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm">
|
<div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm">
|
||||||
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1">
|
<div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
|
||||||
<Award size={12} /> Alter
|
<Award size={12} /> Alter
|
||||||
</div>
|
</div>
|
||||||
<div className="font-bold text-sm dark:text-zinc-200">{bottle.age ? `${bottle.age} J.` : '-'}</div>
|
<div className="font-bold text-sm text-zinc-200">{bottle.age ? `${bottle.age} J.` : '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{bottle.distilled_at && (
|
{bottle.distilled_at && (
|
||||||
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm">
|
<div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm">
|
||||||
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1">
|
<div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
|
||||||
<Calendar size={12} /> Destilliert
|
<Calendar size={12} /> Destilliert
|
||||||
</div>
|
</div>
|
||||||
<div className="font-bold text-sm dark:text-zinc-200">{bottle.distilled_at}</div>
|
<div className="font-bold text-sm text-zinc-200">{bottle.distilled_at}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{bottle.bottled_at && (
|
{bottle.bottled_at && (
|
||||||
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm">
|
<div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm">
|
||||||
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1">
|
<div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
|
||||||
<Package size={12} /> Abgefüllt
|
<Package size={12} /> Abgefüllt
|
||||||
</div>
|
</div>
|
||||||
<div className="font-bold text-sm dark:text-zinc-200">{bottle.bottled_at}</div>
|
<div className="font-bold text-sm text-zinc-200">{bottle.bottled_at}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{bottle.batch_info && (
|
{bottle.batch_info && (
|
||||||
<div className="p-4 bg-zinc-50 dark:bg-zinc-800/30 rounded-2xl border border-dashed border-zinc-200 dark:border-zinc-700/50 md:col-span-1">
|
<div className="p-4 bg-zinc-800/30 rounded-2xl border border-dashed border-zinc-700/50 md:col-span-1">
|
||||||
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1">
|
<div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
|
||||||
<Info size={12} /> Batch / Code
|
<Info size={12} /> Batch / Code
|
||||||
</div>
|
</div>
|
||||||
<div className="font-mono text-xs dark:text-zinc-300 truncate" title={bottle.batch_info}>{bottle.batch_info}</div>
|
<div className="font-mono text-xs text-zinc-300 truncate" title={bottle.batch_info}>{bottle.batch_info}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm">
|
<div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm">
|
||||||
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1">
|
<div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
|
||||||
<Calendar size={12} /> Letzter Dram
|
<Calendar size={12} /> Letzter Dram
|
||||||
</div>
|
</div>
|
||||||
<div className="font-bold text-sm dark:text-zinc-200">
|
<div className="font-bold text-sm text-zinc-200">
|
||||||
{tastings && tastings.length > 0
|
{tastings && tastings.length > 0
|
||||||
? new Date(tastings[0].created_at).toLocaleDateString('de-DE')
|
? new Date(tastings[0].created_at).toLocaleDateString('de-DE')
|
||||||
: 'Noch nie'}
|
: 'Noch nie'}
|
||||||
@@ -164,9 +164,9 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
|
|||||||
|
|
||||||
<div className="pt-2 flex flex-wrap gap-4">
|
<div className="pt-2 flex flex-wrap gap-4">
|
||||||
{isOffline ? (
|
{isOffline ? (
|
||||||
<div className="w-full p-4 bg-zinc-100 dark:bg-zinc-800/50 border border-dashed border-zinc-200 dark:border-zinc-700 rounded-2xl flex items-center justify-center gap-2">
|
<div className="w-full p-4 bg-zinc-900 border border-dashed border-zinc-800 rounded-2xl flex items-center justify-center gap-2">
|
||||||
<Info size={14} className="text-zinc-400" />
|
<Info size={14} className="text-zinc-500" />
|
||||||
<span className="text-[10px] font-black uppercase text-zinc-400 tracking-widest">Bearbeiten & Löschen nur online möglich</span>
|
<span className="text-[10px] font-bold uppercase text-zinc-500 tracking-widest">Bearbeiten & Löschen nur online möglich</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -178,21 +178,21 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<hr className="border-zinc-200 dark:border-zinc-800" />
|
<hr className="border-zinc-800" />
|
||||||
|
|
||||||
{/* Tasting Notes Section */}
|
{/* Tasting Notes Section */}
|
||||||
<section className="space-y-8">
|
<section className="space-y-8">
|
||||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4">
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-3xl font-black text-zinc-900 dark:text-white tracking-tight">Tasting Notes</h2>
|
<h2 className="text-3xl font-bold text-zinc-50 tracking-tight uppercase">Tasting Notes</h2>
|
||||||
<p className="text-zinc-500 mt-1">Hier findest du deine bisherigen Eindrücke.</p>
|
<p className="text-zinc-500 mt-1">Hier findest du deine bisherigen Eindrücke.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-8 items-start">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-8 items-start">
|
||||||
{/* Form */}
|
{/* Form */}
|
||||||
<div className="lg:col-span-1 border border-zinc-200 dark:border-zinc-800 rounded-3xl p-6 bg-white dark:bg-zinc-900/50 md:sticky md:top-24">
|
<div className="lg:col-span-1 border border-zinc-800 rounded-3xl p-6 bg-zinc-900/50 md:sticky md:top-24">
|
||||||
<h3 className="text-lg font-bold mb-6 flex items-center gap-2 text-amber-600">
|
<h3 className="text-lg font-bold mb-6 flex items-center gap-2 text-orange-600 uppercase tracking-widest">
|
||||||
<Droplets size={20} /> Dram bewerten
|
<Droplets size={20} /> Dram bewerten
|
||||||
</h3>
|
</h3>
|
||||||
<TastingNoteForm bottleId={bottle.id} sessionId={sessionId} />
|
<TastingNoteForm bottleId={bottle.id} sessionId={sessionId} />
|
||||||
|
|||||||
@@ -36,71 +36,65 @@ function BottleCard({ bottle, sessionId }: BottleCardProps) {
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={`/bottles/${bottle.id}${sessionId ? `?session_id=${sessionId}` : ''}`}
|
href={`/bottles/${bottle.id}${sessionId ? `?session_id=${sessionId}` : ''}`}
|
||||||
className="block h-[420px] group relative overflow-hidden rounded-2xl border border-white/5 transition-all duration-700 hover:shadow-[0_20px_50px_rgba(0,0,0,0.5)] active:scale-[0.98]"
|
className="block h-fit group relative overflow-hidden rounded-xl bg-zinc-900 border border-zinc-800 transition-all duration-300 hover:border-zinc-700 active:scale-[0.98]"
|
||||||
>
|
>
|
||||||
{/* Image Layer - Edge to Edge */}
|
{/* Image Layer - Clean Split Top */}
|
||||||
<div className="absolute inset-0">
|
<div className="aspect-[4/3] overflow-hidden">
|
||||||
<img
|
<img
|
||||||
src={getStorageUrl(bottle.image_url)}
|
src={getStorageUrl(bottle.image_url)}
|
||||||
alt={bottle.name}
|
alt={bottle.name}
|
||||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-1000 ease-out"
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500 ease-out"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Gradient Overlay as requested: bottom third, black to transparent */}
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/60 to-transparent" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Layer */}
|
{/* Info Layer - Clean Split Bottom */}
|
||||||
<div className="absolute inset-0 flex flex-col justify-end p-6">
|
<div className="p-4 space-y-4">
|
||||||
<div className="space-y-3">
|
<div className="space-y-1">
|
||||||
{/* Tags Layer - Minimalist Glassmorphism */}
|
<p className="text-[10px] font-bold text-orange-500 uppercase tracking-widest leading-none">
|
||||||
<div className="flex flex-wrap gap-2 opacity-0 group-hover:opacity-100 translate-y-4 group-hover:translate-y-0 transition-all duration-500">
|
{bottle.distillery}
|
||||||
<span className="px-3 py-1 bg-white/10 backdrop-blur-md border border-white/10 text-[9px] font-sans font-bold uppercase tracking-widest text-[#C89D46] rounded-full">
|
</p>
|
||||||
{shortenCategory(bottle.category)}
|
<h3 className="font-bold text-lg text-zinc-50 leading-tight">
|
||||||
</span>
|
{bottle.name || t('grid.unknownBottle')}
|
||||||
<span className="px-3 py-1 bg-white/10 backdrop-blur-md border border-white/10 text-[9px] font-sans font-bold uppercase tracking-widest text-white/60 rounded-full">
|
</h3>
|
||||||
{bottle.abv}% VOL
|
</div>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div className="flex flex-wrap gap-2">
|
||||||
<p className="text-[10px] font-sans font-bold text-[#C89D46] uppercase tracking-[0.2em] mb-1">
|
<span className="px-2 py-0.5 bg-zinc-800 text-zinc-400 text-[9px] font-bold uppercase tracking-widest rounded-md">
|
||||||
{bottle.distillery}
|
{shortenCategory(bottle.category)}
|
||||||
</p>
|
</span>
|
||||||
<h3 className="font-display font-bold text-2xl text-white leading-tight drop-shadow-lg">
|
<span className="px-2 py-0.5 bg-zinc-800 text-zinc-400 text-[9px] font-bold uppercase tracking-widest rounded-md">
|
||||||
{bottle.name || t('grid.unknownBottle')}
|
{bottle.abv}% VOL
|
||||||
</h3>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Metadata items */}
|
{/* Metadata items */}
|
||||||
<div className="flex items-center gap-4 pt-2 border-t border-white/10 opacity-60 group-hover:opacity-100 transition-opacity">
|
<div className="flex items-center gap-4 pt-3 border-t border-zinc-800/50">
|
||||||
<div className="flex items-center gap-1.5 text-[10px] font-sans font-medium text-white/70">
|
<div className="flex items-center gap-1 text-[10px] font-medium text-zinc-500">
|
||||||
<Calendar size={12} className="text-[#C89D46]" />
|
<Calendar size={12} className="text-zinc-500" />
|
||||||
{new Date(bottle.created_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')}
|
{new Date(bottle.created_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')}
|
||||||
|
</div>
|
||||||
|
{bottle.last_tasted && (
|
||||||
|
<div className="flex items-center gap-1 text-[10px] font-medium text-zinc-500">
|
||||||
|
<Clock size={12} className="text-zinc-500" />
|
||||||
|
{new Date(bottle.last_tasted).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')}
|
||||||
</div>
|
</div>
|
||||||
{bottle.last_tasted && (
|
)}
|
||||||
<div className="flex items-center gap-1.5 text-[10px] font-sans font-medium text-white/70">
|
|
||||||
<Clock size={12} className="text-[#C89D46]" />
|
|
||||||
{new Date(bottle.last_tasted).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top Overlays */}
|
{/* Top Overlays */}
|
||||||
{(bottle.is_whisky === false || (bottle.confidence && bottle.confidence < 70)) && (
|
{(bottle.is_whisky === false || (bottle.confidence && bottle.confidence < 70)) && (
|
||||||
<div className="absolute top-4 right-4 z-10">
|
<div className="absolute top-3 right-3 z-10">
|
||||||
<div className="bg-red-500/90 backdrop-blur-sm text-white p-2 rounded-full animate-pulse shadow-lg">
|
<div className="bg-red-500 text-white p-1.5 rounded-full shadow-lg">
|
||||||
<AlertCircle size={14} />
|
<AlertCircle size={12} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{sessionId && (
|
{sessionId && (
|
||||||
<div className="absolute top-4 left-4 z-10 bg-[#C89D46] text-[#0F1014] text-[9px] font-black px-3 py-1.5 rounded-full flex items-center gap-2 border border-white/20 shadow-xl">
|
<div className="absolute top-3 left-3 z-10 bg-orange-600 text-white text-[9px] font-bold px-2 py-1 rounded-md flex items-center gap-1.5 shadow-xl">
|
||||||
<PlusCircle size={14} />
|
<PlusCircle size={12} />
|
||||||
ADD TO SESSION
|
ADD
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -200,13 +194,13 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
|
|||||||
<div id="search-filter" className="w-full max-w-6xl mx-auto px-4 space-y-6 scroll-mt-32">
|
<div id="search-filter" className="w-full max-w-6xl mx-auto px-4 space-y-6 scroll-mt-32">
|
||||||
<div className="flex flex-col md:flex-row gap-4">
|
<div className="flex flex-col md:flex-row gap-4">
|
||||||
<div className="relative flex-1 group">
|
<div className="relative flex-1 group">
|
||||||
<Search className="absolute left-0 top-1/2 -translate-y-1/2 text-[#8F9096] group-focus-within:text-[#C89D46] transition-colors" size={20} />
|
<Search className="absolute left-0 top-1/2 -translate-y-1/2 text-zinc-500 group-focus-within:text-orange-500 transition-colors" size={20} />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={t('grid.searchPlaceholder')}
|
placeholder={t('grid.searchPlaceholder')}
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="w-full pl-8 pr-8 py-4 bg-transparent border-b border-white/10 focus:border-[#C89D46] outline-none transition-all text-white placeholder:text-[#8F9096] font-sans"
|
className="w-full pl-8 pr-8 py-4 bg-transparent border-b border-zinc-800 focus:border-orange-500 outline-none transition-all text-zinc-50 placeholder:text-zinc-500"
|
||||||
/>
|
/>
|
||||||
{searchQuery && (
|
{searchQuery && (
|
||||||
<button
|
<button
|
||||||
@@ -222,24 +216,24 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
|
|||||||
<select
|
<select
|
||||||
value={sortBy}
|
value={sortBy}
|
||||||
onChange={(e) => setSortBy(e.target.value as any)}
|
onChange={(e) => setSortBy(e.target.value as any)}
|
||||||
className="bg-transparent border-none text-[#8F9096] text-xs font-sans font-bold uppercase tracking-widest outline-none cursor-pointer hover:text-white transition-colors appearance-none"
|
className="bg-transparent border-none text-zinc-500 text-xs font-bold uppercase tracking-widest outline-none cursor-pointer hover:text-white transition-colors appearance-none"
|
||||||
>
|
>
|
||||||
<option value="created_at" className="bg-[#0F1014]">{t('grid.sortBy.createdAt')}</option>
|
<option value="created_at" className="bg-zinc-950">{t('grid.sortBy.createdAt')}</option>
|
||||||
<option value="last_tasted" className="bg-[#0F1014]">{t('grid.sortBy.lastTasted')}</option>
|
<option value="last_tasted" className="bg-zinc-950">{t('grid.sortBy.lastTasted')}</option>
|
||||||
<option value="name" className="bg-[#0F1014]">{t('grid.sortBy.name')}</option>
|
<option value="name" className="bg-zinc-950">{t('grid.sortBy.name')}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsFiltersOpen(!isFiltersOpen)}
|
onClick={() => setIsFiltersOpen(!isFiltersOpen)}
|
||||||
className={`flex items-center gap-2 text-xs font-sans font-bold uppercase tracking-widest transition-all ${isFiltersOpen || activeFiltersCount > 0
|
className={`flex items-center gap-2 text-xs font-bold uppercase tracking-widest transition-all ${isFiltersOpen || activeFiltersCount > 0
|
||||||
? 'text-[#C89D46]'
|
? 'text-orange-500'
|
||||||
: 'text-[#8F9096] hover:text-white'
|
: 'text-zinc-500 hover:text-white'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Filter size={18} />
|
<Filter size={18} />
|
||||||
<span className="hidden sm:inline">{t('grid.filters')}</span>
|
<span className="hidden sm:inline">{t('grid.filters')}</span>
|
||||||
{activeFiltersCount > 0 && (
|
{activeFiltersCount > 0 && (
|
||||||
<span className="bg-[#C89D46] text-[#0F1014] w-4 h-4 rounded-full flex items-center justify-center text-[8px] font-black">
|
<span className="bg-orange-600 text-white w-4 h-4 rounded-full flex items-center justify-center text-[8px] font-bold">
|
||||||
{activeFiltersCount}
|
{activeFiltersCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -247,13 +241,13 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Category Quick Filter - Glass Chips */}
|
{/* Category Quick Filter - Flat Chips */}
|
||||||
<div className="flex gap-3 overflow-x-auto pb-2 -mx-4 px-4 scrollbar-hide touch-pan-x">
|
<div className="flex gap-3 overflow-x-auto pb-2 -mx-4 px-4 scrollbar-hide touch-pan-x">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedCategory(null)}
|
onClick={() => setSelectedCategory(null)}
|
||||||
className={`px-4 py-2 rounded-full text-[10px] font-sans font-bold uppercase tracking-widest whitespace-nowrap transition-all border ${selectedCategory === null
|
className={`px-4 py-2 rounded-full text-[10px] font-bold uppercase tracking-widest whitespace-nowrap transition-all border ${selectedCategory === null
|
||||||
? 'bg-[#C89D46] border-[#C89D46] text-[#0F1014]'
|
? 'bg-orange-600 border-orange-600 text-white'
|
||||||
: 'bg-white/5 border-white/10 text-[#8F9096] hover:border-white/20'
|
: 'bg-zinc-900 border-zinc-800 text-zinc-400 hover:border-zinc-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t('common.all')}
|
{t('common.all')}
|
||||||
@@ -262,9 +256,9 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
|
|||||||
<button
|
<button
|
||||||
key={cat}
|
key={cat}
|
||||||
onClick={() => setSelectedCategory(selectedCategory === cat ? null : cat)}
|
onClick={() => setSelectedCategory(selectedCategory === cat ? null : cat)}
|
||||||
className={`px-4 py-2 rounded-full text-[10px] font-sans font-bold uppercase tracking-widest whitespace-nowrap transition-all border ${selectedCategory === cat
|
className={`px-4 py-2 rounded-full text-[10px] font-bold uppercase tracking-widest whitespace-nowrap transition-all border ${selectedCategory === cat
|
||||||
? 'bg-[#C89D46] border-[#C89D46] text-[#0F1014]'
|
? 'bg-orange-600 border-orange-600 text-white'
|
||||||
: 'bg-white/5 border-white/10 text-[#8F9096] hover:border-white/20'
|
: 'bg-zinc-900 border-zinc-800 text-zinc-400 hover:border-zinc-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{shortenCategory(cat)}
|
{shortenCategory(cat)}
|
||||||
@@ -272,17 +266,17 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Collapsible Advanced Filters - Minimalist Overlay */}
|
{/* Collapsible Advanced Filters - Industrial Overlay */}
|
||||||
{isFiltersOpen && (
|
{isFiltersOpen && (
|
||||||
<div className="p-8 bg-[#1A1B20] border border-white/10 rounded-3xl space-y-8 animate-in fade-in slide-in-from-top-4 duration-500">
|
<div className="p-8 bg-zinc-900 border border-zinc-800 rounded-2xl space-y-8 animate-in fade-in slide-in-from-top-4 duration-500">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<label className="text-[10px] font-sans font-bold uppercase tracking-[0.2em] text-[#8F9096]">{t('grid.filter.distillery')}</label>
|
<label className="text-[10px] font-bold uppercase tracking-[0.2em] text-zinc-500">{t('grid.filter.distillery')}</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedDistillery(null)}
|
onClick={() => setSelectedDistillery(null)}
|
||||||
className={`px-4 py-2 rounded-full text-[10px] font-sans font-bold uppercase tracking-widest transition-all border ${selectedDistillery === null
|
className={`px-4 py-2 rounded-full text-[10px] font-bold uppercase tracking-widest transition-all border ${selectedDistillery === null
|
||||||
? 'bg-[#C89D46] border-[#C89D46] text-[#0F1014]'
|
? 'bg-orange-600 border-orange-600 text-white'
|
||||||
: 'bg-white/5 border-white/10 text-[#8F9096]'
|
: 'bg-zinc-800 border-zinc-700 text-zinc-400'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t('common.all')}
|
{t('common.all')}
|
||||||
@@ -291,9 +285,9 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
|
|||||||
<button
|
<button
|
||||||
key={dist}
|
key={dist}
|
||||||
onClick={() => setSelectedDistillery(selectedDistillery === dist ? null : dist)}
|
onClick={() => setSelectedDistillery(selectedDistillery === dist ? null : dist)}
|
||||||
className={`px-4 py-2 rounded-full text-[10px] font-sans font-bold uppercase tracking-widest transition-all border ${selectedDistillery === dist
|
className={`px-4 py-2 rounded-full text-[10px] font-bold uppercase tracking-widest transition-all border ${selectedDistillery === dist
|
||||||
? 'bg-[#C89D46] border-[#C89D46] text-[#0F1014]'
|
? 'bg-orange-600 border-orange-600 text-white'
|
||||||
: 'bg-white/5 border-white/10 text-[#8F9096]'
|
: 'bg-zinc-800 border-zinc-700 text-zinc-400'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{dist}
|
{dist}
|
||||||
@@ -302,20 +296,20 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-6 border-t border-white/5 flex justify-between items-center">
|
<div className="pt-6 border-t border-zinc-800 flex justify-between items-center">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedCategory(null);
|
setSelectedCategory(null);
|
||||||
setSelectedDistillery(null);
|
setSelectedDistillery(null);
|
||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
}}
|
}}
|
||||||
className="text-[10px] font-sans font-bold text-red-500 uppercase tracking-widest hover:text-red-400"
|
className="text-[10px] font-bold text-red-500 uppercase tracking-widest hover:text-red-400"
|
||||||
>
|
>
|
||||||
{t('grid.resetAll')}
|
{t('grid.resetAll')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsFiltersOpen(false)}
|
onClick={() => setIsFiltersOpen(false)}
|
||||||
className="px-8 py-3 bg-white text-[#0F1014] text-[10px] font-sans font-bold rounded-full uppercase tracking-widest transition-transform active:scale-95"
|
className="px-8 py-3 bg-zinc-50 text-zinc-950 text-[10px] font-bold rounded-full uppercase tracking-widest transition-transform active:scale-95"
|
||||||
>
|
>
|
||||||
{t('grid.close')}
|
{t('grid.close')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const BottomNavigation = ({ onHome, onShelf, onSearch, onProfile, onScan
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-0 left-0 w-full z-50 pointer-events-none">
|
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 w-[90%] max-w-sm z-50 pointer-events-none">
|
||||||
{/* Hidden Input for Scanning */}
|
{/* Hidden Input for Scanning */}
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
@@ -41,67 +41,49 @@ export const BottomNavigation = ({ onHome, onShelf, onSearch, onProfile, onScan
|
|||||||
className="hidden"
|
className="hidden"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Background Container mit Glassmorphism */}
|
<div className="flex items-center justify-between px-2 py-2 bg-zinc-900/90 backdrop-blur-lg border border-zinc-800 rounded-full shadow-2xl pointer-events-auto">
|
||||||
<div className="relative bg-[#0F1014]/90 backdrop-blur-xl border-t border-white/10 pb-safe pt-2 pointer-events-auto shadow-[0_-10px_40px_rgba(0,0,0,0.5)]">
|
{/* Left Items */}
|
||||||
|
<button
|
||||||
|
onClick={onHome}
|
||||||
|
className="p-3 text-zinc-400 hover:text-white transition-colors active:scale-90"
|
||||||
|
aria-label="Home"
|
||||||
|
>
|
||||||
|
<Home size={22} strokeWidth={2.5} />
|
||||||
|
</button>
|
||||||
|
|
||||||
<div className="flex justify-between items-end px-6 h-16">
|
<button
|
||||||
{/* Left Actions */}
|
onClick={onShelf}
|
||||||
<button
|
className="p-3 text-zinc-400 hover:text-white transition-colors active:scale-90"
|
||||||
onClick={onHome}
|
aria-label="Shelf"
|
||||||
className="flex flex-col items-center gap-1 text-[#C89D46] w-12 transition-all active:scale-90"
|
>
|
||||||
>
|
<Grid size={22} strokeWidth={2.5} />
|
||||||
<Home size={24} />
|
</button>
|
||||||
<span className="text-[10px] font-medium font-sans uppercase tracking-widest opacity-80">Home</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
{/* PRIMARY ACTION - The "Industrial Button" */}
|
||||||
onClick={onShelf}
|
<button
|
||||||
className="flex flex-col items-center gap-1 text-[#8F9096] hover:text-white w-12 transition-all active:scale-90"
|
onClick={handleScanClick}
|
||||||
>
|
className="flex items-center justify-center w-14 h-14 rounded-full bg-orange-600 text-white hover:bg-orange-500 active:scale-95 transition-all mx-2 shadow-lg shadow-orange-950/40"
|
||||||
<Grid size={24} />
|
aria-label="Scan Bottle"
|
||||||
<span className="text-[10px] font-medium font-sans uppercase tracking-widest opacity-80">Shelf</span>
|
>
|
||||||
</button>
|
<Scan size={26} strokeWidth={2.5} />
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* Spacer für den Center Button */}
|
{/* Right Items */}
|
||||||
<div className="w-16" />
|
<button
|
||||||
|
onClick={onSearch}
|
||||||
{/* Right Actions */}
|
className="p-3 text-zinc-400 hover:text-white transition-colors active:scale-90"
|
||||||
<button
|
aria-label="Search"
|
||||||
onClick={onSearch}
|
>
|
||||||
className="flex flex-col items-center gap-1 text-[#8F9096] hover:text-white w-12 transition-all active:scale-90"
|
<Search size={22} strokeWidth={2.5} />
|
||||||
>
|
</button>
|
||||||
<Search size={24} />
|
|
||||||
<span className="text-[10px] font-medium font-sans uppercase tracking-widest opacity-80">Search</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={onProfile}
|
|
||||||
className="flex flex-col items-center gap-1 text-[#8F9096] hover:text-white w-12 transition-all active:scale-90"
|
|
||||||
>
|
|
||||||
<User size={24} />
|
|
||||||
<span className="text-[10px] font-medium font-sans uppercase tracking-widest opacity-80">Admin</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* THE FLOATING MAGIC BUTTON */}
|
|
||||||
<div className="absolute -top-10 left-1/2 -translate-x-1/2 pointer-events-auto">
|
|
||||||
<button
|
|
||||||
onClick={handleScanClick}
|
|
||||||
className="flex items-center justify-center w-20 h-20 rounded-full
|
|
||||||
bg-gradient-to-tr from-[#C89D46] to-[#E0B456]
|
|
||||||
shadow-[0_0_30px_rgba(200,157,70,0.4)]
|
|
||||||
border-[6px] border-[#0F1014]
|
|
||||||
active:scale-95 transition-transform duration-200"
|
|
||||||
aria-label="Scan Bottle"
|
|
||||||
>
|
|
||||||
<div className="bg-[#0F1014] p-3 rounded-full">
|
|
||||||
<Scan color="#C89D46" size={32} strokeWidth={2.5} />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/* Visual Gold Glow */}
|
|
||||||
<div className="absolute inset-0 rounded-full bg-[#C89D46]/20 blur-2xl -z-10 animate-pulse" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={onProfile}
|
||||||
|
className="p-3 text-zinc-400 hover:text-white transition-colors active:scale-90"
|
||||||
|
aria-label="Profile"
|
||||||
|
>
|
||||||
|
<User size={22} strokeWidth={2.5} />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -80,18 +80,18 @@ export default function BuddyList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-[#1A1B21] rounded-3xl p-6 border border-white/5 shadow-xl transition-all duration-300">
|
<div className="bg-zinc-900 rounded-3xl p-6 border border-zinc-800 shadow-xl transition-all duration-300">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h3 className="text-sm font-sans font-bold uppercase tracking-[0.2em] flex items-center gap-2 text-[#8F9096]">
|
<h3 className="text-sm font-bold uppercase tracking-[0.2em] flex items-center gap-2 text-zinc-400">
|
||||||
<Users size={18} className="text-[#C89D46]" />
|
<Users size={18} className="text-orange-600" />
|
||||||
{t('buddy.title')}
|
{t('buddy.title')}
|
||||||
{!isCollapsed && buddies.length > 0 && (
|
{!isCollapsed && buddies.length > 0 && (
|
||||||
<span className="text-[10px] font-sans font-bold opacity-50 ml-2">({buddies.length})</span>
|
<span className="text-[10px] font-bold opacity-50 ml-2">({buddies.length})</span>
|
||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
onClick={handleToggleCollapse}
|
onClick={handleToggleCollapse}
|
||||||
className="p-2 hover:bg-white/5 rounded-xl transition-colors text-[#8F9096] hover:text-[#C89D46]"
|
className="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500 hover:text-orange-600"
|
||||||
title={isCollapsed ? 'Aufklappen' : 'Einklappen'}
|
title={isCollapsed ? 'Aufklappen' : 'Einklappen'}
|
||||||
>
|
>
|
||||||
{isCollapsed ? <ChevronDown size={20} /> : <ChevronUp size={20} />}
|
{isCollapsed ? <ChevronDown size={20} /> : <ChevronUp size={20} />}
|
||||||
@@ -106,23 +106,23 @@ export default function BuddyList() {
|
|||||||
value={newName}
|
value={newName}
|
||||||
onChange={(e) => setNewName(e.target.value)}
|
onChange={(e) => setNewName(e.target.value)}
|
||||||
placeholder={t('buddy.placeholder')}
|
placeholder={t('buddy.placeholder')}
|
||||||
className="flex-1 bg-white/5 border border-white/10 rounded-xl px-4 py-2 text-sm text-white placeholder:text-[#8F9096] focus:outline-none focus:border-[#C89D46] transition-colors"
|
className="flex-1 bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2 text-sm text-zinc-50 placeholder:text-zinc-600 focus:outline-none focus:border-orange-600 transition-colors"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isAdding || !newName.trim()}
|
disabled={isAdding || !newName.trim()}
|
||||||
className="bg-[#C89D46] hover:bg-[#A67D2E] text-[#0F1014] p-2 rounded-xl transition-all disabled:opacity-50"
|
className="bg-orange-600 hover:bg-orange-700 text-white p-2 rounded-xl transition-all disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isAdding ? <Loader2 size={20} className="animate-spin" /> : <UserPlus size={20} />}
|
{isAdding ? <Loader2 size={20} className="animate-spin" /> : <UserPlus size={20} />}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex justify-center py-8 text-[#8F9096]">
|
<div className="flex justify-center py-8 text-zinc-500">
|
||||||
<Loader2 size={24} className="animate-spin" />
|
<Loader2 size={24} className="animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
) : buddies.length === 0 ? (
|
) : buddies.length === 0 ? (
|
||||||
<div className="text-center py-8 text-[#8F9096] text-xs font-sans">
|
<div className="text-center py-8 text-zinc-600 text-xs font-bold">
|
||||||
{t('buddy.noBuddies')}
|
{t('buddy.noBuddies')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -130,22 +130,22 @@ export default function BuddyList() {
|
|||||||
{buddies.map((buddy) => (
|
{buddies.map((buddy) => (
|
||||||
<div
|
<div
|
||||||
key={buddy.id}
|
key={buddy.id}
|
||||||
className="flex items-center justify-between p-4 bg-white/5 rounded-[2rem] border border-white/5 group hover:border-white/10 transition-all duration-300"
|
className="flex items-center justify-between p-4 bg-zinc-950 rounded-2xl border border-zinc-800 group hover:border-zinc-700 transition-all duration-300"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-10 h-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-[#C89D46] font-display font-bold shadow-inner">
|
<div className="w-10 h-10 rounded-xl bg-zinc-900 border border-zinc-800 flex items-center justify-center text-orange-600 font-bold shadow-inner">
|
||||||
{buddy.name[0].toUpperCase()}
|
{buddy.name[0].toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="font-bold text-white text-sm tracking-tight">{buddy.name}</span>
|
<span className="font-bold text-zinc-50 text-sm tracking-tight">{buddy.name}</span>
|
||||||
{buddy.buddy_profile_id && (
|
{buddy.buddy_profile_id && (
|
||||||
<span className="text-[9px] font-sans font-black uppercase text-[#C89D46]/80 tracking-widest">{t('common.link')}</span>
|
<span className="text-[9px] font-bold uppercase text-orange-500/80 tracking-widest">{t('common.link')}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteBuddy(buddy.id)}
|
onClick={() => handleDeleteBuddy(buddy.id)}
|
||||||
className="text-[#8F9096] hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all p-2 hover:bg-white/5 rounded-xl"
|
className="text-zinc-600 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all p-2 hover:bg-zinc-800 rounded-xl"
|
||||||
>
|
>
|
||||||
<Trash2 size={16} />
|
<Trash2 size={16} />
|
||||||
</button>
|
</button>
|
||||||
@@ -160,17 +160,17 @@ export default function BuddyList() {
|
|||||||
<div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1">
|
<div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1">
|
||||||
<div className="flex -space-x-1.5 overflow-hidden">
|
<div className="flex -space-x-1.5 overflow-hidden">
|
||||||
{buddies.slice(0, 5).map((b, i) => (
|
{buddies.slice(0, 5).map((b, i) => (
|
||||||
<div key={b.id} className="w-7 h-7 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center text-[10px] font-display font-bold text-[#C89D46] shadow-sm">
|
<div key={b.id} className="w-7 h-7 rounded-lg bg-zinc-950 border border-zinc-800 flex items-center justify-center text-[10px] font-bold text-orange-600 shadow-sm">
|
||||||
{b.name[0].toUpperCase()}
|
{b.name[0].toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{buddies.length > 5 && (
|
{buddies.length > 5 && (
|
||||||
<div className="w-7 h-7 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center text-[8px] font-sans font-bold text-[#8F9096] shadow-sm">
|
<div className="w-7 h-7 rounded-lg bg-zinc-950 border border-zinc-800 flex items-center justify-center text-[8px] font-bold text-zinc-500 shadow-sm">
|
||||||
+{buddies.length - 5}
|
+{buddies.length - 5}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] text-[#8F9096] font-sans font-bold uppercase tracking-widest ml-1">{buddies.length} Buddies</span>
|
<span className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest ml-1">{buddies.length} Buddies</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={suggestDram}
|
onClick={suggestDram}
|
||||||
disabled={isRolling}
|
disabled={isRolling}
|
||||||
className="flex items-center gap-2 px-4 sm:px-6 py-2 sm:py-3 bg-amber-600 hover:bg-amber-700 text-white rounded-2xl font-black uppercase tracking-widest text-[10px] sm:text-xs transition-all shadow-lg shadow-amber-600/20 active:scale-95 disabled:opacity-50"
|
className="flex items-center gap-2 px-4 sm:px-6 py-2 sm:py-3 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl font-bold uppercase tracking-widest text-[10px] sm:text-xs transition-all shadow-lg shadow-orange-950/20 active:scale-95 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isRolling ? (
|
{isRolling ? (
|
||||||
<Dices size={18} className="animate-spin" />
|
<Dices size={18} className="animate-spin" />
|
||||||
@@ -55,23 +55,23 @@ export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{suggestion && (
|
{suggestion && (
|
||||||
<div className="fixed inset-0 z-[100] flex items-center justify-center p-6 bg-black/60 backdrop-blur-sm animate-in fade-in duration-300">
|
<div className="fixed inset-0 z-[100] flex items-center justify-center p-6 bg-zinc-950/80 backdrop-blur-sm animate-in fade-in duration-300">
|
||||||
<div className="bg-white dark:bg-zinc-900 w-full max-w-sm rounded-[40px] p-8 shadow-2xl border border-amber-500/20 relative animate-in zoom-in-95 duration-300">
|
<div className="bg-zinc-900 w-full max-w-sm rounded-[40px] p-8 shadow-2xl border border-orange-500/20 relative animate-in zoom-in-95 duration-300">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSuggestion(null)}
|
onClick={() => setSuggestion(null)}
|
||||||
className="absolute top-6 right-6 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200"
|
className="absolute top-6 right-6 text-zinc-500 hover:text-orange-600"
|
||||||
>
|
>
|
||||||
<X size={24} />
|
<X size={24} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex flex-col items-center text-center space-y-6">
|
<div className="flex flex-col items-center text-center space-y-6">
|
||||||
<div className="w-20 h-20 bg-amber-100 dark:bg-amber-900/30 rounded-3xl flex items-center justify-center text-amber-600">
|
<div className="w-20 h-20 bg-orange-900/30 rounded-3xl flex items-center justify-center text-orange-600">
|
||||||
<GlassWater size={40} strokeWidth={2.5} />
|
<GlassWater size={40} strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-[10px] font-black uppercase tracking-[0.2em] text-amber-600">{t('home.dramOfDay.title')}</h3>
|
<h3 className="text-[10px] font-bold uppercase tracking-[0.2em] text-orange-600">{t('home.dramOfDay.title')}</h3>
|
||||||
<h2 className="text-2xl font-black text-zinc-900 dark:text-white leading-tight">
|
<h2 className="text-2xl font-bold text-zinc-50 leading-tight uppercase">
|
||||||
{suggestion.name}
|
{suggestion.name}
|
||||||
</h2>
|
</h2>
|
||||||
{suggestion.distillery && (
|
{suggestion.distillery && (
|
||||||
@@ -83,13 +83,13 @@ export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
|
|||||||
<Link
|
<Link
|
||||||
href={`/bottles/${suggestion.id}`}
|
href={`/bottles/${suggestion.id}`}
|
||||||
onClick={() => setSuggestion(null)}
|
onClick={() => setSuggestion(null)}
|
||||||
className="block w-full py-4 bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900 rounded-2xl font-black uppercase tracking-widest text-xs hover:bg-amber-600 dark:hover:bg-amber-600 hover:text-white transition-all shadow-xl"
|
className="block w-full py-4 bg-orange-600 text-white rounded-2xl font-bold uppercase tracking-widest text-xs hover:bg-orange-700 transition-all shadow-xl shadow-orange-950/20"
|
||||||
>
|
>
|
||||||
{t('home.dramOfDay.viewBottle')}
|
{t('home.dramOfDay.viewBottle')}
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={suggestDram}
|
onClick={suggestDram}
|
||||||
className="w-full mt-3 py-2 text-zinc-400 hover:text-amber-600 text-[10px] font-black uppercase tracking-widest transition-colors"
|
className="w-full mt-3 py-2 text-zinc-500 hover:text-orange-600 text-[10px] font-bold uppercase tracking-widest transition-colors"
|
||||||
>
|
>
|
||||||
{t('home.dramOfDay.rollAgain')}
|
{t('home.dramOfDay.rollAgain')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -123,9 +123,9 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 bg-white dark:bg-zinc-900 border border-amber-500/30 rounded-3xl shadow-xl space-y-4 animate-in zoom-in-95 duration-200">
|
<div className="p-6 bg-zinc-900 border border-orange-500/20 rounded-3xl shadow-2xl space-y-4 animate-in zoom-in-95 duration-200">
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="flex justify-between items-center mb-2">
|
||||||
<h3 className="text-lg font-black text-amber-600 uppercase tracking-widest flex items-center gap-2">
|
<h3 className="text-lg font-black text-orange-600 uppercase tracking-widest flex items-center gap-2">
|
||||||
<Info size={18} /> {t('bottle.editTitle')}
|
<Info size={18} /> {t('bottle.editTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
@@ -143,7 +143,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
type="text"
|
type="text"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -152,7 +152,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
type="text"
|
type="text"
|
||||||
value={formData.distillery}
|
value={formData.distillery}
|
||||||
onChange={(e) => setFormData({ ...formData, distillery: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, distillery: e.target.value })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -161,7 +161,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
type="text"
|
type="text"
|
||||||
value={formData.category}
|
value={formData.category}
|
||||||
onChange={(e) => setFormData({ ...formData, category: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, category: e.target.value })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
@@ -172,7 +172,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
step="0.1"
|
step="0.1"
|
||||||
value={formData.abv}
|
value={formData.abv}
|
||||||
onChange={(e) => setFormData({ ...formData, abv: parseFloat(e.target.value) })}
|
onChange={(e) => setFormData({ ...formData, abv: parseFloat(e.target.value) })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -181,7 +181,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
type="number"
|
type="number"
|
||||||
value={formData.age}
|
value={formData.age}
|
||||||
onChange={(e) => setFormData({ ...formData, age: parseInt(e.target.value) })}
|
onChange={(e) => setFormData({ ...formData, age: parseInt(e.target.value) })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -192,7 +192,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleDiscover}
|
onClick={handleDiscover}
|
||||||
disabled={isSearching}
|
disabled={isSearching}
|
||||||
className="text-amber-600 hover:text-amber-700 flex items-center gap-1 normal-case font-bold"
|
className="text-orange-600 hover:text-orange-700 flex items-center gap-1 normal-case font-bold"
|
||||||
>
|
>
|
||||||
{isSearching ? <Loader2 size={10} className="animate-spin" /> : <Search size={10} />}
|
{isSearching ? <Loader2 size={10} className="animate-spin" /> : <Search size={10} />}
|
||||||
{t('bottle.autoSearch')}
|
{t('bottle.autoSearch')}
|
||||||
@@ -202,17 +202,17 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
type="text"
|
type="text"
|
||||||
value={formData.whiskybase_id}
|
value={formData.whiskybase_id}
|
||||||
onChange={(e) => setFormData({ ...formData, whiskybase_id: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, whiskybase_id: e.target.value })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
|
||||||
/>
|
/>
|
||||||
{discoveryResult && (
|
{discoveryResult && (
|
||||||
<div className="mt-2 p-3 bg-zinc-50 dark:bg-zinc-800/50 border border-amber-500/20 rounded-xl animate-in fade-in slide-in-from-top-2">
|
<div className="mt-2 p-3 bg-zinc-950 border border-orange-500/20 rounded-xl animate-in fade-in slide-in-from-top-2">
|
||||||
<p className="text-[10px] text-zinc-500 mb-2">Treffer gefunden:</p>
|
<p className="text-[10px] text-zinc-500 mb-2">Treffer gefunden:</p>
|
||||||
<p className="text-[11px] font-bold text-zinc-800 dark:text-zinc-200 mb-2 truncate">{discoveryResult.title}</p>
|
<p className="text-[11px] font-bold text-zinc-200 mb-2 truncate">{discoveryResult.title}</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={applyDiscovery}
|
onClick={applyDiscovery}
|
||||||
className="px-3 py-1.5 bg-amber-600 text-white text-[10px] font-black uppercase rounded-lg hover:bg-amber-700 transition-colors"
|
className="px-3 py-1.5 bg-orange-600 text-white text-[10px] font-black uppercase rounded-lg hover:bg-orange-700 transition-colors"
|
||||||
>
|
>
|
||||||
{t('bottle.applyId')}
|
{t('bottle.applyId')}
|
||||||
</button>
|
</button>
|
||||||
@@ -236,7 +236,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
value={formData.purchase_price}
|
value={formData.purchase_price}
|
||||||
onChange={(e) => setFormData({ ...formData, purchase_price: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, purchase_price: e.target.value })}
|
||||||
className="w-full px-4 py-2 bg-amber-50 dark:bg-amber-900/10 border border-amber-200 dark:border-amber-900/30 rounded-xl outline-none focus:ring-2 focus:ring-amber-500 font-bold text-amber-700 dark:text-amber-400"
|
className="w-full px-4 py-2 bg-zinc-950 border border-zinc-800 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50 font-bold text-zinc-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
placeholder="z.B. 2010"
|
placeholder="z.B. 2010"
|
||||||
value={formData.distilled_at}
|
value={formData.distilled_at}
|
||||||
onChange={(e) => setFormData({ ...formData, distilled_at: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, distilled_at: e.target.value })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
placeholder="z.B. 2022"
|
placeholder="z.B. 2022"
|
||||||
value={formData.bottled_at}
|
value={formData.bottled_at}
|
||||||
onChange={(e) => setFormData({ ...formData, bottled_at: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, bottled_at: e.target.value })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
placeholder="z.B. Batch 12 oder L-Code"
|
placeholder="z.B. Batch 12 oder L-Code"
|
||||||
value={formData.batch_info}
|
value={formData.batch_info}
|
||||||
onChange={(e) => setFormData({ ...formData, batch_info: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, batch_info: e.target.value })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,7 +279,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className="w-full py-4 bg-amber-600 hover:bg-amber-700 text-white rounded-2xl font-black uppercase tracking-widest transition-all flex items-center justify-center gap-2 shadow-lg shadow-amber-600/20 disabled:opacity-50"
|
className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl font-black uppercase tracking-widest transition-all flex items-center justify-center gap-2 shadow-xl shadow-orange-950/20 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isSaving ? <div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div> : <Save size={20} />}
|
{isSaving ? <div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div> : <Save size={20} />}
|
||||||
{t('bottle.saveChanges')}
|
{t('bottle.saveChanges')}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default function FloatingScannerButton({ onImageSelected }: FloatingScann
|
|||||||
whileTap={{ scale: 0.9 }}
|
whileTap={{ scale: 0.9 }}
|
||||||
initial={{ y: 100, opacity: 0 }}
|
initial={{ y: 100, opacity: 0 }}
|
||||||
animate={{ y: 0, opacity: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
className="relative group p-6 rounded-full bg-amber-600 text-black shadow-[0_0_30px_rgba(217,119,6,0.4)] hover:shadow-[0_0_40px_rgba(217,119,6,0.6)] transition-all overflow-hidden"
|
className="relative group p-6 rounded-full bg-orange-600 text-black shadow-lg shadow-orange-950/40 hover:shadow-orange-950/60 transition-all overflow-hidden"
|
||||||
>
|
>
|
||||||
{/* Shine Animation */}
|
{/* Shine Animation */}
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -57,7 +57,7 @@ export default function FloatingScannerButton({ onImageSelected }: FloatingScann
|
|||||||
<Camera size={32} strokeWidth={2.5} className="relative z-10" />
|
<Camera size={32} strokeWidth={2.5} className="relative z-10" />
|
||||||
|
|
||||||
{/* Pulse ring */}
|
{/* Pulse ring */}
|
||||||
<span className="absolute inset-0 rounded-full border-4 border-amber-600 animate-ping opacity-20" />
|
<span className="absolute inset-0 rounded-full border-4 border-orange-600 animate-ping opacity-20" />
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ const LanguageSwitcher = () => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setLocale('de')}
|
onClick={() => setLocale('de')}
|
||||||
className={`p-1.5 rounded-lg transition-all ${locale === 'de'
|
className={`p-1.5 rounded-lg transition-all ${locale === 'de'
|
||||||
? 'bg-amber-100 dark:bg-amber-900/30 scale-110 shadow-sm'
|
? 'bg-orange-950/30 scale-110 shadow-sm shadow-orange-950/20'
|
||||||
: 'opacity-50 hover:opacity-100 grayscale hover:grayscale-0'
|
: 'opacity-50 hover:opacity-100 grayscale hover:grayscale-0'
|
||||||
}`}
|
}`}
|
||||||
title="Deutsch"
|
title="Deutsch"
|
||||||
>
|
>
|
||||||
@@ -21,8 +21,8 @@ const LanguageSwitcher = () => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setLocale('en')}
|
onClick={() => setLocale('en')}
|
||||||
className={`p-1.5 rounded-lg transition-all ${locale === 'en'
|
className={`p-1.5 rounded-lg transition-all ${locale === 'en'
|
||||||
? 'bg-amber-100 dark:bg-amber-900/30 scale-110 shadow-sm'
|
? 'bg-orange-950/30 scale-110 shadow-sm shadow-orange-950/20'
|
||||||
: 'opacity-50 hover:opacity-100 grayscale hover:grayscale-0'
|
: 'opacity-50 hover:opacity-100 grayscale hover:grayscale-0'
|
||||||
}`}
|
}`}
|
||||||
title="English"
|
title="English"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ export default function OfflineIndicator() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1.5 px-2.5 py-1 bg-green-600/10 border border-green-600/20 rounded-full group cursor-help relative animate-in fade-in zoom-in duration-500">
|
<div className="flex items-center gap-1.5 px-2.5 py-1 bg-green-600/10 border border-green-600/20 rounded-full group cursor-help relative animate-in fade-in zoom-in duration-500">
|
||||||
<ShieldCheck size={10} className="text-green-600" />
|
<ShieldCheck size={10} className="text-green-600" />
|
||||||
<span className="text-[9px] font-black uppercase tracking-widest text-green-600">Offline-Modus aktiv</span>
|
<span className="text-[9px] font-bold uppercase tracking-widest text-green-600">Offline-Modus aktiv</span>
|
||||||
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 w-48 p-2 bg-zinc-900 text-[10px] text-zinc-400 rounded-xl border border-white/10 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 shadow-2xl text-center">
|
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 w-48 p-2 bg-zinc-900 text-[10px] text-zinc-400 rounded-xl border border-zinc-800 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 shadow-2xl text-center">
|
||||||
Alle Funktionen sind vollständig offline verfügbar.
|
Alle Funktionen sind vollständig offline verfügbar.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,9 +112,9 @@ export default function OfflineIndicator() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1.5 px-2.5 py-1 bg-amber-600/10 border border-amber-600/20 rounded-full animate-in fade-in slide-in-from-right-2">
|
<div className="flex items-center gap-1.5 px-2.5 py-1 bg-orange-600/10 border border-orange-600/20 rounded-full animate-in fade-in slide-in-from-right-2">
|
||||||
<Loader2 size={10} className="text-amber-600 animate-spin" />
|
<Loader2 size={10} className="text-orange-600 animate-spin" />
|
||||||
<span className="text-[9px] font-black uppercase tracking-widest text-amber-600">
|
<span className="text-[9px] font-bold uppercase tracking-widest text-orange-600">
|
||||||
{progress > 0 ? `Vorbereitung... ${progress}%` : 'Warte auf System...'}
|
{progress > 0 ? `Vorbereitung... ${progress}%` : 'Warte auf System...'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -129,11 +129,11 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Actions Bar */}
|
{/* Actions Bar */}
|
||||||
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-sm">
|
<div className="bg-zinc-900 rounded-2xl p-6 border border-zinc-800 shadow-sm">
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={handleCreate}
|
onClick={handleCreate}
|
||||||
className="flex-1 py-3 bg-amber-600 hover:bg-amber-700 text-white font-bold rounded-xl transition-colors flex items-center justify-center gap-2"
|
className="flex-1 py-3 bg-orange-600 hover:bg-orange-700 text-white font-bold rounded-2xl transition-colors flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<Plus size={18} />
|
<Plus size={18} />
|
||||||
Create New Plan
|
Create New Plan
|
||||||
@@ -141,7 +141,7 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
|
|||||||
<button
|
<button
|
||||||
onClick={handleGrantCredits}
|
onClick={handleGrantCredits}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="flex-1 py-3 bg-green-600 hover:bg-green-700 text-white font-bold rounded-xl transition-colors flex items-center justify-center gap-2 disabled:opacity-50"
|
className="flex-1 py-3 bg-zinc-800 hover:bg-zinc-700 text-white font-bold rounded-2xl transition-colors flex items-center justify-center gap-2 disabled:opacity-50 border border-zinc-700"
|
||||||
>
|
>
|
||||||
<Zap size={18} />
|
<Zap size={18} />
|
||||||
Grant Monthly Credits
|
Grant Monthly Credits
|
||||||
@@ -151,8 +151,8 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
|
|||||||
|
|
||||||
{message && (
|
{message && (
|
||||||
<div className={`p-4 rounded-xl flex items-center gap-3 ${message.type === 'success'
|
<div className={`p-4 rounded-xl flex items-center gap-3 ${message.type === 'success'
|
||||||
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-500'
|
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-500'
|
||||||
: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-500'
|
: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-500'
|
||||||
}`}>
|
}`}>
|
||||||
{message.type === 'success' ? <Check size={20} /> : <AlertCircle size={20} />}
|
{message.type === 'success' ? <Check size={20} /> : <AlertCircle size={20} />}
|
||||||
{message.text}
|
{message.text}
|
||||||
@@ -164,27 +164,27 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
|
|||||||
{plans.map((plan) => (
|
{plans.map((plan) => (
|
||||||
<div
|
<div
|
||||||
key={plan.id}
|
key={plan.id}
|
||||||
className={`bg-white dark:bg-zinc-900 rounded-2xl p-6 border-2 ${plan.is_active
|
className={`bg-zinc-900 rounded-[32px] p-6 border-2 ${plan.is_active
|
||||||
? 'border-amber-200 dark:border-amber-800'
|
? 'border-orange-500/30'
|
||||||
: 'border-zinc-200 dark:border-zinc-800 opacity-60'
|
: 'border-zinc-800 opacity-60'
|
||||||
} shadow-sm relative`}
|
} shadow-sm relative`}
|
||||||
>
|
>
|
||||||
{!plan.is_active && (
|
{!plan.is_active && (
|
||||||
<div className="absolute top-4 right-4 px-2 py-1 bg-zinc-200 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-400 text-xs font-bold rounded">
|
<div className="absolute top-4 right-4 px-2 py-1 bg-zinc-800 text-zinc-400 text-[8px] font-bold uppercase tracking-widest rounded">
|
||||||
Inactive
|
Inactive
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-2xl font-black text-zinc-900 dark:text-white">{plan.display_name}</h3>
|
<h3 className="text-2xl font-bold text-white uppercase tracking-tighter">{plan.display_name}</h3>
|
||||||
<p className="text-xs text-zinc-500 mt-1">{plan.name}</p>
|
<p className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest mt-1">{plan.name}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="text-3xl font-black text-amber-600">{plan.monthly_credits}</div>
|
<div className="text-3xl font-bold text-orange-600 tracking-tighter">{plan.monthly_credits}</div>
|
||||||
<div className="text-xs text-zinc-500">Credits/Month</div>
|
<div className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">Credits/Month</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="text-2xl font-bold text-zinc-900 dark:text-white">€{plan.price.toFixed(2)}</div>
|
<div className="text-2xl font-bold text-white tracking-tight">€{plan.price.toFixed(2)}</div>
|
||||||
<div className="text-xs text-zinc-500">per month</div>
|
<div className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest leading-none mt-1">per month</div>
|
||||||
</div>
|
</div>
|
||||||
{plan.description && (
|
{plan.description && (
|
||||||
<p className="text-sm text-zinc-600 dark:text-zinc-400 mb-4">{plan.description}</p>
|
<p className="text-sm text-zinc-600 dark:text-zinc-400 mb-4">{plan.description}</p>
|
||||||
@@ -192,14 +192,14 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
|
|||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleEdit(plan)}
|
onClick={() => handleEdit(plan)}
|
||||||
className="flex-1 py-2 bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900 font-bold rounded-lg text-sm transition-colors flex items-center justify-center gap-2"
|
className="flex-1 py-2 bg-zinc-800 hover:bg-zinc-700 text-white font-bold rounded-xl text-[10px] uppercase tracking-widest transition-colors flex items-center justify-center gap-2 border border-zinc-700"
|
||||||
>
|
>
|
||||||
<Edit size={14} />
|
<Edit size={14} />
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDelete(plan.id)}
|
onClick={() => handleDelete(plan.id)}
|
||||||
className="px-3 py-2 bg-red-600 hover:bg-red-700 text-white font-bold rounded-lg text-sm transition-colors"
|
className="px-3 py-2 bg-red-600/20 hover:bg-red-600 text-red-500 hover:text-white font-bold rounded-xl text-sm transition-colors border border-red-900/50"
|
||||||
>
|
>
|
||||||
<Trash2 size={14} />
|
<Trash2 size={14} />
|
||||||
</button>
|
</button>
|
||||||
@@ -210,15 +210,15 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
|
|||||||
|
|
||||||
{/* Edit/Create Modal */}
|
{/* Edit/Create Modal */}
|
||||||
{(editingPlan || isCreating) && (
|
{(editingPlan || isCreating) && (
|
||||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
|
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 z-50">
|
||||||
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-6 max-w-2xl w-full border border-zinc-200 dark:border-zinc-800">
|
<div className="bg-zinc-900 rounded-[32px] p-6 max-w-2xl w-full border border-zinc-800 shadow-2xl">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h3 className="text-2xl font-black text-zinc-900 dark:text-white">
|
<h3 className="text-2xl font-bold text-white uppercase tracking-tighter">
|
||||||
{isCreating ? 'Create Plan' : 'Edit Plan'}
|
{isCreating ? 'Create Plan' : 'Edit Plan'}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => { setEditingPlan(null); setIsCreating(false); }}
|
onClick={() => { setEditingPlan(null); setIsCreating(false); }}
|
||||||
className="p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg transition-colors"
|
className="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500"
|
||||||
>
|
>
|
||||||
<X size={20} />
|
<X size={20} />
|
||||||
</button>
|
</button>
|
||||||
@@ -227,79 +227,79 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Name (ID)</label>
|
<label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Name (ID)</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
placeholder="e.g. starter"
|
placeholder="e.g. starter"
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Display Name</label>
|
<label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Display Name</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.display_name}
|
value={formData.display_name}
|
||||||
onChange={(e) => setFormData({ ...formData, display_name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, display_name: e.target.value })}
|
||||||
placeholder="e.g. Starter"
|
placeholder="e.g. Starter"
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Monthly Credits</label>
|
<label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Monthly Credits</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.monthly_credits}
|
value={formData.monthly_credits}
|
||||||
onChange={(e) => setFormData({ ...formData, monthly_credits: parseInt(e.target.value) || 0 })}
|
onChange={(e) => setFormData({ ...formData, monthly_credits: parseInt(e.target.value) || 0 })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Price (€)</label>
|
<label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Price (€)</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={formData.price}
|
value={formData.price}
|
||||||
onChange={(e) => setFormData({ ...formData, price: parseFloat(e.target.value) || 0 })}
|
onChange={(e) => setFormData({ ...formData, price: parseFloat(e.target.value) || 0 })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Description</label>
|
<label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Description</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
placeholder="Brief description of the plan"
|
placeholder="Brief description of the plan"
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Sort Order</label>
|
<label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Sort Order</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.sort_order}
|
value={formData.sort_order}
|
||||||
onChange={(e) => setFormData({ ...formData, sort_order: parseInt(e.target.value) || 0 })}
|
onChange={(e) => setFormData({ ...formData, sort_order: parseInt(e.target.value) || 0 })}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-end">
|
<div className="flex items-end">
|
||||||
<label className="flex items-center gap-2 cursor-pointer">
|
<label className="flex items-center gap-2 cursor-pointer pb-2">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={formData.is_active}
|
checked={formData.is_active}
|
||||||
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
|
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
|
||||||
className="w-5 h-5 rounded border-zinc-300 text-amber-600 focus:ring-amber-500"
|
className="w-5 h-5 rounded border-zinc-700 bg-zinc-800 text-orange-600 focus:ring-orange-600"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm font-bold text-zinc-900 dark:text-white">Active</span>
|
<span className="text-sm font-bold text-white">Active</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -307,7 +307,7 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="w-full py-3 bg-amber-600 hover:bg-amber-700 text-white font-bold rounded-xl transition-colors disabled:opacity-50 flex items-center justify-center gap-2"
|
className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white font-bold rounded-2xl transition-all shadow-lg shadow-orange-950/20 disabled:opacity-50 flex items-center justify-center gap-2 mt-4"
|
||||||
>
|
>
|
||||||
<Check size={18} />
|
<Check size={18} />
|
||||||
{isCreating ? 'Create Plan' : 'Save Changes'}
|
{isCreating ? 'Create Plan' : 'Save Changes'}
|
||||||
|
|||||||
@@ -34,75 +34,75 @@ export default function ResultCard({ data, bottleName, image, onShare }: ResultC
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ scale: 0.9, opacity: 0, rotateY: 30 }}
|
initial={{ scale: 0.9, opacity: 0, rotateY: 30 }}
|
||||||
animate={{ scale: 1, opacity: 1, rotateY: 0 }}
|
animate={{ scale: 1, opacity: 1, rotateY: 0 }}
|
||||||
transition={{ type: 'spring', damping: 15 }}
|
transition={{ type: 'spring', damping: 20 }}
|
||||||
className="flex flex-col items-center gap-8 w-full max-w-sm"
|
className="flex flex-col items-center gap-6 w-full max-w-sm"
|
||||||
>
|
>
|
||||||
{/* The Trading Card */}
|
{/* The Trading Card */}
|
||||||
<div className="relative w-full aspect-[9/16] rounded-[32px] overflow-hidden shadow-[0_20px_60px_rgba(0,0,0,0.8)] border border-white/20 bg-gradient-to-b from-[#1A1B20] to-black group">
|
<div className="relative w-full aspect-[3/4] rounded-[32px] overflow-hidden shadow-[0_20px_60px_rgba(0,0,0,0.9)] border border-zinc-800 bg-zinc-950 group">
|
||||||
{/* Bottle Image with Vignette */}
|
{/* Bottle Image with Vignette */}
|
||||||
<div className="absolute inset-0">
|
<div className="absolute inset-0">
|
||||||
{image ? (
|
{image ? (
|
||||||
<img src={image} alt={bottleName} className="w-full h-full object-cover" />
|
<img src={image} alt={bottleName} className="w-full h-full object-cover" />
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute inset-0 bg-white/5 flex items-center justify-center opacity-20 text-[20px] font-black uppercase tracking-[1em] rotate-90">No Image</div>
|
<div className="absolute inset-0 bg-zinc-900 flex items-center justify-center opacity-40 text-[20px] font-bold uppercase tracking-[1em] rotate-90">No Data</div>
|
||||||
)}
|
)}
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/20 to-transparent opacity-80" />
|
<div className="absolute inset-0 bg-gradient-to-t from-zinc-950 via-zinc-950/20 to-transparent opacity-90" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Overlay */}
|
{/* Content Overlay */}
|
||||||
<div className="absolute inset-x-0 bottom-0 p-8 space-y-6">
|
<div className="absolute inset-x-0 bottom-0 p-6 space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-0.5">
|
||||||
<p className="text-[10px] font-black uppercase tracking-[0.3em] text-amber-500">Tasting Record</p>
|
<p className="text-[10px] font-bold uppercase tracking-[0.3em] text-orange-600">Verified Tasting Record</p>
|
||||||
<h2 className="text-4xl font-black text-white truncate uppercase tracking-tight leading-none">{bottleName}</h2>
|
<h2 className="text-3xl font-bold text-white truncate uppercase tracking-tighter leading-none">{bottleName}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Radar Chart Area */}
|
{/* Radar Chart Area */}
|
||||||
<div className="h-64 w-full glass-dark rounded-3xl p-4 flex flex-col items-center justify-center">
|
<div className="h-48 w-full bg-zinc-900/40 backdrop-blur-md rounded-[24px] p-2 flex flex-col items-center justify-center border border-zinc-800/50">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<RadarChart cx="50%" cy="50%" outerRadius="80%" data={chartData}>
|
<RadarChart cx="50%" cy="50%" outerRadius="70%" data={chartData}>
|
||||||
<PolarGrid stroke="rgba(255,255,255,0.1)" />
|
<PolarGrid stroke="rgba(255,255,255,0.05)" />
|
||||||
<PolarAngleAxis
|
<PolarAngleAxis
|
||||||
dataKey="subject"
|
dataKey="subject"
|
||||||
tick={{ fill: 'rgba(255,255,255,0.4)', fontSize: 10, fontWeight: 900 }}
|
tick={{ fill: '#71717a', fontSize: 9, fontWeight: 700 }}
|
||||||
/>
|
/>
|
||||||
<Radar
|
<Radar
|
||||||
name="Whisky Profile"
|
name="Whisky Profile"
|
||||||
dataKey="A"
|
dataKey="A"
|
||||||
stroke="#D97706"
|
stroke="#ea580c"
|
||||||
fill="#D97706"
|
fill="#ea580c"
|
||||||
fillOpacity={0.5}
|
fillOpacity={0.6}
|
||||||
/>
|
/>
|
||||||
</RadarChart>
|
</RadarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between pt-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Sparkles size={16} className="text-amber-500" />
|
<Sparkles size={14} className="text-orange-600" />
|
||||||
<span className="text-xs font-black uppercase tracking-widest text-white/40">Verified Report</span>
|
<span className="text-[9px] font-bold uppercase tracking-widest text-zinc-500">Authenticated Report</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Score Badge */}
|
{/* Score Badge */}
|
||||||
<div className="absolute top-8 right-8 w-20 h-20 glass rounded-2xl flex flex-col items-center justify-center border-amber-500/40 shadow-xl">
|
<div className="absolute top-6 right-6 w-16 h-16 bg-zinc-900/60 backdrop-blur-md rounded-2xl flex flex-col items-center justify-center border border-orange-500/30 shadow-2xl">
|
||||||
<span className="text-2xl font-black text-amber-500">{displayScore}</span>
|
<span className="text-2xl font-bold text-orange-600 leading-none">{displayScore}</span>
|
||||||
<span className="text-[8px] font-black uppercase tracking-widest text-white/40">Score</span>
|
<span className="text-[7px] font-bold uppercase tracking-widest text-zinc-500 mt-1">Rating</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Decorative Elements */}
|
{/* Decorative Elements */}
|
||||||
<div className="absolute top-8 left-8 p-2 rounded-xl bg-amber-600 text-white shadow-lg shadow-amber-600/40">
|
<div className="absolute top-6 left-6 p-2 rounded-xl bg-orange-600 text-white shadow-lg shadow-orange-950/40">
|
||||||
<Award size={20} />
|
<Award size={18} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Share Button */}
|
{/* Share Button */}
|
||||||
<button
|
<button
|
||||||
onClick={onShare}
|
onClick={onShare}
|
||||||
className="w-full py-5 bg-zinc-900 hover:bg-zinc-800 border border-white/10 text-white rounded-3xl font-black uppercase tracking-widest text-xs flex items-center justify-center gap-3 transition-all"
|
className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl font-bold uppercase tracking-widest text-[10px] flex items-center justify-center gap-3 transition-all shadow-xl shadow-orange-950/20 active:scale-95"
|
||||||
>
|
>
|
||||||
<Share2 size={18} />
|
<Share2 size={16} />
|
||||||
Share Report
|
Share Tasting Report
|
||||||
</button>
|
</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -133,12 +133,12 @@ export default function ScanAndTasteFlow({ isOpen, onClose, base64Image }: ScanA
|
|||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
className="fixed inset-0 z-[60] bg-[#0F1014] flex flex-col h-[100dvh] w-screen overflow-hidden overscroll-none"
|
className="fixed inset-0 z-[60] bg-zinc-950 flex flex-col h-[100dvh] w-screen overflow-hidden overscroll-none"
|
||||||
>
|
>
|
||||||
{/* Close Button */}
|
{/* Close Button */}
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="absolute top-6 right-6 z-[70] p-2 rounded-full bg-white/5 border border-white/10 text-white/60 hover:text-white transition-colors"
|
className="absolute top-6 right-6 z-[70] p-2 rounded-full bg-zinc-900 border border-zinc-800 text-zinc-400 hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
<X size={24} />
|
<X size={24} />
|
||||||
</button>
|
</button>
|
||||||
@@ -160,15 +160,15 @@ export default function ScanAndTasteFlow({ isOpen, onClose, base64Image }: ScanA
|
|||||||
<motion.div
|
<motion.div
|
||||||
animate={{ rotate: 360 }}
|
animate={{ rotate: 360 }}
|
||||||
transition={{ duration: 3, repeat: Infinity, ease: "linear" }}
|
transition={{ duration: 3, repeat: Infinity, ease: "linear" }}
|
||||||
className="w-32 h-32 rounded-full border-2 border-dashed border-[#C89D46]/30"
|
className="w-32 h-32 rounded-full border-2 border-dashed border-orange-600/30"
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<Loader2 size={48} className="animate-spin text-[#C89D46]" />
|
<Loader2 size={48} className="animate-spin text-orange-600" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center space-y-2">
|
<div className="text-center space-y-2">
|
||||||
<h2 className="text-2xl font-display font-bold text-white uppercase tracking-tight">Analysiere Etikett...</h2>
|
<h2 className="text-2xl font-bold text-zinc-50 uppercase tracking-tight">Analysiere Etikett...</h2>
|
||||||
<p className="text-[#C89D46] font-sans font-bold uppercase tracking-widest text-[10px] flex items-center justify-center gap-2">
|
<p className="text-orange-500 font-bold uppercase tracking-widest text-[10px] flex items-center justify-center gap-2">
|
||||||
<Sparkles size={12} /> KI-gestütztes Scanning
|
<Sparkles size={12} /> KI-gestütztes Scanning
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -223,10 +223,10 @@ export default function ScanAndTasteFlow({ isOpen, onClose, base64Image }: ScanA
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
className="absolute inset-0 z-[80] bg-[#0F1014]/80 backdrop-blur-sm flex flex-col items-center justify-center gap-6"
|
className="absolute inset-0 z-[80] bg-zinc-950/80 backdrop-blur-sm flex flex-col items-center justify-center gap-6"
|
||||||
>
|
>
|
||||||
<Loader2 size={48} className="animate-spin text-[#C89D46]" />
|
<Loader2 size={48} className="animate-spin text-orange-600" />
|
||||||
<h2 className="text-xl font-display font-bold text-white uppercase tracking-tight">Speichere Tasting...</h2>
|
<h2 className="text-xl font-bold text-zinc-50 uppercase tracking-tight">Speichere Tasting...</h2>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
|
|||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[80]"
|
className="fixed inset-0 bg-zinc-950/80 backdrop-blur-sm z-[80]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Sheet */}
|
{/* Sheet */}
|
||||||
@@ -85,12 +85,12 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
|
|||||||
animate={{ y: 0 }}
|
animate={{ y: 0 }}
|
||||||
exit={{ y: '100%' }}
|
exit={{ y: '100%' }}
|
||||||
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
||||||
className="fixed bottom-0 left-0 right-0 bg-[#1A1B20] border-t border-white/10 rounded-t-[32px] z-[90] p-8 pb-12 max-h-[80vh] overflow-y-auto shadow-[0_-10px_40px_rgba(0,0,0,0.5)]"
|
className="fixed bottom-0 left-0 right-0 bg-zinc-950 border-t border-zinc-800 rounded-t-[32px] z-[90] p-8 pb-12 max-h-[80vh] overflow-y-auto shadow-[0_-10px_40px_rgba(0,0,0,0.5)]"
|
||||||
>
|
>
|
||||||
{/* Drag Handle */}
|
{/* Drag Handle */}
|
||||||
<div className="w-12 h-1.5 bg-white/10 rounded-full mx-auto mb-8" />
|
<div className="w-12 h-1.5 bg-zinc-800 rounded-full mx-auto mb-8" />
|
||||||
|
|
||||||
<h2 className="text-2xl font-bold mb-6 font-display text-white">Tasting Session</h2>
|
<h2 className="text-2xl font-bold mb-6 text-zinc-50">Tasting Session</h2>
|
||||||
|
|
||||||
{/* New Session Input */}
|
{/* New Session Input */}
|
||||||
<div className="relative mb-8">
|
<div className="relative mb-8">
|
||||||
@@ -100,12 +100,12 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
|
|||||||
onChange={(e) => setNewSessionName(e.target.value)}
|
onChange={(e) => setNewSessionName(e.target.value)}
|
||||||
onKeyDown={(e) => e.key === 'Enter' && handleCreateSession()}
|
onKeyDown={(e) => e.key === 'Enter' && handleCreateSession()}
|
||||||
placeholder="Neue Session erstellen..."
|
placeholder="Neue Session erstellen..."
|
||||||
className="w-full bg-white/5 border border-white/10 rounded-2xl py-4 px-6 text-white focus:outline-none focus:border-[#C89D46] transition-colors"
|
className="w-full bg-zinc-900 border border-zinc-800 rounded-2xl py-4 px-6 text-zinc-50 focus:outline-none focus:border-orange-600 transition-colors"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleCreateSession}
|
onClick={handleCreateSession}
|
||||||
disabled={isCreating || !newSessionName.trim()}
|
disabled={isCreating || !newSessionName.trim()}
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 p-2 bg-[#C89D46] text-black rounded-xl disabled:opacity-50"
|
className="absolute right-3 top-1/2 -translate-y-1/2 p-2 bg-orange-600 text-white rounded-xl disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isCreating ? <Loader2 size={20} className="animate-spin" /> : <Plus size={20} />}
|
{isCreating ? <Loader2 size={20} className="animate-spin" /> : <Plus size={20} />}
|
||||||
</button>
|
</button>
|
||||||
@@ -113,10 +113,10 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
|
|||||||
|
|
||||||
{/* Session List */}
|
{/* Session List */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-xs font-black uppercase tracking-widest text-white/40 mb-2">Aktuelle Sessions</p>
|
<p className="text-xs font-bold uppercase tracking-widest text-zinc-500 mb-2">Aktuelle Sessions</p>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex justify-center py-8">
|
<div className="flex justify-center py-8">
|
||||||
<Loader2 size={24} className="animate-spin text-white/20" />
|
<Loader2 size={24} className="animate-spin text-zinc-700" />
|
||||||
</div>
|
</div>
|
||||||
) : sessions.length > 0 ? (
|
) : sessions.length > 0 ? (
|
||||||
sessions.map((s) => (
|
sessions.map((s) => (
|
||||||
@@ -126,14 +126,14 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
|
|||||||
setActiveSession({ id: s.id, name: s.name });
|
setActiveSession({ id: s.id, name: s.name });
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
className={`w-full flex items-center justify-between p-4 rounded-2xl border transition-all ${activeSession?.id === s.id ? 'bg-[#C89D46]/10 border-[#C89D46] text-[#C89D46]' : 'bg-white/5 border-white/5 hover:border-white/20 text-white'}`}
|
className={`w-full flex items-center justify-between p-4 rounded-2xl border transition-all ${activeSession?.id === s.id ? 'bg-orange-600/10 border-orange-600 text-orange-500' : 'bg-zinc-900 border-zinc-800 hover:border-zinc-700 text-zinc-50'}`}
|
||||||
>
|
>
|
||||||
<span className="font-bold">{s.name}</span>
|
<span className="font-bold">{s.name}</span>
|
||||||
{activeSession?.id === s.id ? <Check size={20} /> : <ChevronRight size={20} className="text-white/20" />}
|
{activeSession?.id === s.id ? <Check size={20} /> : <ChevronRight size={20} className="text-zinc-700" />}
|
||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 text-white/30 italic">Keine aktiven Sessions gefunden</div>
|
<div className="text-center py-8 text-zinc-600 italic">Keine aktiven Sessions gefunden</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -140,18 +140,18 @@ export default function SessionList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-[#1A1B21] rounded-3xl p-6 border border-white/5 shadow-xl transition-all duration-300">
|
<div className="bg-zinc-900 rounded-3xl p-6 border border-zinc-800 shadow-xl transition-all duration-300">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h3 className="text-sm font-sans font-bold uppercase tracking-[0.2em] flex items-center gap-2 text-[#8F9096]">
|
<h3 className="text-sm font-bold uppercase tracking-[0.2em] flex items-center gap-2 text-zinc-400">
|
||||||
<Calendar size={18} className="text-[#C89D46]" />
|
<Calendar size={18} className="text-orange-600" />
|
||||||
{t('session.title')}
|
{t('session.title')}
|
||||||
{!isCollapsed && sessions.length > 0 && (
|
{!isCollapsed && sessions.length > 0 && (
|
||||||
<span className="text-[10px] font-sans font-bold opacity-50 ml-2">({sessions.length})</span>
|
<span className="text-[10px] font-bold opacity-50 ml-2">({sessions.length})</span>
|
||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
onClick={handleToggleCollapse}
|
onClick={handleToggleCollapse}
|
||||||
className="p-2 hover:bg-white/5 rounded-xl transition-colors text-[#8F9096] hover:text-[#C89D46]"
|
className="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500 hover:text-orange-600"
|
||||||
title={isCollapsed ? 'Aufklappen' : 'Einklappen'}
|
title={isCollapsed ? 'Aufklappen' : 'Einklappen'}
|
||||||
>
|
>
|
||||||
{isCollapsed ? <ChevronDown size={20} /> : <ChevronUp size={20} />}
|
{isCollapsed ? <ChevronDown size={20} /> : <ChevronUp size={20} />}
|
||||||
@@ -166,23 +166,23 @@ export default function SessionList() {
|
|||||||
value={newName}
|
value={newName}
|
||||||
onChange={(e) => setNewName(e.target.value)}
|
onChange={(e) => setNewName(e.target.value)}
|
||||||
placeholder={t('session.sessionName')}
|
placeholder={t('session.sessionName')}
|
||||||
className="flex-1 bg-white/5 border border-white/10 rounded-xl px-4 py-2 text-sm text-white placeholder:text-[#8F9096] focus:outline-none focus:border-[#C89D46] transition-colors"
|
className="flex-1 bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2 text-sm text-zinc-50 placeholder:text-zinc-600 focus:outline-none focus:border-orange-600 transition-colors"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isCreating || !newName.trim()}
|
disabled={isCreating || !newName.trim()}
|
||||||
className="bg-[#C89D46] hover:bg-[#A67D2E] text-[#0F1014] p-2 rounded-xl transition-all disabled:opacity-50"
|
className="bg-orange-600 hover:bg-orange-700 text-white p-2 rounded-xl transition-all disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isCreating ? <Loader2 size={20} className="animate-spin" /> : <Plus size={20} />}
|
{isCreating ? <Loader2 size={20} className="animate-spin" /> : <Plus size={20} />}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex justify-center py-8 text-[#8F9096]">
|
<div className="flex justify-center py-8 text-zinc-500">
|
||||||
<Loader2 size={24} className="animate-spin" />
|
<Loader2 size={24} className="animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
) : sessions.length === 0 ? (
|
) : sessions.length === 0 ? (
|
||||||
<div className="text-center py-8 text-[#8F9096] text-xs font-sans">
|
<div className="text-center py-8 text-zinc-600 text-xs font-bold">
|
||||||
{t('session.noSessions')}
|
{t('session.noSessions')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -191,18 +191,18 @@ export default function SessionList() {
|
|||||||
<div
|
<div
|
||||||
key={session.id}
|
key={session.id}
|
||||||
className={`flex items-center justify-between p-4 rounded-2xl border transition-all ${activeSession?.id === session.id
|
className={`flex items-center justify-between p-4 rounded-2xl border transition-all ${activeSession?.id === session.id
|
||||||
? 'bg-[#C89D46] border-[#C89D46] shadow-lg shadow-[#C89D46]/20'
|
? 'bg-orange-600 border-orange-600 shadow-lg shadow-orange-950/20'
|
||||||
: 'bg-white/5 border-white/5 hover:border-white/10'
|
: 'bg-zinc-950 border-zinc-800 hover:border-zinc-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Link href={`/sessions/${session.id}`} className="flex-1 space-y-1 min-w-0">
|
<Link href={`/sessions/${session.id}`} className="flex-1 space-y-1 min-w-0">
|
||||||
<div className={`font-display font-bold text-lg truncate flex items-center gap-2 ${activeSession?.id === session.id ? 'text-[#0F1014]' : 'text-white'}`}>
|
<div className={`font-bold text-lg truncate flex items-center gap-2 ${activeSession?.id === session.id ? 'text-white' : 'text-zinc-50'}`}>
|
||||||
{session.name}
|
{session.name}
|
||||||
{session.ended_at && (
|
{session.ended_at && (
|
||||||
<span className={`text-[8px] font-sans font-black uppercase px-1.5 py-0.5 rounded border ${activeSession?.id === session.id ? 'bg-black/10 border-black/20 text-[#0F1014]' : 'bg-white/10 border-white/20 text-[#8F9096]'}`}>Closed</span>
|
<span className={`text-[8px] font-bold uppercase px-1.5 py-0.5 rounded border ${activeSession?.id === session.id ? 'bg-black/10 border-black/20 text-white' : 'bg-zinc-800 border-zinc-700 text-zinc-500'}`}>Closed</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={`flex items-center gap-4 text-[10px] font-sans font-bold uppercase tracking-widest ${activeSession?.id === session.id ? 'text-[#0F1014]/60' : 'text-[#8F9096]'}`}>
|
<div className={`flex items-center gap-4 text-[10px] font-bold uppercase tracking-widest ${activeSession?.id === session.id ? 'text-white/60' : 'text-zinc-500'}`}>
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Calendar size={12} />
|
<Calendar size={12} />
|
||||||
{new Date(session.scheduled_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')}
|
{new Date(session.scheduled_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')}
|
||||||
@@ -225,28 +225,28 @@ export default function SessionList() {
|
|||||||
!session.ended_at ? (
|
!session.ended_at ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveSession({ id: session.id, name: session.name })}
|
onClick={() => setActiveSession({ id: session.id, name: session.name })}
|
||||||
className="p-2 bg-white/10 text-white rounded-xl hover:bg-[#C89D46] hover:text-[#0F1014] transition-all"
|
className="p-2 bg-zinc-800 text-zinc-50 rounded-xl hover:bg-orange-600 hover:text-white transition-all"
|
||||||
title="Start Session"
|
title="Start Session"
|
||||||
>
|
>
|
||||||
<GlassWater size={18} />
|
<GlassWater size={18} />
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-2 bg-white/5 text-[#8F9096] rounded-xl border border-white/5 opacity-50">
|
<div className="p-2 bg-zinc-900 text-zinc-500 rounded-xl border border-zinc-800 opacity-50">
|
||||||
<Check size={18} />
|
<Check size={18} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<div className="p-2 bg-black/10 text-[#0F1014] rounded-xl">
|
<div className="p-2 bg-black/10 text-white rounded-xl">
|
||||||
<Check size={18} />
|
<Check size={18} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ChevronRight size={20} className={activeSession?.id === session.id ? 'text-[#0F1014]/40' : 'text-white/20'} />
|
<ChevronRight size={20} className={activeSession?.id === session.id ? 'text-white/40' : 'text-zinc-700'} />
|
||||||
<button
|
<button
|
||||||
onClick={(e) => handleDeleteSession(e, session.id)}
|
onClick={(e) => handleDeleteSession(e, session.id)}
|
||||||
disabled={!!isDeleting}
|
disabled={!!isDeleting}
|
||||||
className={`p-2 rounded-xl transition-all ${activeSession?.id === session.id
|
className={`p-2 rounded-xl transition-all ${activeSession?.id === session.id
|
||||||
? 'text-[#0F1014]/40 hover:text-[#0F1014]'
|
? 'text-white/40 hover:text-white'
|
||||||
: 'text-[#8F9096] hover:text-red-400'
|
: 'text-zinc-600 hover:text-red-500'
|
||||||
}`}
|
}`}
|
||||||
title="Session löschen"
|
title="Session löschen"
|
||||||
>
|
>
|
||||||
@@ -268,17 +268,17 @@ export default function SessionList() {
|
|||||||
<div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1">
|
<div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1">
|
||||||
<div className="flex -space-x-1.5 overflow-hidden">
|
<div className="flex -space-x-1.5 overflow-hidden">
|
||||||
{sessions.slice(0, 3).map((s, i) => (
|
{sessions.slice(0, 3).map((s, i) => (
|
||||||
<div key={s.id} className="w-7 h-7 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center text-[10px] font-display font-bold text-[#C89D46] shadow-sm">
|
<div key={s.id} className="w-7 h-7 rounded-lg bg-zinc-950 border border-zinc-800 flex items-center justify-center text-[10px] font-bold text-orange-600 shadow-sm">
|
||||||
{s.name[0].toUpperCase()}
|
{s.name[0].toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{sessions.length > 3 && (
|
{sessions.length > 3 && (
|
||||||
<div className="w-7 h-7 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center text-[8px] font-sans font-bold text-[#8F9096] shadow-sm">
|
<div className="w-7 h-7 rounded-lg bg-zinc-950 border border-zinc-800 flex items-center justify-center text-[8px] font-bold text-zinc-500 shadow-sm">
|
||||||
+{sessions.length - 3}
|
+{sessions.length - 3}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] text-[#8F9096] font-sans font-bold uppercase tracking-widest ml-1">{sessions.length} Sessions</span>
|
<span className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest ml-1">{sessions.length} Sessions</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,46 +61,46 @@ export default function SessionTimeline({ tastings, sessionStart }: SessionTimel
|
|||||||
return (
|
return (
|
||||||
<div key={tasting.id} className="relative group">
|
<div key={tasting.id} className="relative group">
|
||||||
{/* Dot */}
|
{/* Dot */}
|
||||||
<div className={`absolute -left-[22px] top-4 w-5 h-5 rounded-full border-[3px] border-zinc-100 dark:border-black shadow-sm z-10 flex items-center justify-center ${isSmoky ? 'bg-amber-600' : 'bg-zinc-400'}`}>
|
<div className={`absolute -left-[22px] top-4 w-5 h-5 rounded-full border-[3px] border-zinc-950 shadow-sm z-10 flex items-center justify-center ${isSmoky ? 'bg-orange-600' : 'bg-zinc-600'}`}>
|
||||||
{isSmoky && <Droplets size={8} className="text-white fill-white" />}
|
{isSmoky && <Droplets size={8} className="text-white fill-white" />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white dark:bg-zinc-900 p-4 rounded-2xl border border-zinc-200 dark:border-zinc-800 shadow-sm hover:shadow-md transition-shadow group-hover:border-amber-500/30">
|
<div className="bg-zinc-900 p-4 rounded-2xl border border-zinc-800 shadow-sm hover:shadow-md transition-shadow group-hover:border-orange-500/30">
|
||||||
<div className="flex justify-between items-start gap-3">
|
<div className="flex justify-between items-start gap-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<span className="text-[10px] font-black text-amber-600 uppercase tracking-widest leading-none">Dram #{index + 1}</span>
|
<span className="text-[10px] font-bold text-orange-600 uppercase tracking-widest leading-none">Dram #{index + 1}</span>
|
||||||
<span className="text-[10px] font-bold text-zinc-400 dark:text-zinc-600 uppercase tracking-tight leading-none">
|
<span className="text-[10px] font-bold text-zinc-500 uppercase tracking-tight leading-none">
|
||||||
{wallClockTime} ({index === 0 ? 'Start' : `+${diffMinutes}m`})
|
{wallClockTime} ({index === 0 ? 'Start' : `+${diffMinutes}m`})
|
||||||
</span>
|
</span>
|
||||||
{isSmoky && (
|
{isSmoky && (
|
||||||
<span className="bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400 text-[8px] font-black px-1.5 py-0.5 rounded-md uppercase tracking-tighter">Peat Bomb</span>
|
<span className="bg-orange-900/40 text-orange-500 text-[8px] font-bold px-1.5 py-0.5 rounded-md uppercase tracking-tighter border border-orange-500/20">Peat Bomb</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
href={`/bottles/${tasting.bottle_id}`}
|
href={`/bottles/${tasting.bottle_id}`}
|
||||||
className="text-sm font-bold text-zinc-800 dark:text-zinc-100 hover:text-amber-600 truncate block mt-0.5"
|
className="text-sm font-bold text-zinc-100 hover:text-orange-600 truncate block mt-0.5 uppercase tracking-tight"
|
||||||
>
|
>
|
||||||
{tasting.bottle_name}
|
{tasting.bottle_name}
|
||||||
</Link>
|
</Link>
|
||||||
<div className="mt-2 flex flex-wrap gap-1">
|
<div className="mt-2 flex flex-wrap gap-1">
|
||||||
{tasting.tags.slice(0, 2).map(tag => (
|
{tasting.tags.slice(0, 2).map(tag => (
|
||||||
<span key={tag} className="text-[9px] text-zinc-500 dark:text-zinc-500 bg-zinc-100 dark:bg-zinc-800/50 px-2 py-0.5 rounded-full">
|
<span key={tag} className="text-[9px] text-zinc-500 bg-zinc-800/50 px-2 py-0.5 rounded-full border border-zinc-800">
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0 flex flex-col items-end">
|
<div className="shrink-0 flex flex-col items-end">
|
||||||
<div className="text-lg font-black text-zinc-900 dark:text-white leading-none">{tasting.rating}</div>
|
<div className="text-lg font-bold text-zinc-50 leading-none">{tasting.rating}</div>
|
||||||
<div className="text-[9px] font-bold text-zinc-400 uppercase tracking-tighter mt-1">Punkte</div>
|
<div className="text-[9px] font-bold text-zinc-500 uppercase tracking-tighter mt-1">Punkte</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{wasPreviousSmoky && timeSinceLastDram < 20 && (
|
{wasPreviousSmoky && timeSinceLastDram < 20 && (
|
||||||
<div className="mt-4 p-2 bg-amber-50 dark:bg-amber-900/10 border border-amber-200/50 dark:border-amber-900/30 rounded-xl flex items-center gap-2 animate-in slide-in-from-top-1">
|
<div className="mt-4 p-2 bg-orange-900/10 border border-orange-900/30 rounded-xl flex items-center gap-2 animate-in slide-in-from-top-1">
|
||||||
<AlertTriangle size={12} className="text-amber-600 shrink-0" />
|
<AlertTriangle size={12} className="text-orange-600 shrink-0" />
|
||||||
<p className="text-[9px] text-amber-800 dark:text-amber-400 font-bold leading-tight">
|
<p className="text-[9px] text-orange-400 font-bold leading-tight">
|
||||||
Achtung: Gaumen war noch torf-belegt (nur {timeSinceLastDram}m Abstand).
|
Achtung: Gaumen war noch torf-belegt (nur {timeSinceLastDram}m Abstand).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ export default function StatsDashboard({ bottles }: StatsDashboardProps) {
|
|||||||
label: t('home.stats.avgRating'),
|
label: t('home.stats.avgRating'),
|
||||||
value: `${stats.avgRating}/100`,
|
value: `${stats.avgRating}/100`,
|
||||||
icon: Star,
|
icon: Star,
|
||||||
color: 'text-amber-600',
|
color: 'text-orange-600',
|
||||||
bg: 'bg-amber-50 dark:bg-amber-900/20'
|
bg: 'bg-orange-900/20'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('home.stats.topDistillery'),
|
label: t('home.stats.topDistillery'),
|
||||||
@@ -74,17 +74,18 @@ export default function StatsDashboard({ bottles }: StatsDashboardProps) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-4 h-fit animate-in fade-in slide-in-from-top-4 duration-500">
|
<div className="w-full grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-4 h-fit animate-in fade-in slide-in-from-top-4 duration-500 mb-12">
|
||||||
{statItems.map((item, idx) => {
|
{statItems.map((item, idx) => {
|
||||||
|
const value = idx === 1 ? stats.totalCount : item.value;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
className="flex flex-col gap-1 text-center md:text-left"
|
className="flex flex-col gap-1 text-center md:text-left"
|
||||||
>
|
>
|
||||||
<div className="text-4xl md:text-5xl font-display font-bold text-white tracking-tighter">
|
<div className="text-4xl md:text-5xl font-bold text-zinc-50 tracking-tighter">
|
||||||
{idx === 1 ? stats.totalCount : item.value}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] font-sans font-bold uppercase text-[#8F9096] tracking-widest px-1">
|
<div className="text-[10px] font-bold uppercase text-zinc-500 tracking-[0.2em]">
|
||||||
{item.label}
|
{item.label}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -70,35 +70,35 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
|
|||||||
key={tag.id}
|
key={tag.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onToggleTag(tag.id)}
|
onClick={() => onToggleTag(tag.id)}
|
||||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-amber-600 text-white rounded-full text-[10px] font-black uppercase tracking-tight shadow-sm shadow-amber-600/20 animate-in fade-in zoom-in-95"
|
className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-orange-600 text-white rounded-full text-[10px] font-bold uppercase tracking-tight shadow-sm shadow-orange-600/20 animate-in fade-in zoom-in-95"
|
||||||
>
|
>
|
||||||
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
|
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
|
||||||
<X size={12} />
|
<X size={12} />
|
||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<span className="text-[10px] italic text-zinc-400 font-medium">Noch keine Tags gewählt...</span>
|
<span className="text-[10px] italic text-zinc-500 font-medium">Noch keine Tags gewählt...</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search and Suggest */}
|
{/* Search and Suggest */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="relative flex items-center">
|
<div className="relative flex items-center">
|
||||||
<Search className="absolute left-3 text-zinc-400" size={14} />
|
<Search className="absolute left-3 text-zinc-500" size={14} />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
placeholder="Tag suchen oder hinzufügen..."
|
placeholder="Tag suchen oder hinzufügen..."
|
||||||
className="w-full pl-9 pr-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-xs focus:ring-2 focus:ring-amber-500 outline-none transition-all dark:text-zinc-200 placeholder:text-zinc-400"
|
className="w-full pl-9 pr-4 py-2 bg-zinc-900 border border-zinc-800 rounded-xl text-xs focus:ring-1 focus:ring-orange-600 outline-none transition-all text-zinc-200 placeholder:text-zinc-600"
|
||||||
/>
|
/>
|
||||||
{isCreating && (
|
{isCreating && (
|
||||||
<Loader2 className="absolute right-3 animate-spin text-amber-600" size={14} />
|
<Loader2 className="absolute right-3 animate-spin text-orange-600" size={14} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{search && (
|
{search && (
|
||||||
<div className="absolute z-10 w-full mt-2 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-2xl shadow-xl overflow-hidden animate-in fade-in slide-in-from-top-2">
|
<div className="absolute z-10 w-full mt-2 bg-zinc-900 border border-zinc-800 rounded-2xl shadow-xl overflow-hidden animate-in fade-in slide-in-from-top-2">
|
||||||
<div className="max-h-48 overflow-y-auto">
|
<div className="max-h-48 overflow-y-auto">
|
||||||
{filteredTags.length > 0 ? (
|
{filteredTags.length > 0 ? (
|
||||||
filteredTags.map(tag => (
|
filteredTags.map(tag => (
|
||||||
@@ -109,17 +109,17 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
|
|||||||
onToggleTag(tag.id);
|
onToggleTag(tag.id);
|
||||||
setSearch('');
|
setSearch('');
|
||||||
}}
|
}}
|
||||||
className="w-full px-4 py-2.5 text-left text-xs font-bold text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-zinc-700/50 flex items-center justify-between border-b border-zinc-100 dark:border-zinc-700 last:border-0"
|
className="w-full px-4 py-2.5 text-left text-xs font-bold text-zinc-300 hover:bg-zinc-800/50 flex items-center justify-between border-b border-zinc-800 last:border-0"
|
||||||
>
|
>
|
||||||
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
|
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
|
||||||
{selectedTagIds.includes(tag.id) && <Check size={12} className="text-amber-600" />}
|
{selectedTagIds.includes(tag.id) && <Check size={12} className="text-orange-600" />}
|
||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleCreateTag}
|
onClick={handleCreateTag}
|
||||||
className="w-full px-4 py-3 text-left text-xs font-bold text-amber-600 hover:bg-amber-50 dark:hover:bg-amber-900/10 flex items-center gap-2"
|
className="w-full px-4 py-3 text-left text-xs font-bold text-orange-600 hover:bg-orange-950/10 flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Plus size={14} />
|
<Plus size={14} />
|
||||||
"{search}" als neuen Tag hinzufügen
|
"{search}" als neuen Tag hinzufügen
|
||||||
@@ -133,7 +133,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
|
|||||||
{/* AI Suggestions */}
|
{/* AI Suggestions */}
|
||||||
{!search && suggestedTagNames && suggestedTagNames.length > 0 && (
|
{!search && suggestedTagNames && suggestedTagNames.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1.5 text-[9px] font-black uppercase tracking-widest text-amber-500">
|
<div className="flex items-center gap-1.5 text-[9px] font-bold uppercase tracking-widest text-orange-500">
|
||||||
<Sparkles size={10} /> {t('camera.wbMatchFound') ? 'KI Vorschläge' : 'AI Suggestions'}
|
<Sparkles size={10} /> {t('camera.wbMatchFound') ? 'KI Vorschläge' : 'AI Suggestions'}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
@@ -144,7 +144,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
|
|||||||
key={tag.id}
|
key={tag.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onToggleTag(tag.id)}
|
onClick={() => onToggleTag(tag.id)}
|
||||||
className="px-2.5 py-1 rounded-lg bg-amber-50 dark:bg-amber-900/20 text-amber-600 dark:text-amber-400 text-[10px] font-bold uppercase tracking-tight hover:bg-amber-100 dark:hover:bg-amber-900/30 transition-colors border border-amber-200 dark:border-amber-800/50 flex items-center gap-1.5"
|
className="px-2.5 py-1 rounded-lg bg-orange-950/20 text-orange-500 text-[10px] font-bold uppercase tracking-tight hover:bg-orange-900/30 transition-colors border border-orange-900/50 flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<Sparkles size={10} />
|
<Sparkles size={10} />
|
||||||
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
|
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
|
||||||
@@ -157,7 +157,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
|
|||||||
{/* AI Custom Suggestions */}
|
{/* AI Custom Suggestions */}
|
||||||
{!search && suggestedCustomTagNames && suggestedCustomTagNames.length > 0 && (
|
{!search && suggestedCustomTagNames && suggestedCustomTagNames.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1.5 text-[9px] font-black uppercase tracking-widest text-zinc-400">
|
<div className="flex items-center gap-1.5 text-[9px] font-bold uppercase tracking-widest text-zinc-500">
|
||||||
Dominante Note anlegen?
|
Dominante Note anlegen?
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
@@ -177,7 +177,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
|
|||||||
}
|
}
|
||||||
setCreatingSuggestion(null);
|
setCreatingSuggestion(null);
|
||||||
}}
|
}}
|
||||||
className="px-2.5 py-1 rounded-lg bg-zinc-50 dark:bg-zinc-800/50 text-zinc-500 dark:text-zinc-400 text-[10px] font-bold uppercase tracking-tight hover:bg-amber-600 hover:text-white transition-all border border-dashed border-zinc-200 dark:border-zinc-700/50 flex items-center gap-1.5 disabled:opacity-50"
|
className="px-2.5 py-1 rounded-lg bg-zinc-900/50 text-zinc-400 text-[10px] font-bold uppercase tracking-tight hover:bg-orange-600 hover:text-white transition-all border border-dashed border-zinc-800 flex items-center gap-1.5 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{creatingSuggestion === name ? <Loader2 size={10} className="animate-spin" /> : <Plus size={10} />}
|
{creatingSuggestion === name ? <Loader2 size={10} className="animate-spin" /> : <Plus size={10} />}
|
||||||
{name}
|
{name}
|
||||||
@@ -188,7 +188,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Suggestions Chips (limit to 6 random or most common) */}
|
{/* Suggestions Chips (limit to 6 random or most common) */}
|
||||||
{!search && tags.length > 0 && (
|
{!search && (tags || []).length > 0 && (
|
||||||
<div className="flex flex-wrap gap-1.5 pt-1">
|
<div className="flex flex-wrap gap-1.5 pt-1">
|
||||||
{(tags || [])
|
{(tags || [])
|
||||||
.filter(t => !selectedTagIds.includes(t.id) && (!suggestedTagNames || !suggestedTagNames.some((s: string) => s.toLowerCase() === t.name.toLowerCase())))
|
.filter(t => !selectedTagIds.includes(t.id) && (!suggestedTagNames || !suggestedTagNames.some((s: string) => s.toLowerCase() === t.name.toLowerCase())))
|
||||||
@@ -198,7 +198,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
|
|||||||
key={tag.id}
|
key={tag.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onToggleTag(tag.id)}
|
onClick={() => onToggleTag(tag.id)}
|
||||||
className="px-2.5 py-1 rounded-lg bg-zinc-100 dark:bg-zinc-800 text-zinc-500 dark:text-zinc-400 text-[10px] font-bold uppercase tracking-tight hover:bg-zinc-200 dark:hover:bg-zinc-700 hover:text-zinc-700 dark:hover:text-zinc-200 transition-colors border border-zinc-200/50 dark:border-zinc-700/50"
|
className="px-2.5 py-1 rounded-lg bg-zinc-900 text-zinc-500 text-[10px] font-bold uppercase tracking-tight hover:bg-zinc-800 hover:text-zinc-200 transition-colors border border-zinc-800"
|
||||||
>
|
>
|
||||||
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
|
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -123,18 +123,18 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex flex-col w-full bg-[#0F1014] h-full overflow-hidden">
|
<div className="flex-1 flex flex-col w-full bg-zinc-950 h-full overflow-hidden">
|
||||||
{/* Top Context Bar - Flex Child 1 */}
|
{/* Top Context Bar - Flex Child 1 */}
|
||||||
<div className="w-full bg-black/40 backdrop-blur-md border-b border-white/10 shrink-0">
|
<div className="w-full bg-zinc-900 border-b border-zinc-800 shrink-0">
|
||||||
<button
|
<button
|
||||||
onClick={onOpenSessions}
|
onClick={onOpenSessions}
|
||||||
className="max-w-2xl mx-auto w-full p-6 flex items-center justify-between group"
|
className="max-w-2xl mx-auto w-full p-6 flex items-center justify-between group"
|
||||||
>
|
>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<p className="text-[10px] font-black uppercase tracking-widest text-[#C89D46]">Kontext</p>
|
<p className="text-[10px] font-bold uppercase tracking-widest text-orange-500">Kontext</p>
|
||||||
<p className="font-bold text-white leading-none mt-1">{activeSessionName || 'Trinkst du in Gesellschaft?'}</p>
|
<p className="font-bold text-zinc-50 leading-none mt-1">{activeSessionName || 'Trinkst du in Gesellschaft?'}</p>
|
||||||
</div>
|
</div>
|
||||||
<ChevronDown size={20} className="text-[#C89D46] group-hover:translate-y-1 transition-transform" />
|
<ChevronDown size={20} className="text-orange-500 group-hover:translate-y-1 transition-transform" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -146,50 +146,50 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={{ opacity: 0, y: -20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="p-5 bg-amber-500/10 border border-amber-500/20 rounded-3xl flex items-start gap-3"
|
className="p-5 bg-orange-500/10 border border-orange-500/20 rounded-2xl flex items-start gap-3"
|
||||||
>
|
>
|
||||||
<AlertTriangle size={24} className="text-amber-500 shrink-0 mt-0.5" />
|
<AlertTriangle size={24} className="text-orange-500 shrink-0 mt-0.5" />
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-[10px] font-black uppercase tracking-wider text-amber-500">Palette-Checker</p>
|
<p className="text-[10px] font-bold uppercase tracking-wider text-orange-500">Palette-Checker</p>
|
||||||
<p className="text-xs font-bold text-white leading-relaxed">
|
<p className="text-xs font-bold text-zinc-50 leading-relaxed">
|
||||||
Dein letzter Dram "{lastDramInSession?.name}" war torfig. Trink etwas Wasser!
|
Dein letzter Dram "{lastDramInSession?.name}" war torfig. Trink etwas Wasser!
|
||||||
</p>
|
</p>
|
||||||
<button onClick={() => setShowPaletteWarning(false)} className="text-[10px] font-black uppercase text-amber-500 underline mt-2 block">Verstanden</button>
|
<button onClick={() => setShowPaletteWarning(false)} className="text-[10px] font-bold uppercase text-orange-500 underline mt-2 block">Verstanden</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<div className="w-24 h-32 bg-white/5 rounded-2xl border border-white/10 flex items-center justify-center overflow-hidden shrink-0 shadow-2xl relative">
|
<div className="w-24 h-32 bg-zinc-900 rounded-xl border border-zinc-800 flex items-center justify-center overflow-hidden shrink-0 shadow-2xl relative">
|
||||||
{image ? (
|
{image ? (
|
||||||
<img src={image} alt="Bottle Preview" className="w-full h-full object-cover" />
|
<img src={image} alt="Bottle Preview" className="w-full h-full object-cover" />
|
||||||
) : (
|
) : (
|
||||||
<div className="text-[10px] text-white/20 uppercase font-black rotate-[-15deg]">No Photo</div>
|
<div className="text-[10px] text-zinc-700 uppercase font-bold rotate-[-15deg]">No Photo</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h1 className="text-3xl font-black text-amber-600 mb-1 truncate leading-none uppercase tracking-tight">
|
<h1 className="text-3xl font-bold text-orange-600 mb-1 truncate leading-none uppercase tracking-tight">
|
||||||
{bottleMetadata.distillery || 'Destillerie'}
|
{bottleMetadata.distillery || 'Destillerie'}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-white text-xl font-bold truncate mb-2">{bottleMetadata.name || 'Unbekannter Malt'}</p>
|
<p className="text-zinc-50 text-xl font-bold truncate mb-2">{bottleMetadata.name || 'Unbekannter Malt'}</p>
|
||||||
<p className="text-white/40 text-[10px] font-black uppercase tracking-widest leading-none">
|
<p className="text-zinc-500 text-[10px] font-bold uppercase tracking-widest leading-none">
|
||||||
{bottleMetadata.category || 'Whisky'} {bottleMetadata.abv ? `• ${bottleMetadata.abv}%` : ''} {bottleMetadata.age ? `• ${bottleMetadata.age}y` : ''}
|
{bottleMetadata.category || 'Whisky'} {bottleMetadata.abv ? `• ${bottleMetadata.abv}%` : ''} {bottleMetadata.age ? `• ${bottleMetadata.age}y` : ''}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Rating Slider */}
|
{/* Rating Slider */}
|
||||||
<div className="space-y-6 bg-white/5 p-8 rounded-[40px] border border-white/10 shadow-inner relative overflow-hidden">
|
<div className="space-y-6 bg-zinc-900 p-8 rounded-3xl border border-zinc-800 shadow-inner relative overflow-hidden">
|
||||||
<div className="absolute top-0 right-0 p-4 opacity-5 pointer-events-none">
|
<div className="absolute top-0 right-0 p-4 opacity-5 pointer-events-none">
|
||||||
<Zap size={120} className="text-amber-500" />
|
<Zap size={120} className="text-orange-500" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between relative z-10">
|
<div className="flex items-center justify-between relative z-10">
|
||||||
<label className="text-xs font-black text-white/40 uppercase tracking-[0.2em] flex items-center gap-2">
|
<label className="text-xs font-bold text-zinc-500 uppercase tracking-[0.2em] flex items-center gap-2">
|
||||||
<Star size={14} className="text-amber-500 fill-amber-500" />
|
<Star size={14} className="text-orange-500 fill-orange-500" />
|
||||||
{t('tasting.rating')}
|
{t('tasting.rating')}
|
||||||
</label>
|
</label>
|
||||||
<span className="text-4xl font-black text-amber-600 tracking-tighter">{rating}<span className="text-white/20 text-sm ml-1">/100</span></span>
|
<span className="text-4xl font-bold text-orange-600 tracking-tighter">{rating}<span className="text-zinc-700 text-sm ml-1">/100</span></span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
@@ -197,9 +197,9 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
max="100"
|
max="100"
|
||||||
value={rating}
|
value={rating}
|
||||||
onChange={(e) => setRating(parseInt(e.target.value))}
|
onChange={(e) => setRating(parseInt(e.target.value))}
|
||||||
className="w-full h-2 bg-zinc-800 rounded-full appearance-none cursor-pointer accent-amber-600 transition-all"
|
className="w-full h-2 bg-zinc-800 rounded-full appearance-none cursor-pointer accent-orange-600 transition-all"
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-between text-[10px] text-zinc-500 font-black uppercase tracking-widest px-1 relative z-10">
|
<div className="flex justify-between text-[10px] text-zinc-500 font-bold uppercase tracking-widest px-1 relative z-10">
|
||||||
<span>Swill</span>
|
<span>Swill</span>
|
||||||
<span>Dram</span>
|
<span>Dram</span>
|
||||||
<span>Legendary</span>
|
<span>Legendary</span>
|
||||||
@@ -210,9 +210,9 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
<button
|
<button
|
||||||
key={type}
|
key={type}
|
||||||
onClick={() => setIsSample(type === 'Sample')}
|
onClick={() => setIsSample(type === 'Sample')}
|
||||||
className={`flex-1 py-4 rounded-2xl text-[10px] font-black uppercase tracking-widest border transition-all ${(type === 'Sample' ? isSample : !isSample)
|
className={`flex-1 py-4 rounded-xl text-[10px] font-bold uppercase tracking-widest border transition-all ${(type === 'Sample' ? isSample : !isSample)
|
||||||
? 'bg-zinc-100 border-zinc-100 text-zinc-900 shadow-lg'
|
? 'bg-orange-600 border-orange-600 text-white shadow-lg'
|
||||||
: 'bg-transparent border-white/10 text-white/40 hover:border-white/30'
|
: 'bg-transparent border-zinc-800 text-zinc-500 hover:border-zinc-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{type}
|
{type}
|
||||||
@@ -230,14 +230,14 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
{/* Sections */}
|
{/* Sections */}
|
||||||
<div className="space-y-12 pb-12">
|
<div className="space-y-12 pb-12">
|
||||||
{/* Nose Section */}
|
{/* Nose Section */}
|
||||||
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-[40px] border border-white/5">
|
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-3xl border border-zinc-800">
|
||||||
<div className="flex items-center gap-5">
|
<div className="flex items-center gap-5">
|
||||||
<div className="w-14 h-14 rounded-3xl bg-amber-500/10 flex items-center justify-center text-amber-500 shrink-0 border border-amber-500/20 shadow-xl">
|
<div className="w-14 h-14 rounded-2xl bg-orange-500/10 flex items-center justify-center text-orange-500 shrink-0 border border-orange-500/20 shadow-xl">
|
||||||
<Wind size={28} />
|
<Wind size={28} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-black text-white uppercase tracking-widest leading-none">{t('tasting.nose')}</h3>
|
<h3 className="text-xl font-bold text-zinc-50 uppercase tracking-widest leading-none">{t('tasting.nose')}</h3>
|
||||||
<p className="text-[10px] text-white/30 font-black uppercase tracking-widest mt-2 px-0.5">Aroma & Bouquet</p>
|
<p className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest mt-2 px-0.5">Aroma & Bouquet</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
<CustomSlider label="Nose Intensity" value={noseScore} onChange={setNoseScore} icon={<Sparkles size={16} />} />
|
<CustomSlider label="Nose Intensity" value={noseScore} onChange={setNoseScore} icon={<Sparkles size={16} />} />
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Tags</p>
|
<p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Tags</p>
|
||||||
<TagSelector
|
<TagSelector
|
||||||
category="nose"
|
category="nose"
|
||||||
selectedTagIds={noseTagIds}
|
selectedTagIds={noseTagIds}
|
||||||
@@ -255,26 +255,26 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Eigene Notizen</p>
|
<p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Eigene Notizen</p>
|
||||||
<textarea
|
<textarea
|
||||||
value={noseNotes}
|
value={noseNotes}
|
||||||
onChange={(e) => setNoseNotes(e.target.value)}
|
onChange={(e) => setNoseNotes(e.target.value)}
|
||||||
placeholder={t('tasting.notesPlaceholder') || "Wie riecht er?..."}
|
placeholder={t('tasting.notesPlaceholder') || "Wie riecht er?..."}
|
||||||
className="w-full p-6 bg-zinc-900 border-none rounded-3xl text-sm text-zinc-200 focus:ring-2 focus:ring-amber-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600 shadow-inner"
|
className="w-full p-6 bg-zinc-900 border border-zinc-800 rounded-2xl text-sm text-zinc-200 focus:ring-1 focus:ring-orange-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Palate Section */}
|
{/* Palate Section */}
|
||||||
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-[40px] border border-white/5">
|
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-3xl border border-zinc-800">
|
||||||
<div className="flex items-center gap-5">
|
<div className="flex items-center gap-5">
|
||||||
<div className="w-14 h-14 rounded-3xl bg-amber-500/10 flex items-center justify-center text-amber-500 shrink-0 border border-amber-500/20 shadow-xl">
|
<div className="w-14 h-14 rounded-2xl bg-orange-500/10 flex items-center justify-center text-orange-500 shrink-0 border border-orange-500/20 shadow-xl">
|
||||||
<Utensils size={28} />
|
<Utensils size={28} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-black text-white uppercase tracking-widest leading-none">{t('tasting.palate')}</h3>
|
<h3 className="text-xl font-bold text-zinc-50 uppercase tracking-widest leading-none">{t('tasting.palate')}</h3>
|
||||||
<p className="text-[10px] text-white/30 font-black uppercase tracking-widest mt-2 px-0.5">Geschmack & Textur</p>
|
<p className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest mt-2 px-0.5">Geschmack & Textur</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -282,7 +282,7 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
<CustomSlider label="Taste Impact" value={tasteScore} onChange={setTasteScore} icon={<Sparkles size={16} />} />
|
<CustomSlider label="Taste Impact" value={tasteScore} onChange={setTasteScore} icon={<Sparkles size={16} />} />
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Tags</p>
|
<p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Tags</p>
|
||||||
<TagSelector
|
<TagSelector
|
||||||
category="taste"
|
category="taste"
|
||||||
selectedTagIds={palateTagIds}
|
selectedTagIds={palateTagIds}
|
||||||
@@ -292,35 +292,35 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Eigene Notizen</p>
|
<p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Eigene Notizen</p>
|
||||||
<textarea
|
<textarea
|
||||||
value={palateNotes}
|
value={palateNotes}
|
||||||
onChange={(e) => setPalateNotes(e.target.value)}
|
onChange={(e) => setPalateNotes(e.target.value)}
|
||||||
placeholder={t('tasting.notesPlaceholder') || "Wie schmeckt er?..."}
|
placeholder={t('tasting.notesPlaceholder') || "Wie schmeckt er?..."}
|
||||||
className="w-full p-6 bg-zinc-900 border-none rounded-3xl text-sm text-zinc-200 focus:ring-2 focus:ring-amber-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600 shadow-inner"
|
className="w-full p-6 bg-zinc-900 border border-zinc-800 rounded-2xl text-sm text-zinc-200 focus:ring-1 focus:ring-orange-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Finish Section */}
|
{/* Finish Section */}
|
||||||
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-[40px] border border-white/5">
|
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-3xl border border-zinc-800">
|
||||||
<div className="flex items-center gap-5">
|
<div className="flex items-center gap-5">
|
||||||
<div className="w-14 h-14 rounded-3xl bg-amber-500/10 flex items-center justify-center text-amber-500 shrink-0 border border-amber-500/20 shadow-xl">
|
<div className="w-14 h-14 rounded-2xl bg-orange-500/10 flex items-center justify-center text-orange-500 shrink-0 border border-orange-500/20 shadow-xl">
|
||||||
<Droplets size={28} />
|
<Droplets size={28} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-black text-white uppercase tracking-widest leading-none">{t('tasting.finish')}</h3>
|
<h3 className="text-xl font-bold text-zinc-50 uppercase tracking-widest leading-none">{t('tasting.finish')}</h3>
|
||||||
<p className="text-[10px] text-white/30 font-black uppercase tracking-widest mt-2 px-0.5">Abgang & Nachklang</p>
|
<p className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest mt-2 px-0.5">Abgang & Nachklang</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<CustomSlider label="Finish Duration" value={finishScore} onChange={setFinishScore} icon={<Sparkles size={16} />} />
|
<CustomSlider label="Finish Duration" value={finishScore} onChange={setFinishScore} icon={<Sparkles size={16} />} />
|
||||||
|
|
||||||
<div className="space-y-6 pt-4 border-t border-white/5">
|
<div className="space-y-6 pt-4 border-t border-zinc-800">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Aroma Tags</p>
|
<p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Aroma Tags</p>
|
||||||
<TagSelector
|
<TagSelector
|
||||||
category="finish"
|
category="finish"
|
||||||
selectedTagIds={finishTagIds}
|
selectedTagIds={finishTagIds}
|
||||||
@@ -330,7 +330,7 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Gefühl & Textur</p>
|
<p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Gefühl & Textur</p>
|
||||||
<TagSelector
|
<TagSelector
|
||||||
category="texture"
|
category="texture"
|
||||||
selectedTagIds={textureTagIds}
|
selectedTagIds={textureTagIds}
|
||||||
@@ -341,12 +341,12 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Eigene Notizen</p>
|
<p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Eigene Notizen</p>
|
||||||
<textarea
|
<textarea
|
||||||
value={finishNotes}
|
value={finishNotes}
|
||||||
onChange={(e) => setFinishNotes(e.target.value)}
|
onChange={(e) => setFinishNotes(e.target.value)}
|
||||||
placeholder={t('tasting.notesPlaceholder') || "Der bleibende Eindruck..."}
|
placeholder={t('tasting.notesPlaceholder') || "Der bleibende Eindruck..."}
|
||||||
className="w-full p-6 bg-zinc-900 border-none rounded-3xl text-sm text-zinc-200 focus:ring-2 focus:ring-amber-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600 shadow-inner"
|
className="w-full p-6 bg-zinc-900 border border-zinc-800 rounded-2xl text-sm text-zinc-200 focus:ring-1 focus:ring-orange-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -354,14 +354,14 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
|
|
||||||
{/* Buddy Selection */}
|
{/* Buddy Selection */}
|
||||||
{buddies && buddies.length > 0 && (
|
{buddies && buddies.length > 0 && (
|
||||||
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-[40px] border border-white/5">
|
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-3xl border border-zinc-800">
|
||||||
<div className="flex items-center gap-5">
|
<div className="flex items-center gap-5">
|
||||||
<div className="w-14 h-14 rounded-3xl bg-amber-500/10 flex items-center justify-center text-amber-500 shrink-0 border border-amber-500/20 shadow-xl">
|
<div className="w-14 h-14 rounded-2xl bg-orange-500/10 flex items-center justify-center text-orange-500 shrink-0 border border-orange-500/20 shadow-xl">
|
||||||
<Users size={28} />
|
<Users size={28} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-black text-white uppercase tracking-widest leading-none">Mit wem trinkst du?</h3>
|
<h3 className="text-xl font-bold text-zinc-50 uppercase tracking-widest leading-none">Mit wem trinkst du?</h3>
|
||||||
<p className="text-[10px] text-white/30 font-black uppercase tracking-widest mt-2 px-0.5">Gesellschaft & Buddies</p>
|
<p className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest mt-2 px-0.5">Gesellschaft & Buddies</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
@@ -369,9 +369,9 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
<button
|
<button
|
||||||
key={buddy.id}
|
key={buddy.id}
|
||||||
onClick={() => toggleBuddy(buddy.id)}
|
onClick={() => toggleBuddy(buddy.id)}
|
||||||
className={`px-5 py-3 rounded-2xl text-[10px] font-black uppercase transition-all border flex items-center gap-2 ${selectedBuddyIds.includes(buddy.id)
|
className={`px-5 py-3 rounded-xl text-[10px] font-bold uppercase transition-all border flex items-center gap-2 ${selectedBuddyIds.includes(buddy.id)
|
||||||
? 'bg-amber-600 border-amber-600 text-white shadow-lg'
|
? 'bg-orange-600 border-orange-600 text-white shadow-lg'
|
||||||
: 'bg-transparent border-white/10 text-white/40 hover:border-white/30'
|
: 'bg-transparent border-zinc-800 text-zinc-500 hover:border-zinc-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{selectedBuddyIds.includes(buddy.id) && <Check size={14} />}
|
{selectedBuddyIds.includes(buddy.id) && <Check size={14} />}
|
||||||
@@ -385,15 +385,15 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sticky Footer - Flex Child 3 */}
|
{/* Sticky Footer - Flex Child 3 */}
|
||||||
<div className="w-full p-8 bg-black/60 backdrop-blur-xl border-t border-white/10 shrink-0">
|
<div className="w-full p-8 bg-zinc-950 border-t border-zinc-800 shrink-0">
|
||||||
<div className="max-w-2xl mx-auto">
|
<div className="max-w-2xl mx-auto">
|
||||||
<button
|
<button
|
||||||
onClick={handleInternalSave}
|
onClick={handleInternalSave}
|
||||||
className="w-full py-5 bg-[#C89D46] text-[#0F1014] rounded-3xl font-black uppercase tracking-widest text-xs flex items-center justify-center gap-4 shadow-xl active:scale-[0.98] transition-all"
|
className="w-full py-5 bg-orange-600 text-white rounded-2xl font-bold uppercase tracking-widest text-xs flex items-center justify-center gap-4 shadow-xl active:scale-[0.98] transition-all"
|
||||||
>
|
>
|
||||||
<Send size={20} />
|
<Send size={20} />
|
||||||
{t('tasting.saveTasting')}
|
{t('tasting.saveTasting')}
|
||||||
<div className="ml-auto bg-black/20 px-3 py-1 rounded-full text-[10px] font-black text-[#0F1014]/60">{rating}</div>
|
<div className="ml-auto bg-black/20 px-3 py-1 rounded-full text-[10px] font-bold text-white/60">{rating}</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -404,15 +404,15 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
|
|||||||
|
|
||||||
function CustomSlider({ label, value, onChange, icon }: any) {
|
function CustomSlider({ label, value, onChange, icon }: any) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 bg-zinc-900/50 p-6 rounded-3xl border border-white/5 shadow-inner">
|
<div className="space-y-4 bg-zinc-900/50 p-6 rounded-2xl border border-zinc-800 shadow-inner">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3 text-white/40">
|
<div className="flex items-center gap-3 text-zinc-500">
|
||||||
<div className="p-2 rounded-xl bg-[#C89D46]/10 text-[#C89D46]">
|
<div className="p-2 rounded-xl bg-orange-500/10 text-orange-500">
|
||||||
{icon}
|
{icon}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] font-black uppercase tracking-[0.2em]">{label}</span>
|
<span className="text-[10px] font-bold uppercase tracking-[0.2em]">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-2xl font-black text-[#C89D46] tracking-tighter">{value}</span>
|
<span className="text-2xl font-bold text-orange-500 tracking-tighter">{value}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
@@ -420,7 +420,7 @@ function CustomSlider({ label, value, onChange, icon }: any) {
|
|||||||
max="100"
|
max="100"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(parseInt(e.target.value))}
|
onChange={(e) => onChange(parseInt(e.target.value))}
|
||||||
className="w-full h-1.5 bg-zinc-800 rounded-full appearance-none cursor-pointer accent-[#C89D46] transition-all"
|
className="w-full h-1.5 bg-zinc-800 rounded-full appearance-none cursor-pointer accent-orange-600 transition-all"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -166,61 +166,61 @@ export default function UserManagementClient({ initialUsers, plans }: UserManage
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Search Bar */}
|
{/* Search Bar */}
|
||||||
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-sm">
|
<div className="bg-zinc-900 rounded-[32px] p-6 border border-zinc-800 shadow-sm">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={20} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500" size={20} />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search users by email or username..."
|
placeholder="Search users by email or username..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full pl-10 pr-4 py-3 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full pl-10 pr-4 py-3 bg-zinc-800 border border-zinc-700 rounded-2xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* User Table */}
|
{/* User Table */}
|
||||||
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-sm">
|
<div className="bg-zinc-900 rounded-[32px] p-6 border border-zinc-800 shadow-sm overflow-hidden">
|
||||||
<h2 className="text-xl font-black text-zinc-900 dark:text-white mb-4">Users ({filteredUsers.length})</h2>
|
<h2 className="text-xl font-bold text-white uppercase tracking-tighter mb-6">Users ({filteredUsers.length})</h2>
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto -mx-6">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-zinc-200 dark:border-zinc-800">
|
<tr className="border-b border-zinc-800">
|
||||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">User</th>
|
<th className="text-left py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">User</th>
|
||||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Balance</th>
|
<th className="text-left py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">Balance</th>
|
||||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Used</th>
|
<th className="text-left py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">Used</th>
|
||||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Daily Limit</th>
|
<th className="text-left py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">Daily Limit</th>
|
||||||
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Costs</th>
|
<th className="text-left py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">Costs</th>
|
||||||
<th className="text-right py-3 px-4 text-xs font-black uppercase text-zinc-400">Actions</th>
|
<th className="text-right py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody className="divide-y divide-zinc-800/50">
|
||||||
{filteredUsers.map((user) => (
|
{filteredUsers.map((user) => (
|
||||||
<tr key={user.id} className="border-b border-zinc-100 dark:border-zinc-800/50 hover:bg-zinc-50 dark:hover:bg-zinc-800/30">
|
<tr key={user.id} className="hover:bg-zinc-800/30 transition-colors">
|
||||||
<td className="py-3 px-4">
|
<td className="py-4 px-6">
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold text-zinc-900 dark:text-white">{user.username}</div>
|
<div className="font-bold text-white uppercase tracking-tight text-sm">{user.username}</div>
|
||||||
<div className="text-xs text-zinc-500">{user.email}</div>
|
<div className="text-[10px] font-medium text-zinc-500">{user.email}</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4">
|
<td className="py-4 px-6">
|
||||||
<span className={`text-lg font-black ${user.balance < 10 ? 'text-red-600' : 'text-green-600'}`}>
|
<span className={`text-lg font-bold tracking-tighter ${user.balance < 10 ? 'text-red-500' : 'text-orange-600'}`}>
|
||||||
{user.balance}
|
{user.balance}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4 text-sm text-zinc-600 dark:text-zinc-400">
|
<td className="py-4 px-6 text-sm font-medium text-zinc-400">
|
||||||
{user.total_used}
|
{user.total_used}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4 text-sm text-zinc-600 dark:text-zinc-400">
|
<td className="py-4 px-6 text-xs text-zinc-500 uppercase tracking-widest">
|
||||||
{user.daily_limit || 'Global (80)'}
|
{user.daily_limit || 'Global (80)'}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4 text-xs text-zinc-500">
|
<td className="py-4 px-6 text-[10px] font-bold text-zinc-600 uppercase tracking-widest">
|
||||||
G:{user.google_search_cost} / AI:{user.gemini_ai_cost}
|
G:{user.google_search_cost} / AI:{user.gemini_ai_cost}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4 text-right">
|
<td className="py-4 px-6 text-right">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleEditUser(user)}
|
onClick={() => handleEditUser(user)}
|
||||||
className="inline-flex items-center gap-2 px-3 py-1.5 bg-amber-600 hover:bg-amber-700 text-white rounded-lg text-xs font-bold transition-colors"
|
className="inline-flex items-center gap-2 px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white rounded-xl text-[10px] font-bold uppercase tracking-widest transition-all shadow-md shadow-orange-950/20"
|
||||||
>
|
>
|
||||||
<Edit size={14} />
|
<Edit size={14} />
|
||||||
Edit
|
Edit
|
||||||
@@ -235,18 +235,18 @@ export default function UserManagementClient({ initialUsers, plans }: UserManage
|
|||||||
|
|
||||||
{/* Edit Modal */}
|
{/* Edit Modal */}
|
||||||
{editingUser && (
|
{editingUser && (
|
||||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
|
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 z-50">
|
||||||
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-6 max-w-2xl w-full max-h-[90vh] overflow-y-auto border border-zinc-200 dark:border-zinc-800">
|
<div className="bg-zinc-900 rounded-[32px] p-8 max-w-2xl w-full max-h-[90vh] overflow-y-auto border border-zinc-800 shadow-2xl space-y-8">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-2xl font-black text-zinc-900 dark:text-white">Edit User</h3>
|
<h3 className="text-2xl font-bold text-white uppercase tracking-tighter">Edit User</h3>
|
||||||
<p className="text-sm text-zinc-500 mt-1">{editingUser.email}</p>
|
<p className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest mt-1">{editingUser.email}</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setEditingUser(null)}
|
onClick={() => setEditingUser(null)}
|
||||||
className="p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg transition-colors"
|
className="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500"
|
||||||
>
|
>
|
||||||
<X size={20} />
|
<X size={24} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -262,40 +262,40 @@ export default function UserManagementClient({ initialUsers, plans }: UserManage
|
|||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Current Balance */}
|
{/* Current Balance */}
|
||||||
<div className="p-4 bg-zinc-50 dark:bg-zinc-800 rounded-xl">
|
<div className="p-6 bg-zinc-800/50 rounded-2xl border border-zinc-800">
|
||||||
<div className="text-xs font-black uppercase text-zinc-400 mb-1">Current Balance</div>
|
<div className="text-[10px] font-bold uppercase text-zinc-500 tracking-widest mb-1">Current Balance</div>
|
||||||
<div className="text-3xl font-black text-zinc-900 dark:text-white">{editingUser.balance} Credits</div>
|
<div className="text-4xl font-bold text-orange-600 tracking-tighter">{editingUser.balance} <span className="text-xl text-zinc-500 ml-1">Credits</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Add/Remove Credits */}
|
{/* Add/Remove Credits */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h4 className="font-bold text-zinc-900 dark:text-white">Adjust Credits</h4>
|
<h4 className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">Adjust Credits</h4>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Amount</label>
|
<label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Amount</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={creditAmount}
|
value={creditAmount}
|
||||||
onChange={(e) => setCreditAmount(e.target.value)}
|
onChange={(e) => setCreditAmount(e.target.value)}
|
||||||
placeholder="e.g. 100 or -50"
|
placeholder="e.g. 100 or -50"
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Reason</label>
|
<label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Reason</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={reason}
|
value={reason}
|
||||||
onChange={(e) => setReason(e.target.value)}
|
onChange={(e) => setReason(e.target.value)}
|
||||||
placeholder="e.g. Monthly bonus"
|
placeholder="e.g. Monthly bonus"
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleUpdateCredits}
|
onClick={handleUpdateCredits}
|
||||||
disabled={loading || !creditAmount || !reason}
|
disabled={loading || !creditAmount || !reason}
|
||||||
className="w-full py-3 bg-amber-600 hover:bg-amber-700 text-white font-bold rounded-xl transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white font-bold rounded-2xl transition-all shadow-lg shadow-orange-950/20 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<Plus size={18} />
|
<Plus size={18} />
|
||||||
Update Credits
|
Update Credits
|
||||||
@@ -306,20 +306,20 @@ export default function UserManagementClient({ initialUsers, plans }: UserManage
|
|||||||
|
|
||||||
{/* Subscription Plan */}
|
{/* Subscription Plan */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h4 className="font-bold text-zinc-900 dark:text-white">Subscription Plan</h4>
|
<h4 className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">Subscription Plan</h4>
|
||||||
{currentPlan && (
|
{currentPlan && (
|
||||||
<div className="p-4 bg-amber-50 dark:bg-amber-900/20 rounded-xl border border-amber-200 dark:border-amber-800">
|
<div className="p-4 bg-orange-900/20 rounded-2xl border border-orange-500/30">
|
||||||
<div className="text-xs font-bold text-amber-600 dark:text-amber-400 uppercase mb-1">Current Plan</div>
|
<div className="text-[10px] font-bold text-orange-400 uppercase tracking-widest mb-1">Current Plan</div>
|
||||||
<div className="text-lg font-black text-amber-900 dark:text-amber-100">{currentPlan.display_name}</div>
|
<div className="text-xl font-bold text-orange-100 uppercase tracking-tight">{currentPlan.display_name}</div>
|
||||||
<div className="text-sm text-amber-700 dark:text-amber-300 mt-1">{currentPlan.monthly_credits} credits/month</div>
|
<div className="text-[10px] font-bold text-orange-400/60 uppercase tracking-widest mt-1">{currentPlan.monthly_credits} credits/month</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Assign Plan</label>
|
<label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Assign Plan</label>
|
||||||
<select
|
<select
|
||||||
value={selectedPlan}
|
value={selectedPlan}
|
||||||
onChange={(e) => setSelectedPlan(e.target.value)}
|
onChange={(e) => setSelectedPlan(e.target.value)}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-3 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white appearance-none"
|
||||||
>
|
>
|
||||||
<option value="">Select a plan...</option>
|
<option value="">Select a plan...</option>
|
||||||
{plans.map(plan => (
|
{plans.map(plan => (
|
||||||
@@ -343,41 +343,41 @@ export default function UserManagementClient({ initialUsers, plans }: UserManage
|
|||||||
|
|
||||||
{/* Settings */}
|
{/* Settings */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h4 className="font-bold text-zinc-900 dark:text-white">User Settings</h4>
|
<h4 className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">User Settings</h4>
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Daily Limit</label>
|
<label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Daily Limit</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={dailyLimit}
|
value={dailyLimit}
|
||||||
onChange={(e) => setDailyLimit(e.target.value)}
|
onChange={(e) => setDailyLimit(e.target.value)}
|
||||||
placeholder="Global (80)"
|
placeholder="Global (80)"
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Google Cost</label>
|
<label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Google Cost</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={googleCost}
|
value={googleCost}
|
||||||
onChange={(e) => setGoogleCost(e.target.value)}
|
onChange={(e) => setGoogleCost(e.target.value)}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Gemini Cost</label>
|
<label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Gemini Cost</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={geminiCost}
|
value={geminiCost}
|
||||||
onChange={(e) => setGeminiCost(e.target.value)}
|
onChange={(e) => setGeminiCost(e.target.value)}
|
||||||
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50"
|
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleUpdateSettings}
|
onClick={handleUpdateSettings}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="w-full py-3 bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900 font-bold rounded-xl transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
className="w-full py-4 bg-zinc-800 hover:bg-zinc-700 text-white font-bold rounded-2xl transition-all border border-zinc-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<Check size={18} />
|
<Check size={18} />
|
||||||
Save Settings
|
Save Settings
|
||||||
|
|||||||
Reference in New Issue
Block a user