diff --git a/docs/plan-membership-tiers.md b/docs/plan-membership-tiers.md
new file mode 100644
index 0000000..a7da1ba
--- /dev/null
+++ b/docs/plan-membership-tiers.md
@@ -0,0 +1,51 @@
+# 멤버십(무료/유료) 메뉴·기능 구분 — 설계 (추후 구현)
+
+> 상태: **설계만** (미구현). 결제·게이팅은 추후 작업.
+> 방향: **핵심 기록은 무료로 개방**(진입장벽 ↓), **과금 기능·자동화·고급 분석·무제한·백업은 유료**.
+> 계좌 개수 제한은 **두지 않음**(1~소수 제한은 핵심 기능을 막아 거부감 → 제외).
+
+---
+
+## 1. 무료 / 유료 구분
+
+| 메뉴 / 기능 | 무료 | 유료(프리미엄) | 비고 |
+|---|:--:|:--:|---|
+| **가계부 내역**(수입·지출 기록) | ✅ | ✅ | 핵심 — 무료 개방 |
+| **계좌 관리**(은행·현금·카드·대출) | ✅ **무제한** | ✅ 무제한 | 개수 제한 없음 |
+| 자주 쓰는 내역(빠른 등록) | ✅ | ✅ | 편의, 무료 |
+| 월별 요약(수입/지출/잔액) | ✅ | ✅ | 기본 요약은 무료 |
+| 분류 관리 | ✅ 기본+소수 | ✅ 무제한·소분류(2단계) | 무료는 기본 분류 위주 |
+| 태그 관리 | — | ✅ | |
+| **통계**(순자산 추이·예상 지출·지난달 대비·분류 분석) | — | ✅ | 고급 분석은 유료 |
+| 예산 설정 | — | ✅ | |
+| 고정 지출(자동 생성·확인) | — | ✅ | 자동화 = 유료 |
+| **영수증 OCR** | — | ✅ | **Google Vision 과금** → 유료 필수 |
+| **카드 결제 알림 자동인식** | — | ✅ | 강력한 유료 훅(편의·자동화) |
+| 투자/자산(평가액) 관리 | — | ✅ | |
+| 데이터 백업/복구(엑셀·JSON) | — | ✅ | 추후 백업 기능과 연계 |
+| 게시판(커뮤니티/짠테크/재테크 팁) | ✅ | ✅ | 커뮤니티는 전체 개방 |
+| 다크모드·테마 | ✅ | ✅ | 기본 제공 |
+| 광고 | (도입 시) 표시 | 제거 | 광고 모델은 미정 |
+
+## 2. 구분 기준
+- **무료** = 기본 기록 + (무제한)계좌 + 기본 분류 + 월 요약 → "충분히 써볼 만한" 수준
+- **유료** = ① 과금 기능(OCR) · ② 자동화(카드 알림 인식 · 고정지출) · ③ 고급 분석(통계 전체 · 예산) · ④ 무제한(태그/소분류) · ⑤ 백업/복구
+- 원칙: **무료를 일부러 불편하게 만들지 않고, 유료에 "확실히 편한 것"을 몰아준다**(전환율 우선).
+
+## 3. 구현 시 필요한 것 (추후)
+- **데이터**: `member.plan`(`FREE`/`PREMIUM`) + `plan_expires_at`(만료일) 컬럼.
+- **게이팅(이중)**:
+ - 프론트: 유료 메뉴에 **잠금 배지 + 업그레이드 유도**(데모의 잠금 화면 재사용 가능), 비유료는 진입 차단.
+ - 백엔드: 해당 API에 **plan 권한 체크**(프론트 우회 방지). OCR·알림 인식·통계·예산·고정지출·투자·백업 엔드포인트.
+- **결제 연동**: 안드로이드 인앱결제(Google Play Billing) / 웹·PC는 PG. 정기결제 시 만료일 갱신.
+- **기존 데이터 보호**: 유료→무료 강등(만료) 시 데이터는 보존하되 **유료 메뉴만 잠금**(읽기 정도는 허용 검토).
+
+## 4. 추후 결정 필요(미정)
+- 가격·주기(월/연), 무료체험 기간.
+- 광고 모델 도입 여부(무료=광고 / 유료=제거).
+- 무료 "기본 분류" 범위(개수·기본 템플릿만 허용 여부).
+- 강등 시 잠금 정책(완전 차단 vs 읽기 허용).
+
+---
+
+*참고: 데모(`/demo`)의 잠금 안내 화면을 유료 게이팅 UI 의 기반으로 재사용할 수 있음.*
diff --git a/package-lock.json b/package-lock.json
index f6fe29d..f472ed5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,9 @@
"@capacitor/camera": "^8.2.0",
"@capacitor/cli": "^8.3.4",
"@capacitor/core": "^8.3.4",
+ "@capacitor/filesystem": "^8.1.2",
"@capacitor/preferences": "^8.0.1",
+ "@capacitor/share": "^8.0.1",
"@capawesome/capacitor-google-sign-in": "^0.1.2",
"@toast-ui/editor": "^3.2.2",
"@toast-ui/editor-plugin-code-syntax-highlight": "^3.1.0",
@@ -22,7 +24,8 @@
"prismjs": "^1.30.0",
"sortablejs": "^1.15.7",
"vue": "^3.5.32",
- "vue-router": "^5.0.4"
+ "vue-router": "^5.0.4",
+ "xlsx": "^0.18.5"
},
"devDependencies": {
"@capacitor/assets": "^3.0.5",
@@ -1187,6 +1190,18 @@
"tslib": "^2.1.0"
}
},
+ "node_modules/@capacitor/filesystem": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/@capacitor/filesystem/-/filesystem-8.1.2.tgz",
+ "integrity": "sha512-doaaMfGoFR2hWU6aV6u83I+5ZsGyJVq+Gz4r9lMpJzUKMm1eMu0hLnFdV1aXZlU9FlK/RndFrVD8oRZfNOqWgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@capacitor/synapse": "^1.0.4"
+ },
+ "peerDependencies": {
+ "@capacitor/core": ">=8.0.0"
+ }
+ },
"node_modules/@capacitor/preferences": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/preferences/-/preferences-8.0.1.tgz",
@@ -1196,6 +1211,21 @@
"@capacitor/core": ">=8.0.0"
}
},
+ "node_modules/@capacitor/share": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@capacitor/share/-/share-8.0.1.tgz",
+ "integrity": "sha512-3cSBKBCJVon54rKDROP2rqGyeGks4pBh9TbaEk9S375Kbek/ZHe72N50zIa0Vn9Eac/SuhwgehO/mmA4CsUOiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@capacitor/core": ">=8.0.0"
+ }
+ },
+ "node_modules/@capacitor/synapse": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@capacitor/synapse/-/synapse-1.0.4.tgz",
+ "integrity": "sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==",
+ "license": "ISC"
+ },
"node_modules/@capawesome/capacitor-google-sign-in": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@capawesome/capacitor-google-sign-in/-/capacitor-google-sign-in-0.1.2.tgz",
@@ -4015,6 +4045,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/adler-32": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
+ "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -5182,6 +5221,19 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/cfb": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
+ "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "adler-32": "~1.3.0",
+ "crc-32": "~1.2.0"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/chai": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
@@ -5378,6 +5430,15 @@
"node": ">=4"
}
},
+ "node_modules/codepage": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
+ "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -5782,6 +5843,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "license": "Apache-2.0",
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@@ -7479,6 +7552,15 @@
"node": ">= 6"
}
},
+ "node_modules/frac": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
+ "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -12540,6 +12622,18 @@
"license": "BSD-3-Clause",
"optional": true
},
+ "node_modules/ssf": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
+ "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "frac": "~1.1.2"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/ssri": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz",
@@ -14175,6 +14269,24 @@
"node": ">=8"
}
},
+ "node_modules/wmf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
+ "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/word": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
+ "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -14297,6 +14409,27 @@
"node": ">=10.0.0"
}
},
+ "node_modules/xlsx": {
+ "version": "0.18.5",
+ "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
+ "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "adler-32": "~1.3.0",
+ "cfb": "~1.2.1",
+ "codepage": "~1.15.0",
+ "crc-32": "~1.2.1",
+ "ssf": "~0.11.2",
+ "wmf": "~1.0.1",
+ "word": "~0.3.0"
+ },
+ "bin": {
+ "xlsx": "bin/xlsx.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/xml-js": {
"version": "1.6.11",
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
diff --git a/package.json b/package.json
index 1bb4dbc..4eb8998 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,9 @@
"@capacitor/camera": "^8.2.0",
"@capacitor/cli": "^8.3.4",
"@capacitor/core": "^8.3.4",
+ "@capacitor/filesystem": "^8.1.2",
"@capacitor/preferences": "^8.0.1",
+ "@capacitor/share": "^8.0.1",
"@capawesome/capacitor-google-sign-in": "^0.1.2",
"@toast-ui/editor": "^3.2.2",
"@toast-ui/editor-plugin-code-syntax-highlight": "^3.1.0",
@@ -55,7 +57,8 @@
"prismjs": "^1.30.0",
"sortablejs": "^1.15.7",
"vue": "^3.5.32",
- "vue-router": "^5.0.4"
+ "vue-router": "^5.0.4",
+ "xlsx": "^0.18.5"
},
"devDependencies": {
"@capacitor/assets": "^3.0.5",
diff --git a/src/App.vue b/src/App.vue
index c06bfe2..1d910db 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,6 +1,6 @@
+
+
+ 둘러보기에서는 가계부 내역 · 고정 지출 · 계좌 관리 · 분류 관리만 미리 볼 수 있습니다. 등록한 주기에 맞춰 가계부에 자동으로 '확인 필요' 내역이 생성됩니다. 대분류 아래 소분류로 묶어 관리합니다. (예산·통계는 소분류 기준) {{ activeItem.label }}는 로그인 후 이용할 수 있어요 로그인하면 통계·예산·태그·게시판까지 모두 사용할 수 있습니다. 마음에 드시나요? 로그인하면 내 가계부를 바로 시작할 수 있어요.{{ label }}는 로그인 후 이용할 수 있어요
+
로그인하면 통계 · 예산 · 태그 · 게시판까지 모두 사용할 수 있어요.
-
-
-
-
-
-
-
-
{{ !ID_LOGIN_ENABLED || ui.signupEnabled ? '로그인하면 나의 가계부 요약을 볼 수 있어요.' : '현재 회원가입이 제한되어 있습니다.' }}
@@ -694,10 +700,15 @@ onMounted(load) } .cta { display: flex; + flex-direction: column; + align-items: center; gap: 0.6rem; - justify-content: center; margin-top: 1.4rem; } +.cta .btn { + width: 100%; + max-width: 260px; +} .btn { padding: 0.6rem 1.4rem; border: 1px solid var(--color-border); diff --git a/src/views/settings/SettingsView.vue b/src/views/settings/SettingsView.vue index ac1625e..176ff3f 100644 --- a/src/views/settings/SettingsView.vue +++ b/src/views/settings/SettingsView.vue @@ -4,9 +4,26 @@ import { RouterLink } from 'vue-router' import { Preferences } from '@capacitor/preferences' import { useDialog } from '@/composables/dialog' import { useUiStore } from '@/stores/ui' +import { exportBackup } from '@/utils/backup' const dialog = useDialog() const ui = useUiStore() + +const exporting = ref(false) +async function doExport() { + if (exporting.value) return + exporting.value = true + try { + await exportBackup() + } catch (e) { + window.alert(e?.message || '내보내기에 실패했습니다.') + } finally { + exporting.value = false + } +} +function importBackup() { + dialog.alert('가져오기(복구)는 준비 중입니다. 곧 제공됩니다.') +} const THEMES = [ { value: 'system', label: '시스템' }, { value: 'light', label: '라이트' }, @@ -92,6 +109,24 @@ async function clearAppData() { + +