동국대학교 봄 축제(2026)에서 운영되는 **상록수커피클럽(SCC)**의 음료 주문·결제·제조 현황 추적 모바일 웹앱입니다.
축제 현장에서 음료를 주문할 때 가장 큰 문제는 대기 불투명성입니다. 손으로 쓴 영수증이나 번호표로는 내 주문이 지금 어느 단계인지 알 수 없고, 고객은 카운터 앞에 줄을 서거나 주기적으로 직접 확인해야 했습니다.
이 시스템은 세 가지 문제를 해결하기 위해 만들었습니다.
- 대기 줄 해소 — 고객이 스마트폰으로 직접 주문하고 그 자리에서 결제까지 완료하면, 카운터 앞에 모일 이유가 없습니다.
- 실시간 현황 공유 — Firebase Firestore의 실시간 구독으로 바리스타가 상태를 바꾸는 즉시 고객 화면이 갱신됩니다. 음료가 준비되면 화면에 알림 배너가 뜹니다.
- 운영 부담 경감 — 어드민 페이지에서 전체 주문을 한눈에 보고 상태를 단계별로 전진시킬 수 있어, 종이 장부 없이 운영 가능합니다.
축제 특성상 하루 수백 건의 주문이 짧은 시간에 집중되므로, 오프라인 운영을 최대한 지원하면서도 장비나 인프라 부담이 적은 서버리스·SaaS 조합으로 설계했습니다.
페이지 라우팅과 서버/클라이언트 컴포넌트 분리에 사용합니다. 개발·빌드 모두 Turbopack을 사용해 HMR 속도를 높였습니다. 동적 라우트(/track/[id], /order/[id], /menu/[id])로 주문별 독립 URL을 제공합니다.
메뉴 아이템, 장바구니, 주문, Firebase 문서 등 모든 데이터 구조를 인터페이스로 정의해 런타임 오류를 사전에 차단합니다. OrderStatus 유니언 타입으로 허용되지 않는 상태 전이를 컴파일 타임에 검출합니다.
커스텀 sage 컬러 팔레트를 @theme으로 정의해 브랜드 색상을 전역 유틸리티로 사용합니다. 모바일 퍼스트 반응형 레이아웃과 데스크탑 모달 장바구니를 함께 지원합니다.
주문 데이터 저장 및 실시간 구독에 사용합니다. onSnapshot으로 운영진과 고객 양쪽에서 상태 변화를 새로고침 없이 즉시 반영합니다. iOS PWA 환경에서 WebSocket이 끊기는 문제를 experimentalForceLongPolling 옵션과 visibilitychange/online 이벤트 재연결로 해결했습니다.
GitHub 연동으로 main 브랜치 push 시 자동 배포됩니다. generateBuildId로 매 배포마다 빌드 캐시를 무효화해 로컬과 프로덕션의 CSS·이미지가 항상 일치하도록 합니다.
- Pretendard — 한글/영문 통합 웹폰트
- Lucide React — 아이콘
- localStorage — 장바구니, 진행 중인 주문 목록 클라이언트 보관
메인 화면에서 항상·푸른·나무 세 가지 메뉴를 카드 형태로 확인합니다. 각 카드에는 플레이버 휠 기반 색상 분류 태그(초콜릿·복숭아·플로럴 등)가 표시됩니다. getFlavorColor() 유틸리티가 태그 문자열을 분석해 향미 계열(과일류·시트러스·꽃·초콜릿·너트·단맛·허브·베리)별로 배경·텍스트·보더 색상을 자동 매핑합니다.
메뉴 카드를 탭하면 상세 모달이 열립니다. 상세 화면은 컵 노트 → 원두 정보(산지·비율) → 소개 글 순으로 구성되어 있으며, 히스토리 API(pushState + popstate)로 뒤로가기 제스처를 지원합니다.
핫/아이스 옵션 선택 후 장바구니에 담을 수 있으며, 최대 10잔까지 담을 수 있습니다. 장바구니 상태는 localStorage에 보관해 페이지 이동 후에도 유지됩니다.
데스크탑 화면에서는 장바구니가 화면 중앙 모달로 열려 넓은 화면에서도 사용성을 유지합니다.
항상·푸른·나무는 각기 다른 담당자가 제조하므로, 합산 대기 대신 가장 오래 걸리는 파트를 기준으로 계산합니다.
대기시간(분) = max(항상잔수, 푸른잔수, ceil(나무잔수 / 2)) × 3
항상·푸른은 1잔당 3분, 나무(에이드)는 2잔당 3분으로 산정합니다. 표시는 약 N~N+3분 범위로 보여줘 지나치게 정확한 예측이라는 인상을 주지 않습니다.
대기 큐 계산에는 생성 후 30분 이내이고 pending | paid | preparing 상태인 주문만 포함합니다.
세 가지 결제 수단을 지원합니다.
- 토스 — 딥링크와 QR코드를 함께 제공합니다. 모바일에서는 딥링크로 토스 앱을 바로 열고, QR 스캔도 가능합니다.
- 타행 앱 — 계좌번호와 예금주를 표시하고 복사 버튼을 제공합니다.
- 현금 — 어드민 페이지에서 운영진이 현금 수령 후 별도 버튼으로 결제 확인 처리합니다.
/track/[id] 페이지에서 주문 상태를 실시간으로 확인합니다. 상태 흐름은 접수 → 결제 확인 → 제조 중 → 준비 완료 → 수령 완료이며, 음료가 준비되면 상단에 애니메이션 배너가 표시됩니다.
주문이 picked_up으로 전환되면 1.5초 후 완료 화면(/complete/[id])으로 자동 이동합니다.
주문 생성 후 30분이 지나도 pending | paid 상태면 만료로 간주합니다.
- 메인 화면 — 만료된 진행 중인 주문을 붉은 배너로 표시하고 개별 닫기 버튼을 제공합니다.
- 트래킹 화면 — 만료 감지 시 즉시 만료 안내 화면으로 전환하고 "다시 주문하기" 버튼을 표시합니다.
LocalStorage에서도 만료 주문을 자동으로 제거해 다음 방문 시 오래된 주문이 남지 않습니다.
iOS Safari에서 홈 화면에 추가하면 앱처럼 실행됩니다. manifest와 theme-color 메타태그를 설정해 상단 상태 바 색상을 브랜드 색상으로 통일했습니다. iOS에서 Firebase WebSocket이 끊기는 문제는 long-polling 모드로 해결했으며, 앱이 백그라운드에서 포그라운드로 돌아올 때 Firestore 연결을 자동 복구합니다.
/admin 페이지는 비밀번호 없이 접근할 수 있습니다(내부 운영자 전용 URL로 운영). 전체 주문을 subscribeToAllOrders로 실시간 구독합니다.
데스크탑 — 송금대기 / 결제완료·준비중 / 수령대기 3열 칸반 보드로 주문을 시각화합니다.
모바일 — 탭 UI로 동일한 정보를 제공합니다.
각 주문 카드에서:
- 상태를 한 단계씩 전진(결제 확인 → 준비 중 → 준비 완료 → 수령 완료)
- 현금 결제 고객 별도 확인 처리
- 취소 확인 모달을 통한 주문 취소
상단 통계 바에서 완료 건수, 총 제조 잔수, 매출 합계를 실시간으로 확인합니다.
장부 저장 버튼으로 전체 주문 내역을 CSV로 내보냅니다. 항상(핫/아이스)·푸른(핫/아이스)·나무(아이스) 메뉴별 수량이 열로 분리됩니다.
현재 어드민 페이지는 URL만 알면 누구나 접근할 수 있습니다. Firebase Authentication으로 운영진 로그인을 추가하거나, Vercel Password Protection으로 배포 레벨에서 접근을 제한할 수 있습니다.
완료 화면에 별점 UI가 구현되어 있지만, 현재 데이터를 서버에 저장하지 않습니다. Firestore에 rating 필드를 추가하거나 별도 feedback 컬렉션으로 모아 메뉴 개선에 활용할 수 있습니다.
PWA에서 Web Push API를 활용하면 음료 준비 완료 시 홈 화면 알림을 보낼 수 있습니다. iOS 16.4 이상에서 지원되지만, 구독 관리와 VAPID 키 운영 부담이 따릅니다.
특정 메뉴 재료가 소진되면 어드민에서 해당 메뉴를 임시 품절 처리하는 기능이 필요합니다. Firestore에 메뉴 가용성 플래그를 두고 실시간으로 고객 화면에 반영할 수 있습니다.
현재 주문 번호는 meta/orderCounter에 누적됩니다. 행사 당일 시작 전에 카운터를 초기화하는 어드민 버튼 또는 날짜 기반 자동 리셋이 필요합니다.
부스가 여러 곳이거나 연도가 달라지면, Firestore 컬렉션 경로나 프로젝트를 분리해 독립적으로 운영할 수 있는 구조가 필요합니다.
| 메뉴 | 가격 | 원두 | 옵션 |
|---|---|---|---|
| 항상 | 4,000원 | Black Suit (브라질 60%·콜롬비아 25%·에티오피아 15%) | 핫·아이스 |
| 푸른 | 4,000원 | Velvet White (에티오피아 3종 블렌드) | 핫·아이스 |
| 나무 | 3,500원 | — (블루레몬에이드) | 아이스 |
src/
├── app/
│ ├── page.tsx # 메인 — 메뉴 목록, 장바구니, 주문
│ ├── menu/[id]/ # 메뉴 상세 모달
│ ├── order/[id]/ # 결제 수단 선택 및 QR
│ ├── track/[id]/ # 주문 현황 실시간 추적
│ ├── complete/[id]/ # 수령 완료 및 별점
│ ├── admin/ # 운영진 어드민 대시보드
│ └── info/ # 부스 안내 및 원두 소개
├── components/
│ ├── MenuItem.tsx # 메뉴 카드 컴포넌트
│ ├── CartItem.tsx # 장바구니 아이템
│ └── OrderStatus.tsx # 주문 상태 스텝 트래커
└── lib/
├── menu.ts # 메뉴 데이터
├── orders.ts # Firebase 주문 CRUD · 실시간 구독 · 대기시간 계산
├── activeOrder.ts # localStorage 진행 중인 주문 관리 (30분 만료)
├── cart.ts # localStorage 장바구니
├── flavor.ts # 플레이버 휠 색상 매핑
└── firebase.ts # Firebase 초기화 (iOS PWA long-polling 대응)
npm install
# .env.local 생성
NEXT_PUBLIC_FIREBASE_API_KEY=...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=...
NEXT_PUBLIC_FIREBASE_PROJECT_ID=...
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=...
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=...
NEXT_PUBLIC_FIREBASE_APP_ID=...
NEXT_PUBLIC_TOSS_PAYMENT_URL=...
npm run devVercel에 연결된 GitHub 레포에 push하면 자동 배포됩니다.
- GCP 기반 CI/CD Blue-Green 배포 완료
상록수커피클럽 · 동국대학교 봄 축제 2026