feat(portfolio): 포트폴리오 페이지 전체 구현
- 3탭 구조: 프로필&경력, 프로젝트, 자기소개 - 비밀번호 인증 → 편집 모드 - 클립보드 복사, PDF 내보내기 (window.print) - 사이버펑크 테마 CSS, 모바일 반응형 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
100
src/pages/portfolio/usePortfolioApi.js
Normal file
100
src/pages/portfolio/usePortfolioApi.js
Normal file
@@ -0,0 +1,100 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user