feat: Upgrade to Tailwind CSS v4.1.18

- Migrate from tailwindcss v3.3 to v4.1.18
- Replace @tailwind directives with @import 'tailwindcss'
- Move custom colors to @theme block in globals.css
- Convert custom utilities to @utility syntax
- Update PostCSS config to use @tailwindcss/postcss
- Remove autoprefixer (now built-in)
This commit is contained in:
2026-01-19 22:26:21 +01:00
parent b179a88d4c
commit 096daffb3e
59 changed files with 661 additions and 641 deletions

View File

@@ -162,7 +162,7 @@ export default function BannerManager({ initialBanners }: BannerManagerProps) {
value={formData.title}
onChange={e => setFormData({ ...formData, title: e.target.value })}
placeholder="Banner title"
className="w-full px-4 py-3 bg-zinc-950 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-none focus:border-orange-600"
className="w-full px-4 py-3 bg-zinc-950 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-hidden focus:border-orange-600"
required
/>
</div>
@@ -174,7 +174,7 @@ export default function BannerManager({ initialBanners }: BannerManagerProps) {
value={formData.image_url}
onChange={e => setFormData({ ...formData, image_url: e.target.value })}
placeholder="https://example.com/banner.jpg"
className="w-full px-4 py-3 bg-zinc-950 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-none focus:border-orange-600"
className="w-full px-4 py-3 bg-zinc-950 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-hidden focus:border-orange-600"
required
/>
</div>
@@ -187,7 +187,7 @@ export default function BannerManager({ initialBanners }: BannerManagerProps) {
value={formData.link_target}
onChange={e => setFormData({ ...formData, link_target: e.target.value })}
placeholder="/sessions"
className="w-full px-4 py-3 bg-zinc-950 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-none focus:border-orange-600"
className="w-full px-4 py-3 bg-zinc-950 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-hidden focus:border-orange-600"
/>
</div>
<div>
@@ -197,7 +197,7 @@ export default function BannerManager({ initialBanners }: BannerManagerProps) {
value={formData.cta_text}
onChange={e => setFormData({ ...formData, cta_text: e.target.value })}
placeholder="Open"
className="w-full px-4 py-3 bg-zinc-950 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-none focus:border-orange-600"
className="w-full px-4 py-3 bg-zinc-950 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-hidden focus:border-orange-600"
/>
</div>
</div>
@@ -320,7 +320,7 @@ export default function BannerManager({ initialBanners }: BannerManagerProps) {
/* Display Mode */
<div className="flex gap-4">
{/* Thumbnail */}
<div className="w-32 h-20 rounded-lg overflow-hidden bg-zinc-800 flex-shrink-0">
<div className="w-32 h-20 rounded-lg overflow-hidden bg-zinc-800 shrink-0">
<img
src={banner.image_url}
alt={banner.title}

View File

@@ -123,7 +123,7 @@ export default function AdminBottlesList({ bottles }: AdminBottlesListProps) {
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search bottles, distilleries, or users..."
className="w-full pl-12 pr-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-none focus:border-orange-600"
className="w-full pl-12 pr-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-hidden focus:border-orange-600"
/>
</div>
@@ -133,7 +133,7 @@ export default function AdminBottlesList({ bottles }: AdminBottlesListProps) {
<select
value={filterUser || ''}
onChange={e => setFilterUser(e.target.value || null)}
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-none focus:border-orange-600 appearance-none cursor-pointer"
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-hidden focus:border-orange-600 appearance-none cursor-pointer"
>
<option value="">All Users</option>
{users.map(([id, name]) => (
@@ -145,7 +145,7 @@ export default function AdminBottlesList({ bottles }: AdminBottlesListProps) {
<select
value={filterCategory || ''}
onChange={e => setFilterCategory(e.target.value || null)}
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-none focus:border-orange-600 appearance-none cursor-pointer"
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-hidden focus:border-orange-600 appearance-none cursor-pointer"
>
<option value="">All Categories</option>
{categories.map(cat => (
@@ -161,7 +161,7 @@ export default function AdminBottlesList({ bottles }: AdminBottlesListProps) {
setSortBy(by);
setSortOrder(order);
}}
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-none focus:border-orange-600 appearance-none cursor-pointer"
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-hidden focus:border-orange-600 appearance-none cursor-pointer"
>
<option value="created_at-desc">Newest First</option>
<option value="created_at-asc">Oldest First</option>
@@ -202,7 +202,7 @@ export default function AdminBottlesList({ bottles }: AdminBottlesListProps) {
className="bg-zinc-900 rounded-2xl border border-zinc-800 overflow-hidden hover:border-zinc-700 transition-colors"
>
{/* Image */}
<div className="aspect-[4/3] relative bg-zinc-800">
<div className="aspect-4/3 relative bg-zinc-800">
{bottle.image_url ? (
<img
src={bottle.image_url}
@@ -217,14 +217,14 @@ export default function AdminBottlesList({ bottles }: AdminBottlesListProps) {
{/* Category Badge */}
{bottle.category && (
<span className="absolute top-2 left-2 px-2 py-1 bg-black/60 backdrop-blur-sm text-[10px] font-bold text-white rounded-lg uppercase">
<span className="absolute top-2 left-2 px-2 py-1 bg-black/60 backdrop-blur-xs text-[10px] font-bold text-white rounded-lg uppercase">
{bottle.category}
</span>
)}
{/* Rating Badge */}
{avgRating > 0 && (
<span className="absolute top-2 right-2 px-2 py-1 bg-orange-600/90 backdrop-blur-sm text-xs font-bold text-white rounded-lg flex items-center gap-1">
<span className="absolute top-2 right-2 px-2 py-1 bg-orange-600/90 backdrop-blur-xs text-xs font-bold text-white rounded-lg flex items-center gap-1">
<Star size={12} fill="currentColor" />
{avgRating.toFixed(1)}
</span>

View File

@@ -49,7 +49,7 @@ export default async function OcrLogsPage() {
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
<Camera size={20} className="text-blue-600 dark:text-blue-400" />
@@ -60,7 +60,7 @@ export default async function OcrLogsPage() {
<div className="text-xs text-zinc-500 mt-1">All time</div>
</div>
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-green-100 dark:bg-green-900/30 rounded-lg">
<Calendar size={20} className="text-green-600 dark:text-green-400" />
@@ -71,7 +71,7 @@ export default async function OcrLogsPage() {
<div className="text-xs text-zinc-500 mt-1">Scans today</div>
</div>
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-amber-100 dark:bg-amber-900/30 rounded-lg">
<Percent size={20} className="text-amber-600 dark:text-amber-400" />
@@ -82,7 +82,7 @@ export default async function OcrLogsPage() {
<div className="text-xs text-zinc-500 mt-1">Recognition quality</div>
</div>
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
<TrendingUp size={20} className="text-purple-600 dark:text-purple-400" />
@@ -100,7 +100,7 @@ export default async function OcrLogsPage() {
{/* Top Distilleries */}
{stats.topDistilleries.length > 0 && (
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<h2 className="text-xl font-black text-zinc-900 dark:text-white mb-4">Most Scanned Distilleries</h2>
<div className="flex flex-wrap gap-2">
{stats.topDistilleries.map((d, i) => (
@@ -119,7 +119,7 @@ export default async function OcrLogsPage() {
)}
{/* OCR Logs Grid */}
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<h2 className="text-xl font-black text-zinc-900 dark:text-white mb-4">Recent OCR Scans</h2>
{logs.length === 0 ? (
@@ -136,7 +136,7 @@ export default async function OcrLogsPage() {
className="bg-zinc-50 dark:bg-zinc-800/50 rounded-xl p-4 border border-zinc-200 dark:border-zinc-700 hover:border-orange-500/50 transition-colors"
>
{/* Image Preview */}
<div className="relative aspect-[4/3] rounded-lg overflow-hidden bg-zinc-200 dark:bg-zinc-700 mb-3">
<div className="relative aspect-4/3 rounded-lg overflow-hidden bg-zinc-200 dark:bg-zinc-700 mb-3">
{log.image_thumbnail ? (
<img
src={log.image_thumbnail}
@@ -175,7 +175,7 @@ export default async function OcrLogsPage() {
{log.distillery}
</span>
{log.distillery_source && (
<span className="text-[10px] px-1.5 py-0.5 bg-zinc-200 dark:bg-zinc-700 rounded text-zinc-500">
<span className="text-[10px] px-1.5 py-0.5 bg-zinc-200 dark:bg-zinc-700 rounded-sm text-zinc-500">
{log.distillery_source}
</span>
)}
@@ -190,22 +190,22 @@ export default async function OcrLogsPage() {
<div className="flex flex-wrap gap-1.5">
{log.abv && (
<span className="px-2 py-0.5 bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-400 rounded text-[10px] font-bold">
<span className="px-2 py-0.5 bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-400 rounded-sm text-[10px] font-bold">
{log.abv}%
</span>
)}
{log.age && (
<span className="px-2 py-0.5 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400 rounded text-[10px] font-bold">
<span className="px-2 py-0.5 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400 rounded-sm text-[10px] font-bold">
{log.age}y
</span>
)}
{log.vintage && (
<span className="px-2 py-0.5 bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-400 rounded text-[10px] font-bold">
<span className="px-2 py-0.5 bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-400 rounded-sm text-[10px] font-bold">
{log.vintage}
</span>
)}
{log.volume && (
<span className="px-2 py-0.5 bg-zinc-100 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-400 rounded text-[10px] font-bold">
<span className="px-2 py-0.5 bg-zinc-100 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-400 rounded-sm text-[10px] font-bold">
{log.volume}
</span>
)}
@@ -218,7 +218,7 @@ export default async function OcrLogsPage() {
<summary className="text-[10px] font-bold text-zinc-400 cursor-pointer hover:text-orange-500 uppercase">
Raw Text
</summary>
<pre className="mt-2 p-2 bg-zinc-100 dark:bg-zinc-900 rounded text-[9px] text-zinc-500 overflow-x-auto max-h-20 whitespace-pre-wrap">
<pre className="mt-2 p-2 bg-zinc-100 dark:bg-zinc-900 rounded-sm text-[9px] text-zinc-500 overflow-x-auto max-h-20 whitespace-pre-wrap">
{log.raw_text}
</pre>
</details>

View File

@@ -158,7 +158,7 @@ export default async function AdminPage() {
{/* Global Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
<BarChart3 size={20} className="text-blue-600 dark:text-blue-400" />
@@ -169,7 +169,7 @@ export default async function AdminPage() {
<div className="text-xs text-zinc-500 mt-1">All time</div>
</div>
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-green-100 dark:bg-green-900/30 rounded-lg">
<Calendar size={20} className="text-green-600 dark:text-green-400" />
@@ -188,7 +188,7 @@ export default async function AdminPage() {
</div>
</div>
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-amber-100 dark:bg-amber-900/30 rounded-lg">
<TrendingUp size={20} className="text-amber-600 dark:text-amber-400" />
@@ -199,7 +199,7 @@ export default async function AdminPage() {
<div className="text-xs text-zinc-500 mt-1">Whiskybase searches</div>
</div>
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
<Users size={20} className="text-purple-600 dark:text-purple-400" />
@@ -212,7 +212,7 @@ export default async function AdminPage() {
</div>
{/* Top Users */}
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<h2 className="text-xl font-black text-zinc-900 dark:text-white mb-4">Top Users by API Usage</h2>
<div className="space-y-3">
{topUsersWithStats.map((user, index) => (
@@ -235,7 +235,7 @@ export default async function AdminPage() {
</div>
{/* Recent API Calls */}
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<h2 className="text-xl font-black text-zinc-900 dark:text-white mb-4">Recent API Calls</h2>
<div className="text-sm text-zinc-500 mb-4">
Total calls logged: {recentUsage?.length || 0}
@@ -296,7 +296,7 @@ export default async function AdminPage() {
{call.response_text && (
<details className="text-[10px]">
<summary className="cursor-pointer text-orange-600 hover:text-orange-700 font-bold uppercase transition-colors">Response</summary>
<pre className="mt-2 p-2 bg-zinc-100 dark:bg-zinc-950 rounded border border-zinc-200 dark:border-zinc-800 overflow-x-auto max-w-sm whitespace-pre-wrap font-mono text-[9px] text-zinc-400">
<pre className="mt-2 p-2 bg-zinc-100 dark:bg-zinc-950 rounded-sm border border-zinc-200 dark:border-zinc-800 overflow-x-auto max-w-sm whitespace-pre-wrap font-mono text-[9px] text-zinc-400">
{call.response_text}
</pre>
</details>
@@ -310,7 +310,7 @@ export default async function AdminPage() {
<div className="group relative">
<span className="text-red-600 dark:text-red-400 font-black text-xs cursor-help">ERR</span>
{call.error_message && (
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 w-48 p-2 bg-red-600 text-white text-[9px] rounded shadow-xl opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50">
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 w-48 p-2 bg-red-600 text-white text-[9px] rounded-sm shadow-xl opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50">
{call.error_message}
</div>
)}

View File

@@ -93,7 +93,7 @@ export default function AdminSessionsList({ sessions }: AdminSessionsListProps)
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search sessions or hosts..."
className="w-full pl-12 pr-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-none focus:border-orange-600"
className="w-full pl-12 pr-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-hidden focus:border-orange-600"
/>
</div>
@@ -101,7 +101,7 @@ export default function AdminSessionsList({ sessions }: AdminSessionsListProps)
<select
value={filterHost || ''}
onChange={e => setFilterHost(e.target.value || null)}
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-none"
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-hidden"
>
<option value="">All Hosts</option>
{hosts.map(([id, name]) => (
@@ -112,7 +112,7 @@ export default function AdminSessionsList({ sessions }: AdminSessionsListProps)
<select
value={filterStatus}
onChange={e => setFilterStatus(e.target.value as any)}
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-none"
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-hidden"
>
<option value="all">All Status</option>
<option value="active">Active</option>

View File

@@ -75,7 +75,7 @@ export default function AdminSplitsList({ splits }: AdminSplitsListProps) {
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search bottles, hosts, or slugs..."
className="w-full pl-12 pr-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-none focus:border-orange-600"
className="w-full pl-12 pr-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-hidden focus:border-orange-600"
/>
</div>
@@ -83,7 +83,7 @@ export default function AdminSplitsList({ splits }: AdminSplitsListProps) {
<select
value={filterHost || ''}
onChange={e => setFilterHost(e.target.value || null)}
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-none"
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-hidden"
>
<option value="">All Hosts</option>
{hosts.map(([id, name]) => (
@@ -94,7 +94,7 @@ export default function AdminSplitsList({ splits }: AdminSplitsListProps) {
<select
value={filterStatus}
onChange={e => setFilterStatus(e.target.value as any)}
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-none"
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-hidden"
>
<option value="all">All Status</option>
<option value="active">Active</option>
@@ -124,7 +124,7 @@ export default function AdminSplitsList({ splits }: AdminSplitsListProps) {
}`}
>
{/* Image */}
<div className="aspect-[16/9] relative bg-zinc-800">
<div className="aspect-video relative bg-zinc-800">
{split.bottle?.image_url ? (
<img
src={split.bottle.image_url}
@@ -147,7 +147,7 @@ export default function AdminSplitsList({ splits }: AdminSplitsListProps) {
</span>
{/* Participants Badge */}
<span className="absolute top-2 right-2 px-2 py-1 bg-black/60 backdrop-blur-sm text-xs font-bold text-white rounded-lg flex items-center gap-1">
<span className="absolute top-2 right-2 px-2 py-1 bg-black/60 backdrop-blur-xs text-xs font-bold text-white rounded-lg flex items-center gap-1">
<Users size={12} />
{split.participantCount}
</span>
@@ -170,7 +170,7 @@ export default function AdminSplitsList({ splits }: AdminSplitsListProps) {
</div>
<div className="h-2 bg-zinc-800 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-orange-500 to-orange-600 transition-all"
className="h-full bg-linear-to-r from-orange-500 to-orange-600 transition-all"
style={{ width: `${fillPercent}%` }}
/>
</div>

View File

@@ -88,7 +88,7 @@ export default function AdminTagsPage() {
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Tags suchen..."
className="w-full pl-10 pr-4 py-2 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:ring-2 focus:ring-amber-500 outline-none transition-all dark:text-zinc-200"
className="w-full pl-10 pr-4 py-2 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:ring-2 focus:ring-amber-500 outline-hidden transition-all dark:text-zinc-200"
/>
</div>
<div className="flex items-center gap-2">
@@ -96,7 +96,7 @@ export default function AdminTagsPage() {
<select
value={categoryFilter}
onChange={(e) => setCategoryFilter(e.target.value as any)}
className="bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl px-3 py-2 text-sm font-bold uppercase tracking-tight outline-none focus:ring-2 focus:ring-amber-500 dark:text-zinc-200"
className="bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl px-3 py-2 text-sm font-bold uppercase tracking-tight outline-hidden focus:ring-2 focus:ring-amber-500 dark:text-zinc-200"
>
<option value="all">Alle Kategorien</option>
<option value="nose">Nose</option>
@@ -154,7 +154,7 @@ export default function AdminTagsPage() {
key={score}
onClick={() => updatePopularity(tag.id, score)}
className={`w-6 h-6 rounded-md flex items-center justify-center text-[10px] font-black transition-all ${tag.popularity_score === score
? 'bg-amber-600 text-white shadow-sm'
? 'bg-amber-600 text-white shadow-xs'
: 'bg-zinc-100 text-zinc-400 dark:bg-zinc-800 hover:bg-zinc-200 dark:hover:bg-zinc-700'
}`}
>

View File

@@ -86,7 +86,7 @@ export default function AdminTastingsList({ tastings }: AdminTastingsListProps)
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search bottles, users, or notes..."
className="w-full pl-12 pr-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-none focus:border-orange-600"
className="w-full pl-12 pr-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-white placeholder-zinc-600 focus:outline-hidden focus:border-orange-600"
/>
</div>
@@ -94,7 +94,7 @@ export default function AdminTastingsList({ tastings }: AdminTastingsListProps)
<select
value={filterUser || ''}
onChange={e => setFilterUser(e.target.value || null)}
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-none"
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-hidden"
>
<option value="">All Users</option>
{users.map(([id, name]) => (
@@ -105,7 +105,7 @@ export default function AdminTastingsList({ tastings }: AdminTastingsListProps)
<select
value={filterRating ?? ''}
onChange={e => setFilterRating(e.target.value ? parseInt(e.target.value) : null)}
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-none"
className="px-4 py-3 bg-zinc-900 border border-zinc-800 rounded-xl text-zinc-300 focus:outline-hidden"
>
<option value="">All Ratings</option>
<option value="5">5 Stars</option>
@@ -134,7 +134,7 @@ export default function AdminTastingsList({ tastings }: AdminTastingsListProps)
>
<div className="flex gap-4">
{/* Bottle Image */}
<div className="w-16 h-16 rounded-xl overflow-hidden bg-zinc-800 flex-shrink-0">
<div className="w-16 h-16 rounded-xl overflow-hidden bg-zinc-800 shrink-0">
{tasting.bottle?.image_url ? (
<img
src={tasting.bottle.image_url}
@@ -159,7 +159,7 @@ export default function AdminTastingsList({ tastings }: AdminTastingsListProps)
{tasting.bottle?.distillery || 'Unknown Distillery'}
</p>
</div>
<div className="flex-shrink-0">
<div className="shrink-0">
{tasting.rating > 0 ? renderStars(tasting.rating) : (
<span className="text-xs text-zinc-600">No rating</span>
)}

View File

@@ -53,7 +53,7 @@ export default async function AdminUsersPage() {
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
<Users size={20} className="text-blue-600 dark:text-blue-400" />
@@ -63,7 +63,7 @@ export default async function AdminUsersPage() {
<div className="text-3xl font-black text-zinc-900 dark:text-white">{totalUsers}</div>
</div>
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-green-100 dark:bg-green-900/30 rounded-lg">
<Coins size={20} className="text-green-600 dark:text-green-400" />
@@ -73,7 +73,7 @@ export default async function AdminUsersPage() {
<div className="text-3xl font-black text-zinc-900 dark:text-white">{totalCreditsInCirculation.toLocaleString()}</div>
</div>
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-amber-100 dark:bg-amber-900/30 rounded-lg">
<TrendingUp size={20} className="text-amber-600 dark:text-amber-400" />
@@ -83,7 +83,7 @@ export default async function AdminUsersPage() {
<div className="text-3xl font-black text-zinc-900 dark:text-white">{totalCreditsPurchased.toLocaleString()}</div>
</div>
<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-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-xs">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
<TrendingDown size={20} className="text-purple-600 dark:text-purple-400" />