- 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:
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { accountApi } from '@/api/accountApi'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||
import { imageToBlob, parseReceiptText } from '@/utils/receiptOcr'
|
||||
@@ -9,6 +10,8 @@ import { Capacitor } from '@capacitor/core'
|
||||
// @capacitor/camera 는 네이티브에서만 동적 로드 (웹은 file input 사용 — 정적 import 시 모바일 웹 청크 로드 실패 유발)
|
||||
|
||||
const dialog = useDialog()
|
||||
const auth = useAuthStore()
|
||||
const isPremium = computed(() => auth.isPremium) // 태그·OCR 등 유료 기능 노출 제어
|
||||
const now = new Date()
|
||||
const year = ref(now.getFullYear())
|
||||
const month = ref(now.getMonth() + 1)
|
||||
@@ -329,6 +332,11 @@ function toggleTag(id) {
|
||||
}
|
||||
|
||||
async function loadTagOptions() {
|
||||
// 태그는 유료 전용 — 무료 회원은 호출하지 않음(403/업그레이드 리다이렉트 방지)
|
||||
if (!isPremium.value) {
|
||||
tagOptions.value = []
|
||||
return
|
||||
}
|
||||
try {
|
||||
tagOptions.value = await accountApi.tags()
|
||||
} catch {
|
||||
@@ -832,7 +840,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
|
||||
<!-- 카드 결제 알림 자동인식 권한 안내 (네이티브, 미허용 시) -->
|
||||
<div v-if="notifNative && !notifEnabled" class="notif-banner">
|
||||
<div v-if="isPremium && notifNative && !notifEnabled" class="notif-banner">
|
||||
<span>💳 카드 결제 알림을 자동으로 가계부에 등록하려면 <b>알림 접근 권한</b>이 필요합니다.</span>
|
||||
<button type="button" class="notif-btn" @click="openNotifSettings">설정 열기</button>
|
||||
</div>
|
||||
@@ -959,6 +967,7 @@ onMounted(async () => {
|
||||
/>
|
||||
<div class="rb-actions">
|
||||
<button
|
||||
v-if="isPremium"
|
||||
type="button" class="receipt-btn"
|
||||
:disabled="submitting || ocrRunning" @click="pickReceipt"
|
||||
>📷 영수증 스캔</button>
|
||||
@@ -1074,7 +1083,7 @@ onMounted(async () => {
|
||||
<label v-if="!isRepayment">금액<input v-model.number="form.amount" type="number" min="0" placeholder="원" :disabled="submitting" /></label>
|
||||
<label>메모<input v-model="form.memo" type="text" placeholder="(선택)" :disabled="submitting" /></label>
|
||||
|
||||
<div v-if="!isRepayment" class="tag-field">
|
||||
<div v-if="!isRepayment && isPremium" class="tag-field">
|
||||
<span class="tag-label">태그</span>
|
||||
<div class="tag-badges">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user