import { useState } from 'react'; import type { FormEvent } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { createRecipe, importRecipeFromUrl } from '../services/api'; import type { RecipeDraft, UrlImportResult } from '../types/recipe'; type ImportErrorType = 'invalid-url' | 'parse-failure' | 'timeout' | 'generic'; function toTextBlock(items: string[]): string { return items.join('\n'); } function toList(text: string): string[] { return text .split('\n') .map((line) => line.trim()) .filter((line) => line.length > 0); } function getImportErrorDetails(message: string): { type: ImportErrorType; message: string } { const normalized = message.toLowerCase(); if (normalized.includes('valid url')) { return { type: 'invalid-url', message: 'Please enter a valid URL (including https://).', }; } if (normalized.includes('timed out')) { return { type: 'timeout', message: 'The import request timed out. Please try again in a moment.', }; } if (normalized.includes('network error') || normalized.includes('could not fetch the page')) { return { type: 'generic', message: 'We could not reach that recipe page right now. Please try again in a moment.', }; } if (normalized.includes('did not return an html page')) { return { type: 'generic', message: 'That link did not point to an HTML recipe page. Try the direct recipe URL.', }; } return { type: 'generic', message, }; } export function ImportUrlPage() { const navigate = useNavigate(); const [url, setUrl] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [errorType, setErrorType] = useState(null); const [result, setResult] = useState(null); const [draft, setDraft] = useState(null); const [draftError, setDraftError] = useState(null); const [isSaving, setIsSaving] = useState(false); const handleSubmit = async (event: FormEvent) => { event.preventDefault(); setLoading(true); setError(null); setErrorType(null); setResult(null); setDraft(null); setDraftError(null); try { const imported = await importRecipeFromUrl(url); setResult(imported); setDraft(imported.draft_recipe); if (!imported.draft_recipe) { setErrorType('parse-failure'); setError('We could fetch this page, but could not find recipe fields to import.'); } } catch (err) { const message = err instanceof Error ? err.message : 'Failed to import recipe URL'; const details = getImportErrorDetails(message); setErrorType(details.type); setError(details.message); } finally { setLoading(false); } }; const handleSave = async (event: FormEvent) => { event.preventDefault(); if (!draft) { setDraftError('No draft recipe to save.'); return; } const title = draft.title.trim(); const ingredients = draft.ingredients.map((item) => item.trim()).filter(Boolean); const instructions = draft.instructions.map((item) => item.trim()).filter(Boolean); if (!title) { setDraftError('Title is required.'); return; } if (ingredients.length === 0) { setDraftError('At least one ingredient is required.'); return; } if (instructions.length === 0) { setDraftError('At least one instruction step is required.'); return; } setIsSaving(true); setDraftError(null); try { const created = await createRecipe({ ...draft, title, ingredients, instructions, }); navigate(`/recipe/${created.id}`); } catch (err) { const message = err instanceof Error ? err.message : 'Failed to save recipe'; setDraftError(message); setIsSaving(false); } }; return (

Import from URL

Paste a recipe URL and we'll try to fetch the page and extract recipe data.

setUrl(event.target.value)} placeholder="https://example.com/my-recipe" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{error && (

{errorType === 'invalid-url' && 'Invalid URL:'} {errorType === 'timeout' && 'Import timed out:'} {errorType === 'parse-failure' && 'Parse failed:'} {errorType === 'generic' && 'Error:'} {' '} {error}

)} {result && (

Parsed Preview

Source: {result.source_url}

JSON-LD blocks found: {result.json_ld_blocks.length}

{draft ? (

Review and edit before saving.

{draftError && (
{draftError}
)}
setDraft({ ...draft, title: event.target.value })} className="w-full px-3 py-2 border border-gray-300 rounded-lg" />