13장 : 실전 프로젝트프론트엔드 통합 (React, Angular)
프로젝트 목록 UI
프로젝트 목록을 조회하고 생성·삭제 동작을 백엔드 API와 연결합니다.
프로젝트 및 파일 관리 UI 구현
프론트엔드 통합은 API 계약, 인증 토큰 전달, CORS, 로딩/오류 상태 기준으로 읽습니다.
이제 로그인 후 접근할 수 있는 프로젝트 목록 페이지와 에디터 페이지를 구현합니다.
프로젝트 목록
import React, { useEffect, useState } from 'react';
import api from '../api/axiosInstance';
import { useAuth } from '../context/AuthContext';
import { Link, useNavigate } from 'react-router-dom';
interface Project {
id: string;
name: string;
createdAt: string;
updatedAt: string;
}
const ProjectsPage: React.FC = () => {
const { user, logout } = useAuth();
const [projects, setProjects] = useState<Project[]>([]);
const [newProjectName, setNewProjectName] = useState('');
const navigate = useNavigate();
useEffect(() => {
const fetchProjects = async () => {
try {
const response = await api.get('/projects');
setProjects(response.data);
} catch (error) {
console.error('Failed to fetch projects:', error);
}
};
fetchProjects();
}, []);
const handleCreateProject = async (e: React.FormEvent) => {
e.preventDefault();
if (!newProjectName.trim()) return;
try {
const response = await api.post('/projects', { name: newProjectName });
setProjects([...projects, response.data]);
setNewProjectName('');
navigate(`/project/${response.data.id}`); // 생성 후 바로 에디터 페이지로 이동
} catch (error) {
console.error('Failed to create project:', error);
}
};
const handleDeleteProject = async (projectId: string) => {
if (!window.confirm('Are you sure you want to delete this project?')) return;
try {
await api.delete(`/projects/${projectId}`);
setProjects(projects.filter(p => p.id !== projectId));
} catch (error) {
console.error('Failed to delete project:', error);
}
};
return (
<div>
<h2>Welcome, {user?.nickname || user?.email}!</h2>
<button onClick={logout}>Logout</button>
<h3>Create New Project</h3>
<form onSubmit={handleCreateProject}>
<input
type="text"
value={newProjectName}
onChange={(e) => setNewProjectName(e.target.value)}
placeholder="Project Name"
required
/>
<button type="submit">Create</button>
</form>
<h3>Your Projects</h3>
{projects.length === 0 ? (
<p>No projects yet. Create one!</p>
) : (
<ul>
{projects.map((project) => (
<li key={project.id}>
<Link to={`/project/${project.id}`}>{project.name}</Link> ({new Date(project.updatedAt).toLocaleDateString()})
<button onClick={() => handleDeleteProject(project.id)} style={{ marginLeft: '10px' }}>Delete</button>
</li>
))}
</ul>
)}
</div>
);
};
export default ProjectsPage;