RoboDine 키오스크 개발 - React 기반 고객 주문 인터페이스
서론
RoboDine 프로젝트에서 저는 고객이 직접 사용하는 키오스크 시스템의 프론트엔드 개발을 담당했습니다. React를 기반으로 직관적이고 접근성이 뛰어난 주문 인터페이스를 구축하여, 다양한 연령대의 고객이 쉽게 사용할 수 있는 터치 친화적인 시스템을 완성했습니다. 이 글에서는 실제 키오스크 환경에서 동작하는 주문 시스템을 개발하면서 얻은 경험과 기술적 구현을 정리해보겠습니다.
키오스크 시스템 개요
완성된 키오스크 메인 화면
위 이미지는 실제 구현된 RoboDine 키오스크의 메인 화면입니다. 좌측에 카테고리 메뉴, 중앙에 메뉴 선택 영역, 우측에 장바구니가 배치된 구조로 되어 있으며, 상단에는 언어 변경 기능이 포함되어 있습니다.
실제 구현된 키오스크 구조
RoboDine 키오스크는 다음과 같은 실제 구조로 구현되었습니다:
kiosk/
├── src/
│ ├── components/ # 재사용 가능한 UI 컴포넌트
│ │ ├── Cart/ # 장바구니 관련 컴포넌트
│ │ ├── Checkout/ # 결제 관련 컴포넌트
│ │ ├── Layout/ # 레이아웃 관련 컴포넌트
│ │ ├── Menu/ # 메뉴 관련 컴포넌트
│ │ ├── Notification/ # 알림 관련 컴포넌트
│ │ ├── CleaningButton.jsx # 청소 완료 버튼 (96 lines)
│ │ └── LiveStreamComponent.jsx # 실시간 스트리밍 (729 lines)
│ ├── pages/ # 주요 페이지 컴포넌트 (5개)
│ │ ├── HomePage.js # 메인 메뉴 페이지 (650 lines)
│ │ ├── CheckoutPage.js # 결제 페이지 (280 lines)
│ │ ├── OrderCompletePage.js # 주문 완료 페이지 (147 lines)
│ │ ├── OrderStatusPage.js # 주문 상태 페이지 (1,038 lines)
│ │ └── CartPage.js # 장바구니 페이지 (147 lines)
│ ├── context/ # Context API 상태 관리 (3개)
│ │ ├── CartContext.js # 장바구니 상태 (145 lines)
│ │ ├── LanguageContext.js # 언어 설정 (100 lines)
│ │ └── UnifiedWebSocketProvider.jsx # WebSocket 통신 (318 lines)
│ ├── api/ # API 통신 로직
│ ├── locale/ # 다국어 지원 파일
│ │ └── translations.js # 4개 언어 번역 (660 lines)
│ └── utils/ # 유틸리티 함수
├── package.json # React 의존성 관리 (47개 패키지)
└── README.md # 상세 기능 문서 (115 lines)
주요 기능별 실제 구현
1. 사용자 친화적 인터페이스 설계
터치 최적화 UI:
- 큰 버튼과 명확한 시각적 피드백
- 직관적인 아이콘과 이미지 기반 메뉴 표시
- 간결한 3단계 주문 프로세스 (선택 → 확인 → 결제)
- 접근성을 고려한 색상 대비와 폰트 크기
실제 구현된 라우팅 구조:
// App.js - 실제 구현된 라우트
function App() {
// 키오스크에서 사용하는 WebSocket 토픽 지정
const topics = ['menu', 'orders', 'tables'];
return (
<UnifiedWebSocketProvider topics={topics}>
<CartProvider>
<LanguageProvider>
<Router>
{/* 청소 완료 버튼 - 모든 페이지에서 표시 */}
<CleaningButtonComponent />
<Routes>
<Route index element={<HomePage />} />
<Route path="/order-status" element={<OrderStatusPage />} />
<Route path="/checkout" element={<CheckoutPage />} />
<Route path="/order-complete" element={<OrderCompletePage />} />
</Routes>
</Router>
</LanguageProvider>
</CartProvider>
</UnifiedWebSocketProvider>
);
}
2. 다국어 지원 시스템
LanguageContext 기반 언어 전환:
- 한국어/영어/일본어/중국어 실시간 언어 전환
- 로컬 스토리지를 통한 언어 설정 저장 (
kioskLanguage
키) - 메뉴명, 버튼, 안내 문구 전체 다국어 지원 (660줄 번역 파일)
- 언어별 아이콘 지원
// LanguageContext.js - 실제 구현 (100 lines)
export const LanguageProvider = ({ children }) => {
// 로컬 스토리지에서 언어 설정 로드
const [language, setLanguage] = useState(() => {
const savedLanguage = localStorage.getItem('kioskLanguage');
return savedLanguage || 'ko'; // 기본값은 한국어
});
// 언어 변경시 로컬 스토리지에 저장
useEffect(() => {
localStorage.setItem('kioskLanguage', language);
}, [language]);
// 현재 언어에 따른 아이콘 가져오기
const getLanguageIcon = () => {
switch(language) {
case 'en': return '🇺🇸';
case 'ja': return '🇯🇵';
case 'zh': return '🇨🇳';
default: return '🇰🇷';
}
};
// 번역된 텍스트 가져오기 함수
const t = (key, params = {}) => {
// 점 표기법 키 분리 (예: 'sidebar.orderStatus')
const keys = key.split('.');
let translation = translations[language];
// 각 키 단계별로 번역 객체 탐색
for (const k of keys) {
if (translation && translation[k]) {
translation = translation[k];
} else {
return key; // 번역이 없으면 키 그대로 반환
}
}
// 매개변수 치환 처리
let result = translation;
Object.keys(params).forEach(paramKey => {
result = result.replace(`{${paramKey}}`, params[paramKey]);
});
return result;
};
return (
<LanguageContext.Provider value=>
{children}
</LanguageContext.Provider>
);
};
3. 실시간 주문 시스템
UnifiedWebSocketProvider를 통한 실시간 통신:
- 메뉴, 주문, 테이블 정보 실시간 동기화
- 백엔드와의 안정적인 WebSocket 연결
- 주문 상태 실시간 업데이트
- 자동 재연결 기능
// UnifiedWebSocketProvider.js - 실제 구현
const UnifiedWebSocketProvider = ({ children, topics }) => {
// 키오스크에서 사용하는 WebSocket 토픽: ['menu', 'orders', 'tables']
const [connections, setConnections] = useState({});
useEffect(() => {
topics.forEach(topic => {
connectToTopic(topic);
});
}, [topics]);
};
4. 장바구니 및 주문 관리
CartContext를 통한 상태 관리:
- 메뉴 추가/제거/수량 변경
- 실시간 총액 계산
- 주문 검증 및 확인
- 장바구니 데이터 로컬 저장
페이지별 주요 기능
1. 메인 페이지 (HomePage.js)
메뉴 탐색 및 선택:
- 카테고리별 메뉴 분류 (추천, 음식, 음료)
- 이미지 기반 메뉴 카드 인터페이스
- 실시간 장바구니 업데이트
- 드래그 앤 드롭 방식의 메뉴 추가
언어 전환 기능:
- 상단 헤더에 언어 변경 버튼
- 즉시 적용되는 인터페이스 언어 변경
- 메뉴명과 설명의 실시간 번역
2. 결제 페이지 (CheckoutPage.js)
주문 확인 및 결제:
- 선택한 메뉴의 최종 확인
- 총액 및 세부 내역 표시
- 결제 방식 선택 인터페이스
- 주문 완료 처리
3. 주문 완료 페이지 (OrderCompletePage.js)
주문 완료 안내:
- 주문 번호 및 예상 대기 시간 표시
- 주문 내역 요약 정보
- 다음 고객을 위한 초기화 버튼
4. 주문 상태 페이지 (OrderStatusPage.js)
실시간 주문 추적:
- 현재 주문 상태 실시간 업데이트
- 조리 진행 상황 표시
- 완료 예정 시간 안내
특별한 기능 구현
1. 청소 완료 버튼 (CleaningButtonComponent)
모든 페이지에서 접근 가능한 청소 기능:
- 화면 상단에 고정 위치한 청소 버튼
- 테이블 청소 완료 시 백엔드에 상태 업데이트
- 시각적 피드백을 통한 작업 완료 확인
// CleaningButtonComponent - 실제 구현
const CleaningButtonComponent = () => {
const handleCleaningComplete = async () => {
try {
// 백엔드에 청소 완료 상태 전송
await sendCleaningCompleteSignal();
showSuccessMessage('청소가 완료되었습니다.');
} catch (error) {
showErrorMessage('청소 완료 처리 중 오류가 발생했습니다.');
}
};
return (
<button
onClick={handleCleaningComplete}
className="cleaning-complete-btn"
>
청소 완료
</button>
);
};
2. 실시간 조리 라이브 시청 기능
WebRTC 기반 조리 과정 실시간 스트리밍:
- 고객 주문 후 조리 과정을 실시간으로 시청 가능
- 쿡봇 카메라를 통한 개인화된 조리 영상 제공
- 주문 상태와 연동된 자동 영상 스위칭
// LiveStreamComponent.jsx - 실제 구현 (729 lines)
const LiveStreamComponent = ({ onError, onConnected }) => {
const videoRef = useRef(null);
const peerConnectionRef = useRef(null);
const [loading, setLoading] = useState(true);
const [reconnecting, setReconnecting] = useState(false);
const [selectedStreamId, setSelectedStreamId] = useState(null);
const [streamList, setStreamList] = useState([]);
const [errorMessage, setErrorMessage] = useState(null);
// ICE 서버 설정 - STUN 서버 사용 (HTTP 환경 호환)
const iceServers = [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
];
// 스트림 목록 가져오기
const loadStreams = useCallback(async () => {
try {
const host = window.location.hostname;
const url = `http://${host}:8000/streams`;
const response = await fetch(url);
const data = await response.json();
if (data.streams) {
setStreamList(data.streams);
// 웹캠 스트림 찾기
const webcamStream = data.streams.find(stream => stream.type === 'webcam');
if (webcamStream) {
setSelectedStreamId(webcamStream.id);
return webcamStream.id;
}
}
} catch (error) {
setErrorMessage(`스트림 목록 가져오기 오류: ${error.message}`);
throw error;
}
}, []);
// HTTP 요청으로 WebRTC offer 전송
const sendOfferViaHttp = useCallback(async (offer, streamId) => {
const host = window.location.hostname;
const url = `http://${host}:8000/webrtc/offer`;
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
streamId: streamId,
sdp: offer.sdp,
clientIp: 'kiosk-frontend' // 키오스크 클라이언트 식별자
}),
});
if (!response.ok) {
throw new Error(`서버 응답 오류: ${response.status}`);
}
return await response.json();
}, []);
return (
<div className="live-stream-container">
<h3>🍳 실시간 조리 영상</h3>
{loading ? (
<div className="stream-placeholder">조리 준비 중입니다...</div>
) : (
<video
ref={videoRef}
autoPlay
muted
className="cooking-stream-video"
onError={onError}
/>
)}
{errorMessage && (
<div className="error-message">{errorMessage}</div>
)}
</div>
);
};
핵심 기술 구현:
- WebRTC 스트리밍: STUN 서버 기반 P2P 연결로 실시간 영상 전송
- HTTP 시그널링:
/webrtc/offer
엔드포인트를 통한 SDP 교환 - 스트림 목록 관리:
/streams
API로 사용 가능한 카메라 조회 - 자동 연결 복구: 연결 실패 시 자동 재연결 메커니즘
- 클라이언트 식별:
kiosk-frontend
식별자로 키오스크 구분
실제 구현 특징:
- 729줄 대용량 컴포넌트: 복잡한 WebRTC 연결 로직 포함
- WebRTC PeerConnection: RTCPeerConnection 기반 실시간 통신
- HTTP 환경 호환: STUN 서버만 사용하여 보안 문제 해결
- SDP 검증: aiortc 생성 SDP의 표준 준수 확인
3. 반응형 디자인
다양한 화면 크기 지원:
- 테블릿부터 대형 키오스크 화면까지 대응
- Tailwind CSS를 활용한 반응형 레이아웃
- 터치 인터페이스 최적화
- 고해상도 디스플레이 지원
기술적 구현 세부사항
1. Context API 기반 상태 관리
실제 구현된 Context 구조:
// 3개의 주요 Context Provider
- CartProvider: 장바구니 상태 관리
- LanguageProvider: 다국어 지원
- UnifiedWebSocketProvider: 실시간 통신
2. 백엔드 API 연동
실제 연동된 API 엔드포인트:
-
GET /api/menu
: 메뉴 목록 조회 -
POST /api/orders
: 새로운 주문 생성 -
GET /api/orders/{id}
: 주문 상태 조회 -
WS /ws/orders
: 주문 상태 실시간 업데이트
3. 실시간 통신 시스템
WebSocket 토픽 관리:
// 키오스크에서 구독하는 토픽들
const topics = ['menu', 'orders', 'tables'];
// 각 토픽별 데이터 처리
- menu: 메뉴 정보 실시간 업데이트
- orders: 주문 상태 변경 알림
- tables: 테이블 상태 동기화
사용자 경험 최적화
1. 직관적인 주문 흐름
3단계 주문 프로세스:
- 메뉴 선택: 카테고리별 메뉴 탐색 및 장바구니 추가
- 주문 확인: 선택한 메뉴 검토 및 수정
- 결제 완료: 결제 처리 및 주문 번호 발급
2. 접근성 고려사항
다양한 사용자 고려:
- 큰 버튼과 명확한 텍스트
- 고대비 색상 사용
- 간단한 언어와 아이콘 활용
- 터치 영역 최적화 (최소 44px)
3. 에러 처리 및 피드백
사용자 친화적 에러 처리:
- 명확한 에러 메시지 표시
- 재시도 기능 제공
- 자동 복구 메커니즘
- 진행 상태 실시간 표시
개발 과정에서의 도전과 해결
1. 다양한 사용자층 대응
도전: 연령대와 기술 수준이 다른 다양한 고객 대응
해결책:
- 직관적인 아이콘과 이미지 중심 인터페이스
- 단순화된 네비게이션 구조
- 명확한 시각적 피드백
- 다국어 지원으로 접근성 향상
2. 터치 인터페이스 최적화
도전: 터치 환경에서의 정확성과 반응성 확보
해결책:
- 터치 영역 크기 최적화 (최소 44px)
- 터치 시 즉각적인 시각적 피드백
- 실수 방지를 위한 확인 단계
- 스와이프 제스처 지원
3. 실시간 데이터 동기화
도전: 메뉴 변경사항과 주문 상태의 실시간 반영
해결책:
- WebSocket을 통한 양방향 통신
- 토픽별 독립적인 데이터 스트림
- 연결 끊김 시 자동 재연결
- 로컬 캐싱으로 오프라인 대응
4. 개인화된 조리 영상 스트리밍
도전: 여러 주문이 동시에 진행될 때 각 고객에게 맞는 조리 영상 제공
해결책:
// 주문 ID 기반 스트림 라우팅 시스템
const StreamRouter = {
// 주문과 쿡봇 매핑 테이블
orderToCookbotMapping: new Map(),
// 주문별 전용 스트림 채널 생성
createPersonalizedStream: async (orderId, cookbotId) => {
const streamChannel = `cookbot-${cookbotId}-order-${orderId}`;
await setupWebRTCChannel(streamChannel);
return streamChannel;
},
// 조리 단계별 카메라 앵글 자동 전환
switchCameraAngle: (stage) => {
const angles = {
'prep': 'top-view', // 재료 준비: 위에서 내려다보는 앵글
'cooking': 'side-view', // 조리 중: 측면 앵글
'plating': 'close-up' // 플레이팅: 클로즈업 앵글
};
return angles[stage] || 'default';
}
};
기술적 혁신:
- 동적 스트림 라우팅: 주문 ID와 쿡봇 ID를 매핑한 개인화 스트림
- 지능형 카메라 제어: 조리 단계에 따른 최적 앵글 자동 선택
- 대역폭 적응: 키오스크 네트워크 상태에 따른 화질 자동 조정
- 스트림 풀링: 사용하지 않는 스트림 자동 해제로 리소스 최적화
사용자 경험 개선:
- 개인 전용 조리 쇼 경험 제공
- 대기 시간 체감 단축 효과
- 조리 품질에 대한 신뢰도 향상
실제 구현 성과
기능별 구현 현황
기능 | 구현 상태 | 주요 특징 |
---|---|---|
메뉴 선택 | ✅ 완료 | 카테고리별 분류, 이미지 기반 UI |
장바구니 | ✅ 완료 | 실시간 업데이트, 수량 조절 |
언어 전환 | ✅ 완료 | 한/영 지원, 즉시 적용 |
주문 처리 | ✅ 완료 | WebSocket 실시간 통신 |
결제 시스템 | ✅ 완료 | 다양한 결제 방식 지원 |
청소 기능 | ✅ 완료 | 모든 페이지에서 접근 |
기술적 성과
코드 품질:
- 총 5개 페이지로 구성된 체계적인 구조 (2,262 lines)
- 3개 Context Provider로 체계적인 상태 관리 (563 lines)
- 핵심 LiveStreamComponent 729줄의 복잡한 WebRTC 로직
- 47개 npm 패키지로 구성된 모듈화된 설계
다국어 지원:
- 4개 언어 완전 지원 (한/영/일/중)
- 660줄의 상세한 번역 파일
- 점 표기법 기반 번역 키 시스템
- 매개변수 치환 지원 번역 함수
실시간 기술:
- WebRTC 기반 실시간 영상 스트리밍
- WebSocket 3개 토픽 동시 구독
- HTTP 시그널링 서버와 안정적 연동
향후 개선 계획
1. AI 기반 기능 확장
- 사용자 선호도 기반 메뉴 추천
- 음성 인식을 통한 주문 지원
- 시간대별 인기 메뉴 표시
2. 접근성 향상
- 시각 장애인을 위한 음성 안내
- 더 큰 폰트 크기 옵션
- 고대비 모드 지원
3. 개인화 기능
- 고객별 주문 이력 저장
- 맞춤형 메뉴 구성
- 알레르기 정보 표시
배운 교훈과 인사이트
1. 사용자 중심 설계의 중요성
키오스크는 다양한 연령대와 기술 수준의 사용자가 이용하는 시스템입니다. 직관적인 인터페이스와 명확한 피드백이 사용자 만족도에 직결됨을 깨달았습니다.
2. 단순함의 힘
복잡한 기능보다는 핵심 기능을 간단하고 확실하게 구현하는 것이 더 효과적이었습니다. 3단계 주문 프로세스로 단순화한 것이 성공 요인이었습니다.
3. 실시간 시스템의 안정성
WebSocket 연결의 안정성과 에러 처리가 사용자 경험에 미치는 영향이 컸습니다. 자동 재연결과 로컬 캐싱이 필수적이었습니다.
결론
RoboDine 키오스크 개발은 사용자 친화적인 주문 인터페이스를 구축하는 도전이었습니다. React 기반의 모듈화된 컴포넌트 설계, Context API를 통한 효율적인 상태 관리, 그리고 WebSocket을 활용한 실시간 통신을 통해 실제 레스토랑 환경에서 사용 가능한 키오스크 시스템을 완성했습니다.
특히 다국어 지원, 터치 최적화, 직관적인 UI/UX 설계를 통해 다양한 고객이 쉽게 사용할 수 있는 포용적인 인터페이스를 구현할 수 있었습니다. 이 과정에서 사용자 중심 설계의 중요성과 단순함의 힘을 깊이 이해하게 되었습니다.
키오스크 개발에 대한 더 자세한 내용이나 구체적인 구현 방법이 궁금하시면 언제든 댓글로 질문해주세요. 함께 더 나은 사용자 경험을 만들어가는 논의를 나눠보면 좋겠습니다.
Enjoy Reading This Article?
Here are some more articles you might like to read next: