Frontend · 정규직 · 2021.11 – 2025.05
빌리오Billyo
공간 대여 플랫폼 '빌리오'에 공간을 등록한 호스트가 예약 확인, 일정 관리, 요금 설정, 정산 확인, 고객 문의 응대를 한 곳에서 하는 웹 서비스입니다. 프론트엔드를 처음부터 끝까지 단독으로 설계하고 개발했습니다.
- 역할
- 프론트엔드 단독 개발
- 규모
- 퇴사 시점 호스트 100+명 · 등록 업체 300+개
Case 01
협업 프로세스 개선
연동 단계의 수정 반복을 개인 실수가 아닌 작업 순서의 문제로 보고, 개발 전에 API 스펙을 먼저 합의하는 방식을 제안해 정착시켰습니다.
문제
- 백엔드 API의 응답 구조가 프론트엔드가 예상한 것과 어긋나, 연동 단계에서 수정이 반복됐습니다.
판단 근거
- 서로 구현을 마친 뒤에 맞춰 보면 어긋남이 늦게 드러납니다. 원인은 개인의 실수가 아니라, 맞춰 보는 시점이 늦은 작업 순서에 있다고 봤습니다.
해결
- 개발을 시작하기 전에 요청·응답의 필드명, 타입, 에러 코드를 FE와 BE가 먼저 합의하는 방식을 제안했습니다.
- 합의한 스펙으로 프론트엔드가 타입을 먼저 작성하고, 양쪽이 동시에 개발합니다.
결과
- 팀의 작업 방식으로 정착했고, 연동 단계의 수정 반복을 줄임
Case 02
외부 예약 채널 통합 캘린더
여러 예약 채널의 일정을 iCal URL로 받아 한 캘린더에 모으고, 채널 예약과 자체 예약을 유형별로 구분해 보여줬습니다.
문제
- 호스트는 자체 예약 외에 네이버 예약, 카카오톡 채널, 스페이스클라우드 같은 채널로도 예약을 받습니다. 채널마다 일정을 따로 확인하면 같은 시간에 예약이 겹칠 위험이 있습니다.
- 자체 예약만 보여주는 캘린더로는 호스트가 실제 공간 사용 현황을 알 수 없습니다.
판단 근거
- 채널마다 연동을 따로 개발하면 채널이 늘 때마다 작업이 필요합니다. 표준 형식인 iCal URL 하나로 받으면 호스트가 URL만 붙여 넣어 연동할 수 있습니다.
- 연동 URL을 바꾸면 이전 캘린더에서 가져온 일정이 애매하게 남습니다. 유지할지 삭제할지는 호스트가 정하도록 했습니다.
- 일정 유형이 많아서(시간, 패키지, 승인 대기, 외부, 중복) 한 가지 색으로 표시하면 구분이 안 됩니다.
해결
- 공간별로 iCal URL을 등록, 수정, 삭제하는 화면을 만들었습니다(공간당 최대 5개). URL을 바꿀 때 기존 일정을 '그대로 유지하기 / 모두 삭제하기' 중 고르게 했습니다.
- 연동이 안 되는 채널을 위해 외부 예약 직접 등록 모달을 만들었습니다. 카테고리, 시작·종료 일시, 매주 반복, 이용 인원, 금액을 입력합니다.
- 캘린더 일정을 외부, 중복, 승인 대기, 패키지, 시간 5가지로 분류해 표시를 다르게 했습니다. 외부 예약과 겹치는 건은 서버가 내려준 중복 값으로 따로 표시합니다.
- 월·주·일 뷰를 지원하고, 모바일에서는 월 뷰의 '+N 더보기'를 누르면 그 날짜의 예약 목록으로 이동합니다. 예약 상세를 바로 여는 딥링크도 지원합니다.
Case 03
Socket.IO 실시간 문의 채팅
Socket.IO로 호스트와 고객의 대화를 실시간으로 주고받게 하고, 과거 대화 조회와 읽지 않은 메시지 표시를 붙였습니다.
문제
- 고객 문의에 늦게 답하면 예약 전환을 놓치기 쉬운데, 호스트가 새 문의가 왔다는 것을 바로 알 방법이 필요했습니다.
- 채팅은 초기 목록, 과거 메시지 추가 조회, 실시간 수신이 섞입니다. 그래서 같은 메시지가 두 번 보이거나 순서가 뒤바뀔 수 있었습니다.
- 한글 입력 중 Enter를 누르면 조합 중인 글자가 전송되는 문제가 생길 수 있습니다.
판단 근거
- 메시지마다 고유한 text_id가 있으므로, 이 값을 기준으로 중복을 제거하고 정렬하면 어느 경로로 들어와도 결과가 같습니다.
- 과거 대화는 한 번에 다 받지 않고 스크롤이 맨 위에 닿았을 때 페이지 단위로 받아, 첫 진입을 가볍게 유지했습니다.
- 읽지 않은 메시지는 메뉴 배지로 보여줘야 다른 화면에 있어도 알아챌 수 있습니다.
해결
- 소켓 이벤트를 한 모듈에 모아 화면은 함수만 호출하게 했습니다.
- 과거 메시지를 받으면 이미 있는 text_id를 걸러낸 뒤 합쳐서 text_id 순으로 정렬하고, 스크롤 위치를 유지합니다.
- Enter는 전송, Shift+Enter는 줄바꿈으로 처리하고, 한글 조합 중(isComposing)에는 전송하지 않습니다.
- 읽지 않은 메시지 여부를 React Query로 조회해 메뉴의 '고객문의'에 배지를 표시합니다. 창에 다시 포커스가 오거나 채팅방을 나갈 때 갱신합니다.
- 소켓 연결이 끊기면 화면을 새로고침해 연결을 복구합니다.
결과
- 메시지 중복 노출 없이 실시간 대화와 이력 조회를 한 화면에서 제공
Case 04
요일×시간대 변동 요금 설정 UI
8개 요일 유형 × 24개 시간대의 가격표를 다중 선택과 일괄 입력으로 줄이고, 서버에는 기본가와 다른 값만 보냈습니다.
문제
- 공간 요금은 요일(월~일과 공휴일 8종)과 시간대(24개)에 따라 다를 수 있어서, 칸을 하나씩 입력하면 최대 192칸을 채워야 합니다.
- 같은 화면에서 시간제 상품과 패키지 상품, 기준 인원과 추가 요금까지 다뤄야 해서 입력 상태가 복잡합니다.
- 공간은 새로 등록하기, 수정하기, 복사해서 등록하기 3가지 방식으로 들어옵니다. 방식마다 화면을 따로 만들면 코드가 갈라집니다.
판단 근거
- 대부분의 공간은 대부분의 시간대 가격이 같습니다. 그래서 기본가를 먼저 정하고, 다른 시간대만 예외로 지정하는 흐름이 호스트에게 가장 빠릅니다.
- 서버로 모든 칸을 보내면 요청이 크고, 나중에 기본가를 바꿨을 때 예외와 구분이 안 됩니다. 기본가와 다른 값만 보내는 편이 데이터 의미가 분명합니다.
- 3가지 방식은 입력 항목이 거의 같으므로, 방식 값만 다르게 받는 제출 훅 하나로 묶는 편이 유지보수에 유리합니다.
해결
- '시간 가격이 모두 동일해요'를 체크하면 변동 가격 설정을 숨기고, 아니면 요일을 고른 뒤 시간대를 여러 개 체크해 가격을 한 번에 입력하게 했습니다. 요일을 처음 고를 때 기본가로 24칸을 채웁니다.
- 최소 가격 1,000원 검증, 최소 예약 시간, 기준 인원과 최대 인원, 인원당 추가 요금을 입력받습니다.
- 등록할 때는 기본가와 다른 시간대만 추려서 보냅니다.
- 제출 훅 하나로 새로 등록, 수정, 복사를 모두 처리합니다.
결과
- 가격 입력을 최대 192칸에서 '기본가 1회 + 예외 구간 일괄 입력'으로 줄임
Case 05
배포 즉시 반영과 인증·캐시 구조
새로 배포하면 열려 있는 탭이 스스로 새로고침하게 하고, 토큰·캐시·상태 처리를 각각 한 곳으로 모았습니다.
문제
- 웹 앱은 한번 열어 둔 탭에 새 배포가 반영되지 않아, 호스트가 오래 열어 둔 탭에서 옛 화면으로 작업할 수 있습니다.
- 인증 토큰, 캐시, 화면 상태가 곳곳에 퍼지면 로그아웃 때 정리 누락이 생깁니다.
판단 근거
- 사용자에게 새로고침을 안내하는 것보다, 배포할 때 서버의 버전 값 하나만 바꾸면 열린 탭이 스스로 확인하는 편이 운영 부담이 적습니다.
- 토큰은 요청 인터셉터 한 곳에서 처리하면 화면은 인증을 몰라도 됩니다.
해결
- Firebase로 서버 버전을 구독해, 새 배포가 올라오면 열려 있는 탭을 자동으로 새로고침합니다.
- 토큰은 axios 인터셉터 한 곳에서 처리하고, React Query 캐시 정책을 한 곳에서 정했습니다.
- 로그아웃할 때 토큰, 상태, 캐시를 한꺼번에 초기화합니다.
결과
- 새 배포가 열린 탭에도 바로 반영되고, 로그아웃 후 이전 계정의 데이터가 남지 않음
Retrospective
회고
가장 큰 한계는 자동화 테스트가 없다는 점이고, 다음 개선은 요금 변환 같은 순수 함수부터 테스트를 붙이는 것입니다.
- 테스트 부재: 요금 변환, 일정 유형 분류처럼 입력과 출력이 분명한 함수부터 단위 테스트를 붙이겠습니다.
- 채팅의 보완점: 과거 대화를 불러온 뒤 스크롤 위치를 근사값으로 계산합니다. 가상 스크롤이나 기준 메시지 앵커로 바꾸면 더 안정적입니다.
- 성과 측정: 문의 건수와 등록 소요 시간을 처음부터 기록했다면 개선 효과를 숫자로 보여줄 수 있었을 것입니다.