본문으로 건너뛰기

안동민 개발노트

본문 시작
13장 : 실전 프로젝트프론트엔드 통합 (React, Angular)

프로젝트 목록 UI

프로젝트 목록을 조회하고 생성·삭제 동작을 백엔드 API와 연결합니다.

프로젝트 및 파일 관리 UI 구현

프론트엔드 통합은 API 계약, 인증 토큰 전달, CORS, 로딩/오류 상태 기준으로 읽습니다.

이제 로그인 후 접근할 수 있는 프로젝트 목록 페이지와 에디터 페이지를 구현합니다.

프로젝트 목록

src/pages/ProjectsPage.tsx (간략화)
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;