190 lines
6.2 KiB
JavaScript
190 lines
6.2 KiB
JavaScript
import { useState, useEffect } from 'react';
|
|
import { useParams, useNavigate, Link } from 'react-router-dom';
|
|
import { fetchRecipe, updateRecipe, deleteRecipe } from '../api';
|
|
import { useAuth } from '../context/AuthContext';
|
|
import CategoryBadge from '../components/CategoryBadge';
|
|
import IngredientList from '../components/IngredientList';
|
|
import StepList from '../components/StepList';
|
|
|
|
const CATEGORIES = ['Frühstück', 'Hauptgericht', 'Dessert', 'Snack', 'Getränk', 'Sonstiges'];
|
|
|
|
export default function RecipeDetail() {
|
|
const { id } = useParams();
|
|
const navigate = useNavigate();
|
|
const { user } = useAuth();
|
|
const [recipe, setRecipe] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [editing, setEditing] = useState(false);
|
|
const [form, setForm] = useState({});
|
|
|
|
useEffect(() => { load(); }, [id]);
|
|
|
|
const load = async () => {
|
|
try {
|
|
const data = await fetchRecipe(id);
|
|
setRecipe(data);
|
|
setForm(data);
|
|
} catch (e) {
|
|
navigate('/');
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
await updateRecipe(id, { ...form, user_id: user?.id || null });
|
|
setRecipe(form);
|
|
setEditing(false);
|
|
};
|
|
|
|
const handleDelete = async () => {
|
|
if (!confirm('Rezept wirklich löschen?')) return;
|
|
await deleteRecipe(id);
|
|
navigate('/');
|
|
};
|
|
|
|
const updateField = (field, value) => setForm(prev => ({ ...prev, [field]: value }));
|
|
|
|
if (loading) return <div className="text-center py-12 text-gray-400">Laden...</div>;
|
|
if (!recipe) return null;
|
|
|
|
const sourceUrl = recipe.source_url || recipe.tiktok_url;
|
|
const sourceType = recipe.source || 'tiktok';
|
|
|
|
let videoId = null;
|
|
let embedUrl = null;
|
|
if (sourceUrl) {
|
|
const ttMatch = sourceUrl.match(/video\/(\d+)/);
|
|
if (ttMatch) {
|
|
videoId = ttMatch[1];
|
|
embedUrl = `https://www.tiktok.com/embed/v2/${videoId}`;
|
|
}
|
|
}
|
|
|
|
const author = recipe.source_author || recipe.tiktok_author;
|
|
|
|
return (
|
|
<div className="max-w-2xl mx-auto">
|
|
<Link to="/" className="text-blue-600 hover:text-blue-700 text-sm font-medium mb-4 inline-block">
|
|
← Zurück
|
|
</Link>
|
|
|
|
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
|
{embedUrl && (
|
|
<div className="w-full" style={{ paddingBottom: '177.78%', position: 'relative' }}>
|
|
<iframe
|
|
src={embedUrl}
|
|
className="absolute inset-0 w-full h-full"
|
|
allowFullScreen
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="p-6 space-y-6">
|
|
<div className="flex items-start justify-between gap-3">
|
|
{editing ? (
|
|
<input
|
|
type="text"
|
|
value={form.name}
|
|
onChange={e => updateField('name', e.target.value)}
|
|
className="text-2xl font-bold flex-1 px-2 py-1 border border-gray-300 rounded"
|
|
/>
|
|
) : (
|
|
<h1 className="text-2xl font-bold">{recipe.name}</h1>
|
|
)}
|
|
<CategoryBadge category={editing ? form.category : recipe.category} />
|
|
</div>
|
|
|
|
{editing && (
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Kategorie</label>
|
|
<select
|
|
value={form.category}
|
|
onChange={e => updateField('category', e.target.value)}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
|
>
|
|
{CATEGORIES.map(c => <option key={c} value={c}>{c}</option>)}
|
|
</select>
|
|
</div>
|
|
)}
|
|
|
|
{!editing && recipe.description && (
|
|
<p className="text-gray-500">{recipe.description}</p>
|
|
)}
|
|
|
|
{editing && (
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung</label>
|
|
<textarea
|
|
value={form.description}
|
|
onChange={e => updateField('description', e.target.value)}
|
|
rows={2}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm resize-none"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{editing ? (
|
|
<IngredientList
|
|
ingredients={form.ingredients}
|
|
editable
|
|
onChange={ingredients => updateField('ingredients', ingredients)}
|
|
/>
|
|
) : (
|
|
<IngredientList ingredients={recipe.ingredients} />
|
|
)}
|
|
|
|
{editing ? (
|
|
<StepList
|
|
steps={form.steps}
|
|
editable
|
|
onChange={steps => updateField('steps', steps)}
|
|
/>
|
|
) : (
|
|
<StepList steps={recipe.steps} />
|
|
)}
|
|
|
|
{author && (
|
|
<p className="text-xs text-gray-400">
|
|
Quelle: @{author} (TikTok)
|
|
</p>
|
|
)}
|
|
|
|
<div className="flex gap-3 pt-2 border-t border-gray-100">
|
|
{editing ? (
|
|
<>
|
|
<button
|
|
onClick={handleSave}
|
|
className="flex-1 bg-blue-600 hover:bg-blue-700 text-white py-2 rounded-lg font-medium transition-colors"
|
|
>
|
|
Speichern
|
|
</button>
|
|
<button
|
|
onClick={() => { setEditing(false); setForm(recipe); }}
|
|
className="px-4 py-2 border border-gray-300 rounded-lg font-medium hover:bg-gray-50 transition-colors"
|
|
>
|
|
Abbrechen
|
|
</button>
|
|
</>
|
|
) : (
|
|
<>
|
|
<button
|
|
onClick={() => setEditing(true)}
|
|
className="flex-1 border border-blue-300 text-blue-600 hover:bg-blue-50 py-2 rounded-lg font-medium transition-colors"
|
|
>
|
|
Bearbeiten
|
|
</button>
|
|
<button
|
|
onClick={handleDelete}
|
|
className="px-4 py-2 border border-red-300 text-red-600 hover:bg-red-50 rounded-lg font-medium transition-colors"
|
|
>
|
|
Löschen
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|