'use client'; import React, { useRef, useState } from 'react'; import { Camera, Upload, CheckCircle2, AlertCircle, Sparkles, ExternalLink, ChevronRight } from 'lucide-react'; import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'; import { useRouter, useSearchParams } from 'next/navigation'; import { analyzeBottle } from '@/services/analyze-bottle'; import { saveBottle } from '@/services/save-bottle'; import { BottleMetadata } from '@/types/whisky'; import { savePendingBottle } from '@/lib/offline-db'; import { v4 as uuidv4 } from 'uuid'; import { findMatchingBottle } from '@/services/find-matching-bottle'; import { validateSession } from '@/services/validate-session'; import Link from 'next/link'; interface CameraCaptureProps { onImageCaptured?: (base64Image: string) => void; onAnalysisComplete?: (data: BottleMetadata) => void; onSaveComplete?: () => void; } export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onSaveComplete }: CameraCaptureProps) { const supabase = createClientComponentClient(); const router = useRouter(); const searchParams = useSearchParams(); const sessionId = searchParams.get('session_id'); const [validatedSessionId, setValidatedSessionId] = React.useState(null); React.useEffect(() => { const checkSession = async () => { if (sessionId) { const isValid = await validateSession(sessionId); setValidatedSessionId(isValid ? sessionId : null); } else { setValidatedSessionId(null); } }; checkSession(); }, [sessionId]); const fileInputRef = useRef(null); const [isProcessing, setIsProcessing] = useState(false); const [isSaving, setIsSaving] = useState(false); const [previewUrl, setPreviewUrl] = useState(null); const [error, setError] = useState(null); const [analysisResult, setAnalysisResult] = useState(null); const [isQueued, setIsQueued] = useState(false); const [matchingBottle, setMatchingBottle] = useState<{ id: string; name: string } | null>(null); const [lastSavedId, setLastSavedId] = useState(null); const handleCapture = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; setIsProcessing(true); setError(null); setAnalysisResult(null); setIsQueued(false); setMatchingBottle(null); try { let fileToProcess = file; // HEIC / HEIF Check const isHeic = file.type === 'image/heic' || file.type === 'image/heif' || file.name.toLowerCase().endsWith('.heic') || file.name.toLowerCase().endsWith('.heif'); if (isHeic) { console.log('HEIC detected, converting...'); const heic2any = (await import('heic2any')).default; const convertedBlob = await heic2any({ blob: file, toType: 'image/jpeg', quality: 0.8 }); // heic2any can return an array if the file contains multiple images const blob = Array.isArray(convertedBlob) ? convertedBlob[0] : convertedBlob; fileToProcess = new File([blob], file.name.replace(/\.(heic|heif)$/i, '.jpg'), { type: 'image/jpeg' }); } const compressedBase64 = await compressImage(fileToProcess); setPreviewUrl(compressedBase64); if (onImageCaptured) { onImageCaptured(compressedBase64); } // Check if Offline if (!navigator.onLine) { console.log('Offline detected. Queuing image...'); await savePendingBottle({ id: uuidv4(), imageBase64: compressedBase64, timestamp: Date.now(), }); setIsQueued(true); return; } const response = await analyzeBottle(compressedBase64); if (response.success && response.data) { setAnalysisResult(response.data); // Duplicate Check const match = await findMatchingBottle(response.data); if (match) { setMatchingBottle(match); } if (onAnalysisComplete) { onAnalysisComplete(response.data); } } else { setError(response.error || 'Analyse fehlgeschlagen.'); } } catch (err) { console.error('Processing failed:', err); setError('Verarbeitung fehlgeschlagen. Bitte erneut versuchen.'); } finally { setIsProcessing(false); } }; const handleSave = async () => { if (!analysisResult || !previewUrl) return; setIsSaving(true); setError(null); try { // Get current user (simple check for now, can be improved with Auth) const { data: { user } } = await supabase.auth.getUser(); if (!user) { throw new Error('Bitte melde dich an, um Flaschen zu speichern.'); } const response = await saveBottle(analysisResult, previewUrl, user.id); if (response.success && response.data) { setLastSavedId(response.data.id); if (onSaveComplete) onSaveComplete(); } else { setError(response.error || 'Speichern fehlgeschlagen.'); } } catch (err) { console.error('Save failed:', err); setError(err instanceof Error ? err.message : 'Speichern fehlgeschlagen.'); } finally { setIsSaving(false); } }; const compressImage = (file: File): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = (event) => { const img = new Image(); img.src = event.target?.result as string; img.onload = () => { const canvas = document.createElement('canvas'); const MAX_WIDTH = 1024; let width = img.width; let height = img.height; if (width > MAX_WIDTH) { height = (height * MAX_WIDTH) / width; width = MAX_WIDTH; } canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); if (!ctx) { reject(new Error('Canvas context not available')); return; } ctx.drawImage(img, 0, 0, width, height); const base64 = canvas.toDataURL('image/jpeg', 0.8); resolve(base64); }; img.onerror = reject; }; reader.onerror = reject; }); }; const triggerUpload = () => { fileInputRef.current?.click(); }; return (

Magic Shot

{previewUrl ? ( Preview ) : (
Flasche scannen
)} {isProcessing && (
)}
{lastSavedId ? (
Erfolgreich gespeichert!
) : matchingBottle ? ( Zum Whisky im Vault ) : ( )} {error && (
{error}
)} {isQueued && (
Offline! Foto wurde gemerkt – wird automatisch analysiert, sobald du wieder Netz hast. 📡
)} {matchingBottle && (
Bereits im Vault!

Du hast diesen Whisky bereits in deiner Sammlung. Willst du direkt zur Flasche gehen?

)} {previewUrl && !isProcessing && !error && !isQueued && !matchingBottle && (
Bild erfolgreich analysiert
{analysisResult && (
Ergebnisse
Name: {analysisResult.name || '-'}
Distille: {analysisResult.distillery || '-'}
Kategorie: {analysisResult.category || '-'}
ABV: {analysisResult.abv ? `${analysisResult.abv}%` : '-'}
)}
)}
); }