이사 일정·행정 절차·맞춤 정책을 한 곳에서 관리하는 대시보드. 다크 테마 + 오렌지 포인트 컬러 (Figma 목업 기반).
- Next.js 15 (App Router) / React 19 / TypeScript
- Tailwind CSS v4 — 디자인 토큰은
src/app/globals.css의@theme에 정의 (색상 코드는 Figma 팔레트 기준) - 브랜드 폰트 NordicClub (
next/font/local), 로고/트럭 에셋은public/ - 백엔드 연동됨 — FastAPI 서버(
NEXT_PUBLIC_API_BASE)와 통신.src/lib/api/가 그 경계. - 보드 상태의 진실 공급원은 여전히
store.tsx+localStorage다. 백엔드 세션은 인메모리라 서버 재시작 시 사라지므로, 백엔드를 DB가 아니라 파생 엔진으로 취급하고 응답을 로컬 상태에 병합한다. mockData.ts는 삭제하지 않았다 — 시드 데이터 겸 장애 폴백이다.
npm install
cp .env.example .env.local # NEXT_PUBLIC_API_BASE 설정
npm run dev # http://localhost:3000
npm run build # 프로덕션 빌드 검증백엔드는 배포된 개발 서버를 그대로 쓰면 된다(.env.example 기본값). CORS는 localhost:3000에서 통과한다.
개발 서버가 떠 있는 동안에는
npm run build를 돌리지 말 것 (.next캐시 충돌로 500 발생). 타입만 볼 땐npx tsc --noEmit.
| 경로 | 화면 | 비고 |
|---|---|---|
/login |
로그인 | 좌측 브랜드 그라데이션 패널 + 폼 / Google 로그인 (로그인 시 /로 이동) |
/signup |
회원가입 | 로그인과 동일한 split 카드 (가입 후 /userinfo로 이동) |
/userinfo |
기본 정보 | 가입 직후 기본 정보 수집 5단계 위저드 (완료 시 대시보드가 채워짐) |
/ |
대시보드 | 초기엔 빈 상태(온보딩) → 챗봇 Q&A(연령·지역) 완료 시 대시보드가 채워짐 |
/calendar |
캘린더 | 월간 그리드 + 이벤트 + New event 폼 + 일정/체크리스트 (그룹) |
/policy |
정책 | 맞춤 정책 추천 상세 카드 2열 그리드 + 정렬 (PolicyDetailCard) |
/admin |
행정 | 맞춤 행정 절차 상세 카드 2열 그리드 + 정렬 (AdminDetailCard) |
온보딩은 별도 페이지가 아니라 대시보드의 초기 상태(store.onboarded === false)이며, 완료하면 true로 저장되어 이후엔 바로 채워진 대시보드가 표시됨.
- 그룹 패널: 카테고리(캘린더+일정 / 체크리스트 / 정책 / 행정)를 밝은 그룹(#1b1b1b) 안에 어두운 카드(#111)로 묶음
- 페이지 세로 스크롤 없음: 콘텐츠는 뷰포트 높이에 고정되고, 요소가 많아지면 그룹 내부에서만 스크롤 (얇은 스크롤바)
- 캘린더 그룹은 스크롤 없이 캘린더+오늘 일정을 항상 표시
- 채팅 패널: 왼쪽 경계를 드래그해 너비 조절 가능. 최소 너비는 콘텐츠가 빈 공간 없이 차는 지점에 반응형으로 계산 (창 크기·콘텐츠 변화에 맞춰 재계산, localStorage 저장)
- 진행바: 오른쪽에 짧게 배치, 트럭이 진행률을 따라 이동. 진행률은 체크리스트 완료율에서 파생
- 일정: 생성(New event)·수정(모달)·삭제, 캘린더 월 이동·날짜 선택
- 체크리스트: 토글·추가·항목 수정/삭제, 목록 이름 변경·완료 삭제·비우기
- 정책/행정 상세 카드: 핀 고정·숨기기·복원, 정렬(관련도순 등), "자세히 보기"·"신청하기"
- 각 카드 미트볼(⋯) 메뉴로 수정/삭제
- 챗: SSE 스트리밍으로 진행 단계 표시 → 실제 에이전트 응답. 연결 실패 시 POST 폴백, 서버 장애 시 캔드 응답 폴백
- 로그인/회원가입/로그아웃 (JWT), 라우트 가드, 온보딩 완료/리셋
- 이사 예정일 기준 D-day·마감 임박 알림
src/
├─ app/
│ ├─ page.tsx # 대시보드(+온보딩 초기 상태)
│ ├─ calendar/ # 캘린더
│ ├─ policy/ · admin/ # 정책 · 행정
│ ├─ login/ # 로그인
│ ├─ layout.tsx # 폰트 로드 + MoveMateProvider
│ └─ globals.css # @theme 디자인 토큰
├─ components/ # AuthLayout, WorkspaceLayout, TopNav, ProgressBanner,
│ # GroupPanel, ScrollArea, DashboardView, MiniCalendar,
│ # FullCalendar, TodayCard, ScheduleCard, ChecklistCard,
│ # PolicyCard(대시보드 compact), PolicyDetailCard,
│ # AdminDetailCard, cardParts, RecommendHeader,
│ # RestoreCardsButton, NewEventForm, EventEditModal,
│ # MeatballMenu, ChatPanel, Brand, icons
└─ lib/
├─ api/ # 백엔드 경계 — client·types·adapters·auth·chat·stream·schedule·session
├─ store.tsx # React Context 상태 스토어 (localStorage 영속)
├─ mockData.ts # 시드 데이터 + 타입 + 폴백 (PolicyItem·AdminItem·UserProfile 등)
├─ dates.ts # 날짜 포맷 · 상대 날짜 라벨(그제~모레)
├─ lunar.ts # 손없는 날(음력 끝자리 9·0) 판정
└─ accents.ts # 이벤트 색상 토큰 매핑
상태 전역 관리는 lib/store.tsx의 MoveMateProvider / useStore() 하나로 통일 (events·checklists·chat·정책 숨김·onboarded·auth).
| 영역 | 상태 |
|---|---|
| 채팅 (POST + SSE) | 완료 |
| 이사 일정 · 캘린더 · 체크리스트 | 완료 (수정 시 서버 역동기화) |
인증 (회원가입·로그인·/me·가드) |
완료 |
행정 카드 (/admin) |
완료 — 일정 데이터의 필수 항목 기반 |
정책 카드 (/policy) |
완료 |
계정별 보드 저장 (/api/v1/state) |
완료 — 기기가 바뀌어도 따라온다 |
관리 콘솔 (/console) |
완료 — 배포 빌드에서는 기본 꺼짐 |
설계 배경과 남은 과제는 MoveMMate/docs 저장소를 본다.
Vercel에 올린다. 단, Vercel의 GitHub 연동은 쓰지 않는다 — 조직 소유
저장소를 Hobby 요금제에서 막기 때문이다. 대신 GitHub Actions가 직접 빌드해
올린다(.github/workflows/deploy.yml). Vercel은 GitHub을 보지 않으므로
저장소가 누구 것인지 따지지 않고, 저장소도 이 하나로 유지된다.
main push → Actions: vercel build --prod → vercel deploy --prebuilt --prod
PR → 같은 흐름에서 --prod만 빠짐 (프리뷰 주소)
npx vercel login
npx vercel link # .vercel/project.json 에 orgId·projectId 가 생긴다저장소 시크릿에 세 개를 넣는다.
| 시크릿 | 어디서 |
|---|---|
VERCEL_TOKEN |
Vercel → Account Settings → Tokens |
VERCEL_ORG_ID |
.vercel/project.json의 orgId |
VERCEL_PROJECT_ID |
〃 projectId |
NEXT_PUBLIC_*는 저장소가 아니라 Vercel 프로젝트 설정에 넣는다.
vercel pull이 받아오므로 값이 한 군데에만 있게 된다.
| 변수 | 값 |
|---|---|
NEXT_PUBLIC_API_BASE |
https://d2t93gx9e6tmmx.cloudfront.net |
NEXT_PUBLIC_ENABLE_CONSOLE |
관리 콘솔을 배포본에서도 쓸 때만 true |
.vercel/은 gitignore된다. 시크릿은 아니지만 계정에 묶인 값이라 저장소에 두지 않는다.
NEXT_PUBLIC_*는 빌드 시점에 번들에 박힌다. 값을 바꾸면 재배포해야 반영된다.- 서버리스 함수가 0개다(전 페이지 정적). Hobby의 함수 타임아웃과 무관하고, 채팅 요청은 브라우저에서 CloudFront로 곧장 간다.
- 배포 주소가 정해지면 백엔드
.env의CORS_ORIGINS를 좁힌다. Vercel은 프리뷰마다 주소가 바뀌므로CORS_ORIGIN_REGEX를 함께 쓴다."*"를 빼지 않으면 아무것도 좁혀지지 않는다.
- 상단 검색창 실제 필터링
- 구글 캘린더 이벤트 (
google_events가 항상 빈 배열이라 미검증) - 토큰을 httpOnly 쿠키로 (BFF 프록시 도입 시)
- 두 기기에서 동시에 고치면 나중에 저장한 쪽이 이긴다