feat: Implement PDF recipe parser and quick import service for file and URL inputs
This commit is contained in:
@@ -1,43 +1,85 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import Navigation from '../../Navigation';
|
||||
import { parseErrorResponse } from '../../../lib/error-handler';
|
||||
|
||||
export default function ImportFilePage() {
|
||||
const [selectedMethod, setSelectedMethod] = useState<'file' | 'url' | null>(null);
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
const router = useRouter();
|
||||
const [selectedMethod, setSelectedMethod] = useState<'file' | 'url' | null>('file');
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
const [url, setUrl] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
setError(null);
|
||||
setUploadProgress(0);
|
||||
|
||||
// Placeholder för filuppladdning
|
||||
// I framtiden kan detta hanteras med backend-endpoint för PDF-parsing
|
||||
if (file.type === 'application/pdf') {
|
||||
setError('PDF-import är under utveckling. Använd "Skriv in recept" för att mata in recept manuellt.');
|
||||
} else {
|
||||
setError('Endast PDF-filer stöds för närvarande.');
|
||||
}
|
||||
|
||||
setUploadProgress(0);
|
||||
};
|
||||
|
||||
const handleURLSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
const handleFileSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const url = formData.get('url') as string;
|
||||
|
||||
if (!url) {
|
||||
setError('Vänligen ange en URL');
|
||||
if (!selectedFile) {
|
||||
setError('Välj en PDF eller bildfil först.');
|
||||
return;
|
||||
}
|
||||
|
||||
setError('Länk-import är under utveckling. Använd "Skriv in recept" för att mata in recept manuellt.');
|
||||
setError(null);
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile);
|
||||
|
||||
const res = await fetch('/api/quick-import-proxy', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errorMessage = await parseErrorResponse(res);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
sessionStorage.setItem('prefilled_markdown', data.markdown ?? '');
|
||||
router.push('/recipes/write');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Importen misslyckades.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUrlSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!url.trim()) {
|
||||
setError('Vänligen ange en URL.');
|
||||
return;
|
||||
}
|
||||
|
||||
setError(null);
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/quick-import-proxy', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ input: url.trim() }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errorMessage = await parseErrorResponse(res);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
sessionStorage.setItem('prefilled_markdown', data.markdown ?? '');
|
||||
router.push('/recipes/write');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Importen misslyckades.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -45,7 +87,7 @@ export default function ImportFilePage() {
|
||||
<Navigation />
|
||||
<h1 style={{ marginBottom: '0.5rem' }}>Importera från fil eller länk</h1>
|
||||
<p style={{ color: '#666', marginBottom: '1.5rem' }}>
|
||||
Ladda upp en receptfil (PDF) eller ange en URL för att importera ett recept.
|
||||
Ladda upp en PDF eller bild för OCR, eller ange en receptlänk.
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
@@ -60,7 +102,7 @@ export default function ImportFilePage() {
|
||||
fontSize: '0.95rem',
|
||||
}}
|
||||
>
|
||||
⚠️ {error}
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -72,7 +114,6 @@ export default function ImportFilePage() {
|
||||
marginBottom: '2rem',
|
||||
}}
|
||||
>
|
||||
{/* Fil-upload */}
|
||||
<div
|
||||
onClick={() => setSelectedMethod('file')}
|
||||
style={{
|
||||
@@ -81,84 +122,48 @@ export default function ImportFilePage() {
|
||||
borderRadius: '8px',
|
||||
background: selectedMethod === 'file' ? '#f0f9ff' : '#f9fafb',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
}}
|
||||
>
|
||||
<h2 style={{ margin: '0 0 1rem 0', fontSize: '1.2rem', color: '#0070f3' }}>
|
||||
📄 Ladda upp fil
|
||||
Ladda upp PDF eller bild
|
||||
</h2>
|
||||
<p style={{ color: '#666', margin: '0 0 1rem 0', fontSize: '0.95rem' }}>
|
||||
Ladda upp ett recept från en PDF eller textfil
|
||||
Stöd för PDF, PNG, JPG, JPEG, WEBP och BMP.
|
||||
</p>
|
||||
|
||||
{selectedMethod === 'file' && (
|
||||
<div style={{ marginTop: '1rem' }}>
|
||||
<label
|
||||
<form onSubmit={handleFileSubmit} style={{ display: 'grid', gap: '0.75rem' }}>
|
||||
<input
|
||||
type="file"
|
||||
accept=".pdf,.png,.jpg,.jpeg,.webp,.bmp"
|
||||
onChange={(e) => setSelectedFile(e.target.files?.[0] ?? null)}
|
||||
style={{
|
||||
display: 'block',
|
||||
padding: '1rem',
|
||||
padding: '0.75rem',
|
||||
background: 'white',
|
||||
border: '2px dashed #0070f3',
|
||||
border: '1px solid #cbd5e1',
|
||||
borderRadius: '6px',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
}}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
(e.currentTarget as HTMLElement).style.background = '#e3f2fd';
|
||||
}}
|
||||
onDragLeave={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.background = 'white';
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!selectedFile || isLoading}
|
||||
style={{
|
||||
padding: '0.75rem',
|
||||
background: '#0070f3',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: !selectedFile || isLoading ? 'not-allowed' : 'pointer',
|
||||
opacity: !selectedFile || isLoading ? 0.6 : 1,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
accept=".pdf,.txt,.docx"
|
||||
onChange={handleFileUpload}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
<p style={{ margin: '0', color: '#0070f3', fontWeight: 600 }}>
|
||||
Dra och släpp fil här
|
||||
</p>
|
||||
<p style={{ margin: '0.5rem 0 0 0', color: '#999', fontSize: '0.85rem' }}>
|
||||
eller klicka för att välja
|
||||
</p>
|
||||
<p style={{ margin: '0.5rem 0 0 0', color: '#999', fontSize: '0.8rem' }}>
|
||||
PDF, TXT, DOCX stöds
|
||||
</p>
|
||||
</label>
|
||||
|
||||
{uploadProgress > 0 && uploadProgress < 100 && (
|
||||
<div style={{ marginTop: '0.75rem' }}>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '6px',
|
||||
background: '#e5e7eb',
|
||||
borderRadius: '3px',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
background: '#0070f3',
|
||||
width: `${uploadProgress}%`,
|
||||
transition: 'width 0.3s',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p style={{ margin: '0.5rem 0 0 0', fontSize: '0.85rem', color: '#666' }}>
|
||||
{uploadProgress}%
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isLoading ? 'Importerar...' : 'Importera fil'}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* URL-import */}
|
||||
<div
|
||||
onClick={() => setSelectedMethod('url')}
|
||||
style={{
|
||||
@@ -167,21 +172,21 @@ export default function ImportFilePage() {
|
||||
borderRadius: '8px',
|
||||
background: selectedMethod === 'url' ? '#f0fdf4' : '#f9fafb',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
}}
|
||||
>
|
||||
<h2 style={{ margin: '0 0 1rem 0', fontSize: '1.2rem', color: '#10b981' }}>
|
||||
🔗 Länk till recept
|
||||
Länk till recept
|
||||
</h2>
|
||||
<p style={{ color: '#666', margin: '0 0 1rem 0', fontSize: '0.95rem' }}>
|
||||
Ange URL till en receptsida eller blogg
|
||||
Ange URL till exempelvis ICA eller en annan receptsida.
|
||||
</p>
|
||||
|
||||
{selectedMethod === 'url' && (
|
||||
<form onSubmit={handleURLSubmit} style={{ marginTop: '1rem' }}>
|
||||
<form onSubmit={handleUrlSubmit} style={{ display: 'grid', gap: '0.75rem' }}>
|
||||
<input
|
||||
type="url"
|
||||
name="url"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
placeholder="https://exempel.se/recept/..."
|
||||
style={{
|
||||
width: '100%',
|
||||
@@ -190,11 +195,11 @@ export default function ImportFilePage() {
|
||||
borderRadius: '6px',
|
||||
fontSize: '0.9rem',
|
||||
boxSizing: 'border-box',
|
||||
marginBottom: '0.75rem',
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!url.trim() || isLoading}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '0.75rem',
|
||||
@@ -202,41 +207,33 @@ export default function ImportFilePage() {
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontWeight: 500,
|
||||
cursor: !url.trim() || isLoading ? 'not-allowed' : 'pointer',
|
||||
opacity: !url.trim() || isLoading ? 0.6 : 1,
|
||||
fontWeight: 600,
|
||||
fontSize: '0.95rem',
|
||||
}}
|
||||
>
|
||||
Importera från länk
|
||||
{isLoading ? 'Importerar...' : 'Importera från länk'}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info-box */}
|
||||
<div
|
||||
style={{
|
||||
background: '#fef3c7',
|
||||
border: '1px solid #fcd34d',
|
||||
background: '#f0fdf4',
|
||||
border: '1px solid #86efac',
|
||||
borderRadius: '6px',
|
||||
padding: '1rem',
|
||||
marginBottom: '1.5rem',
|
||||
color: '#92400e',
|
||||
color: '#166534',
|
||||
fontSize: '0.9rem',
|
||||
}}
|
||||
>
|
||||
<strong>💡 Tips:</strong> För närvarande är PDF och länk-import under utveckling. Du kan{' '}
|
||||
<Link
|
||||
href="/recipes/write"
|
||||
style={{ color: '#0070f3', textDecoration: 'none', fontWeight: 600 }}
|
||||
>
|
||||
skriv in receptet manuellt
|
||||
</Link>{' '}
|
||||
eller prova att ladda upp en fil och se om det fungerar.
|
||||
Efter import öppnas receptet automatiskt i redigeringsläget.
|
||||
</div>
|
||||
|
||||
{/* Knapp för att gå tillbaka */}
|
||||
<div style={{ display: 'flex', gap: '1rem' }}>
|
||||
<Link
|
||||
href="/recipes/create"
|
||||
@@ -245,14 +242,12 @@ export default function ImportFilePage() {
|
||||
background: 'transparent',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '1rem',
|
||||
textDecoration: 'none',
|
||||
color: '#333',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
← Tillbaka
|
||||
Tillbaka
|
||||
</Link>
|
||||
<Link
|
||||
href="/recipes/write"
|
||||
@@ -260,11 +255,8 @@ export default function ImportFilePage() {
|
||||
padding: '0.75rem 1.5rem',
|
||||
background: '#0070f3',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
textDecoration: 'none',
|
||||
cursor: 'pointer',
|
||||
fontSize: '1rem',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user