feat: implement security headers and rate limiting; update environment variables and documentation

This commit is contained in:
Nils-Johan Gynther
2026-04-21 08:06:21 +02:00
parent c1d51c771e
commit 7748ad311f
13 changed files with 133 additions and 23 deletions
@@ -1,6 +1,6 @@
'use client';
import { useState, useEffect, useTransition } from 'react';
import { useState, useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation';
import { useAuthFetch } from '../../../lib/use-auth-fetch';
import type {
@@ -96,7 +96,8 @@ export default function RecipeDetailClient({ recipe: initialRecipe }: { recipe:
// Inventarieförhandsgranskning
const [preview, setPreview] = useState<RecipeInventoryPreview | null>(null);
const [previewError, setPreviewError] = useState<string | null>(null);
const [isPreviewing, startPreviewTransition] = useTransition();
const [isPreviewing, setIsPreviewing] = useState(false);
const previewSectionRef = useRef<HTMLElement>(null);
// Bilduppdatering
const [imageUrlInput, setImageUrlInput] = useState('');
@@ -124,17 +125,23 @@ export default function RecipeDetailClient({ recipe: initialRecipe }: { recipe:
};
// ── Inventarieförhandsgranskning ──
const loadPreview = () => {
const loadPreview = async () => {
if (preview) {
previewSectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
return;
}
setPreviewError(null);
startPreviewTransition(async () => {
try {
const res = await fetch(`/api/recipe-preview-proxy?id=${recipe.id}`, { cache: 'no-store' });
if (!res.ok) throw new Error(await parseErrorResponse(res));
setPreview(await res.json());
} catch (err) {
setPreviewError(err instanceof Error ? err.message : 'Fel vid hämtning av inventariedata');
}
});
setIsPreviewing(true);
try {
const res = await fetch(`/api/recipe-preview-proxy?id=${recipe.id}`, { cache: 'no-store' });
if (!res.ok) throw new Error(await parseErrorResponse(res));
setPreview(await res.json());
setTimeout(() => previewSectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 50);
} catch (err) {
setPreviewError(err instanceof Error ? err.message : 'Fel vid hämtning av inventariedata');
} finally {
setIsPreviewing(false);
}
};
// ── Ta bort recept ──
@@ -313,7 +320,7 @@ export default function RecipeDetailClient({ recipe: initialRecipe }: { recipe:
{/* Lagergranskning */}
{(preview || previewError) && (
<section style={{ ...sectionStyle, marginTop: '1.5rem' }}>
<section ref={previewSectionRef} style={{ ...sectionStyle, marginTop: '1.5rem' }}>
<h2 style={sectionTitle}>🛒 Vad behöver jag köpa?</h2>
{previewError && <p style={errorStyle}>{previewError}</p>}
{preview && (