This commit is contained in:
nadja
2026-11-01 20:14:25 +01:00
parent 828c38c779
commit c4685c2152
1686 changed files with 570761 additions and 560 deletions

View File

@@ -3,6 +3,8 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#2563eb" />
<title>TikTok Rezepte</title>
</head>
<body>

View File

@@ -0,0 +1,15 @@
{
"short_name": "Rezepte",
"name": "TikTok Rezepte",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#2563eb",
"background_color": "#ffffff"
}

23
frontend/public/sw.js Normal file
View File

@@ -0,0 +1,23 @@
const CACHE_NAME = 'rezepte-v1';
const ASSETS = [
'/',
'/index.html',
];
self.addEventListener('install', (event) => {
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS)));
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => response || fetch(event.request))
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))
)
);
});

View File

@@ -21,12 +21,31 @@ function normalizeIngredients(ingredients) {
);
}
export default function IngredientList({ ingredients, editable, onChange }) {
const [checked, setChecked] = useState({});
export default function IngredientList({ ingredients, editable, onChange, multiplier = 1 }) {
const [checked, setChecked] = useState(() => {
try {
const saved = localStorage.getItem(`ingredients-checked-${window.location.pathname}`);
return saved ? JSON.parse(saved) : {};
} catch { return {}; }
});
const normalized = useMemo(() => normalizeIngredients(ingredients), [ingredients]);
const toggle = (i) => setChecked(prev => ({ ...prev, [i]: !prev[i] }));
const toggle = (i) => {
const newChecked = { ...checked, [i]: !checked[i] };
setChecked(newChecked);
localStorage.setItem(`ingredients-checked-${window.location.pathname}`, JSON.stringify(newChecked));
};
const formatAmount = (amount, mult) => {
if (!amount || mult === 1) return amount;
// Versuche Zahl zu extrahieren und zu multiplizieren
const num = parseFloat(amount.replace(',', '.'));
if (isNaN(num)) return amount;
const result = num * mult;
// Formatiere zurück (z.B. 1.5 -> 1,5)
return result.toLocaleString('de-DE', { maximumFractionDigits: 2 });
};
const updateIngredient = (i, field, value) => {
const updated = normalized.map((ing, idx) =>
@@ -84,7 +103,7 @@ export default function IngredientList({ ingredients, editable, onChange }) {
</>
) : (
<span className={`${checked[i] ? 'line-through text-gray-400' : ''}`}>
{ing.amount && `${ing.amount} `}
{ing.amount && `${formatAmount(ing.amount, multiplier)} `}
{ing.unit && `${ing.unit} `}
{ing.name}
</span>

View File

@@ -4,6 +4,12 @@ import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './index.css';
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js');
});
}
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>

View File

@@ -12,8 +12,9 @@ export default function RecipeDetail() {
const { id } = useParams();
const navigate = useNavigate();
const { user } = useAuth();
const [recipe, setRecipe] = useState(null);
const [loading, setLoading] = useState(true);
const [servings, setServings] = useState(2);
const [baseServings, setBaseServings] = useState(2);
const [editing, setEditing] = useState(false);
const [form, setForm] = useState({});
@@ -24,6 +25,10 @@ export default function RecipeDetail() {
const data = await fetchRecipe(id);
setRecipe(data);
setForm(data);
if (data.servings) {
setServings(data.servings);
setBaseServings(data.servings);
}
} catch (e) {
navigate('/');
}
@@ -130,7 +135,28 @@ export default function RecipeDetail() {
onChange={ingredients => updateField('ingredients', ingredients)}
/>
) : (
<IngredientList ingredients={recipe.ingredients} />
<div>
<div className="flex items-center justify-between mb-3">
<h3 className="font-semibold text-lg">Zutaten</h3>
<div className="flex items-center gap-2 text-sm bg-gray-50 px-3 py-1 rounded-lg border border-gray-200">
<span className="text-gray-500 font-medium">Portionen:</span>
<button
onClick={() => setServings(Math.max(1, servings - 1))}
className="w-6 h-6 flex items-center justify-center bg-white border border-gray-300 rounded font-bold hover:bg-gray-100"
>
-
</button>
<span className="w-6 text-center font-semibold">{servings}</span>
<button
onClick={() => setServings(servings + 1)}
className="w-6 h-6 flex items-center justify-center bg-white border border-gray-300 rounded font-bold hover:bg-gray-100"
>
+
</button>
</div>
</div>
<IngredientList ingredients={recipe.ingredients} multiplier={servings / baseServings} />
</div>
)}
{editing ? (