Files
web-page/src/pages/portfolio/usePortfolioApi.js
gahusb 60655f8ba9 fix(portfolio): apiFetch에서 Content-Type 헤더가 options.headers에 덮여 사라지는 문제 수정
PUT /api/profile/profile 등 인증 헤더를 함께 보내는 요청에서 Content-Type이
빠져 FastAPI가 body를 JSON으로 파싱하지 못해 422를 반환하던 문제. spread 순서를
뒤집어 options 펼친 뒤 headers를 마지막에 머지하도록 수정.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 02:01:14 +09:00

101 lines
4.3 KiB
JavaScript

import { useState, useCallback } from 'react';
const BASE = '/api/profile';
async function apiFetch(path, options = {}) {
const res = await fetch(`${BASE}${path}`, {
...options,
headers: { 'Content-Type': 'application/json', ...options.headers },
});
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: res.statusText }));
throw new Error(err.detail || res.statusText);
}
return res.json();
}
export default function usePortfolioApi() {
const [token, setToken] = useState(null);
const [authError, setAuthError] = useState('');
const authHeaders = token ? { Authorization: `Bearer ${token}` } : {};
const login = useCallback(async (password) => {
setAuthError('');
try {
const data = await apiFetch('/auth', {
method: 'POST',
body: JSON.stringify({ password }),
});
setToken(data.token);
return true;
} catch (err) {
setAuthError(err.message);
return false;
}
}, []);
const logout = useCallback(() => setToken(null), []);
// ── Public ──
const fetchPublic = useCallback(() => apiFetch('/public'), []);
// ── Profile ──
const fetchProfile = useCallback(() =>
apiFetch('/profile', { headers: authHeaders }), [token]);
const saveProfile = useCallback((data) =>
apiFetch('/profile', { method: 'PUT', headers: authHeaders, body: JSON.stringify(data) }), [token]);
// ── Careers ──
const fetchCareers = useCallback(() =>
apiFetch('/careers', { headers: authHeaders }), [token]);
const addCareer = useCallback((data) =>
apiFetch('/careers', { method: 'POST', headers: authHeaders, body: JSON.stringify(data) }), [token]);
const editCareer = useCallback((id, data) =>
apiFetch(`/careers/${id}`, { method: 'PUT', headers: authHeaders, body: JSON.stringify(data) }), [token]);
const removeCareer = useCallback((id) =>
apiFetch(`/careers/${id}`, { method: 'DELETE', headers: authHeaders }), [token]);
// ── Projects ──
const fetchProjects = useCallback(() =>
apiFetch('/projects', { headers: authHeaders }), [token]);
const addProject = useCallback((data) =>
apiFetch('/projects', { method: 'POST', headers: authHeaders, body: JSON.stringify(data) }), [token]);
const editProject = useCallback((id, data) =>
apiFetch(`/projects/${id}`, { method: 'PUT', headers: authHeaders, body: JSON.stringify(data) }), [token]);
const removeProject = useCallback((id) =>
apiFetch(`/projects/${id}`, { method: 'DELETE', headers: authHeaders }), [token]);
// ── Skills ──
const fetchSkills = useCallback(() =>
apiFetch('/skills', { headers: authHeaders }), [token]);
const addSkill = useCallback((data) =>
apiFetch('/skills', { method: 'POST', headers: authHeaders, body: JSON.stringify(data) }), [token]);
const editSkill = useCallback((id, data) =>
apiFetch(`/skills/${id}`, { method: 'PUT', headers: authHeaders, body: JSON.stringify(data) }), [token]);
const removeSkill = useCallback((id) =>
apiFetch(`/skills/${id}`, { method: 'DELETE', headers: authHeaders }), [token]);
// ── Introductions ──
const fetchIntros = useCallback(() =>
apiFetch('/introductions', { headers: authHeaders }), [token]);
const addIntro = useCallback((data) =>
apiFetch('/introductions', { method: 'POST', headers: authHeaders, body: JSON.stringify(data) }), [token]);
const editIntro = useCallback((id, data) =>
apiFetch(`/introductions/${id}`, { method: 'PUT', headers: authHeaders, body: JSON.stringify(data) }), [token]);
const removeIntro = useCallback((id) =>
apiFetch(`/introductions/${id}`, { method: 'DELETE', headers: authHeaders }), [token]);
const setMainIntro = useCallback((id) =>
apiFetch(`/introductions/${id}/main`, { method: 'PATCH', headers: authHeaders }), [token]);
return {
token, authError, login, logout,
fetchPublic,
fetchProfile, saveProfile,
fetchCareers, addCareer, editCareer, removeCareer,
fetchProjects, addProject, editProject, removeProject,
fetchSkills, addSkill, editSkill, removeSkill,
fetchIntros, addIntro, editIntro, removeIntro, setMainIntro,
};
}