메뉴 취합하는 막내를 위해 Day3

개발공부를해보자·2026년 2월 24일

프로젝트

목록 보기
4/4

동기 : "쓸 수 있는" 서비스에서 "쓰고 싶은" 서비스로

  • Day2에서 누구나 방을 만들고 링크를 공유할 수 있게 만들었다.
  • 기능적으로는 다 된 셈인데, 막상 써보면 아쉬운 부분이 한둘이 아니었다.
    • 메뉴 정리해서 주문할 때 일일이 타이핑해야 한다. 복사 기능이 있으면 좋겠는데?
    • 카카오톡으로 링크 보내면 미리보기가 안 뜬다. 밋밋한 URL만 덩그러니.
    • 밤에 쓰면 눈이 부시다. 다크모드가 없으니까.
    • 옆에 있는 사람한테 링크를 공유하려면? 카톡을 보내야 하나?
    • 누군가 악의적으로 투표를 도배하면? 과금 폭탄 맞으면?
  • 그래서 Day3의 목표는 "실제로 쓸 때 불편한 점을 하나씩 잡아내는 것"이었다.

큰 흐름

1. 주문 요약 복사

  • 투표가 끝나면 결국 주문을 해야 한다. 그때 "짜장 3개, 짬뽕 2개..." 이걸 일일이 적고 있으면 귀찮다.
  • 📋 주문 복사 버튼을 누르면 팝업이 뜨고, 두 가지 형식 중 하나를 고를 수 있다.
    • 메뉴만 복사 : 짜장면 3 / 짬뽕 2
    • 사람 포함 복사 : 짜장면 3 — 철수, 영희, 민수 / 짬뽕 2 — 지원, 하늘
  • 복사하면 하단에 토스트 알림이 잠깐 뜨고 사라진다.
  • 검색 키워드 : navigator.clipboard.writeText, toast notification

2. 카카오톡 미리보기 (OG 태그)

  • 카카오톡에 링크를 보내면 제목, 설명, 이미지가 미리보기로 뜬다.
  • index.html에 Open Graph 메타 태그를 추가했다.
  • og:title, og:description, og:image 이 세 개가 핵심이다.
  • Twitter Card 메타 태그도 같이 넣어서 트위터에서도 미리보기가 나온다.
  • 검색 키워드 : og:title, og:description, og:image, twitter:card

3. 만료 시간 안내

  • Day2에서 24시간 자동 만료를 넣었는데, 사용자 입장에서는 "이 방 언제 사라지지?"를 모른다.
  • 페이지 최하단에 남은 시간을 작게 표시했다.
  • 1분마다 자동 갱신되고, 남은 시간에 따라 색상이 바뀐다.
    • 6시간 이상 → 회색 (안심)
    • 1~6시간 → 주황 (주의)
    • 1시간 미만 → 빨강 (긴급)
  • 투표나 사진 업로드 같은 활동을 하면 24시간이 리셋되면서 안내도 갱신된다.
  • 검색 키워드 : setInterval, Date.now, 조건부 CSS 클래스

4. SEO (검색엔진 최적화)

  • 구글이나 네이버에서 검색하면 이 사이트가 나오게 하고 싶었다.
  • 한 작업들 :
    • sitemap.xml 생성 → 검색엔진에 페이지 구조 알려주기
    • robots.txt 수정 → 크롤러 허용 범위 설정
    • index.html 메타 태그 보강 → 제목, 설명, 키워드
    • Google Search Console 등록 → google-site-verification 메타 태그
    • 네이버 서치어드바이저 등록 → naver-site-verification 메타 태그
  • 사이트 제목을 "vote-eat | 메뉴 수합(아아 하나 추가요!)"로 정했다.
  • 검색 키워드 : sitemap.xml, robots.txt, Google Search Console, 네이버 서치어드바이저

5. 악의적 트래픽 방어

  • Firebase는 무료 한도를 넘으면 과금된다. 누군가 자동화 도구로 투표를 수천 번 날리면?
  • 두 가지 레이어로 막았다.

서버단 : Firestore 보안 규칙

  • 그룹 이름 50자 제한
  • 투표 항목 이름 50자 제한
  • 투표자 배열 100명 제한
  • 데이터 타입 검증 (문자열 필수 등)

클라이언트단 : 쿨다운

  • 투표 : 2초
  • 빠른 참여 : 2초
  • 이미지 업로드 : 5초 + 방당 최대 20장
  • 방 생성 : 3초
  • useRef로 마지막 액션 시각을 추적하고, 쿨다운 중에는 버튼이 비활성화된다.
  • 검색 키워드 : Firestore Security Rules, rate limiting, useRef cooldown

6. 다크모드

  • 밤에 쓰면 눈이 부시다는 건 사실 처음부터 알고 있었다. 미루다가 드디어 넣었다.
  • 구현 방식 :
    • CSS 변수(--bg-color, --text-color 등)로 전체 색상을 관리
    • [data-theme="dark"] 선택자로 다크모드 색상 오버라이드
    • themeUtils.js에서 테마 상태를 관리 (localStorage 저장 + 시스템 설정 감지)
    • 헤더에 ☀️/🌙 토글 버튼
  • 시스템 다크모드를 감지해서 처음 접속 시 자동 적용되고, 수동으로 바꾸면 그 선택을 기억한다.
  • 카드, 입력 필드, 팝업, 토글 스위치 등 모든 UI 요소에 다크모드를 적용했다. 이게 생각보다 손이 많이 갔다.
  • 검색 키워드 : CSS custom properties, data-theme, prefers-color-scheme, localStorage

7. 반응형 UI 개선

  • 모바일에서 쓰는 사람이 대부분인데, 작은 화면에서 깨지는 부분들이 있었다.
  • 480px, 360px 브레이크포인트를 추가하고 세부 조정했다.
    • 투표 항목 헤더가 한 줄에 안 들어가면 자연스럽게 줄바꿈
    • 메뉴 이름이 너무 길면 말줄임(...)
    • 가격 행, 배지, 버튼 크기 축소
    • 터치 타겟 확대 (손가락으로 누르기 편하게)
  • 검색 키워드 : media query, flex-wrap, text-overflow ellipsis, min-width 44px

8. 공유 팝업 (QR 코드 + 네이티브 공유)

  • Day2에서 만든 🔗 공유 버튼은 링크 복사 한 가지뿐이었다.
  • 근데 옆에 있는 사람한테 공유하려면? 카톡을 보내야 하나? QR이면 바로 스캔하면 되는데.
  • 기존 버튼을 공유 팝업으로 교체했다. 누르면 3가지 옵션이 나온다.
    • 🔗 링크 복사 — 클립보드에 복사
    • QR 코드 — 화면에 QR 표시, 옆 사람이 카메라로 스캔
    • 다른 앱으로 공유 — 카카오톡, 메시지, AirDrop 등 (모바일에서만 표시)
  • QR 코드는 qrcode.react 라이브러리로 SVG 렌더링. 다크모드에서도 잘 보이도록 색상 분기 처리.
  • "다른 앱으로 공유"는 Web Share API를 사용하는데, 이건 모바일 브라우저에서만 지원한다. PC에서는 자동으로 숨겨진다.
  • 검색 키워드 : qrcode.react, QRCodeSVG, navigator.share, Web Share API

9. 관리자 페이지

  • Firebase 과금이 걱정되면 서비스를 통째로 꺼버릴 수 있어야 한다.
  • /admin?key=비밀키 URL로 접근하는 관리자 페이지를 만들었다.
  • 두 가지 토글 스위치 :
    • 🌐 전체 서비스 on/off — 끄면 방 생성, 투표, 참여 모두 차단
    • 📷 사진 업로드 on/off — 끄면 업로드만 차단 (Storage 비용 절감)
  • Firestore의 config/service 문서에 설정값을 저장하고, 앱 로드 시 1회 읽기.
  • 끈 상태에서 사용자에게는 커스텀 점검 안내 메시지가 표시된다. 메시지도 관리자 페이지에서 수정 가능.
  • 관리자 키는 .env에 저장해서 코드에 노출되지 않게 했다.
  • 검색 키워드 : Firestore config document, admin panel, feature toggle, environment variable

조금 헤맸던 부분들

1. 다크모드 CSS 변수 지옥

  • 처음에는 "색상만 바꾸면 되지" 했는데, 실제로는 카드, 입력 필드, 팝업, 모달, 프로그레스 바, 캐러셀 화살표 등등 전부 개별적으로 색상을 잡아줘야 했다.
  • CSS 변수를 50개 넘게 만들었다. --card-bg, --border-color, --input-bg, --carousel-arrow-bg ...
  • 한번 체계를 잡아놓으니 이후에 추가되는 컴포넌트도 변수만 쓰면 자동으로 다크모드가 적용되어서, 투자할 만했다.

2. Web Share API의 브라우저 호환성

  • navigator.share()는 모바일 Safari, Chrome에서는 잘 동작하는데 데스크톱에서는 대부분 지원하지 않는다.
  • 그래서 typeof navigator.share === "function"으로 체크해서, 지원하지 않는 환경에서는 해당 버튼을 아예 숨겼다.
  • 사용자가 공유를 취소하면 AbortError가 발생하는데, 이건 에러가 아니니까 무시하도록 처리했다.

3. QR 코드 다크모드 대응

  • QR 코드의 기본 배경은 흰색, 패턴은 검정이다. 다크모드에서 이러면 네모난 흰색 덩어리가 떠 있는 것처럼 보인다.
  • bgColor="transparent"로 배경을 투명하게 하고, fgColor를 테마에 따라 분기시켰다.
    • 라이트모드 : #1f2937 (어두운 회색)
    • 다크모드 : #f1f5f9 (밝은 회색)

오늘 추가된 기능 정리

기능설명
주문 요약 복사메뉴만 / 사람 포함, 두 가지 형식 클립보드 복사
OG 태그카카오톡 등 SNS 링크 미리보기
만료 시간 안내페이지 하단에 남은 시간 표시 (색상 변화)
SEO 최적화sitemap, robots.txt, Google/네이버 등록
트래픽 방어Firestore 보안 규칙 + 클라이언트 쿨다운
다크모드시스템 감지 + 수동 토글 + 전체 UI 대응
반응형 UI480px/360px 미디어 쿼리 보강
공유 팝업링크 복사 + QR 코드 + 네이티브 공유
관리자 페이지전체 서비스 / 사진 업로드 on·off 원격 제어

현재까지의 전체 기능

기능설명Day
실시간 투표이름 + 메뉴 입력 → 모든 사용자 화면에 즉시 반영1
빠른 참여기존 메뉴에 "+ 참여" 버튼으로 바로 합류1
메뉴 사진다중 업로드, 캐러셀, 전체화면 모달, 개별 삭제1
가격 & 총 금액메뉴별 가격 입력 → 소계, 총 예상 금액 자동 계산1
그룹여러 팀이 독립 그룹으로 동시 사용 가능1
전체 초기화투표 + 사진 한 번에 리셋1
이미지 자동 압축큰 사진 자동 리사이즈 + JPEG 압축2
공개 서비스 모드방 만들기 + 내 투표방 + 링크 공유2
자동 만료24시간 미사용 방 자동 삭제2
주문 요약 복사메뉴만/사람 포함 두 가지 형식 복사3
OG 태그카카오톡 링크 미리보기3
만료 시간 안내남은 시간 표시 + 색상 변화3
SEO구글/네이버 검색엔진 등록3
트래픽 방어Firestore 규칙 + 쿨다운3
다크모드시스템 감지 + 토글 + 전체 UI3
반응형 UI모바일 최적화3
공유 팝업링크 복사 + QR + 네이티브 공유3
관리자 페이지서비스/업로드 원격 on·off3

기술 스택

분류기술
프론트엔드React 19, CSS (순수)
라우팅react-router-dom
백엔드/DBFirebase Firestore (실시간 NoSQL)
파일 저장Firebase Storage
배포Firebase Hosting
QR 코드qrcode.react
기타Canvas API, localStorage, Web Share API, CSS custom properties

결과

👉 https://vote-eat.web.app

  • Day1은 "돌아가게", Day2는 "남한테 보여줄 수 있게", Day3는 "편하게 쓸 수 있게" 다듬었다.
  • 기능 자체보다는 쓰는 사람 입장에서의 편의성에 집중했다. 주문 복사, 다크모드, QR 공유 같은 건 없어도 되지만 있으면 확실히 다르다.
  • 관리자 페이지까지 넣으니 과금 걱정 없이 서비스를 열어둘 수 있게 됐다. 위험하면 토글 하나로 끄면 되니까.
profile
개발 공부하는 30대 비전공자 직장인

0개의 댓글