feat: implement offline queue, background sync and AI robustness
This commit is contained in:
@@ -6,6 +6,8 @@ import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
|
||||
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';
|
||||
|
||||
interface CameraCaptureProps {
|
||||
onImageCaptured?: (base64Image: string) => void;
|
||||
@@ -21,6 +23,7 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
|
||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [analysisResult, setAnalysisResult] = useState<BottleMetadata | null>(null);
|
||||
const [isQueued, setIsQueued] = useState(false);
|
||||
|
||||
const handleCapture = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
@@ -29,6 +32,7 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
|
||||
setIsProcessing(true);
|
||||
setError(null);
|
||||
setAnalysisResult(null);
|
||||
setIsQueued(false);
|
||||
|
||||
try {
|
||||
const compressedBase64 = await compressImage(file);
|
||||
@@ -38,6 +42,18 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
|
||||
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) {
|
||||
@@ -162,7 +178,7 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
|
||||
/>
|
||||
|
||||
<button
|
||||
onClick={previewUrl && analysisResult ? handleSave : triggerUpload}
|
||||
onClick={isQueued ? () => setPreviewUrl(null) : (previewUrl && analysisResult ? handleSave : triggerUpload)}
|
||||
disabled={isProcessing || isSaving}
|
||||
className="w-full py-4 px-6 bg-amber-600 hover:bg-amber-700 text-white rounded-xl font-semibold flex items-center justify-center gap-2 transition-all active:scale-[0.98] shadow-lg shadow-amber-600/20 disabled:opacity-50"
|
||||
>
|
||||
@@ -171,6 +187,11 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div>
|
||||
Wird gespeichert...
|
||||
</>
|
||||
) : isQueued ? (
|
||||
<>
|
||||
<CheckCircle2 size={20} />
|
||||
Nächste Flasche
|
||||
</>
|
||||
) : previewUrl && analysisResult ? (
|
||||
<>
|
||||
<CheckCircle2 size={20} />
|
||||
@@ -196,7 +217,14 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
|
||||
</div>
|
||||
)}
|
||||
|
||||
{previewUrl && !isProcessing && !error && (
|
||||
{isQueued && (
|
||||
<div className="flex items-center gap-2 text-purple-500 text-sm bg-purple-50 dark:bg-purple-900/10 p-4 rounded-xl w-full border border-purple-100 dark:border-purple-800/30 font-medium">
|
||||
<Sparkles size={16} />
|
||||
Offline! Foto wurde gemerkt – wird automatisch analysiert, sobald du wieder Netz hast. 📡
|
||||
</div>
|
||||
)}
|
||||
|
||||
{previewUrl && !isProcessing && !error && !isQueued && (
|
||||
<div className="flex flex-col gap-3 w-full animate-in fade-in slide-in-from-top-4 duration-500">
|
||||
<div className="flex items-center gap-2 text-green-500 text-sm bg-green-50 dark:bg-green-900/10 p-3 rounded-lg w-full">
|
||||
<CheckCircle2 size={16} />
|
||||
|
||||
Reference in New Issue
Block a user