feat: 멤버십(무료/유료) 게이팅 — 프론트
CI / build (push) Failing after 15m5s

- auth 스토어 isPremium/isAdmin 추가 (plan===PREMIUM 또는 관리자)
- 사이드바: 통계·고정지출·예산·태그 메뉴에 무료 회원 잠금 배지(🔒)
- 라우트 가드: 유료 전용 화면(requiresPremium) → 무료 회원은 /upgrade
- UpgradeView: 무료/유료 기능 비교 + 업그레이드 안내
- http 인터셉터: 403 PREMIUM_REQUIRED → premium:required 이벤트 → 업그레이드 화면
- 무료 화면의 유료 API 호출 차단(홈 예산·내역 태그·OCR·카드알림 게이팅)
- 회원관리: 멤버십 컬럼 + 무료/유료 토글(관리자)
- 설정: 데이터 백업/복구 유료 게이팅, 계정정보에 멤버십 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-28 11:02:44 +09:00
parent 2755d118c2
commit 9861ba90ae
12 changed files with 309 additions and 32 deletions
+29 -15
View File
@@ -1,13 +1,16 @@
<script setup>
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { RouterLink } from 'vue-router'
import { Preferences } from '@capacitor/preferences'
import { useDialog } from '@/composables/dialog'
import { useUiStore } from '@/stores/ui'
import { useAuthStore } from '@/stores/auth'
import { exportBackup, importBackup } from '@/utils/backup'
const dialog = useDialog()
const ui = useUiStore()
const auth = useAuthStore()
const isPremium = computed(() => auth.isPremium) // 데이터 백업/복구는 유료 전용
const exporting = ref(false)
async function doExport() {
@@ -143,23 +146,34 @@ async function clearAppData() {
</div>
</section>
<!-- 데이터 백업 -->
<!-- 데이터 백업 (유료 전용) -->
<section class="card">
<button type="button" class="row row-btn" :disabled="exporting" @click="doExport">
<template v-if="isPremium">
<button type="button" class="row row-btn" :disabled="exporting" @click="doExport">
<div class="row-main">
<span class="row-label">엑셀로 내보내기</span>
<span class="row-sub">가계부 전체 데이터를 .xlsx 파일로 저장</span>
</div>
<span class="row-value">{{ exporting ? '내보내는 중…' : '내보내기' }}</span>
</button>
<button type="button" class="row row-btn" :disabled="importing" @click="pickImportFile">
<div class="row-main">
<span class="row-label">엑셀에서 가져오기</span>
<span class="row-sub">백업 파일로 데이터 복구 (기존 데이터 덮어씀)</span>
</div>
<span class="row-value">{{ importing ? '복구 중…' : '가져오기' }}</span>
</button>
<input ref="fileInput" type="file" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" class="hidden-file" @change="onImportFile" />
</template>
<RouterLink v-else to="/upgrade" class="row row-link">
<div class="row-main">
<span class="row-label">엑셀로 내보내기</span>
<span class="row-sub">가계부 전체 데이터를 .xlsx 파일로 저장</span>
<span class="row-label">데이터 백업 / 복구 👑</span>
<span class="row-sub">엑셀 내보내기·가져오기는 유료 멤버십 기능입니다</span>
</div>
<span class="row-value">{{ exporting ? '내보내는 중…' : '내보내기' }}</span>
</button>
<button type="button" class="row row-btn" :disabled="importing" @click="pickImportFile">
<div class="row-main">
<span class="row-label">엑셀에서 가져오기</span>
<span class="row-sub">백업 파일로 데이터 복구 (기존 데이터 덮어씀)</span>
</div>
<span class="row-value">{{ importing ? '복구 중…' : '가져오기' }}</span>
</button>
<input ref="fileInput" type="file" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" class="hidden-file" @change="onImportFile" />
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 18l6-6-6-6" />
</svg>
</RouterLink>
</section>
<section class="card">