Skip to content

manNomi/frontend

 
 

Repository files navigation

FootballSquare

FC 프로클럽 커뮤니티 & 매칭 플랫폼

프로젝트 개요

FootballSquare는 FC24 프로클럽 유저를 위한 온라인 축구팀 매칭 및 커뮤니티 플랫폼입니다. 팀 구성, 경기 매칭, 대회 운영, 커뮤니티 활동을 하나의 서비스에서 제공합니다.

FC 프로클럽은 FIFA 시리즈의 온라인 모드로, 실제 사용자들이 각자의 포지션을 맡아 팀을 이루어 경기하는 게임입니다. 이 플랫폼은 게임 경험을 웹으로 확장하여, 게임처럼 빠르고 반응적인 UI를 목표로 했습니다.

핵심 목표

  • 매치 생성·삭제·스코어 수정 시 0-latency 반응
  • 실시간 채팅과 즉각적인 UI 피드백
  • 토너먼트 브라켓의 인터랙티브한 시각화

주요 기능

팀 관리

  • 팀 생성 및 프로필 관리
  • 선수 로스터 구성
  • 팀원 역할 및 권한 설정
  • 팀 통계 및 전적 관리

매치 시스템

  • 자유 매치: 실시간 매칭 로비에서 상대팀 찾기
  • 팀 매치: 일정 기반 경기 예약
  • 경기 결과 및 선수별 스탯 기록
  • 매치 히스토리 및 통계 분석

대회 운영

  • 리그 및 토너먼트 형식 지원
  • 인터랙티브 토너먼트 브라켓 (16강, 8강, 4강, 결승)
  • 실시간 스코어 업데이트
  • 대회 통계 및 순위표

커뮤니티

  • 커뮤니티 허브 생성 및 운영
  • 자유게시판, 팀 게시판, 커뮤니티 게시판
  • 댓글 및 좋아요 시스템
  • 커뮤니티 스태프 관리

실시간 소통

  • Socket.io 기반 실시간 채팅
  • 팀 채팅방
  • 읽지 않은 메시지 알림

사용자 인증

  • 이메일 회원가입/로그인
  • Discord OAuth 연동
  • 자동 토큰 리프레시
  • 영구 로그인 (Remember Me)

기술 스택

Frontend Core

  • React 18.3.1 - UI 라이브러리
  • TypeScript 5.5.3 - 정적 타입 시스템
  • Vite 5.4.1 - 빌드 도구

상태 관리 & 데이터 페칭

  • Zustand 5.0.3 - 전역 상태 관리
  • Axios 1.8.1 - HTTP 클라이언트
  • Socket.io Client 4.8.1 - 실시간 통신

UI & 폼

  • Tailwind CSS 4.0.6 - 유틸리티 기반 스타일링
  • React Hook Form 7.54.2 - 폼 상태 관리
  • Yup 1.6.1 - 스키마 기반 유효성 검증

라우팅 & 인증

  • React Router 7.1.5 - 클라이언트 사이드 라우팅
  • react-cookie 7.2.2 - 쿠키 기반 인증
  • crypto-js 4.2.0 - 로컬 스토리지 암호화

아키텍처

Feature-Sliced Design 기반 구조

src/
├── 0_App/                    # 앱 전역 설정
│   ├── index.tsx            # 메인 앱 컴포넌트
│   ├── ui/Nav/              # 네비게이션 바
│   └── style/               # Tailwind 테마
│
├── 1_Page/                   # 페이지 레벨 (라우트별)
│   ├── Championship/        # 대회 대시보드
│   ├── Team/                # 팀 프로필
│   ├── FreeMatch/           # 자유매치 로비
│   ├── Boards/              # 게시판 목록
│   └── Chat/                # 채팅
│
├── 2_Widget/                 # 재사용 가능한 UI 위젯
│   ├── ChatWidget/          # 실시간 채팅 위젯
│   ├── MatchModal/          # 매치 상세 모달
│   ├── TeamSummaryCard/     # 팀 요약 카드
│   └── PlayerCard/          # 플레이어 카드
│
├── 3_Entity/                 # 비즈니스 도메인 (API 레이어)
│   ├── Account/             # 인증 (21개 훅)
│   ├── Match/               # 매치 (19개 훅)
│   ├── Team/                # 팀 (18개 훅)
│   ├── Championship/        # 대회 (10개 훅)
│   ├── Board/               # 게시판 (12개 훅)
│   └── Chat/                # 채팅 (1개 훅)
│
└── 4_Shared/                 # 공유 유틸리티
    ├── util/apiUtil.ts      # 중앙 API 클라이언트
    ├── lib/useMyInfo.ts     # 인증 상태 관리
    ├── zustand/             # Zustand 스토어
    ├── hookForm/            # 폼 컴포넌트
    └── components/          # 기본 UI 컴포넌트

데이터 흐름

Page (비즈니스 로직 오케스트레이션)
    ↓
Widget (재사용 UI 컴포넌트)
    ↓
Entity (API 호출 훅)
    ↓
Shared (공통 유틸리티)

핵심 구현

1. Optimistic UI & 롤백 시스템

매치 생성·삭제·수정 시 즉각적인 UI 반영과 실패 시 자동 롤백을 구현했습니다.

const useGetChampionshipMatchListHandler = () => {
  const originalListRef = useRef(matchList);
  const backupRef = useRef(new Map());
  const [optimisticMatchList, setOptimisticMatchList] = useState([]);

  const handleDeleteMatch = (matchIdx) => {
    backupRef.current.set(matchIdx, originalItem);
    setOptimisticMatchList(prev => prev.filter(m => m.id !== matchIdx));
  };

  const handleRollback = (matchIdx) => {
    const backup = backupRef.current.get(matchIdx);
    setOptimisticMatchList(prev => /* 원래 위치에 삽입 */);
  };

  return { optimisticMatchList, handleDeleteMatch, handleRollback };
};

Map을 사용해 개별 항목을 백업하고, 실패 시 원래 위치에 정확히 복원합니다. 사용자는 0ms 지연으로 즉각적인 피드백을 받습니다.

2. 실시간 채팅 시스템

Socket.io 기반 실시간 채팅을 구현했습니다.

const useSocketHandler = () => {
  const socketRef = useRef(null);
  const [chatLog, setChatLog] = useState([]);

  useEffect(() => {
    const socket = io(API_URL, {
      transports: ['websocket'],
      auth: { token: accessToken }
    });

    socket.on('message', (data) => {
      setChatLog(prev => [...prev, messageWithTimestamp]);
    });

    return () => socket.disconnect();
  }, []);

  const sendMessage = (content) => {
    socketRef.current?.emit('message', { content });
  };

  return { chatLog, sendMessage };
};

WebSocket 기반 양방향 통신으로 실시간 메시지를 주고받고, 연결 상태를 모니터링하며 자동 재연결을 처리합니다.

React Query 없이 배운 것들

왜 React Query를 사용하지 않았을까?

프로젝트를 시작하면서 의도적으로 React Query를 배제했습니다. React Query가 해결하는 문제를 직접 경험하고, 상태 관리와 캐싱의 근본적인 개념을 체득하기 위해서였습니다.

직접 구현의 현실

구현 내용 직접 구현 React Query
기본 데이터 페칭 useFetchData (143줄) useQuery (1줄)
Optimistic Update useRef + Map + 동기화 (236줄) onMutate (15줄)
토큰 리프레시 전역 큐 + 인터셉터 (80줄) 자동 retry
캐시 무효화 window.location.reload() invalidateQueries (1줄)
총 코드량 약 2000줄 약 100줄

얻은 깊은 이해

React Query를 단순히 사용만 했다면 "편하네!"로 끝났을 것입니다. 하지만 직접 구현하면서 내부 동작 원리를 이해하게 되었습니다.

Optimistic UI의 원리: 백업 → 즉시 반영 → 동기화 → 롤백 사이클을 직접 구현하면서, React Query의 onMutateonError가 정확히 이 패턴을 추상화한 것임을 깨달았습니다.

토큰 관리의 복잡성: 동시 다발적 요청에서 토큰이 만료되면, 모든 요청이 401을 받습니다. 이를 해결하려면 Queue 패턴이 필요합니다. React Query는 이를 자동으로 처리합니다.

보일러플레이트의 고통: 80개의 훅을 작성하면서 같은 패턴을 반복했습니다. 로딩 상태 관리, 에러 처리, 재시도 로직 등이 모든 훅에 중복되었습니다.

캐시 무효화의 어려움: 데이터가 변경되면 관련된 모든 컴포넌트를 업데이트해야 합니다. 직접 구현할 때는 window.location.reload()로 해결했지만, React Query의 invalidateQueries가 얼마나 우아한 해결책인지 알게 되었습니다.

핵심 깨달음

"도구를 진정으로 이해하는 방법은 그것 없이 살아보는 것이다."

직접 구현한 2000줄의 코드는 React Query 도입 후 100줄로 줄어들 것입니다. 하지만 이 과정이 없었다면, React Query가 왜 그렇게 동작하는지 진정으로 이해하지 못했을 것입니다.

React Query는 단순한 편의 라이브러리가 아닙니다. 서버 상태 관리의 근본적인 문제들을 해결하는 검증된 패턴의 집합입니다. 이를 직접 구현해보면서, 라이브러리의 진짜 가치를 이해하게 되었습니다.

시작하기

개발 환경 설정

# 의존성 설치
npm install

# 개발 서버 실행
npm run dev

빌드 및 배포

# 프로덕션 빌드
npm run build

# 빌드 미리보기
npm run preview

# 린트 검사
npm run lint

환경 변수 설정

VITE_SERVER_URL=your_api_server_url
VITE_API_URL=your_socket_server_url

향후 계획

  • React Query 도입: 검증된 패턴으로 코드 간소화
  • 테스트 커버리지: Jest + React Testing Library
  • 성능 최적화: Code splitting, lazy loading
  • PWA 지원: 오프라인 사용 및 푸시 알림

이 프로젝트는 "라이브러리 없이 직접 구현해보기"라는 학습 여정의 결과물입니다. React Query의 진가를 이해하기 위해 돌아간 길이었지만, 그 과정에서 얻은 깊은 이해는 어떤 도구를 사용하든 더 나은 개발자가 되는 데 도움이 되었습니다.

About

풋볼스퀘어 홈페이지

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 99.7%
  • Other 0.3%