- 3탭 구조: 프로필&경력, 프로젝트, 자기소개 - 비밀번호 인증 → 편집 모드 - 클립보드 복사, PDF 내보내기 (window.print) - 사이버펑크 테마 CSS, 모바일 반응형 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
101 lines
4.3 KiB
JavaScript
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}`, {
|
|
headers: { 'Content-Type': 'application/json', ...options.headers },
|
|
...options,
|
|
});
|
|
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,
|
|
};
|
|
}
|