프론트엔드백엔드

의견 메인 탭 승격 + 코스 무료 슬롯 폐지 + 등급별 월 부스트 한도 — POP-SPOT v2.12

김동현
··4분 읽기

Footer + MY 카드 만으로는 "있는 줄도 모름" → BottomDock 메인 탭으로 승격. 무료 사용자 코스 1 개 제한 폐지. "확성기 소모성 아이템" 대신 등급별 월 부스트 (MASTER 5/HUNTER 3/BEGINNER 1).

v2.11 의 의견 기능을 Footer + MY 카드로만 둘 수준에서는 있는 줄도 모르는 사람이 좀 많았다. 동시에 코스 무료 슬롯 제한·확성기 소모성 아이템 두 정책도 같이 재설계.

이 글에서 다루는 것

  • "Footer 에 넘어가면 있는 줄도 모른다" 의 대안 — BottomDock 메인 탭
  • 무료 코스 저장 1 개 제한을 폐지한 이유 (도리어 UX 마이너스)
  • 확성기 소모성 아이템 → 등급별 월 부스트 한도 전환
  • 등급 임계값 (3/6/12) 이 프론트 + 백엔드 두 곳에 있는 관리 부담 소개
  • 모르는 단어 한 줄로

    용어한 줄 설명
    BottomDock모바일 하단의 고정 메뉴바. 아이콘 4~5 개로 주요 탭 이동
    부스트동행 게시판의 글을 상단에 노출시키는 일. 광고 느낌이 아닌 "프롤·특별" 느낌을 노림
    BEGINNER/HUNTER/MASTER스탬프 개수에 따른 등급. 3/6/12 스탬프 임계값
    단일 진실의 원천같은 숫자 (3/6/12 등) 가 코드 상 한 곳에서만 정의되도록 하는 원칙

    무엇이 바뀌었나

    항목v2.11 까지v2.12
    의견 동선Footer + MY 카드 (limit=3)BottomDock 메인 탭 (lucide Inbox)
    코스 저장 슬롯무료 1 개 (새로 저장하면 자동 삭제)모든 사용자 무제한
    동행 상단 노출"확성기 사용" (소모성 아이템)등급별 월 부스트 한도 (MASTER 5 / HUNTER 3 / BEGINNER 1 / NONE 0)
    등급의 의미RankCard 에 표시만스탬프 → 등급 → 부스트 한도 연동
    표현 톤📢 · 🔥 · AD 아이콘평서체 + lucide TrendingUp

    왜 이렇게 했음

    메인 탭 승격 — v2.11 의 Footer 링크는 사용자 입장에서 "한 번도 볼 일이 없는 공간" 이다. BottomDock 은 항상 눈에 있으니 의견이 솤아조있는 상태. "의견" 아이콘이 있으면 이게 있는 서비스구나 명확하게 드러난다.

    도리어 UX 마이너스 — "무료는 1 개" 제한이 계속 계산되면 사용자가 "다음에 읽을 건데 입력하면 이전 게 사라지는 건가" 를 매번 신경 써야 함. 수익화 자체가 필요한 시점에 다시 고민하는 게 낫다.

    등급별 월 부스트 — 확성기 소모성 아이템은 광고체대와 닮아 입다. "월에 몇 번은 넘은 사람" 은 상품·돈 찴고 "원장·특별·경험 많은 사람" 을 자이아론이 먴한다. 등급을 "스탬프 모은 사람" 으로 연결시키고 서비스 아이던티티와 더 잘 맞다.

    단일 진실 — 임계 3/6/12 를 각각 프론트 (src/lib/rank.ts) + 백엔드 (BoostPolicy.java) 에 둘었는데 둘 다 같은 값. JavaDoc·JSDoc 에 "동시 갱신 경고" 을 명시해 둕. 다음에 기준이 바뀌면 두 곳 모두 수정.


    코드로는 어떻게 (필요한 부분만)

    파일: popspot-frontend/src/lib/rank.ts (v2.12 신규)

    typescript
    // v2.12 — src/lib/rank.ts. 프론트 장의 등급 임계
    export const RANK_THRESHOLD = { BEGINNER: 3, HUNTER: 6, MASTER: 12 } as const;
    //                              ^^^^^^^^^^^^  ^^^^^^^^^^^ ^^^^^^^^^^
    //                              스탬프 3개    6개        12개
    // as const 은 추론된 타입을 최소로 고정. RANK_THRESHOLD.MASTER 가 number 가 아닌 12 리터럴 타입으로 고정됨
    export function rankOf(stamps: number) {
      // 위: 입력 stamps (수집한 스탬프 개수) 에 따른 등급 계산
      if (stamps >= RANK_THRESHOLD.MASTER)   return 'MASTER';
      if (stamps >= RANK_THRESHOLD.HUNTER)   return 'HUNTER';
      if (stamps >= RANK_THRESHOLD.BEGINNER) return 'BEGINNER';
      // 위: 높은 등급부터 차례대로 검사. 순서 중요 — 반대로 하면 12보다 큰 수도 BEGINNER 로 결론 내려지게 됨
      return 'NONE';
      // 위: 스탬프 0~2개 — 아직 아무 등급도 못 달은 가입자. 부스트 쿠타 0.
    }

    파일: popspot-frontend/src/lib/boost.ts (v2.12 신규)

    typescript
    // v2.12 — src/lib/boost.ts. 등급별 월간 부스트 사용 한도
    export const BOOST_QUOTA = {
      MASTER: 5, HUNTER: 3, BEGINNER: 1, NONE: 0,
      //      ^         ^             ^         ^   등급이 높을수로 동행 부스트 획득 기회 많이
      //                                              "돈 내면 키워" 가 아닌 "스탬프 모으면 더 송출" 원리
    } as const;

    파일: popspot-backend/src/main/java/com/popspot/service/BoostPolicy.java (v2.12 신규)

    java
    // v2.12 — BoostPolicy.java (백엔드). 프론트와 동일한 수치 — 둘 같이 갱신
    public enum Rank {
    //     ^^^^   자바 enum. 타입 안전 — Rank.MASTER 형식으로 쓰며 switch 에서 누락 감지 가능
        MASTER(12, 5), HUNTER(6, 3), BEGINNER(3, 1), NONE(0, 0);
        //     ^^  ^         ^  ^             ^  ^        ^  ^
        //     |   |         |  |             |  |        |  +-- monthlyBoost (등급별 월 한도)
        //     |   |         |  |             |  |        +----- minStamps (이 이상이면 해당 등급)
        //     |   |         |  |             |  +-------------- BEGINNER: 3 스탬프 이상 + 월 1회 부스트
        //     |   |         |  +------------------------------- HUNTER:   6 스탬프 + 월 3회
        //     |   +----------------------------------------- MASTER:   12 스탬프 + 월 5회
        private final int minStamps;
        private final int monthlyBoost;
        //                ^^^^^^^^^^^^   이 값을 BoostUsage.used_count 와 비교해 이번 달 추가 사용 가능 여부 판단
    }

    파일: popspot-backend/src/main/resources/db/migration/V8__boost.sql

    sql
    -- v2.12 — V8__boost.sql. 동행 게시글의 부스트 상태·사용 관리
    ALTER TABLE mate_post
      ADD COLUMN boosted_until TIMESTAMP NULL;
      --         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^   이 시각까지 상단 노출. NULL 이면 부스트 안 된 일반 게시글
      --                                        조회 시 NOW() < boosted_until 조건을 ORDER BY 원조건으로 상단 정렬
    
    CREATE TABLE boost_usage (
      user_id    BIGINT NOT NULL,
      year_month CHAR(7) NOT NULL,           -- '2026-05' 표기. 월 단위 추적
      used_count INT NOT NULL DEFAULT 0,     -- 이번 달에 쓴 부스트 회수. BoostPolicy.monthlyBoost 와 비교
      PRIMARY KEY (user_id, year_month)
      --           ^^^^^^^^^^^^^^^^^^^^^^   복합 PK. user·월 조합 1 개 레코드 유일.
      --                                     INSERT ... ON CONFLICT (user_id, year_month) DO UPDATE SET used_count=used_count+1
      --                                     패턴으로 upsert — 있으면 증가, 없으면 삽입
    );

    핵심 파일: popspot-frontend/src/lib/rank.ts, popspot-frontend/src/lib/boost.ts, popspot-frontend/src/components/layout/BottomDock.tsx, popspot-backend/.../service/BoostPolicy.java (신규)


    관련 글

  • 이전 — v2.11, 의견 게시판
  • 다음 — v2.13, SearchBox 정확도 + 자동수집 95 키워드
  • 공유

    댓글