feat: Add Spotify-style backdrop, Cascade OCR, Smart Scan Flow & OCR Dashboard
- BottleGrid: Implement blurred backdrop effect for bottle cards - Cascade OCR: TextDetector → RegEx → Fuzzy Match → window.ai pipeline - Smart Scan: Native OCR for Android, Live Text fallback for iOS - OCR Dashboard: Admin page at /admin/ocr-logs with stats and scan history - Features: Add feature flags in src/config/features.ts - SQL: Add ocr_logs table migration - Services: Update analyze-bottle to use OpenRouter, add save-ocr-log
This commit is contained in:
@@ -8,6 +8,8 @@ import { useI18n } from '@/i18n/I18nContext';
|
||||
import { deleteTasting } from '@/services/delete-tasting';
|
||||
import { useLiveQuery } from 'dexie-react-hooks';
|
||||
import { db } from '@/lib/db';
|
||||
import FlavorRadar from './FlavorRadar';
|
||||
|
||||
|
||||
interface Tasting {
|
||||
id: string;
|
||||
@@ -38,6 +40,13 @@ interface Tasting {
|
||||
}[];
|
||||
user_id: string;
|
||||
isPending?: boolean;
|
||||
flavor_profile?: {
|
||||
smoky: number;
|
||||
fruity: number;
|
||||
spicy: number;
|
||||
sweet: number;
|
||||
floral: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface TastingListProps {
|
||||
@@ -92,7 +101,8 @@ export default function TastingList({ initialTastings, currentUserId, bottleId }
|
||||
isPending: true,
|
||||
tasting_buddies: [],
|
||||
tasting_sessions: undefined,
|
||||
tasting_tags: []
|
||||
tasting_tags: [],
|
||||
flavor_profile: undefined
|
||||
}))
|
||||
];
|
||||
|
||||
@@ -198,35 +208,44 @@ export default function TastingList({ initialTastings, currentUserId, bottleId }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 relative">
|
||||
{/* Visual Divider for MD and up */}
|
||||
<div className="hidden md:block absolute left-1/3 top-0 bottom-0 w-px bg-zinc-100 dark:bg-zinc-800/50" />
|
||||
<div className="hidden md:block absolute left-2/3 top-0 bottom-0 w-px bg-zinc-100 dark:bg-zinc-800/50" />
|
||||
<div className={`grid grid-cols-1 ${note.flavor_profile ? 'md:grid-cols-4' : 'md:grid-cols-3'} gap-6 relative`}>
|
||||
{note.flavor_profile && (
|
||||
<div className="md:col-span-1 bg-zinc-950/50 rounded-2xl border border-white/5 p-2 flex flex-col items-center justify-center">
|
||||
<div className="text-[8px] font-black text-zinc-500 uppercase tracking-[0.2em] mb-1">Flavor Profile</div>
|
||||
<FlavorRadar profile={note.flavor_profile} size={140} showAxis={false} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{note.nose_notes && (
|
||||
<div className="space-y-1">
|
||||
<div className="text-[10px] font-black text-zinc-400 uppercase tracking-widest mb-1">Nose</div>
|
||||
<p className="text-sm text-zinc-700 dark:text-zinc-300 leading-relaxed italic border-l-2 border-zinc-100 dark:border-zinc-800 pl-3">
|
||||
{note.nose_notes}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{note.palate_notes && (
|
||||
<div className="space-y-1">
|
||||
<div className="text-[10px] font-black text-zinc-400 uppercase tracking-widest mb-1">Palate</div>
|
||||
<p className="text-sm text-zinc-700 dark:text-zinc-300 leading-relaxed italic border-l-2 border-zinc-100 dark:border-zinc-800 pl-3">
|
||||
{note.palate_notes}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{note.finish_notes && (
|
||||
<div className="space-y-1">
|
||||
<div className="text-[10px] font-black text-zinc-400 uppercase tracking-widest mb-1">Finish</div>
|
||||
<p className="text-sm text-zinc-700 dark:text-zinc-300 leading-relaxed italic border-l-2 border-zinc-100 dark:border-zinc-800 pl-3">
|
||||
{note.finish_notes}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className={`${note.flavor_profile ? 'md:col-span-3' : 'md:col-span-3'} grid grid-cols-1 md:grid-cols-3 gap-6 relative`}>
|
||||
{/* Visual Divider for MD and up */}
|
||||
<div className="hidden md:block absolute left-1/3 top-0 bottom-0 w-px bg-zinc-100 dark:bg-zinc-800/50" />
|
||||
<div className="hidden md:block absolute left-2/3 top-0 bottom-0 w-px bg-zinc-100 dark:bg-zinc-800/50" />
|
||||
|
||||
{note.nose_notes && (
|
||||
<div className="space-y-1">
|
||||
<div className="text-[10px] font-black text-zinc-400 uppercase tracking-widest mb-1">Nose</div>
|
||||
<p className="text-sm text-zinc-700 dark:text-zinc-300 leading-relaxed italic border-l-2 border-zinc-100 dark:border-zinc-800 pl-3">
|
||||
{note.nose_notes}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{note.palate_notes && (
|
||||
<div className="space-y-1">
|
||||
<div className="text-[10px] font-black text-zinc-400 uppercase tracking-widest mb-1">Palate</div>
|
||||
<p className="text-sm text-zinc-700 dark:text-zinc-300 leading-relaxed italic border-l-2 border-zinc-100 dark:border-zinc-800 pl-3">
|
||||
{note.palate_notes}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{note.finish_notes && (
|
||||
<div className="space-y-1">
|
||||
<div className="text-[10px] font-black text-zinc-400 uppercase tracking-widest mb-1">Finish</div>
|
||||
<p className="text-sm text-zinc-700 dark:text-zinc-300 leading-relaxed italic border-l-2 border-zinc-100 dark:border-zinc-800 pl-3">
|
||||
{note.finish_notes}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{note.tasting_tags && note.tasting_tags.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user