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:
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user