refactor: useAuthFetch-hook för automatisk JWT-header i klientanrop
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useTransition } from 'react';
|
import { useState, useEffect, useTransition } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useAuthFetch } from '../../../lib/use-auth-fetch';
|
||||||
import type {
|
import type {
|
||||||
Recipe,
|
Recipe,
|
||||||
Product,
|
Product,
|
||||||
@@ -65,7 +65,7 @@ function StatusBadge({ status }: { status: 'enough' | 'missing' | 'unit_mismatch
|
|||||||
|
|
||||||
export default function RecipeDetailClient({ recipe: initialRecipe }: { recipe: Recipe }) {
|
export default function RecipeDetailClient({ recipe: initialRecipe }: { recipe: Recipe }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data: session } = useSession();
|
const authFetch = useAuthFetch();
|
||||||
const [recipe, setRecipe] = useState(initialRecipe);
|
const [recipe, setRecipe] = useState(initialRecipe);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [isLiked, setIsLiked] = useState(false);
|
const [isLiked, setIsLiked] = useState(false);
|
||||||
@@ -142,9 +142,7 @@ export default function RecipeDetailClient({ recipe: initialRecipe }: { recipe:
|
|||||||
if (!confirm(`Ta bort receptet "${recipe.name}"? Det går inte att ångra.`)) return;
|
if (!confirm(`Ta bort receptet "${recipe.name}"? Det går inte att ångra.`)) return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/recipes/${recipe.id}`, { method: 'DELETE',
|
const res = await authFetch(`/api/recipes/${recipe.id}`, { method: 'DELETE' });
|
||||||
headers: { Authorization: `Bearer ${session?.accessToken}` },
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error(await parseErrorResponse(res));
|
if (!res.ok) throw new Error(await parseErrorResponse(res));
|
||||||
router.push('/recipes');
|
router.push('/recipes');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -166,9 +164,8 @@ export default function RecipeDetailClient({ recipe: initialRecipe }: { recipe:
|
|||||||
quantity: Number(ing.quantity),
|
quantity: Number(ing.quantity),
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
const res = await fetch(`/api/recipes/${recipe.id}`, {
|
const res = await authFetch(`/api/recipes/${recipe.id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session?.accessToken}` },
|
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(await parseErrorResponse(res));
|
if (!res.ok) throw new Error(await parseErrorResponse(res));
|
||||||
@@ -188,9 +185,8 @@ export default function RecipeDetailClient({ recipe: initialRecipe }: { recipe:
|
|||||||
setIsUploadingImage(true);
|
setIsUploadingImage(true);
|
||||||
setImageError(null);
|
setImageError(null);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/recipes/${recipe.id}/image`, {
|
const res = await authFetch(`/api/recipes/${recipe.id}/image`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session?.accessToken}` },
|
|
||||||
body: JSON.stringify({ sourceUrl: imageUrlInput.trim() }),
|
body: JSON.stringify({ sourceUrl: imageUrlInput.trim() }),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(await parseErrorResponse(res));
|
if (!res.ok) throw new Error(await parseErrorResponse(res));
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useAuthFetch } from '../../../lib/use-auth-fetch';
|
||||||
import { fetchJson } from '../../../lib/api';
|
import { fetchJson } from '../../../lib/api';
|
||||||
import { parseErrorResponse } from '../../../lib/error-handler';
|
import { parseErrorResponse } from '../../../lib/error-handler';
|
||||||
import type { Product } from '../../../features/inventory/types';
|
import type { Product } from '../../../features/inventory/types';
|
||||||
@@ -38,7 +38,7 @@ type Step = 'input' | 'review' | 'saving';
|
|||||||
|
|
||||||
export default function ImportRecipePage() {
|
export default function ImportRecipePage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data: session } = useSession();
|
const authFetch = useAuthFetch();
|
||||||
const [step, setStep] = useState<Step>('input');
|
const [step, setStep] = useState<Step>('input');
|
||||||
const [markdown, setMarkdown] = useState('');
|
const [markdown, setMarkdown] = useState('');
|
||||||
const [parsed, setParsed] = useState<ParseResult | null>(null);
|
const [parsed, setParsed] = useState<ParseResult | null>(null);
|
||||||
@@ -136,9 +136,8 @@ export default function ImportRecipePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/recipes', {
|
const res = await authFetch('/api/recipes', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session?.accessToken}` },
|
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useAuthFetch } from '../../../lib/use-auth-fetch';
|
||||||
import { fetchJson } from '../../../lib/api';
|
import { fetchJson } from '../../../lib/api';
|
||||||
import { parseErrorResponse } from '../../../lib/error-handler';
|
import { parseErrorResponse } from '../../../lib/error-handler';
|
||||||
import type { Product } from '../../../features/inventory/types';
|
import type { Product } from '../../../features/inventory/types';
|
||||||
@@ -37,7 +37,7 @@ type Step = 'input' | 'review' | 'saving' | 'saved';
|
|||||||
|
|
||||||
export default function WriteRecipePage() {
|
export default function WriteRecipePage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data: session } = useSession();
|
const authFetch = useAuthFetch();
|
||||||
const [step, setStep] = useState<Step>('input');
|
const [step, setStep] = useState<Step>('input');
|
||||||
const [markdown, setMarkdown] = useState('');
|
const [markdown, setMarkdown] = useState('');
|
||||||
const [parsed, setParsed] = useState<ParseResult | null>(null);
|
const [parsed, setParsed] = useState<ParseResult | null>(null);
|
||||||
@@ -193,9 +193,8 @@ export default function WriteRecipePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/recipes', {
|
const res = await authFetch('/api/recipes', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session?.accessToken}` },
|
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook som returnerar en fetch-funktion med Authorization-header automatiskt ifylld.
|
||||||
|
* Används i klientkomponenter som gör anrop till endpoints som Caddy routar direkt
|
||||||
|
* till NestJS (t.ex. /api/recipes*, /api/products*, /api/inventory*).
|
||||||
|
*
|
||||||
|
* Exempel:
|
||||||
|
* const authFetch = useAuthFetch();
|
||||||
|
* const res = await authFetch('/api/recipes/1', { method: 'PATCH', body: JSON.stringify(data) });
|
||||||
|
*/
|
||||||
|
export function useAuthFetch() {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
(url: string, init: RequestInit = {}): Promise<Response> => {
|
||||||
|
const headers = new Headers(init.headers);
|
||||||
|
headers.set('Authorization', `Bearer ${session?.accessToken ?? ''}`);
|
||||||
|
if (!headers.has('Content-Type') && init.body && typeof init.body === 'string') {
|
||||||
|
headers.set('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
return fetch(url, { ...init, headers });
|
||||||
|
},
|
||||||
|
[session?.accessToken],
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user