Compare commits

...

360 Commits

Author SHA1 Message Date
ByungCheol 21d79fa040 chore(android): versionCode 6, versionName 1.0.5
Deploy / deploy (push) Failing after 14m42s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-11 03:35:31 +09:00
sb f738c6f5b1 docs: LAUNCH-PROGRESS 현행화 — 애플 로그인·iOS App Store 제출·UI 페이지/바텀시트 개편(07-09~11)
Deploy / deploy (push) Failing after 14m15s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 02:56:50 +09:00
sb 75587e7645 chore(ios): Sign in with Apple/Push capability 반영 — CODE_SIGN_ENTITLEMENTS 연결
Deploy / deploy (push) Failing after 18m5s
- Xcode에서 capability 추가로 entitlements가 빌드에 포함되도록 프로젝트에 반영
- aps-environment(Push) entitlement 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 02:53:05 +09:00
sb f50f1b8fc5 chore: 연락처 이메일 → dondwaeji.app@gmail.com (약관·계정삭제·스토어 문의)
Deploy / deploy (push) Failing after 19m10s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 02:52:20 +09:00
sb f1dc11d402 chore(ios): ITSAppUsesNonExemptEncryption=false — App Store 수출규정 질문 생략(표준 HTTPS만 사용)
Deploy / deploy (push) Failing after 21m20s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 01:00:10 +09:00
sb f367379c84 chore(ios): iOS 번들ID kr.sblog.slimbudget.app 로 변경 — 애플 로그인 App ID 대응
Deploy / deploy (push) Failing after 22m12s
- 기존 kr.sblog.slimbudget 는 타 계정 선점 → iOS 전용 새 번들ID
- 안드로이드 패키지(kr.sblog.slimbudget)는 유지(플랫폼 독립)
- appleAuth clientId 정합(네이티브 aud=번들ID)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 23:43:08 +09:00
sb 42cfeccbaf refactor(nav): 화면 헤더 뒤로가기 버튼 제거 — 하단 네비 뒤로가기로 유도
Deploy / deploy (push) Failing after 14m3s
- 계좌 폼·정기결제 폼·계좌 상세·내역 폼·예산 폼·포인트 상점 헤더의 뒤로가기(←/‹) 제거
- 폼 취소 버튼·공개(약관/삭제) 페이지 돌아가기는 유지
- 뒤로가기는 하단 네비게이션 바(뒤로) 사용

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 23:20:04 +09:00
sb 466fbe8136 refactor(budget): 예산 추가/수정 모달 → 인라인 페이지 + 분류 바텀시트
Deploy / deploy (push) Failing after 15m36s
- AppModal 제거, 목록/폼 v-if·v-else 전환(헤더·하단바 유지 + 뒤로가기)
- ?budget 라우트 바인딩으로 뒤로가기 시 닫힘
- 분류 선택은 트리거 → 바텀시트(CategoryPicker)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 23:13:11 +09:00
ByungCheol 1a85e448aa feat(shop): 포인트 상점 프론트엔드 구현
Deploy / deploy (push) Failing after 11m25s
- accountApi: shopStatus(), buyShopItem(item) 추가; buyShopItem은 DEMO_WRITES에 등록
- router: /settings/shop → ShopView.vue 라우트 추가
- ShopView.vue: 포인트 잔액·상품 카드(계좌 슬롯/AI 통계) 구매 화면, PREMIUM 회원 안내 분기
- AccountInfoView: '기프트콘 교환' → '포인트 상점' 로 이름 변경 + 클릭 시 /settings/shop 이동
- AccountDashboardView: AI 코멘트 402 응답 시 크레딧 소진 안내 + 상점 이동 버튼 노출
- LAUNCH-PROGRESS: 포인트 상점  완료 표시, 다음 재개 지점 현행화

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-10 00:33:13 +09:00
sb 00d2945e39 refactor(entries): 내역 추가/수정을 오버레이 모달 → 인라인 페이지로(톤 통일)
Deploy / deploy (push) Failing after 13m43s
- 폼을 Teleport 전체화면 모달 대신 콘텐츠 영역 인라인 페이지로 렌더
  (앱 헤더·하단바 유지 + 상단 뒤로가기, 계좌·정기결제 페이지와 동일 톤)
- 목록 뷰는 formOpen 시 숨김(v-if/v-else), ?entry 라우트 바인딩 유지
- 폼 로직(AI·OCR·상환 등)은 그대로 — 프레젠테이션만 변경

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 00:07:47 +09:00
sb 24db9b0587 feat(entries): 내역 추가/수정 폼을 라우트(?entry) 바인딩 — 뒤로가기로 닫히는 페이지 전환
Deploy / deploy (push) Failing after 14m57s
- openForm/closeForm: 폼 열 때 히스토리에 ?entry 추가, 닫을 때 뒤로가기로 URL 정리
- 뒤로가기(브라우저·앱 뒤로)로 폼 닫힘 → 페이지 전환처럼 동작
- AI 빠른입력·OCR·상환 로직은 목록에 그대로 두어 회귀 위험 없음

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 00:01:34 +09:00
ByungCheol 6f49a7eef0 feat(point): 포인트 reason 한글화 + 월별 조회 네비게이션
Deploy / deploy (push) Failing after 12m8s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-09 23:49:01 +09:00
sb cdaa7d09ac feat(filter): 검색 분류를 CategoryPicker(대분류→소분류)로, 추가 버튼만 숨김
Deploy / deploy (push) Failing after 12m11s
- CategoryPicker에 hideAdd prop 추가(+대분류/+소분류 숨김)
- 내역 검색 필터 분류: 평면 칩 → 트리거→시트(CategoryPicker hide-add) + '분류 전체'
- 타입은 구분 필터(기본 지출) 기준

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:44:19 +09:00
sb 2ee5cae6ea feat(ui): 계좌 선택을 종류→계좌 아코디언(AccountPicker)으로
Deploy / deploy (push) Failing after 11m53s
- AccountPicker.vue: 종류(대분류) 클릭 시 해당 계좌(소분류) 펼침. CategoryPicker 형태
- 정기결제 폼 출금/입금 계좌: 종류칩+시트 → AccountPicker 단일 선택
- 내역 검색 필터 계좌, 상환 대상: AccountPicker 적용(필터는 '전체' 옵션)
- 계좌 많아도 종류별로 접혀 스크롤 최소화

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:34:36 +09:00
sb 2d70fced79 style(ui): SheetSelect 칩을 CategoryPicker 그리드(4열 균일 박스)로 통일
Deploy / deploy (push) Failing after 11m55s
- pill 칩 → 4열 균일 박스(.acc-chip 동일), 활성은 연한 초록. 목록/셀렉트보다 깔끔

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:19:35 +09:00
sb ecc3ef0f4e feat(ui): SheetSelect 시트 내용을 리스트 → 칩 그리드(CategoryPicker 형태)
Deploy / deploy (push) Successful in 33s
- 셀렉트형 선택(계좌·태그·구분 등)을 바텀시트 안에서 칩 그리드로 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:13:24 +09:00
sb c7875b0577 feat(entries): 검색 필터·내역 폼 선택을 바텀시트로
Deploy / deploy (push) Failing after 14m48s
- 검색 필터(구분·분류·계좌·태그) select → SheetSelect(바텀시트)
- 내역 추가/수정 폼: 계좌·상환대상 ChipSelect → SheetSelect,
  분류 CategoryPicker → 트리거 → 바텀시트(선택 시 자동 닫힘)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:06:42 +09:00
sb d20d3c3027 feat(ui): 계좌/분류 선택을 바텀시트로 (정기결제 폼)
Deploy / deploy (push) Failing after 13m8s
- BottomSheet.vue: 아래에서 올라오는 콘텐츠 높이 시트(재사용)
- SheetSelect.vue: 필드형 트리거 → 바텀시트 단일 선택(계좌/카드)
- 정기결제 폼: 계좌는 SheetSelect, 분류는 트리거 → 시트 안 CategoryPicker
  (선택 전에는 목록 미노출, 선택 시 값 채움)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 22:53:23 +09:00
sb ed607e1cda refactor(recurring): 정기결제 추가/수정을 모달 → 별도 화면(화면전환)
Deploy / deploy (push) Failing after 15m19s
- RecurringFormView.vue 신규: /account/recurrings/new · /:id/edit
- RecurringView: 목록 전용으로 정리(폼·모달·계좌/분류 로직 제거), +/수정은 화면 이동
- 무료 한도 초과 시 업셀은 목록에서 확인 후 이동

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 22:46:11 +09:00
ByungCheol 641d0649ae fix(settings): 데이터 초기화 모달 닫은 후 confirm 표시 — 팝업 겹침·클릭 막힘 해결
Deploy / deploy (push) Failing after 12m0s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-09 22:44:07 +09:00
ByungCheol a4e0b38b2f feat(settings): 가계부 데이터 초기화 기능 — 엑셀 백업 안내·선택·진행상태바·완료토스트
Deploy / deploy (push) Successful in 34s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-09 22:39:17 +09:00
sb ca09dce2bd refactor(wallets): 계좌 추가/수정을 모달 → 별도 화면(화면전환)
Deploy / deploy (push) Failing after 12m40s
- WalletFormView.vue 신규: /account/wallets/new · /:id/edit 라우트
- 생성 시 '계좌 종류' 선택 UI 복원(탭 제거 후 빠졌던 부분)
- AccountWalletView: 모달·폼 로직 제거, +/수정 버튼은 화면 이동
- 상세 라우트는 :id(\d+)로 제한(new와 충돌 방지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 22:33:50 +09:00
sb 13a0f0d40f fix(layout): 본문 하단에 고정 내비 높이만큼 여백 — 마지막 내용 가림 해결
Deploy / deploy (push) Failing after 13m57s
- 하단 내비가 position:fixed 인데 본문 하단 여백은 광고 있을 때만 있어,
  광고 OFF 상태에서 마지막 내용/버튼이 내비 뒤로 가려져 클릭 불가
- .layout-body 에 기본 padding-bottom(56px+safe-area) 항상 적용(PC·모바일)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 22:17:31 +09:00
sb 28712a026d feat(budget): 예산 추가를 '월 예산'만으로 단순화 + 1회성 예산 제거
Deploy / deploy (push) Failing after 12m24s
- 예산 폼: 고정/비고정 방식·일/주/년 예산 입력 제거 → 월 예산 단일 입력
- 1회성 예산 섹션·모달·관련 로직 전체 제거(백엔드 엔드포인트는 유지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 22:14:05 +09:00
ByungCheol d63c6997c9 build(android): versionCode 5, versionName 1.0.4 — Play Store v5 AAB 빌드
Deploy / deploy (push) Failing after 10m48s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:55:28 +09:00
sb d8326568ff docs: LAUNCH-PROGRESS 현행화 — 07-07 배포 완료(정기결제·계좌개편·상환조회·월상환금·홈 상환예정)
Deploy / deploy (push) Failing after 10m58s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:50:32 +09:00
ByungCheol 486942b1c9 docs: 2026-07-07 작업 현행화 — 고정지출주기·태그순서·PMT·교통팁·상환일정·포인트상점견적
Deploy / deploy (push) Failing after 11m16s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:44:59 +09:00
sb ba7ffaff92 fix(entry): 대출 상환 자동세팅에 월 상환금(loanPayment) 우선 반영
Deploy / deploy (push) Failing after 13m58s
- 가계부에서 원리금균등 대출 선택 시 최초원금 PMT 추정값(옛 방식) 대신
  저장된 월 상환금을 그대로 사용 → 은행/상환표와 일치
- 잔액이 작은 마지막 회차는 원금을 잔액까지만(초과 상환 방지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:42:33 +09:00
sb 5567a239df chore(home): 이번 달 상환 예정 카드 유료 전용 — 무료는 계산·노출 생략
Deploy / deploy (push) Failing after 13m1s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:38:05 +09:00
sb 2b58e976fc feat(home): 대시보드에 이번 달 상환 예정 금액 카드
Deploy / deploy (push) Successful in 34s
- 대출 월 상환금 + 카드 할부(이번 달 회차) 합계를 홈 대시보드에 표기
- 현재일 기준, 가계부에 상환 기록(REPAYMENT) 있는 계좌는 제외
- 모두 기록 시 '이번 달 상환 완료' 표시, 부채 없으면 카드 숨김
- 계산은 utils/repayment.js 재사용

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:37:03 +09:00
sb e4ab683ac8 feat(wallet): 대출 연이자율 소수점 3자리 입력 허용 (step 0.001)
Deploy / deploy (push) Failing after 15m2s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:31:27 +09:00
sb a5264f4dff fix(repayment): 원리금균등 마지막 회차에 잔여 반올림 합산 — 꼬리 회차 제거
Deploy / deploy (push) Failing after 14m27s
- 매월 이자 반올림 누적으로 마지막 정상 회차 뒤 수십원(원금 62·이자 1) 추가 회차
  생기던 문제. 잔액<=월상환금이면 마지막 회차로 남은 잔액을 한 번에 정산(은행 동일)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:22:02 +09:00
sb 18f015f7fd feat(repayment): 대출 월 상환금 입력 시 원금·이자 은행과 정확 일치
Deploy / deploy (push) Failing after 11m59s
- 계좌 관리 대출 폼에 '월 상환금(선택)' 입력(원리금균등)
- repayment.js: loanPayment 있으면 이자=잔액×이율, 원금=상환금-이자로 상각
  → 남은 개월수 추정 오차(±1회) 없이 은행 조회액과 일치. 미입력 시 기존 추정

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:08:00 +09:00
sb b039069d6c fix(repayment): 원리금균등 월 상환액을 현재 잔액 기준 상각으로 계산
Deploy / deploy (push) Failing after 12m40s
- 원금 음수·잔액 증가 버그 수정: 최초원금 기준 상환액을 쓰면 잔액>원금 등
  데이터 불일치 시 상환액<이자가 되어 원금이 음수가 되고 잔액이 늘어남
- 남은 개월수(n)는 기존대로 잔액 역산, 상환액은 현재 잔액을 n개월 상각 →
  원금 항상 양수, 잔액 0 수렴. 역산 불가 시 이번 달 이자만(안내)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:53:49 +09:00
sb a42715834b fix(repayment): 원리금균등 남은 회차를 잔액으로 역산 + 라벨 정리
Deploy / deploy (push) Failing after 12m41s
- 기존 대출(loanStart 없음)이 남은 회차=전체(60회)로 나오던 문제 수정:
  현재 잔액과 최초 상환액으로 남은 개월수 역산(날짜 정보 불필요)
- '이번 달 상환' → '이번 달 상환 예정금액', 금액 앞 '이번 달' 제거
- '남은 N회' → '남은 회차 N회'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:43:49 +09:00
sb d40c75d3e5 feat(repayment): 대출·카드할부 상환 조회 화면 (유료)
Deploy / deploy (push) Failing after 14m46s
- 사이드바 '대출·할부 상환' 메뉴(프리미엄 잠금), /account/repayments 라우트
- 대출: 상환방식별(원리금균등·원금균등·만기일시) 남은 회차 상각 스케줄
- 카드 할부: 무이자 가정, 진행 중 할부 건별 남은 회차
- 회차별 원금·이자·합계 + 남은 잔액 표기, 이번 달/남은 총액 합계
- 계산은 프런트(utils/repayment.js), 지갑·카드 내역만으로 산출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:31:43 +09:00
ByungCheol fcd3f23a32 fix(loan): 원리금균등 PMT 자동계산 — 납입금액 수동입력 불필요
Deploy / deploy (push) Failing after 11m34s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 22:24:42 +09:00
sb 1ee264f2ab chore(i18n): 전체 화면 '고정지출' → '정기결제' 문구 변경
Deploy / deploy (push) Failing after 11m5s
- 사이드바·헤더·홈·업그레이드·데모·정기결제/내역 화면 라벨 통일
- 백업 엑셀 시트명(backup.js)은 기존 백업 복원 호환 위해 '고정지출' 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:20:24 +09:00
sb 98ad7b9e98 feat(wallets): 투자계좌는 상세 이동 비활성 — 내역 없음, 목록에 투자금 표기로 충분
Deploy / deploy (push) Failing after 13m55s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:12:35 +09:00
sb b70742b79c feat(wallets): 순서변경 잠금 모드 + 계좌명 클릭 이동 (오작동 방지)
Deploy / deploy (push) Successful in 33s
- 화면이동 화살표(›) 제거 — 삭제 버튼 옆이라 오탭 위험
- 계좌 클릭 대신 '계좌명' 클릭 시에만 상세 이동(링크 색상 표시)
- 순서변경은 기본 잠금 — '순서변경' 버튼으로 켤 때만 드래그 핸들·재정렬 활성
- 순서변경 모드에선 수정/삭제 버튼 숨겨 오작동 방지, 드래그 낙관적 반영

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:08:53 +09:00
ByungCheol 5beb999535 feat(ui): 태그관리 순서변경 토글, 고정지출 월/년 주기만
Deploy / deploy (push) Failing after 11m46s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 22:03:14 +09:00
sb 654fc44f48 style(wallets): 투자계좌 목록 금액 앞 '투자금' 라벨 제거
Deploy / deploy (push) Failing after 15m19s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:01:11 +09:00
sb 061fb47b97 feat(wallets): 계좌 관리 종류별 그룹 목록 + 계좌 상세 화면 분리
Deploy / deploy (push) Failing after 15m10s
- 탭 제거, 종류별(은행/현금/카드/대출/마이너스/투자) 섹션 목록으로 변경
- 계좌 클릭 시 인라인 드롭다운 대신 별도 상세 화면(/account/wallets/:id)으로 이동
- 상세 화면: 계좌 요약 + 월별 내역(월 네비/합계), 투자는 투자금(원금)만 표기
- 목록에서 투자 계좌는 평가액 제거하고 투자금만 표기
- 드래그 순서변경은 종류별 그룹 단위로 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:56:19 +09:00
sb e95e14378b style(recurring): 무료 한도 안내 문구 축약 — 한 줄에 맞게
Deploy / deploy (push) Failing after 11m8s
'무료 2개까지 등록 완료 · 유료 무제한 →' 로 짧게(기존 두 줄 넘침 해소).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:30:00 +09:00
sb f446fb3f03 docs(upgrade): 플랜 비교 현행화 — 고정지출 무료 2건·유료 무제한, 계좌 종류별 2건
Deploy / deploy (push) Failing after 12m34s
무료: 계좌 종류별 2개·고정지출 2개 자동반영. 유료: 계좌·고정지출 무제한.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:28:56 +09:00
sb 4d8881342a feat(recurring): 고정지출 무료 2건 UI — 유료 잠금 해제·한도 안내·업셀
Deploy / deploy (push) Failing after 12m10s
라우터 requiresPremium 제거, 사이드바 🔒 제거, 자동실행을 전 사용자로,
RecurringView 한도 도달 시 추가 버튼 업셀 다이얼로그+힌트(→/upgrade).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:22:45 +09:00
sb c694dd9d04 docs: 화면 밀림 버그 해결 표기 — 원인은 iOS input 자동확대(viewport)
Deploy / deploy (push) Failing after 10m53s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:05:37 +09:00
sb ec130e85a6 fix(ios): input 포커스 시 자동확대로 화면 밀림 — viewport maximum-scale 추가
Deploy / deploy (push) Failing after 14m13s
원인: viewport에 maximum-scale 없어 iOS가 16px 미만 input 포커스 시 페이지 자동확대
→ 모든 화면에서 폼 선택 시 가로로 밀려 잘려 보임. maximum-scale=1,user-scalable=no로 해결.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:02:18 +09:00
sb 1169f3e542 docs: 미해결 - 내역모달 가로 오버플로우, 다음 세션 실측/bottom-sheet 대안 기록
Deploy / deploy (push) Failing after 13m56s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 00:07:34 +09:00
sb 3429ddc235 fix(ui): 내역 모달 min-width:0 — flex 아이템이 내용물만큼 커져 넘치던 근본 원인
Deploy / deploy (push) Failing after 12m42s
backdrop(flex)의 모달 아이템 기본 min-width:auto 가 넓은 그리드만큼 모달을 뷰포트보다
넓혔음. 모달 min-width:0 으로 고정(+칩 min-width:0, overflow-x:hidden 조합)으로 가로 오버플로우 해결.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 00:03:48 +09:00
sb d84101ecda fix(ui): 계좌 칩 min-width:0 — 그리드 아이템이 안 줄어 넘치던 진짜 원인
Deploy / deploy (push) Failing after 11m18s
칩(그리드 아이템)의 기본 min-width:auto(=글자 전체너비)가 minmax(0,1fr)을 무력화해
긴 계좌명이 그리드를 넓혀 모달이 가로로 넘침. 칩에 min-width:0 추가로 축소+…처리.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:55:13 +09:00
sb d7cddba77c fix(ui): 내역 모달 가로 오버플로우 — 계좌 그리드/소분류 입력이 화면 밀어내던 문제
Deploy / deploy (push) Failing after 11m5s
- 모달 overflow-x:hidden(내부 넘침이 페이지를 가로로 밀지 않게)
- ChipSelect min-width:0+box-sizing로 긴 계좌명이 그리드를 넓히지 않게
- cat-input(소분류 추가) flex-wrap로 버튼 잘림 방지
+ 계좌 선택 영역 배경 패널(3열)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:50:26 +09:00
sb e655e79133 style(ui): 계좌 칩 3열 고정 + 박스 배경/테두리 뚜렷하게
Deploy / deploy (push) Failing after 12m6s
모달 배경과 색이 같아 안 보이던 문제 → 회색 틴트+진한 테두리(테마 무관). 한 줄 3개 고정.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:34:24 +09:00
sb 17e7dd53a6 style(ui): 계좌 칩을 균일 박스 그리드로 — 이름 길이 달라도 정렬
Deploy / deploy (push) Failing after 14m4s
ChipSelect를 배경 있는 박스 그리드(auto-fill minmax 120px)로 변경.
자유 줄바꿈으로 들쭉날쭉하던 정렬 해소, 긴 이름은 …+툴팁.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:27:26 +09:00
sb 1f128940f5 fix(recurring): 이체 입금 종류 카드 제외 — AccountView와 일관
Deploy / deploy (push) Failing after 12m30s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:23:26 +09:00
sb 951cbdce34 fix(account): 이체 입금 종류 카드·대출 제외 + 계좌 칩 자동 줄바꿈
Deploy / deploy (push) Failing after 13m59s
- 이체 입금 계좌 종류에서 카드·대출 제외(계좌→카드/대출은 상환)
- 계좌 선택 칩을 고정 2열→자동 줄바꿈(이름 길이/폭에 맞춰 채움, 잘림 없음)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:22:30 +09:00
sb 1b2d6a2c02 fix(account): 출금 계좌 종류 — 카드는 지출일 때만 노출
Deploy / deploy (push) Failing after 15m27s
수입·이체·상환은 카드/대출에서 나가지 않으므로 카드 제외(대출은 이미 제외).
구분 변경 시 허용 안 되는 계좌 종류면 초기화. AccountView·RecurringView 동일 적용.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:16:03 +09:00
sb 810a5d9380 feat(ui): 공통 ChipSelect + 계좌 선택을 칩(2열)으로
Deploy / deploy (push) Failing after 15m12s
select 대체 ChipSelect 컴포넌트(cols 지원) 신설. 계좌 선택 드롭다운을 칩(한 줄 2개)으로 교체:
- AccountView 출금·입금·상환대상 계좌
- RecurringView 출금·입금 계좌
계좌 종류 뱃지로 이미 걸러지고 무료 한도(종류별 2개)라 칩 수도 적당.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:06:17 +09:00
sb e2d9b8ea37 fix(account): 출금 계좌 종류 뱃지에서 대출 제외
Deploy / deploy (push) Successful in 36s
대출은 지출·이체의 출처가 아니라 상환 대상 → 계좌 종류(출금) 선택지에서 대출 숨김.
이체 입금 종류·상환 대상(대출/카드)은 그대로 유지.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:51:46 +09:00
sb cdf94b75cb fix(account): 빠른입력 대출상환 — 금리 있으면 이자/원금 자동분리
Deploy / deploy (push) Failing after 15m15s
금액을 principal 이 아니라 납입금액(loanPaymentAmount)에 넣어 기존 자동분리 로직이 작동하게 함
(toWalletId 리셋 감시자 뒤 nextTick). 금리 미설정/카드는 전액 원금.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:36:15 +09:00
sb e3ab2f9f3c fix(account): 빠른입력 — 계좌→대출/카드 이체는 상환(REPAYMENT)으로 재분류
Deploy / deploy (push) Failing after 11m2s
AI가 TRANSFER로 준 것을, 받는 계좌가 대출/카드면 상환으로 바꿔 폼을 상환모드로 염
(출금=walletId·대상=toWalletId·원금=금액). 이자 있으면 사용자가 분리 입력.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:25:29 +09:00
sb 769aa38b52 feat(account): 빠른입력 이체 폼 반영 — 출금/입금 계좌 자동 채움
Deploy / deploy (push) Failing after 13m5s
applyParsed 가 TRANSFER 처리: type=이체, 분류 비움, wallet→출금(walletId)/toWallet→입금(toWalletId) 매칭.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 22:16:52 +09:00
sb d5b4c46163 feat(stats): 예상 지출 10일부터 노출 — 초반 표본부족 튐 방지
Deploy / deploy (push) Failing after 13m52s
경과일 < 10 이면 값 대신 '10일부터 표시돼요' 안내. 초반 소표본+일회성으로 과대 추정되던 문제 완화.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 02:17:38 +09:00
sb 5b46ba93f3 fix(stats): 예상지출 — 비정기 큰 지출(대출상환 등)을 모두 일평균에서 제외
Deploy / deploy (push) Failing after 14m58s
가장 큰 하루 1건만 제외하던 것을, 중앙값 3배(또는 10만원) 초과 '모든' 날 제외로 변경.
대출상환·다건 일회성이 일평균에 남아 남은 날에 곱해져 예상지출이 과다 추정되던 문제 해결.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 02:01:33 +09:00
sb a52ac6532c docs: 백로그에 포인트 상점(AI 사용량 종량제·계좌 슬롯) 추가
Deploy / deploy (push) Failing after 12m15s
AI 텍스트 월5회/OCR 월3회 무료 + 포인트 추가충전(300P/500P), 계좌슬롯 500P·최대5·영구.
설계·확정필요·견적(~1~1.5일) 기록.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 01:54:17 +09:00
sb 253e88e0fa feat(wallet): 무료 회원 종류별 2개 한도 안내 — 추가 버튼 비활성·힌트
Deploy / deploy (push) Failing after 14m9s
한도 도달 시 추가 버튼 비활성 + '무료 종류별 2개·유료 무제한' 안내, openCreate 가드.
백엔드 400 메시지는 폼 에러로 폴백 노출.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 01:40:41 +09:00
sb 296ae594f8 fix(layout): 앱 셸 고정 높이 — 페이지 전체 스크롤 제거 + 상·하단 고정
Deploy / deploy (push) Failing after 12m26s
.layout 을 100dvh 고정 + overflow hidden, .layout-body 에 min-height:0 로
본문만 세로 스크롤(가로 숨김). 기기별 뷰포트·안전영역 차로 생기던 불필요한
상하좌우 스크롤과 하단 내비 밀림 해결.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 01:19:04 +09:00
ByungCheol ac20174378 build(android): versionCode 4, versionName 1.0.3 — Play Store v4 AAB 빌드
Deploy / deploy (push) Successful in 33s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 17:48:15 +09:00
sb 87c7695cc3 docs: 오늘 작업 현행화 — AI 편의기능·홈 넛지·통계 통합·개근 보너스·iOS 수정
Deploy / deploy (push) Failing after 13m21s
- LAUNCH-PROGRESS.md: 2026-07-05 추가분 반영
- README.md 주요 기능: AI 보조·활동 포인트·통계 현행화

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:28:11 +09:00
sb 9d1ae0e402 feat(home): 인사말을 '오늘 기록 넛지'로 — 습관 유도
Deploy / deploy (push) Failing after 10m46s
'안녕하세요 OO님' 자리를 오늘 기록 상태로 대체:
- 오늘 기록 없음 → 'OO님, 오늘은 아직 기록이 없어요' + 지금 기록하기 CTA
- 오늘 기록 있음 → '오늘 N건 기록했어요' / 연속 2일+ → 'N일 연속 기록 중 🔥'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 16:40:45 +09:00
ByungCheol fb4accdca6 docs: SQL_INIT_MODE=always 적용 완료 — 안내 문구 제거
Deploy / deploy (push) Failing after 13m55s
2026-07-05 16:27:33 +09:00
ByungCheol 03e11cc79c docs: LAUNCH-PROGRESS 충돌 해결 — AI 기능 반영, BudgetOnetime 완료 표기
Deploy / deploy (push) Failing after 11m55s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 16:24:33 +09:00
sb d1dd19d5a3 docs+policy: AI(Anthropic) 데이터 처리 고지 + 런치 문서 현행화
Deploy / deploy (push) Failing after 14m28s
- 개인정보처리방침(terms.js §2·§3): 처리자 Anthropic(Claude) 명시, 전송 데이터 범위·학습 미사용·서버 키보관 고지. OCR은 AI 보조/폴백으로 갱신. 적용일 2026-07-05
- play-store-listing.md 데이터안전: AI 편의 기능 처리 위탁 명시(공유 아님)
- LAUNCH-PROGRESS.md: AI 기능 완료 반영, 가계부 오류 해결 표시, v3/재개지점 정리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 16:15:45 +09:00
sb f3f11fa15b fix(ui): 사이드바 드로어 상단 safe-area 여백 — iOS 상태바 겹침 해결
Deploy / deploy (push) Failing after 11m23s
드로어 최상단(로고/제목)이 iOS 상태바에 가리던 문제. app-sidebar 에 padding-top: env(safe-area-inset-top) 추가.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 16:00:08 +09:00
sb 4c325544a6 fix(account): 모달 열 때 클립보드 자동읽기 제거 — iOS '붙여넣기' 토스트 방지
Deploy / deploy (push) Failing after 13m7s
내역 추가·빠른입력 모달이 열릴 때 navigator.clipboard.readText() 호출로
iOS 시스템 붙여넣기 토스트가 매번 떴음. 자동 읽기/배지 감지 제거(수동 붙여넣기는 유지).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:53:24 +09:00
sb 0090c31294 fix(stats): 예상 지출을 결정적 계산으로 — 매 접속 동일값
Deploy / deploy (push) Failing after 11m2s
일별 지출에서 일회성 큰 지출일(중앙값 3배 초과)을 하루평균 산정에서 제외 →
실제 지출 + 남은 날 × 평소 하루 지출. AI 비결정 값(40만~500만 변동) 문제 해결.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:43:47 +09:00
sb 59a17f9087 feat(stats): AI 코멘트 로딩 인디케이터 바 + 예상지출 '계산 중' 표시
Deploy / deploy (push) Failing after 12m6s
코멘트 생성 지연 동안 애니메이션 진행바와 예상지출 계산 중 상태 노출.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:34:23 +09:00
sb e9e397434f feat(stats): 예상 지출을 AI 추정으로 교체 — 일회성 큰 지출 왜곡 해결
Deploy / deploy (push) Failing after 10m53s
선형 계산(expenseForecast) 제거, AI 코멘트의 forecast/근거 표시. 경과일·총일수 전달.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:29:16 +09:00
sb 9f9fc14c64 refactor(stats): 예산·수입 대비 지출을 '이번 달 현황' 단일 패널로 통합
Deploy / deploy (push) Failing after 11m32s
두 패널을 하나로 합쳐 지출 중복 표기 제거·화면 밀도 완화(도넛+수입/지출 좌우 배치, 모바일 세로).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:19:59 +09:00
sb a9d4ea8410 feat(account): 통계 AI 코멘트 카드에 절약 가이드 섹션 추가
Deploy / deploy (push) Failing after 11m37s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:13:19 +09:00
sb 7affe5c318 feat(account): 통계 화면에 AI 재무 코멘트 카드
Deploy / deploy (push) Failing after 14m16s
집계만 /account/ai-comment 로 보내 코멘트 수신, 상단에 카드 표시(로딩·빈값 처리).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:06:56 +09:00
ByungCheol 5660451f04 docs: LAUNCH-PROGRESS 백로그 정리 (2026-07-05)
Deploy / deploy (push) Failing after 15m25s
완료 항목 v2 이후 추가 섹션으로 이동:
- 게시판 포인트 정책 / 가계부 포인트 / 스팸 자동 블라인드 / 관리자 신고 목록 고도화 / 외화 환율 갱신 시각
백로그 3항목 제거(완료), 다음 세션 재개 지점 업데이트

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 15:01:04 +09:00
ByungCheol c069e70de4 feat(fx): 환율 갱신 시각 표시 및 자동 업데이트 레이블
Deploy / deploy (push) Failing after 13m33s
- 환율 자동조회 후 '방금 업데이트 / N분 전 / N시간 전' 표시
- 30초 간격 ticker로 레이블 자동 갱신
- 폼 열기/통화 변경 시 rateUpdatedAt 초기화

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 14:52:02 +09:00
sb e23a694675 fix(account): 영수증 AI 힌트를 단일 필드로 전송(멀티파트 파트수 제한 회피)
Deploy / deploy (push) Failing after 13m37s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:46:44 +09:00
sb 7e13a42848 feat(account): 영수증 사진 AI 구조화 — 비전 우선, OCR 폴백
Deploy / deploy (push) Failing after 14m23s
영수증 선택 시 Claude Vision(/receipt-ai)으로 금액·상호·날짜·분류·계좌 자동 채움.
AI 실패/미인식 시 기존 Google Vision OCR+규칙파서로 폴백. 결과 배너에 ·분류 표시.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:22:07 +09:00
sb 6a4d01b6e1 feat(account): 자연어 빠른입력 완성 — 분류·계좌 자동추천 + 내역화면 진입점
Deploy / deploy (push) Failing after 11m25s
- applyParsed의 미선언 categoryMajor 참조 버그 수정(응답 처리 실패 원인)
- 빠른입력 모달 버튼 텍스트화 + 안전영역 하단 고정(가림 해결)
- 클립보드 자동입력을 거래성 텍스트일 때만(로컬 판단, 불필요 AI 호출 제거)
- AI 응답의 분류·결제수단을 폼에 자동 매칭(사용자 실제 목록 기준)
- 내역 화면에 ' 빠른 입력' 텍스트 바 진입점 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:10:05 +09:00
ByungCheol 9994456f30 feat(admin): 스팸 감지 항목 관리자 신고 목록 표시
Deploy / deploy (push) Failing after 11m46s
- 스팸 감지 항목에 🤖 스팸감지 뱃지 표시 (🚩 신고 수 대신)
- blockReason(블라인드 사유) 카드에 표시
- 스팸 항목에는 신고 무시 버튼 숨김
- 안내 문구에 스팸 자동 블라인드 정책 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 13:54:41 +09:00
sb 066ada961a feat(ai): 빠른 입력 버튼 라벨 '문자·푸시'→'빠른 입력' (자연어 입력 발견성)
Deploy / deploy (push) Failing after 10m50s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 11:50:39 +09:00
sb a561eccef8 feat(ai): 빠른 입력 모달 — 자연어 입력 안내 (백엔드 AI 파싱 연계)
Deploy / deploy (push) Failing after 11m36s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 11:24:14 +09:00
sb 902b213f60 ci: setup 캐시 제거 — 'Save cache' 후처리 hang 회피
Deploy / deploy (push) Failing after 15m35s
Gitea 캐시 서버 미연결 시 setup-node cache:npm 의 저장 후처리가 수 분 hang →
'job succeeded 인데 계속 실행중 → 타임아웃 가짜 실패' 유발. 캐시 제거로 즉시 종료.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 09:55:57 +09:00
sb 42406b4c75 fix(ui): 내역 추가 모달 닫기(×) 버튼이 상태바에 가려 클릭 안 되던 문제
Deploy / deploy (push) Failing after 14m18s
전체화면 entry 모달의 .close 에 env(safe-area-inset-top) 누락 → 상태바/노치 아래로 내림.
중앙 모달(확인/붙여넣기)에는 미적용(닫기가 다이얼로그 기준이라 불필요).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 22:42:37 +09:00
sb 16e8973532 feat(ios): 구글 로그인 iOS 설정 — GIDClientID + URL Scheme(역순 클라이언트 ID)
Info.plist 에 iOS OAuth 클라이언트 ID 와 리다이렉트 URL 스킴 추가.
plugin initialize 의 web 클라이언트 ID 는 그대로(백엔드 aud 검증).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 22:24:15 +09:00
sb 0d7e9ba4f5 docs: iOS 가이드(IOS.md) — 빌드·실기기 실행·Apple 로그인·safe-area·배포 정리
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 18:48:20 +09:00
sb 1525460149 fix(ui): 헤더 로그인 버튼과 하단 구분선 겹침 — 세로 패딩 추가
Deploy / deploy (push) Failing after 14m23s
- .app-header 및 모바일 미디어쿼리(≤768px) 둘 다 세로 패딩(0.5rem) 적용
  (미디어쿼리가 padding:0 1rem 으로 덮어써 앱에서 효과 없던 문제 포함)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 17:26:54 +09:00
sb cbb3d47bfa fix(ui): iOS safe-area 겹침 수정 — 헤더·하단 내비 콘텐츠 눌림 해결
- border-box 전역에서 고정 height 가 safe-area 패딩을 포함해 콘텐츠 행이 눌리던 문제
- AppBottomNav: height = calc(56px + safe-area-inset-bottom) → 아이콘 행 온전한 56px + 홈인디케이터 분리
- AppHeader: height → min-height → layout-top 상단 safe-area 패딩만큼 헤더가 커지도록

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 17:12:50 +09:00
sb 1cbf47ff87 feat(auth): iOS 애플 로그인 버튼 — Sign in with Apple(App Store 4.8 대응)
- @capacitor-community/apple-sign-in + LoginModal Apple 버튼(iOS 전용, HIG 검은 버튼)
- appleAuth 네이티브 래퍼 + authApi/auth store appleLogin
- SPM capacitor-swift-pm 8.x 로 패치(patch-package) — @capawesome 구글 플러그인과 iOS 공존
- App.entitlements(applesignin) 준비 — Xcode 'Sign in with Apple' Capability 추가 시 사용

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 17:01:43 +09:00
sb a7e5bf9c0f fix(ui): 하단 내비 상단 그림자 추가 — 콘텐츠와 시각적 분리(iOS 탭바 스타일)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 16:32:29 +09:00
sb 9de1949da8 feat(ios): Capacitor iOS 플랫폼 추가 — 시뮬레이터 실행 확인
- @capacitor/ios 추가, cap add ios (SPM, 플러그인 7종)
- Info.plist 카메라·사진 보관함 권한 문구
- 브랜드 앱 아이콘 생성, 번들 ID kr.sblog.slimbudget (안드와 동일)
- iPhone 17 시뮬레이터(iOS 26.5) 빌드·구동 확인

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 16:23:28 +09:00
sb 7861eef05b feat(admin): 신고 관리 화면 — 신고된 글/댓글 목록·블라인드·신고 무시
Deploy / deploy (push) Failing after 14m51s
- /admin/reports 라우트 + 사이드바 메뉴 + 헤더 타이틀
- boardApi.reports()/dismissReport() 추가(기존 block/unblock 재사용)
- 글 보기·블라인드(사유)·해제·신고 무시 액션

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 12:10:06 +09:00
sb a261dd2ca2 docs: README 실제 문서화 + FRONTEND.md 현행화(브랜딩·플랫폼·화면·투자)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 12:10:06 +09:00
ByungCheol ea9fbc359b feat(ui): 입력 모달 전체화면 슬라이드업 + 셀렉트→뱃지 전면 전환
Deploy / deploy (push) Failing after 12m1s
- AppModal.vue: 전체화면 슬라이드업 공통 모달 (Teleport+Transition)
- CategoryPicker.vue: 대/소분류 아코디언 칩 공통 컴포넌트
  admin 모드(일반: 행 단위+수정/삭제, 순서변경: flat+SortableJS)
- AccountView: 계좌종류·구분 셀렉트→뱃지, 분류→CategoryPicker
- RecurringView: 구분·주기 셀렉트→뱃지, 계좌/카드→뱃지, 분류→CategoryPicker
- AccountWalletView: 카드종류·상환방식 셀렉트→뱃지
- BudgetView: 비고정/고정 라디오→뱃지, 카테고리→CategoryPicker
- CategoryView: 리스트 UI → CategoryPicker 칩 그리드 관리 모드

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 01:24:15 +09:00
ByungCheol d364b52c73 fix(account): wallets ref TDZ 오류 수정 — computed 선언 전 초기화되도록 위로 이동
Deploy / deploy (push) Failing after 14m35s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 00:56:14 +09:00
ByungCheol 0b64f4f8cd Merge branch 'dev'
Deploy / deploy (push) Failing after 11m10s
2026-07-02 01:50:09 +09:00
ByungCheol c0e5421c33 fix(account): AccountView.vue 첫 줄 잘못된 '1' 문자 제거
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 01:43:01 +09:00
ByungCheol 282fb7476d Merge branch 'dev'
Deploy / deploy (push) Failing after 12m58s
2026-07-02 00:31:53 +09:00
ByungCheol 1b53af525e feat(account): 대출 실행금액 필드 추가 + 상환방식별 자동계산 분기
CI / build (push) Failing after 14m30s
- loan_amount(대출 실행 금액) 컬럼 추가(DB/도메인/DTO/mapper)
- 계좌 폼: 대출 실행 금액 입력 / 기록 시작 시 잔액(기존 openingBalance) 레이블 분리
- 계좌 카드: 실행금액·금리·상환방식 표시
- 상환 자동계산 3방식 분기:
  - 원리금균등: 납입금액 입력 → 이자(잔액×월이율) / 원금(납입-이자) 분리
  - 원금균등: 실행금액÷기간=월원금, 잔액×월이율=이자 자동계산
  - 만기일시: 잔액×월이율=이자만 자동계산, 원금 0
- 자동계산 후 이자·원금 수동 조정 가능

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 00:31:50 +09:00
ByungCheol 31cb4b7198 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m36s
2026-07-02 00:02:13 +09:00
ByungCheol f8e6c6b0ee feat(account): 대출 계좌 금리/상환방식 저장 + 상환 시 납입금액 자동분리
CI / build (push) Failing after 14m10s
- wallet 테이블에 loan_rate/loan_method/loan_months/loan_start 컬럼 추가(ALTER TABLE)
- 대출 계좌 폼에 연이자율(%), 상환방식(원리금균등/원금균등/만기일시), 기간, 시작일 입력 필드 추가
- 계좌 목록에 금리·상환방식 표시
- 상환 폼: 금리 설정된 대출 계좌 선택 시 납입금액 입력 → 이자/원금 자동계산
  - 원리금균등/원금균등: 잔액 × 연이율/12 = 이자, 납입액-이자 = 원금
  - 만기일시상환: 납입액 전체 이자로 처리
  - 자동계산 후 수동 조정 가능

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 00:02:10 +09:00
ByungCheol f15820f204 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m12s
2026-07-01 00:41:46 +09:00
ByungCheol e1c077dccd fix(account): 소분류 표시 조건 — template 인라인으로 reactivity 보장
CI / build (push) Failing after 14m42s
rowContainsSelected 함수 대신 template에서 row.some(m => m.id === selectedMajorObj.id) 직접 평가.
함수 호출 시 의존성 추적 불안정 문제 해소.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 00:41:43 +09:00
ByungCheol 7965706167 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m16s
2026-07-01 00:33:41 +09:00
ByungCheol 1e9ff0408c feat(account): 내역추가 분류 — 행 삽입형 아코디언 + 대/소분류 추가 버튼 분리
CI / build (push) Failing after 12m46s
- 대분류를 4개씩 행으로 렌더링(majorRows computed)
- 선택된 대분류가 속한 행 바로 아래 소분류 칩 삽입(rowContainsSelected)
- + 소분류 버튼: 소분류 행 내부, 선택된 대분류 하위로 추가
- + 대분류 버튼: 맨 아래, 선택 초기화 후 대분류로 추가
- 입력 placeholder에 추가 맥락 표시(대분류명 > 새 소분류)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 00:32:59 +09:00
ByungCheol 8d1567cb4b Merge branch 'dev'
Deploy / deploy (push) Failing after 11m7s
2026-07-01 00:20:17 +09:00
ByungCheol b2f3b156b2 fix: 분류 소분류 영역 배경색 구분 — 회색 박스로 대분류와 시각적 분리
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 00:19:27 +09:00
ByungCheol 1996c3355e Merge branch 'dev'
Deploy / deploy (push) Failing after 11m28s
2026-07-01 00:14:58 +09:00
ByungCheol 28ebe06767 fix: 분류 선택 UI — 세로 아코디언→가로 칩 그리드로 변경
- 대분류: flex-wrap 그리드(한 줄 4개)로 나열, 분류 수 증가해도 가로로 쌓임
- 대분류 선택 시 소분류가 구분선 아래 동일 그리드로 펼침
- 선택된 항목 초록 테두리·배경 강조

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 00:13:58 +09:00
ByungCheol 37b4a9f4cf Merge branch 'dev'
Deploy / deploy (push) Successful in 40s
2026-07-01 00:07:29 +09:00
ByungCheol afbec5e3c3 fix: 분류 아코디언 화면 교정 — 분류관리 롤백 · 내역추가 적용
- CategoryView(분류 관리): 잘못 적용된 대분류 아코디언 제거, 소분류 항상 표시로 복원
- AccountView(내역 추가/수정): 2단 드롭다운 → 대분류 아코디언으로 교체
  · 대분류 클릭 → 소분류 펼침(없으면 바로 선택)
  · 소분류 클릭 → 즉시 선택 / 선택 항목 초록 하이라이트
  · 수정 진입 시 해당 대분류 자동 펼침 유지

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 23:23:08 +09:00
ByungCheol ee7a1fb471 docs(brand): 백로그에 AI 기능(입력 보조·재무 코멘트) 추가 — 출시 전 적용 희망 2026-06-30 23:02:41 +09:00
ByungCheol daf6b840a9 Merge branch 'dev'
Deploy / deploy (push) Failing after 14m24s
2026-06-30 22:45:34 +09:00
ByungCheol 1a8aa8e13c chore: versionCode 3 / 1.0.2 (v3) + 진행상황 문서 정리
CI / build (push) Failing after 10m53s
- 이번 세션 적용분(외화·월별예산·전체화면모달·아코디언·계좌셀렉트·잠금유예) 정리
- 현재 상태(비공개 테스트 게시·검토통과, 테스터 opt-in 대기)로 갱신

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:45:32 +09:00
ByungCheol e87bc244fe docs(brand): 월별 예산·복사 등 진행상황 갱신 2026-06-30 22:37:09 +09:00
ByungCheol 27d405b154 Merge branch 'dev'
Deploy / deploy (push) Failing after 10m43s
2026-06-30 22:35:52 +09:00
ByungCheol f8369f5108 feat: 예산 월별 분리 + 전월/다음달 복사 버튼
CI / build (push) Failing after 12m14s
- budgets/createBudget에 year/month 전달, copyBudget 추가
- BudgetView: '전월 예산 가져오기' / '다음 달로 복사' 버튼
- 백업 export는 현재 월 예산을 담음

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:35:49 +09:00
ByungCheol 8604fb5030 Merge branch 'dev'
Deploy / deploy (push) Failing after 10m54s
2026-06-30 22:14:05 +09:00
ByungCheol 80d7b8d552 fix: 내역추가 계좌 셀렉트 — 안내문 종류별 표시 + 옵션 이름만
CI / build (push) Failing after 12m23s
- 계좌 선택 안내문을 선택한 종류에 맞게('계좌/카드/대출/증권 선택')
- 계좌·카드 옵션을 '이름 (은행)' → 이름만(없으면 은행/카드사) 표시로 간결화

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:14:02 +09:00
ByungCheol 98172b3c56 Merge branch 'dev'
Deploy / deploy (push) Failing after 10m43s
2026-06-30 22:09:15 +09:00
ByungCheol 764436ebc9 fix: 분류 관리 탭 줄바꿈 — 순서변경 버튼 추가로 '지출분류' 두 줄 깨짐 수정
CI / build (push) Failing after 12m13s
- 탭 텍스트 white-space:nowrap, 탭 영역 flex:none(안 줄어듦)
- tabs-row flex-wrap: 좁으면 액션 버튼이 아래 줄로

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:09:12 +09:00
ByungCheol 8d659ea956 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m53s
2026-06-30 22:06:04 +09:00
ByungCheol 970e9e14ad feat: 내역 추가/수정 전체화면 모달 + 분류 대분류 아코디언
CI / build (push) Failing after 15m22s
- 내역 추가/수정 폼을 전체화면 모달로(바깥 영역 없음·× 로만 닫힘)
  → 입력 중 바깥 실수 터치로 닫히는 불편 해소. PC는 내용 폭 460px 가운데정렬
- 분류 화면: 대분류 삼각형(▶) 토글로 소분류 펼침/접힘(기본 접힘, 개수 뱃지)
  소분류 추가 시 해당 대분류 자동 펼침. 순서변경 모드에선 모두 펼침

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:06:02 +09:00
ByungCheol 46a8cb5515 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m53s
2026-06-30 21:56:06 +09:00
ByungCheol f996075da7 fix: 외화 입력폼 다듬기 — 자동 버튼 높이 일치 + 환율 4자리 반올림
CI / build (push) Failing after 15m22s
- 환율 input과 '↻ 자동' 버튼을 한 행(stretch)으로 묶어 높이 일치
- 자동조회 환율을 소수점 4자리로 반올림(입력폼 step·표시 일관)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:56:04 +09:00
ByungCheol 4c846c5bee Merge branch 'dev'
Deploy / deploy (push) Failing after 10m46s
2026-06-30 21:44:14 +09:00
ByungCheol 5795f328a1 fix: 앱 잠금 유예시간 도입 — 잦은 PIN 입력 불편 해소
CI / build (push) Failing after 12m14s
- 백그라운드 전환 즉시 잠그던 것을, 1시간 이상 백그라운드였을 때만 잠금
- 잠깐 다른 앱(계산기 등) 다녀오는 정도로는 PIN 안 뜸
- 완전 종료/재시작은 onMounted 에서 그대로 잠금
- 카메라/갤러리/공유 suppress 는 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:44:12 +09:00
ByungCheol 4f08b47fc3 docs(brand): 외화 결제 기능 완료 기록 2026-06-30 21:38:19 +09:00
ByungCheol e2d3be29da Merge branch 'dev'
Deploy / deploy (push) Failing after 14m4s
2026-06-30 21:37:26 +09:00
ByungCheol dab6dc3411 feat: 외화 입/지출 입력 — 통화 선택 + 환율 자동조회 → 원화 환산
CI / build (push) Failing after 15m36s
- 입력폼: 통화 드롭다운(KRW 기본 + 15개), 외화면 외화금액+환율 입력
  → 환산 원화 자동 계산(amount). 환율은 /fx/rate 자동조회('↻ 자동') + 수정 가능
- 저장: 외화면 currency/originalAmount/exchangeRate 전송, amount=환산 원화
- 목록 상세에 'USD 10.00 @1,350' 외화 뱃지 표시
- accountApi.fxRate 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:37:24 +09:00
ByungCheol caa591a16d chore: versionCode 2 / 1.0.1 (v2 비공개 테스트 빌드) + 백로그 갱신 2026-06-29 21:42:05 +09:00
ByungCheol 20f67bbc76 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m42s
2026-06-29 21:36:18 +09:00
ByungCheol 971e08282f feat: 분류 순서변경 보호 — 기본 잠금 + '순서변경' 버튼으로만 drag
CI / build (push) Failing after 15m14s
- 스크롤 중 실수로 순서가 바뀌는 것 방지
- 기본은 drag&drop 비활성, '순서변경' 토글 켤 때만 Sortable 생성 + 손잡이(≡) 표시
- 끄면(완료) destroy. 안내 문구도 모드별로 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:36:16 +09:00
ByungCheol f42805f96e docs(brand): 백로그 #6 앱잠금-카메라 버그 수정완료 표시 2026-06-29 20:27:30 +09:00
ByungCheol 8e7955b5d7 Merge branch 'dev'
Deploy / deploy (push) Failing after 14m6s
2026-06-29 20:25:52 +09:00
ByungCheol a0aba1d578 fix: 앱 잠금이 카메라/갤러리/공유 복귀 시 오작동하던 문제 수정
CI / build (push) Failing after 15m40s
- 원인: visibilitychange(숨김)로 백그라운드를 감지하는데 카메라/파일선택/공유
  호출 시에도 웹뷰가 숨겨져 잠금이 걸렸음(복귀 시 PIN 화면)
- appLock.suppressLock(): 네이티브 동작 직전 일시중지 창을 열어 그 복귀는 잠그지 않음
- 파일선택(input[type=file])은 App.vue 전역 클릭 리스너로 일괄 처리
- 카메라(OCR)·공유(백업)는 호출부에서 직접 suppressLock 호출
- 복귀(visible) 시 일시중지 창 해제

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:25:50 +09:00
ByungCheol ce7488076a docs(brand): 백로그에 앱잠금-카메라복귀 PIN 오작동 버그 추가 2026-06-29 17:48:19 +09:00
ByungCheol 2eaf8695b0 docs(brand): 출시 후 업데이트 백로그 추가(업데이트안내·신고글관리·포인트정책·분류순서변경보호) 2026-06-29 09:52:42 +09:00
ByungCheol 2329da1090 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m28s
2026-06-29 09:33:35 +09:00
ByungCheol f23b4963a9 feat: 분류 빈 상태에 '기본 분류 불러오기' CTA 추가
CI / build (push) Failing after 12m57s
- 신규 유저 온보딩(홈 시작하기 → 분류) 흐름이 빈 분류 화면에서 끊기던 문제 보완
- 빈 상태에 이모지·안내 + '기본 분류 불러오기' 버튼(importDefaults) 노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 09:33:31 +09:00
ByungCheol 60794733a1 Merge branch 'dev'
Deploy / deploy (push) Failing after 14m0s
2026-06-29 09:25:59 +09:00
ByungCheol b50a81028f fix: 하단 내비 뒤로 버튼도 하드웨어 뒤로와 동일 동작
CI / build (push) Failing after 15m32s
- 통합 핸들러 appBack.handleBack 추출: 오버레이 닫기 → 화면 뒤로 →
  최상위에서 '한 번 더 누르면 종료' 토스트(네이티브)
- 하단 내비 뒤로 버튼이 옛 로직(홈에서 무반응)이라 하드웨어 뒤로와
  동작이 달랐던 문제 해결 — 둘이 같은 핸들러 사용
- capacitor.js 중복 로직 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 09:25:57 +09:00
ByungCheol 6972cb5a63 docs(brand): 출시 전 다듬기 진행상황 갱신(빈상태·오프라인·앱잠금·에러폴백·뒤로가기) 2026-06-29 09:11:17 +09:00
ByungCheol b39190baa2 Merge branch 'dev'
Deploy / deploy (push) Successful in 36s
2026-06-29 09:09:03 +09:00
ByungCheol bd5f1afd47 feat: 전역 에러 폴백 + 안드로이드 뒤로가기 동작 개선
CI / build (push) Failing after 12m27s
전역 에러 처리(흰 화면 방지):
- app.config.errorHandler + onErrorCaptured → 렌더/라이프사이클 오류 시
  빈 화면 대신 '문제가 발생했어요 + 새로고침' 폴백 화면 표시

안드로이드 뒤로가기 우선순위 개선:
- 잠금 화면이면 우회 불가(종료) → 열린 오버레이(회원가입/로그인/비번/사이드바)
  닫기 → 화면 뒤로 → 최상위에서 '한 번 더 누르면 종료'(실수 종료 방지)
- 종료 안내 토스트(커스텀 이벤트, 플러그인 없이 App.vue 렌더)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 09:09:01 +09:00
ByungCheol 48c3664136 Merge branch 'dev'
Deploy / deploy (push) Failing after 14m22s
2026-06-29 08:45:40 +09:00
ByungCheol bac3ce1ded feat: 앱 잠금(PIN) 추가 — 가계부 보안 강화
CI / build (push) Failing after 10m50s
- 자체 구현(네이티브 플러그인 없음): PIN은 SHA-256 해시만 저장, 기본 OFF 옵트인
- 앱 시작(저장 세션 재진입)·백그라운드 복귀 시 잠금 화면 노출
- 설정에 앱 잠금 토글 + PIN 설정(입력→확인 2단계) 모달
- 갇힘 방지: 잠금 화면에서 'PIN 분실 → 로그아웃'(잠금 해제 후 로그아웃)
- 신규/민감정보 앱 신뢰도 향상

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:45:38 +09:00
ByungCheol 091b42eb81 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m40s
2026-06-29 08:33:22 +09:00
ByungCheol 3f9b0daf53 feat: 오프라인·네트워크 오류 안내 배너 추가
CI / build (push) Failing after 13m9s
- http 인터셉터: 응답 없는 네트워크 오류(오프라인/타임아웃/서버다운) 시
  net:error 이벤트 발생 (취소 요청 제외)
- App.vue: navigator.onLine + online/offline + net:error 구독 →
  상단 고정 배너로 안내(+새로고침). 서버 응답 없음은 5초 후 자동 숨김
- 폐쇄 테스트(다양한 네트워크 환경)에서 무반응 대신 명확한 피드백 제공

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:33:20 +09:00
ByungCheol d5be3c4fcd Merge branch 'dev'
Deploy / deploy (push) Failing after 13m54s
2026-06-29 08:26:05 +09:00
ByungCheol cbe469552d feat: 신규 유저 빈 상태 행동 유도(온보딩) 추가
CI / build (push) Failing after 15m26s
- 홈 대시보드: 데이터 없을 때 '시작하기' 카드(계좌→분류→내역 3단계 안내, 닫기 가능)
- 가계부 내역: 빈 상태를 '첫 내역 추가' CTA로 (필터 결과 없음과 구분)
- 계좌 관리: 빈 상태를 '계좌 추가' CTA로
- 스토어 신규 유저(데이터 0)의 첫인상·활성화 개선

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:26:02 +09:00
ByungCheol 659ea3b2fa docs(brand): Play 스토어 출시 준비 문서·스크린샷 정리
- LAUNCH-PROGRESS.md: 출시 준비 종합 진행 상황 + 남은 로드맵(단일 기준점)
- play-store-listing.md: 등록정보·데이터안전·콘텐츠등급 자료
- production-access-draft.md: 프로덕션 액세스 신청서 초안
- screenshots/: 스토어용 스크린샷 7컷

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:40:19 +09:00
ByungCheol b7d8ede48f Merge branch 'dev'
Deploy / deploy (push) Failing after 15m28s
2026-06-28 20:29:33 +09:00
ByungCheol 7633bdc166 docs: 개인정보처리방침 광고 문구 정합화 (광고 OFF 반영)
CI / build (push) Failing after 11m57s
- 광고를 첫 출시에서 비활성화함에 따라 방침의 광고 관련 문구를
  현재 상태(광고 없음) + 미래형(향후 도입 가능)으로 수정
- 4항: '광고가 표시되며' → '현재 광고 미표시, 향후 도입 시…'
- 수집/목적/제3자(카카오) 항목도 '향후 도입 시' 조건부로 완화
- Play 데이터 안전 양식(광고 식별자 미수집)과 정합성 확보

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:29:31 +09:00
ByungCheol 6bb6a9727d Merge branch 'dev'
Deploy / deploy (push) Failing after 15m31s
2026-06-28 20:24:28 +09:00
ByungCheol 9d60ea8f12 feat: 첫 출시 광고(카카오 애드핏) 비활성화
CI / build (push) Failing after 12m3s
- ADS_ENABLED=false 플래그 추가(features.js)
- 광고단위 미승인 + 데이터 안전 양식 단순화(광고 식별자 수집/제3자 공유 제거)
- AdBanner showAd + App.vue showAds 양쪽 게이팅 → 앱/웹 광고 모두 미노출,
  레이아웃 하단 광고 여백도 함께 제거
- 애드핏 승인 후 true 로 되돌리면 무료 회원 광고 재노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:24:25 +09:00
ByungCheol 5534a2438b Merge branch 'dev'
Deploy / deploy (push) Failing after 12m32s
2026-06-28 19:57:30 +09:00
ByungCheol ee5e9d4adb fix: 둘러보기(데모) 모드에서 메뉴(사이드바) 열기 불가 수정
CI / build (push) Failing after 14m1s
- 사이드바 메뉴 항목은 데모에서도 렌더되지만, 여는 햄버거 버튼이
  로그인 전용(v-if=isAuthenticated)이라 모바일에서 메뉴에 도달 불가했음
- 햄버거 조건에 demo.on 추가 → 데모에서도 가계부/계좌/분류/고정지출 이동 가능

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:57:28 +09:00
ByungCheol 36178572e1 chore(brand): 스토어 그래픽 에셋 생성기 + 산출물
- brand/gen-store-assets.mjs: 1024 마스터에서 스토어 이미지 생성(sharp)
- brand/logo-1024.png: 돈돼지 마스터 아트(1024x1024) 백업
- brand/store/: icon-512, feature-1024x500(텍스트 유/무) — Play Console 업로드용

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:26:10 +09:00
ByungCheol 791aaad1c0 Merge branch 'dev'
Deploy / deploy (push) Failing after 14m19s
2026-06-28 19:20:42 +09:00
ByungCheol a2cda9b4ee feat: 첫 출시 무료 앱 — 인앱결제(구독) 진입점 비활성화
CI / build (push) Failing after 10m51s
- BILLING_ENABLED=false 플래그 추가(features.js)
- 실연동 전 구매 흐름이 테스트 스텁이라, 활성화 시 결제 없이 프리미엄이
  부여되는 문제가 있어 구매 진입점을 모두 숨김
- UpgradeView: 구매 버튼 대신 '구독 곧 출시 예정' 안내
- AccountInfoView: 정기결제 관리 카드 + 업그레이드 링크 미노출
- 무료 기능은 정상 동작, 실 Google Play Billing 연동은 다음 업데이트

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:20:14 +09:00
ByungCheol 6b8001b4ad Merge branch 'dev'
Deploy / deploy (push) Failing after 11m38s
2026-06-28 18:43:24 +09:00
ByungCheol e8d3549f02 feat: 계정삭제 안내 페이지 + 카드알림(알림접근) 첫 출시 비활성화
CI / build (push) Failing after 13m7s
- /account-deletion 공개 페이지(웹 접근) — Play 데이터 삭제 정책 URL, 웹 안내 하단 링크
- CARD_NOTIF_ENABLED=false: 카드알림 자동인식 UI 숨김
- AndroidManifest: CardNotifListenerService 선언 주석 처리(알림접근 정책 회피)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:43:22 +09:00
ByungCheol ddf0074047 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m43s
2026-06-28 18:29:12 +09:00
ByungCheol 4838eba9af chore(ads): 앱 배너를 320x50 모바일 단위(임시)로, 웹은 728x90 유지
CI / build (push) Failing after 14m11s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:29:10 +09:00
ByungCheol 27b8ace100 Merge branch 'dev'
Deploy / deploy (push) Successful in 35s
2026-06-28 18:26:18 +09:00
ByungCheol 29ff09a8d6 feat: 내 글·댓글 모아보기 화면 + 사이드바 메뉴
CI / build (push) Failing after 12m7s
- MyBoardView(탭: 내 글/내 댓글, 페이징, 글로 이동), /my-board 라우트
- 사이드바 게시판 영역에 내 글·댓글 링크, 헤더 타이틀

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:26:16 +09:00
ByungCheol 6cbe1440b3 Merge branch 'dev'
Deploy / deploy (push) Failing after 15m17s
2026-06-28 18:17:13 +09:00
ByungCheol 8be7072614 feat: 댓글 신고 블라인드 관리자 해제 버튼
CI / build (push) Failing after 11m47s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:17:11 +09:00
ByungCheol 077b9598ef Merge branch 'dev'
Deploy / deploy (push) Failing after 11m13s
2026-06-28 18:05:45 +09:00
ByungCheol 59f38b3348 feat: 게시판 신고 UI — 글/댓글 신고 버튼 + 블라인드 표시
CI / build (push) Failing after 12m42s
- 작성자·관리자 외 신고 버튼(글 액션·각 댓글)
- 신고 누적 블라인드 댓글: 비관리자 가림, 관리자만 본문 + 신고 블라인드 뱃지
- 신고 후 새로고침으로 상태 반영

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:05:43 +09:00
ByungCheol 9c932e7258 Merge branch 'dev'
Deploy / deploy (push) Failing after 15m2s
2026-06-28 17:43:27 +09:00
ByungCheol 0fa8c95139 chore(android): local-notifications 플러그인 동기화(cap sync)
CI / build (push) Failing after 11m31s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:43:24 +09:00
ByungCheol 98e413b1a1 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m58s
2026-06-28 17:42:02 +09:00
ByungCheol 127551da12 feat: 로컬 알림 — 가계부 기록 리마인더 + 구독 만료 임박 (앱 전용)
CI / build (push) Failing after 14m29s
- @capacitor/local-notifications 추가, src/native/reminders.js
- 매일 설정 시각(기본 21시) 가계부 미기록 시 알림(오늘 기록하면 오늘자 제외, 7일 롤링)
- 구독 해지 상태 만료 3일 전 알림
- 설정에 알림 받기 토글 + 시간 선택(앱에서만), 권한 요청
- 앱 시작/홈/내역에서 스케줄 보정

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:42:00 +09:00
ByungCheol bcc0c4821f Merge branch 'dev'
Deploy / deploy (push) Failing after 12m13s
2026-06-28 17:29:32 +09:00
ByungCheol fa3b12b369 feat: 계정정보 구매 복원 + 기프트콘 교환(업데이트 예정)
CI / build (push) Failing after 13m41s
- 정기결제 관리에 구매 복원 버튼(billingApi.restore)
- 포인트 옆 기프트콘 교환 항목(업데이트 예정 안내)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:29:30 +09:00
ByungCheol d56a8c75c3 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m59s
2026-06-28 17:17:01 +09:00
ByungCheol db68ea1680 build(android): 릴리스 서명 구성 + AAB - key.properties 기반 서명
CI / build (push) Failing after 14m30s
- key.properties(있으면) 로 릴리스 서명, 없으면 디버그 폴백
- 키스토어/key.properties 는 gitignore (커밋 금지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:16:59 +09:00
ByungCheol aed3f996fc Merge branch 'dev'
Deploy / deploy (push) Failing after 15m24s
2026-06-28 17:09:37 +09:00
ByungCheol e8dc6b5568 feat: 개인정보처리방침/이용약관 공개 페이지 + 보강 (스토어 정책)
CI / build (push) Failing after 11m53s
- terms.js 보강: 구글 로그인·인앱결제·광고·OCR·제3자·탈퇴·문의 반영
- /privacy, /terms 공개 라우트 + LegalView (웹 브라우저에서도 접근 = 앱 마켓 URL)
- 웹 안내 페이지 하단·설정 화면에 정책 링크 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:09:35 +09:00
ByungCheol 8cca35151c Merge branch 'dev'
Deploy / deploy (push) Failing after 15m2s
2026-06-28 16:59:27 +09:00
ByungCheol d243600f41 feat: 계정정보에 회원 탈퇴 — 2단계 확인 후 계정·데이터 삭제
CI / build (push) Failing after 11m31s
- authApi.withdraw(), 강한 경고 + 2차 확인 → DELETE /auth/me → 세션 정리·홈 이동

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:59:25 +09:00
ByungCheol c114e7672b Merge branch 'dev'
Deploy / deploy (push) Successful in 36s
2026-06-28 16:38:08 +09:00
ByungCheol d7639225f7 feat: 카카오 애드핏 광고 — 웹 안내 하단 + 앱 하단내비 위 (유료 제외)
CI / build (push) Successful in 36s
- AdBanner 컴포넌트(애드핏 DAN-n0I5XRBOwAdSFbEX, 728x90), 유료 회원에겐 미표시
- 웹 안내 페이지 하단 배치
- 앱 셸 하단 내비 바로 위 고정 배치(본문 하단 여백 보정)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:38:07 +09:00
ByungCheol 0b8064e569 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m55s
2026-06-28 16:21:04 +09:00
ByungCheol 1000bf06af fix(board): 수정 화면도 게시판별 태그만 — 단, 글이 가진 기존 태그는 보존
CI / build (push) Failing after 15m27s
- 새글·수정 모두 boardApi.tagGroups(category)로 이 게시판 매핑 그룹만 노출
- 수정 시 전체 그룹으로 기존 태그 id 해석 → 이 게시판에 없는 태그는 별도 배지로 보존
- 다른 게시판 태그가 수정 화면에 모두 뜨던 문제 해결

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:14:43 +09:00
ByungCheol 5349cf55f4 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m8s
2026-06-28 16:04:42 +09:00
ByungCheol 821aec72b9 feat: 계정정보 정기결제 관리 — 현황·해지/재개·플랜 변경
CI / build (push) Failing after 13m36s
- 결제 플랜/만료일/다음 결제일 표시
- 구독 해지(안내 후 자동갱신 중단, 만료일까지 이용)/재개
- 결제 가능한 플랜 목록 + 변경/구독(Android 결제, PC는 앱 안내)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:04:41 +09:00
ByungCheol 199b0c84e9 Merge branch 'dev'
Deploy / deploy (push) Successful in 35s
2026-06-28 15:44:42 +09:00
ByungCheol 0dda0b6ccd fix: 게시판 목록 태그 필터를 게시판별로 로드(게시판 전환 시 갱신)
CI / build (push) Failing after 13m31s
- boardApi.tags(category), 게시판 변경 시 태그 필터 재로드
- 매핑된 그룹의 태그만 필터 칩으로 노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:44:40 +09:00
ByungCheol ad219fd0bb Merge branch 'dev'
Deploy / deploy (push) Failing after 12m16s
2026-06-28 15:24:29 +09:00
ByungCheol 09634757f7 style: 태그 관리 - 미매핑 그룹 안내 갱신(미노출), bm-none 표기
CI / build (push) Failing after 13m44s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:24:27 +09:00
ByungCheol 3b542cb33d Merge branch 'dev'
Deploy / deploy (push) Failing after 13m43s
2026-06-28 15:13:01 +09:00
ByungCheol 823bdcafcf feat: 업그레이드 화면 인앱 결제 — Android 구매, PC는 앱 유도
CI / build (push) Failing after 15m11s
- billingApi(products/verifyGoogle), native/billing(테스트 구매 토큰 생성)
- Android 앱: 상품(월간/연간) 구매 버튼 → 검증 → 프로필 갱신
- PC(웹/데스크톱): 구매 대신 앱 다운로드 안내
- 프리미엄 이용 중이면 만료일 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:12:59 +09:00
ByungCheol f0b5f87656 Merge branch 'dev'
Deploy / deploy (push) Failing after 14m5s
2026-06-28 14:50:26 +09:00
ByungCheol ce5cefe64a style: 분류 화면 대분류 대 뱃지 제거 (분류 관리·기본 분류)
CI / build (push) Failing after 15m33s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:50:24 +09:00
ByungCheol 9be60000d9 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m0s
2026-06-28 14:45:01 +09:00
ByungCheol 56d2ad6a8a feat: 계정정보 포인트 클릭 시 적립내역 모달
CI / build (push) Failing after 13m30s
- 포인트 행 클릭 → 적립/차감 내역 모달(사유·일시·증감)
- authApi.pointHistory(), 사유 라벨(BOARD_WRITE=게시판 작성)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:44:59 +09:00
ByungCheol c2499c382a Merge branch 'dev'
Deploy / deploy (push) Failing after 11m31s
2026-06-28 14:05:14 +09:00
ByungCheol 7e096d5e7b refactor: board_setting 제거에 따른 정리 + 수정 시 태그 유실 방지
CI / build (push) Failing after 12m59s
- adminApi getBoardSetting/setBoardSetting 제거
- 글 수정 화면은 전체 그룹 로드(기존 글 태그 유지), 새 글은 게시판 매핑 그룹만

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:05:12 +09:00
ByungCheol bf40cbb254 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m10s
2026-06-28 13:34:42 +09:00
ByungCheol c167875567 feat: 관리자 태그 화면 - 그룹별 게시판 매핑 체크박스
CI / build (push) Failing after 13m38s
- 전역 단일 카테고리 선택 제거 → 각 태그 그룹에 사용 게시판 체크박스(커뮤니티/짠테크/재테크 팁)
- 저장 시 boards 함께 전송, 선택 없으면 전체 게시판
- 글쓰기 화면은 현재 게시판에 매핑된 그룹 태그만 노출(boardApi.tagGroups(category))

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 13:34:40 +09:00
ByungCheol 3eee749ab9 Merge branch 'dev'
Deploy / deploy (push) Failing after 15m5s
2026-06-28 13:21:39 +09:00
ByungCheol 7e38eae2b7 feat: 게시판 인기/블라인드/자기글 차단·헤더 아바타·댓글 새로고침 등
CI / build (push) Failing after 11m35s
- 헤더에 내 아바타(계정정보 링크) + 앱 시작 시 프로필 동기화(재로그인 없이 아바타/포인트)
- 목록: 인기 글 상단 배지(🔥), 비추천 20+ 블라인드 표시
- 상세: 본인 글/댓글 추천·비추천 비활성, 비추천 20+ 글/댓글 블라인드(그래도 보기)
- 댓글: 인기 댓글 배지, 본문 작성자에 작성자 뱃지, 목록 하단 새로고침 버튼(입력폼 위)
- 추천자 목록 가로 줄바꿈(아바타+이름 단위, 이름 안 잘리게)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 13:21:37 +09:00
ByungCheol 8733cf1ee0 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m18s
2026-06-28 12:48:45 +09:00
ByungCheol 7699cfc55f refine(account): 프로필 사진 - 되돌리기 버튼을 삭제로 변경
CI / build (push) Failing after 12m47s
- 구글사진으로/기본으로 버튼 제거, 삭제 버튼으로 단순화
- 등록한 사진 삭제 시 구글 계정 아바타로 표시(없으면 이니셜)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:48:43 +09:00
ByungCheol 6125bf9ad8 Merge branch 'dev'
Deploy / deploy (push) Failing after 14m34s
2026-06-28 12:45:29 +09:00
ByungCheol 1636c35aef refine(board): 목록 추천 열·상세 추천수·추천자 인라인·아바타 축소
CI / build (push) Failing after 11m2s
- 목록: 제목 옆 추천 표시 제거 → 작성일 옆 추천 열로 표기(모바일도 라벨)
- 상세: 메타에 조회수 옆 추천수 표기
- 추천자: 클릭 시 모달 대신 하단에 아바타+이름 목록 펼침(토글)
- 아바타 크기를 본문 폰트 크기에 맞춰 16px 로 축소(목록/상세/댓글/추천자)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:45:27 +09:00
ByungCheol 09a00a1c37 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m15s
2026-06-28 12:36:50 +09:00
ByungCheol 206364d2f1 fix(board): 상세→목록 이동 시 페이지·검색조건 유지
CI / build (push) Failing after 14m43s
- 목록 행 클릭 시 현재 쿼리(page/tag/keyword/searchType)를 상세로 전달
- 상세 목록 버튼·삭제 후 이동도 같은 쿼리로 복귀 → 1페이지 초기화 방지
  (브라우저 뒤로가기는 기존에도 URL 쿼리로 복원됨)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:36:48 +09:00
ByungCheol 1da98b6618 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m57s
2026-06-28 12:31:55 +09:00
ByungCheol ca66c1d53e feat: 게시판 추천/비추천·작성자 아바타·추천자·포인트 (프론트)
CI / build (push) Failing after 13m27s
- UserAvatar 컴포넌트(커스텀>구글>이니셜, 로드 실패 시 이니셜 폴백)
- 목록: 작성자 아바타 + 추천 수 표기
- 상세: 작성자 아바타, 추천/비추천 토글 버튼(내 투표 강조),
  추천자 본문 하단 아바타 10개 + 초과 시 (+N명이 추천했습니다), 클릭 시 전체 목록 모달
- 댓글: 작성자 아바타 + 추천/비추천 버튼
- 계정정보: 포인트 표기(GET /auth/points 최신값)
- boardApi.votePost/voteComment/recommenders, authApi.points 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:28:20 +09:00
ByungCheol 7855298eac feat: 계정정보 프로필 사진 — 구글 아바타 우선 + 변경/되돌리기
- 아바타 표시 우선순위: 사용자 지정(profileImage) > 구글(googlePicture) > 이니셜
- 사진 변경: 이미지 선택 → 정사각형 크롭·192px 축소(JPEG) → PUT /auth/profile-image
- 되돌리기: 사용자 지정 사진 해제 → 구글 사진(없으면 이니셜)으로 폴백
- authApi.updateProfileImage 추가, utils/avatar.js(표시 소스/이니셜/다운스케일)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:39:48 +09:00
ByungCheol 24ab91b257 fix(electron): 중복 실행 방지 + 로딩 스플래시 — 재시작 빈 화면 해소
- 단일 인스턴스 잠금(requestSingleInstanceLock): 두 번째 실행 시 새 창 대신 기존 창 활성화
  (중복 실행 시 라이브 사이트를 다시 로드하며 흰 화면이 오래 보이던 현상 해결)
- 시작 시 전체 캐시 비우기 제거 → index.html 만 캐시버스트(?_=t), 해시 자산은 캐시 유지(빠른 재시작)
- 로드되는 동안 스플래시(로딩) 창 표시, ready-to-show 후 메인 창 노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:25:00 +09:00
ByungCheol 866abee860 fix: 무료 회원 가계부 내역 진입 시 유료 안내로 튕기는 버그
- onMounted 의 runRecurrings(고정지출 자동반영)는 유료 전용 경로(/recurrings/run)라
  무료 회원이 403 → premium:required → /upgrade 로 리다이렉트되던 문제
- isPremium 일 때만 runRecurrings 호출, '이 내역을 고정지출로 등록' 버튼도 유료에만 노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:24:59 +09:00
ByungCheol 2d6af690d4 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m35s
2026-06-28 11:08:46 +09:00
ByungCheol 9861ba90ae 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>
2026-06-28 11:02:44 +09:00
ByungCheol 2755d118c2 fix(electron): 기본 메뉴(File/Edit/View) 전역 제거 — 구글 로그인 팝업 메뉴바 제거
Menu.setApplicationMenu(null) 로 메인·자식 창 모두 메뉴바 비표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 09:40:18 +09:00
ByungCheol 3f809b80bb Merge branch 'dev'
Deploy / deploy (push) Failing after 13m40s
2026-06-28 09:22:51 +09:00
ByungCheol c9b58f4ad0 ci: main 푸시 시 CI 중복 실행 제거(Deploy만) — 배포 시간 단축
CI / build (push) Successful in 38s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 09:22:48 +09:00
ByungCheol d8ab99d1ec Merge branch 'dev'
Deploy / deploy (push) Failing after 13m50s
CI / build (push) Failing after 15m19s
2026-06-28 09:04:27 +09:00
ByungCheol 9aa21696d9 fix: 백업/복구 - 계좌 ID 컬럼 추가, 내역/고정/자주내역을 계좌ID 기준으로
CI / build (push) Failing after 11m48s
- 내보내기: 계좌 시트 ID 컬럼, 내역/고정지출/자주내역에 계좌ID·입금계좌ID
- 가져오기: 계좌ID 기준 파싱 (이름 충돌 무관 정확 복구)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 09:03:24 +09:00
ByungCheol e64de9c247 Merge branch 'dev'
CI / build (push) Failing after 10m48s
Deploy / deploy (push) Failing after 14m20s
2026-06-28 08:38:55 +09:00
ByungCheol 7d8f947156 feat: 설정 - 엑셀 가져오기(복구) + 창 크기 가로·세로 분리 조절
CI / build (push) Failing after 12m18s
- backup.js importBackup: .xlsx 파싱 → 이름 기준 payload → POST /account/restore
- SettingsView: '가져오기' 파일 선택 + 파괴적 작업 확인(이중) + 완료 후 새로고침
- accountApi.restore (데모에선 차단)
- 창 크기: 가로/세로 각각 선택(세로 700~1200), 한 축 변경 시 나머지 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:38:38 +09:00
ByungCheol b8260e9860 Merge branch 'dev'
CI / build (push) Successful in 37s
Deploy / deploy (push) Failing after 10m45s
2026-06-28 08:17:41 +09:00
ByungCheol 457fd8127a feat: 설정 - 엑셀 데이터 내보내기(백업)
CI / build (push) Successful in 44s
- utils/backup.js: 가계부 전체(내역·계좌·분류·고정지출·예산·태그·자주내역)를
  이름 기준 참조로 .xlsx 다중 시트 생성. xlsx 동적 import(별도 청크)
- 웹/PC=다운로드, 앱(네이티브)=Filesystem 저장 + Share
- SettingsView: '데이터 백업' 카드(엑셀로 내보내기 동작 / 가져오기 버튼=준비중)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:14:19 +09:00
ByungCheol 4ffc01f484 feat: 둘러보기(데모)를 실제 화면 그대로 — 데모 모드 + API 더미 응답
- src/demo: demo.on 플래그 + DTO 형태 더미데이터 + mock API(읽기 더미/쓰기 차단)
- accountApi: Proxy 로 데모 시 읽기→더미, 쓰기→안내. 실제 뷰 그대로 렌더
- App: authed=인증||데모 → 실제 사이드바/셸 표시 + 데모 배너(로그인/나가기)
- 라우터: 데모 허용(가계부/고정/계좌/분류) 외 보호화면은 DemoLockedView(로그인 유도)
- 사이드바: 데모 시 잠금(🔒) 배지, 메뉴는 showMenu(인증||데모)
- 로그인/로그아웃 시 데모 자동 해제, 랜딩 '둘러보기'→enterDemo
- 기존 독립 DemoView 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 07:49:37 +09:00
ByungCheol 80e1faec09 docs: 멤버십(무료/유료) 메뉴·기능 구분 설계 (추후 구현)
- 계좌 개수 제한 없음, 핵심 기록 무료
- 유료: OCR·카드알림 자동인식·통계·예산·고정지출·투자·태그·백업

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 00:52:02 +09:00
ByungCheol a6829d4157 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m37s
CI / build (push) Failing after 15m5s
2026-06-28 00:44:57 +09:00
ByungCheol cee5b661e7 feat: 로그인 없이 둘러보기(데모) — 가계부/고정지출/계좌/분류 더미 + 나머지 로그인 유도
CI / build (push) Failing after 11m36s
- DemoView: 자체 메뉴로 섹션 전환, 4개 화면 샘플 데이터,
  통계·예산·태그·커뮤니티는 잠금(로그인 유도) 안내
- 공개 라우트 /demo, 랜딩에 '로그인 없이 둘러보기' 버튼, 헤더 타이틀 '둘러보기'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 00:42:48 +09:00
ByungCheol 54a285a3b6 docs: 변경 내역 갱신 — 구글 로그인·다크모드·UI 개편 (2026-06-28)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 00:12:59 +09:00
ByungCheol a3bdd2e76d Merge branch 'dev'
Deploy / deploy (push) Successful in 35s
CI / build (push) Failing after 12m53s
2026-06-28 00:02:12 +09:00
ByungCheol e4e1be49ca feat: PC 화면 해상도 변경 — 설정에서 창 크기 프리셋 선택
CI / build (push) Failing after 14m21s
- electron/preload.cjs: contextBridge 로 window.desktop.setWindowSize 노출
- main.cjs: preload 연결 + ipcMain 'window:setSize'/'getSize' 핸들러
- SettingsView: PC(window.desktop 존재 시)에서 해상도 선택 추가
  (1024×720 ~ 1920×1080), 선택 시 창 크기 변경+가운데 정렬

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:57:41 +09:00
ByungCheol f34980048b feat: 계정정보 단순화 + 구글 계정 이름 변경 허용
- 계정정보: 아이디·가입유형 행 제거 (이름/이메일/권한만), '이름 변경' 버튼
- ProfileEdit: 소셜(구글) 계정은 비밀번호 재인증 없이 바로 이름만 변경
  (아이디/이메일 편집/비밀번호 변경 섹션은 LOCAL 전용으로 숨김)
- 백엔드 updateProfile 은 provider 무관하게 동작(변경 없음)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:53:32 +09:00
ByungCheol 85670a1b9f fix: PC 레이아웃 — 사이드바를 좌측 상단까지 확장, 헤더는 우측 영역만
- App.vue 그리드 'top top' → 'left top' (사이드바가 헤더 행까지 차지)
- 사이드바 브랜드 영역 높이 56px 로 헤더와 정렬(보더 일치)
- 모바일은 기존 드로어 구조 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:49:02 +09:00
ByungCheol 5bfa45ed30 feat: 자주 쓰는 내역 UX 정리 + 빠른등록 견고화
- 등록 모달의 '자주 쓰는 내역 불러오기' 버튼·피커 모달 제거(번거로운 3단계 해소)
  (저장 기능은 유지)
- 가계부 quick-bar 에 ' 자주 쓰는 내역' 라벨 추가
- useQuick: 등록 성공과 목록 갱신 분리 — 갱신 단계 오류가 '등록 실패'로
  오인되던 문제 방지(앱에서 등록 안 됨처럼 보이던 케이스 대응)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:43:57 +09:00
ByungCheol c4c58d2e24 fix: 월 네비 가운데 유지·액션 우측 정렬 + 검색 모달 버튼 넘침 수정
- 가계부/예산 month-nav: 좌 spacer + 우 mn-actions(flex:1)로 네비 중앙 고정,
  추가/검색·필터 버튼 우측 정렬
- 커뮤니티 검색 모달: input min-width:0 + select/버튼 flex:none 으로
  검색 아이콘이 모달 밖으로 밀리는 문제 해결

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:35:24 +09:00
ByungCheol 4e03bf6393 Merge branch 'dev'
Deploy / deploy (push) Failing after 14m4s
CI / build (push) Failing after 15m32s
2026-06-27 23:19:30 +09:00
ByungCheol 59b17051a4 fix: 화면별 액션 아이콘 위치 정리(타이틀 제거 후 빈 헤더 해소)
CI / build (push) Failing after 12m2s
- 가계부: 내역 추가 → month-nav(검색·필터 옆), 헤더는 pending 있을 때만
- 예산: 예산 추가 → month-nav(월 변경 옆)
- 고정지출: 지금 반영/추가 → 안내문구 아래
- 커뮤니티: 글쓰기 → 태그 필터와 같은 줄(list-top)
- 분류 관리: 기본 분류 불러오기 → 지출/수입 탭과 같은 줄(tabs-row)
- 미사용 boardTitle/boardLabel 정리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:18:42 +09:00
ByungCheol 639a50f8bc Merge branch 'dev'
Deploy / deploy (push) Failing after 13m5s
CI / build (push) Failing after 14m33s
2026-06-27 23:00:17 +09:00
ByungCheol c4872c93a8 feat: 페이지 타이틀 제거(헤더만 표기) + 게시글 상세 목록아이콘/공지 체크박스화
CI / build (push) Failing after 11m0s
- 각 화면 내 <h1> 페이지 타이틀 제거 → 헤더 타이틀로 일원화
  (가계부 pending 카운트는 유지, 액션 헤더는 우측 정렬)
- 게시글 상세: 공지 등록/해제 버튼 제거 + '목록' 아이콘 추가(list 아이콘 신규)
- 글 작성/수정 공지 체크박스로 일원화(편집 시 기존값 로드)
- ProfileEdit/AccountInfo 화면 내 뒤로가기 제거, 미사용 router 정리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:59:20 +09:00
ByungCheol 6fe7f8b168 Merge branch 'dev'
Deploy / deploy (push) Failing after 10m58s
CI / build (push) Failing after 12m25s
2026-06-27 22:27:17 +09:00
ByungCheol b737720d29 fix: 전 화면 본문 폭 통일 + 화면 내 뒤로가기 버튼 제거
CI / build (push) Failing after 13m55s
- 본문 폭: --page-max(760px) 변수 + .layout-body>* 전역 규칙으로 일관화
  (각 뷰가 제각각 두던 root max-width 대체)
- 화면 내 '뒤로가기/가계부로/목록' 버튼 제거(하단 내비 뒤로가기는 유지)
  대상: 계좌/분류/예산/고정지출/태그/기본분류/게시글상세/계정정보
- BoardDetail actions 우측 정렬, AccountInfo 미사용 goBack 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:26:25 +09:00
ByungCheol 75228cbb64 feat: 사이드바 브랜드/헤더 타이틀 + 라이트·다크 테마 설정
1) 사이드바 상단에 돼지 로고 + '돈돼지 가계부'('메뉴' 텍스트 대체, 데스크톱/모바일 공통)
2) 헤더에서 돼지 로고 제거 → 현재 화면 타이틀 표시(라우트별)
3) 설정에 화면 테마(시스템/라이트/다크) 선택 추가
   - base.css: data-theme 기반 수동 다크 + 시스템 설정 폴백
   - theme.js 유틸 + ui 스토어 theme 상태, main.js 시작 시 적용(깜빡임 방지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:15:27 +09:00
ByungCheol 63b4792e58 fix: 웹 안내문 줄바꿈을 절 단위로 (inline-block 묶음)
- 'PC는 설치형 앱으로,' / '모바일은 앱으로 이용해요.' 등 절 경계에서만 줄바꿈

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:08:51 +09:00
ByungCheol f07cb349e2 feat: 게시판 공지 UI (관리자) — 작성 체크박스/상세 토글/목록 배지
- BoardWriteView: 관리자가 커뮤니티 새 글 작성 시 '공지로 등록' 체크박스
- BoardDetailView: 관리자 공지 등록/해제 버튼 + 제목 공지 배지
- BoardListView: 공지 배지 + 행 강조(최상단은 백엔드 정렬)
- boardApi: setNotice/unsetNotice

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:07:03 +09:00
ByungCheol ef1d718fb8 feat: 통계 분류별 집계를 대분류 기준으로 (소분류 펼침 제거로 스크롤 단축)
- catByMajor: 소분류를 부모 대분류로 합산해 파이/범례 표기
- 중복되던 '대분류 합계' 섹션 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:38:07 +09:00
ByungCheol a92063dd64 chore: PC 설치를 사용자 폴더로 변경 (UAC 프롬프트 제거)
- installer.nsh: 기본 설치경로를 %LOCALAPPDATA%\Programs\DonDwaeji 로
  (perMachine=false 와 함께 관리자 권한 불필요 → '디바이스 변경 허용' UAC 안 뜸)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:09:34 +09:00
ByungCheol a86d77b2c5 Merge branch 'dev'
CI / build (push) Failing after 11m24s
Deploy / deploy (push) Failing after 14m56s
2026-06-27 20:48:38 +09:00
ByungCheol 6aa3590c5a chore: PC 설치파일명 DonDwaeji-Setup.exe + 기본 설치경로 DonDwaeji
CI / build (push) Failing after 12m51s
- electron-builder artifactName: SlimBudget-Setup → DonDwaeji-Setup
- build/installer.nsh: 기본 설치경로를 $PROGRAMFILES\DonDwaeji 로 고정
  (productName 한글이라 name='sb_pt' 로 폴백되던 문제 회피)
- WebOnlyNotice 다운로드 URL 도 DonDwaeji-Setup.exe 로 변경

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:47:46 +09:00
ByungCheol 4916d28604 fix: 웹 안내 페이지 한국어 줄바꿈(word-break: keep-all) — 'O요.' 고아줄 방지
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:34:24 +09:00
ByungCheol d87dd0f56b Merge branch 'dev'
Deploy / deploy (push) Failing after 12m5s
CI / build (push) Failing after 13m36s
2026-06-27 20:16:26 +09:00
ByungCheol dbe255cf1c feat: 사이드바 메뉴에 아이콘 추가 (lucide 스타일 인라인 SVG)
CI / build (push) Failing after 15m5s
- 가계부/게시판/관리자 각 메뉴 항목에 18px 아이콘, 하단 내비와 톤 통일
- .menu-item flex 정렬, 활성 항목 아이콘 강조

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:56:01 +09:00
ByungCheol 579ee3e4e6 feat: 게시판 작성일 1일 이후 표기 YYYYMMDD → YYYY.MM.DD
- formatRelative: 1시간 이내 N분 전 / 1일 이내 N시간 전 / 그 이상 YYYY.MM.DD
- formatYmdDot 헬퍼 추가(기존 formatYmd 컴팩트 형식은 유지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:51:49 +09:00
ByungCheol 11ab23ed14 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m52s
CI / build (push) Failing after 13m19s
2026-06-27 19:41:42 +09:00
ByungCheol 8254460751 feat: 구글 로그인 전용 — 아이디 로그인 폼·회원가입 UI 숨김
CI / build (push) Failing after 14m50s
- src/config/features.js ID_LOGIN_ENABLED 플래그(기본 false)로 게이트
- LoginModal: 아이디/비번 폼·'또는' 구분선·회원가입 링크 숨김(구글 버튼만)
- HomeView: 랜딩 회원가입 버튼 숨김, 안내문구 정리
- 백엔드 /api/auth/login·signup 은 비상용으로 유지(플래그 true 로 복구 가능)
- LoginModal.spec: 아이디 로그인 플로우는 플래그 mock 으로 계속 검증

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:41:04 +09:00
ByungCheol 82b08df826 Merge branch 'dev'
Deploy / deploy (push) Successful in 35s
CI / build (push) Failing after 12m53s
2026-06-27 19:02:09 +09:00
ByungCheol b8c1440e4d fix: 앱 헤더 로고만(모바일)·런처 라벨 한 줄·Electron 구글 로그인 팝업
CI / build (push) Successful in 39s
- AppHeader: 좁은 화면(폰)은 브랜드명 텍스트 숨기고 로고만 → 두 줄 줄바꿈 방지
- 안드로이드 런처 라벨 '돈돼지가계부'(한 줄), 앱 내부 브랜드명은 유지
- Electron: accounts.google.com 팝업을 앱 내부 자식 창으로 열어
  GIS 로그인 결과(credential)가 앱 창으로 돌아오게 함(외부 브라우저 X)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:01:57 +09:00
ByungCheol a5c81cc0f2 Merge branch 'dev'
CI / build (push) Failing after 11m57s
Deploy / deploy (push) Failing after 15m29s
2026-06-27 18:03:39 +09:00
ByungCheol a310c7a471 feat: 구글 로그인/가입 — 웹(GIS)·앱(네이티브 Credential Manager)
CI / build (push) Failing after 13m30s
- LoginModal: 서버에서 구글 클라이언트 ID 수신 시에만 버튼 노출
  - 웹/PC: Google Identity Services 렌더 버튼
  - 앱(안드로이드): @capawesome/capacitor-google-sign-in 네이티브 버튼
- 네이티브/웹 모두 ID 토큰을 /api/auth/google 로 전달(aud=웹 클라이언트 ID)
- auth 스토어/authApi 에 googleLogin·googleClientId 추가
- src/native/googleAuth.js 네이티브 래퍼 신규

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 18:02:19 +09:00
ByungCheol 091abd40be Merge branch 'dev'
Deploy / deploy (push) Failing after 13m6s
CI / build (push) Failing after 14m35s
2026-06-27 16:51:30 +09:00
ByungCheol 1ca09b7439 feat: 앱명을 '돈돼지 가계부'로 통일 (가계부 인식 + 마스코트)
CI / build (push) Failing after 11m38s
- 헤더·웹안내·홈·약관 서비스명·푸터/설정 저작권·타이틀 → 돈돼지 가계부
- productName/capacitor appName/안드로이드 app_name·창제목/Electron app.setName·window title → 돈돼지 가계부
- 기술 식별자(appId, 설치파일명)는 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:51:29 +09:00
ByungCheol 38484c51a6 Merge branch 'dev'
CI / build (push) Failing after 11m23s
Deploy / deploy (push) Failing after 14m55s
2026-06-27 16:29:42 +09:00
ByungCheol 4ef5850613 fix: 아이콘 배경 회색박스 제거 — 원본 체커판(불투명) 키잉해 진짜 투명 돼지로
CI / build (push) Failing after 13m27s
- 저장된 piggy-fg.png 는 투명이 아니라 체커판이 회색/흰색으로 박힘 → 헤더·파비콘·PC에 회색 박스
- 회색/흰색 배경 키잉 + 돼지 본체 bbox 크롭 → assets/piggy-src.png(투명)
- 파비콘·헤더·PC ico = 투명 + 돼지만, 안드로이드는 금색 배경 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:29:42 +09:00
ByungCheol 1c8d9c427c Merge branch 'dev'
Deploy / deploy (push) Failing after 12m51s
CI / build (push) Failing after 14m20s
2026-06-27 16:21:43 +09:00
ByungCheol d706339649 feat: 파비콘·PC 아이콘 금색배경(외곽선) 제거 → 투명 + 돼지 크게
CI / build (push) Failing after 11m23s
- favicon.png / apple-touch-icon.png / build/icon.ico: 투명 배경 + 돼지 ~95%
- 안드로이드: 금색 배경 유지(적응형 특성) + 전경/단일 돼지 크게
- 헤더 로고도 동일 재생성

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:21:43 +09:00
ByungCheol 383977bcb5 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m6s
CI / build (push) Failing after 14m37s
2026-06-27 16:16:26 +09:00
ByungCheol dc62d9883a feat: 헤더 로고를 '돈돼지' 이미지로 변경
CI / build (push) Failing after 11m36s
- AppHeader brand-mark(초록 막대 SVG) → public/logo-piggy.png(투명 돼지)
- 텍스트는 SlimBudget 유지, 비율 유지(height 28 / width auto)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:16:26 +09:00
ByungCheol e455e8b3a8 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m48s
CI / build (push) Failing after 14m16s
2026-06-27 16:06:49 +09:00
ByungCheol e4d7df2554 fix(electron): 설치파일 손상 방지 — win.icon 을 표준 .ico(≤256)로 지정
CI / build (push) Failing after 11m20s
- 1024px PNG 자동변환 ico(1024 프레임)가 일부 환경에서 손상 인식되던 문제
- build/icon.ico(16~256px, png-to-ico) 생성해 win.icon 으로 지정

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:06:48 +09:00
ByungCheol 25b5938ab8 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m23s
CI / build (push) Failing after 13m52s
2026-06-27 15:52:11 +09:00
ByungCheol 4e4c28bafe feat: 앱 아이콘·파비콘을 '돈돼지' 이미지로 변경
CI / build (push) Failing after 10m56s
- piggy-fg.png(투명 전경)에서 icon-foreground/background(금색)/only 합성
- @capacitor/assets generate --android 로 안드로이드 런처/적응형 아이콘 재생성
- 파비콘: public/favicon.png·apple-touch-icon.png 생성, index.html 갱신(구 logo.svg→png)
- PC(Electron) 설치 아이콘은 icon-only.png 사용(설치파일 재빌드 시 반영)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 15:52:11 +09:00
ByungCheol b4b8833fc1 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m16s
CI / build (push) Failing after 14m44s
2026-06-27 15:31:20 +09:00
ByungCheol 0eb3eaaa67 feat: 영수증·문자푸시 버튼 같은 행 + 문자/푸시 붙여넣기 모달화
CI / build (push) Failing after 11m48s
- 영수증 스캔·문자·푸시 버튼을 상단 한 행(rb-actions)에 나란히, 안내문구는 아래
- 문자·푸시 붙여넣기를 하단 인라인 → 모달로(텍스트영역+감지+분석·채우기)
- 팝업 열 때 클립보드 자동감지 시 버튼에 ● 배지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 15:31:20 +09:00
ByungCheol 02290413b2 Merge branch 'dev'
CI / build (push) Failing after 11m51s
Deploy / deploy (push) Failing after 15m20s
2026-06-27 15:19:14 +09:00
ByungCheol 5a5959a26e feat: 자주 쓰는 내역 불러오기 모달 피커 + 붙여넣기 버튼 영수증 행으로
CI / build (push) Failing after 13m52s
- 불러오기: 셀렉트 → 모달 리스트(시인성↑), 항목 탭하면 폼 채움
- 문자·푸시 붙여넣기 버튼을 영수증 스캔과 같은 행(우측)으로 이동, 감지/수동입력은 아래로 분리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 15:19:13 +09:00
ByungCheol c8c774f060 Merge branch 'dev'
Deploy / deploy (push) Failing after 10m37s
CI / build (push) Failing after 12m5s
2026-06-27 15:08:59 +09:00
ByungCheol eeea19e20b fix: 자주 쓰는 내역 저장 안내를 수정 모달 안에 표시
CI / build (push) Failing after 14m9s
- 페이지 상단 flash → 모달 내 formInfo(성공 메시지)로 변경(모달에 가려 안 보이던 문제)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 15:08:59 +09:00
ByungCheol 0f56db744c Merge branch 'dev'
Deploy / deploy (push) Successful in 36s
CI / build (push) Failing after 11m54s
2026-06-27 14:59:09 +09:00
ByungCheol ca803272c4 fix: 신규 추가는 '자주 쓰는 내역 불러오기', 수정은 '저장'으로 분기
CI / build (push) Failing after 14m0s
- 신규 추가 폼: 자주 쓰는 내역 선택해 폼 채우기(불러오기) 셀렉트
- '자주 쓰는 내역으로 저장' 버튼은 수정(editId) 시에만 노출

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 14:59:09 +09:00
ByungCheol b5d6204a2d Merge branch 'dev'
Deploy / deploy (push) Failing after 12m0s
CI / build (push) Failing after 13m32s
2026-06-27 14:50:11 +09:00
ByungCheol 8e651c786f feat: 자주 쓰는 내역 칩(바로 등록) + 문자/푸시 붙여넣기 자동채움
CI / build (push) Failing after 15m32s
- 가계부 상단 자주 쓰는 내역 칩: 탭하면 오늘 날짜로 즉시 등록(+안내), 편집 모드로 삭제
- 내역 추가/수정 폼: ' 자주 쓰는 내역으로 저장' 버튼(수입/지출)
- 추가 팝업 열 때 클립보드 자동감지 배너 + '📋 문자·푸시 붙여넣기' 버튼
  (실패 시 수동 붙여넣기 입력칸) → 구분/금액/메모/날짜 자동 채움
- accountApi: parseText / quickEntries CRUD

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 14:50:10 +09:00
ByungCheol 67ac385e26 Merge branch 'dev'
Deploy / deploy (push) Failing after 10m39s
CI / build (push) Failing after 12m8s
2026-06-24 23:03:52 +09:00
ByungCheol dd17fae5f4 feat: 기본 분류 — 관리자 설정 화면 + 사용자 '기본 분류 불러오기'
CI / build (push) Failing after 14m11s
- 관리자 전용 기본 분류 관리 화면(DefaultCategoryView, 계층 드래그) + 라우트/사이드바 링크
- adminApi 기본분류 CRUD/reorder
- CategoryView '기존 분류 불러오기' → '기본 분류 불러오기'(관리자 정의 분류 복사)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 23:03:52 +09:00
ByungCheol 2e48570876 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m58s
CI / build (push) Failing after 13m26s
2026-06-24 21:57:50 +09:00
ByungCheol f533df0024 feat: 네이티브 alert/confirm/prompt → 공용 인앱 다이얼로그 (PC 'sb_pt' 제거)
CI / build (push) Failing after 15m30s
- composables/dialog + components/ui/AppDialog(App 루트 마운트)
- window.alert 전역 오버라이드로 모든 alert 인앱화
- confirm/prompt(동기)는 각 호출부를 await dialog.confirm/prompt 로 교체
  (가계부·예산·고정지출·계좌·태그·관리자·게시판·설정 — 삭제/열람제한 등)
- 웹/PC/APK 일관 UI, 의미있는 제목 부여

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:57:50 +09:00
ByungCheol 40b517ad10 Merge branch 'dev'
CI / build (push) Successful in 43s
Deploy / deploy (push) Failing after 13m2s
2026-06-24 21:31:39 +09:00
ByungCheol 643bb8011b fix: 분류 관리 — 삭제 확인 인앱 모달 / 행별 대분류 select 제거 / 추가옵션 대분류명만
CI / build (push) Failing after 11m34s
- 삭제·불러오기 네이티브 confirm('sb_pt' 제목) → 인앱 확인 모달로 교체, 오류도 인라인 표시
- 각 행 아래 '대분류' 선택 박스 제거(불필요) — 계층은 추가 폼·드래그로 관리
- 추가 폼 대분류 옵션 'OOO의 소분류' → 'OOO'(대분류명만)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:31:39 +09:00
ByungCheol f9aca601b6 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m37s
CI / build (push) Failing after 14m10s
2026-06-23 21:02:09 +09:00
ByungCheol 8add41d94f fix: 고정지출 등록 확인 인앱 모달 + 분류 관리 계층 드래그
CI / build (push) Successful in 36s
1. PC 네이티브 confirm 제목('sb_pt') 노출 → '고정지출 등록' 인앱 확인 모달로 교체
   (성공 시 토스트 안내). electron app.setName('Slim Budget')로 다른 네이티브
   대화상자 제목도 개선(다음 Electron 빌드 시 반영).
2. 분류 관리 드래그를 계층형으로: 대분류는 블록 단위(소분류 함께) 이동,
   소분류는 그 대분류 안에서만 이동(고유 group). 로컬 즉시반영으로 스냅백 방지.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:02:09 +09:00
ByungCheol 9f8d9bd7b5 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m36s
CI / build (push) Failing after 14m4s
2026-06-22 23:57:19 +09:00
ByungCheol cad1aba0ca feat: 가계부 분류를 대분류→소분류 2단 선택으로 변경
CI / build (push) Failing after 11m9s
- 단일 긴 드롭다운(대분류 많으면 스크롤 과다) → 대분류 선택 후 소분류 선택
- 자식 없는 대분류는 그 자체가 분류(소분류 select 숨김)
- 편집 진입 시 현재 분류로부터 대분류 자동 추론, 구분 변경 시 초기화
- 인라인 분류 추가는 선택된 대분류의 소분류로 생성

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:57:19 +09:00
ByungCheol a25fef65f8 Merge branch 'dev'
CI / build (push) Failing after 11m45s
Deploy / deploy (push) Failing after 15m17s
2026-06-22 23:49:39 +09:00
ByungCheol 27beaf28d4 fix: 분류 관리 행 가로 오버플로우 — 이름+버튼 윗줄 / 대분류 select 아랫줄
CI / build (push) Failing after 13m50s
- 좁은 화면에서 버튼이 화면 밖으로 밀리던 문제 해결
- 행을 2단(이름·저장·삭제 / 대분류 선택)으로 분리, 버튼 sm 크기
- 추가 폼도 대분류 선택은 윗줄, 이름+추가는 아랫줄로 분리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:49:39 +09:00
ByungCheol 5abe6cfc09 Merge branch 'dev'
CI / build (push) Failing after 11m45s
Deploy / deploy (push) Failing after 15m17s
2026-06-22 23:39:38 +09:00
ByungCheol f55443b823 fix: 폼 셀렉트 높이를 input·버튼과 통일 (font 상속 리셋)
CI / build (push) Successful in 36s
- 네이티브 select 가 폰트 미상속으로 input 과 높이 달라 보이던 이질감 해소
- 전역 input/select/textarea { font: inherit } + select 기본 padding/border 정렬
- CategoryView 대분류 select 폰트 오버라이드 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:39:37 +09:00
ByungCheol d5ea6f048e Merge branch 'dev'
Deploy / deploy (push) Failing after 13m27s
CI / build (push) Failing after 14m56s
2026-06-22 23:16:46 +09:00
ByungCheol 6ab8d331e3 feat: 분류 대/소분류 UI — 관리·입력·예산·고정지출·통계
CI / build (push) Failing after 12m0s
- 분류 관리: 대분류 선택·소분류 들여쓰기(드래그 정렬 유지)
- 입력/예산/고정지출 분류 드롭다운 대분류 optgroup 그룹핑(값은 소분류명)
- 대시보드: 파이는 소분류 기준 + 대분류 합계 롤업 표시
- 릴리스 노트 33

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:16:46 +09:00
ByungCheol 74017243bb Merge branch 'dev'
Deploy / deploy (push) Failing after 11m25s
CI / build (push) Failing after 12m54s
2026-06-22 22:38:22 +09:00
ByungCheol b1f85cee52 feat: 통계·가계부 UX 개선 + 계좌 탭 한 줄
CI / build (push) Failing after 14m57s
- 통계: 순자산 추이 주간(최근 3개월), 이번달 예상 지출(run-rate), 지난달 대비 지출
- 가계부: 내역→고정지출 바로 등록, 날짜별 접기/펴기(기본 접힘·오늘만 펼침·모두 토글)
- 계좌 관리 탭 한 줄(넘치면 가로 스크롤)
- 릴리스 노트 갱신(23~32)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:38:22 +09:00
ByungCheol ca6c8be229 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m27s
CI / build (push) Failing after 13m1s
2026-06-22 21:53:22 +09:00
ByungCheol 8cc9cbb383 fix(android): 키보드(IME)가 입력폼 가리는 문제 — WebView를 키보드 위로 리사이즈
CI / build (push) Failing after 14m59s
- MainActivity 인셋 리스너에 IME 인셋 반영: 하단 패딩 = max(시스템바, 키보드 높이)
  → 키보드가 뜨면 WebView가 키보드 위로 줄어들어 입력 모달이 재배치됨
- AndroidManifest activity에 windowSoftInputMode=adjustResize 추가
- 모달은 기존 max-height+overflow로 스크롤되어 모든 필드 확인 가능
- 네이티브 전용(웹/PC 무영향) → APK 재빌드 필요

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:53:22 +09:00
ByungCheol f54db96a30 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m41s
CI / build (push) Failing after 13m12s
2026-06-20 17:13:26 +09:00
ByungCheol 43b01da1ff docs: 고정지출 안내 — '확인 필요'로 생성 후 확정 안내
CI / build (push) Failing after 15m14s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 17:13:26 +09:00
ByungCheol 1ae5f7ec69 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m7s
CI / build (push) Failing after 12m34s
2026-06-14 15:53:56 +09:00
ByungCheol 8133963f89 feat: 이체에도 현금 지원 — ATM 출금(은행→현금) 등 현금 계좌 이체 가능
CI / build (push) Failing after 14m41s
- WALLET_KINDS에 현금 추가, 중복되던 별도 '현금' 라디오 제거
- 출금/입금 종류 모두에서 현금 선택 가능(이체 포함)
- 현금 계좌 선택 셀렉트 상시 노출, 수입/지출 현금은 '미지정'(무계좌) 허용 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 15:53:56 +09:00
ByungCheol 5f99fb3b5c Merge branch 'dev'
Deploy / deploy (push) Failing after 11m7s
CI / build (push) Failing after 12m36s
2026-06-14 15:43:45 +09:00
ByungCheol a4a4aec314 feat: 가계부·계좌관리에 현금 계좌 추가
CI / build (push) Failing after 14m41s
- 계좌 관리: '현금' 탭 + 폼(이름·초기 잔액, 발급사/계좌번호 등 미노출)
- 가계부 입력: 현금 선택 시 등록된 현금 계좌를 고를 수 있음(없으면 종전처럼 미지정)
- 고정 지출: 현금 종류 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 15:43:45 +09:00
ByungCheol a56b8265c7 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m57s
CI / build (push) Failing after 13m25s
2026-06-12 22:52:34 +09:00
ByungCheol 9aa5f5d690 feat: 상환/납부 연회비 입력(카드 대상 시) + 원금/이자 안내
CI / build (push) Failing after 15m31s
- 대상이 카드일 때만 연회비 입력란 노출(repayTargetIsCard)
- 원금=이체·이자/연회비=지출 안내 placeholder, 검증 '원금·이자·연회비 중 하나'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 22:52:34 +09:00
ByungCheol 2582bbb59b Merge branch 'dev'
Deploy / deploy (push) Failing after 11m39s
CI / build (push) Failing after 13m7s
2026-06-12 22:33:01 +09:00
ByungCheol 9f079fcead docs: 릴리스 노트 갱신 — 투자 단순화·알림 입출금 분류·광고차단 강화·분류 학습·상환 수정
CI / build (push) Failing after 15m13s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 22:33:01 +09:00
ByungCheol eaa5c33d2c Merge branch 'dev'
Deploy / deploy (push) Failing after 13m12s
CI / build (push) Failing after 14m46s
2026-06-12 22:06:20 +09:00
ByungCheol d88854d11b fix: 상환/납부를 수정(편집) 시에도 선택 가능하게
CI / build (push) Failing after 11m45s
- 구분 드롭다운의 '상환/납부'가 신규 등록에서만 보이던 제약(v-if="!editId") 제거
- 수정 중 상환/납부로 전환 시: 상환 2건(원금=이체, 이자=지출) 생성 후
  원본 1건 삭제 — 생성 성공 후 삭제 순서로 중간 실패 시 원본 보존
- openEdit에서 principal/interest 초기화(이전 값 잔존 방지)
- 잘못 입력한 단순 이체/지출을 원금·이자 분리 상환으로 정정 가능

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 22:06:16 +09:00
ByungCheol 76b86712ef Merge branch 'dev'
Deploy / deploy (push) Successful in 34s
CI / build (push) Failing after 13m41s
2026-06-09 23:12:40 +09:00
ByungCheol dba0741cca feat: 투자 계좌를 투자금·평가액 입력형으로 단순화 (종목 관리 제거)
CI / build (push) Successful in 34s
- 종목(매수/매도/현재가) 관리 UI(InvestPortfolio) 제거 — 퇴직연금·연금처럼
  투자금(투입원금)과 현재 평가액만 입력하면 손익·수익률 자동 계산
- 계좌 펼침: 종목 포트폴리오 → 투자금/평가액/손익 요약 카드
- 폼: '현재 평가액'을 기본 입력 필드로, 안내 문구 정리
- 투자 탭 진입 시 자동 시세갱신 로직 제거(불필요)
- 백엔드 변경 없음(평가액 직접입력 경로 그대로 사용)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 23:12:22 +09:00
ByungCheol 73895edca6 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m55s
CI / build (push) Failing after 13m24s
2026-06-09 22:50:23 +09:00
ByungCheol 9807472474 feat: 알림 입금 인식 + 미확인 수입 기본 은행계좌
CI / build (push) Successful in 34s
- 네이티브 알림 필터에 입금/이체/송금 키워드 추가(입금 알림도 수집)
- 미확인(pending) 내역: 수입(입금)이면 기본 계좌종류 은행, 지출이면 카드

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:50:23 +09:00
ByungCheol 162768d030 Merge branch 'dev'
CI / build (push) Failing after 10m43s
Deploy / deploy (push) Failing after 14m17s
2026-06-07 21:05:47 +09:00
ByungCheol 2cde893d55 docs: 릴리스 노트 갱신 — 데스크톱 클라이언트·브랜드 아이콘·캘린더 팝업·월별 내역·카드 매칭·이미지 업로드·에디터 팝업
CI / build (push) Successful in 34s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:05:47 +09:00
ByungCheol 89eca761b5 Merge branch 'dev'
CI / build (push) Failing after 11m29s
Deploy / deploy (push) Failing after 15m2s
2026-06-07 21:00:01 +09:00
ByungCheol c67c2ba7ed fix: 모바일 에디터 팝업 좌측 잘림 — Toast UI margin-left:-150px 취소
CI / build (push) Failing after 13m37s
- Toast UI 가 max-width:480px 에서 popup 에 margin-left:-150px 를 줘 좌측으로 밀려 잘렸음
- 중앙 모달 규칙에 margin:0 !important 추가 → 표/링크 팝업도 화면 중앙에 온전히 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:00:00 +09:00
ByungCheol bbe6e93130 Merge branch 'dev'
Deploy / deploy (push) Failing after 10m34s
CI / build (push) Failing after 12m0s
2026-06-07 20:49:28 +09:00
ByungCheol 7276d878dd fix: 모바일에서 에디터 팝업(표/링크 등) 화면 중앙 모달로 — 좌측 잘림 해결
CI / build (push) Failing after 14m9s
- 좁은 화면(댓글창 등)에서 Toast UI 팝업이 좌측으로 넘쳐 OK/입력칸이 잘리던 문제
- @media(max-width:768px)에서 .toastui-editor-popup 을 fixed 중앙 모달로(항상 보임)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 20:49:28 +09:00
ByungCheol bb6621fae5 Merge branch 'dev'
Deploy / deploy (push) Successful in 38s
CI / build (push) Failing after 15m16s
2026-06-07 20:16:14 +09:00
ByungCheol 2e2c784f4c fix: 에디터 이미지 버튼 = 바로 업로드(팝업 제거) — 댓글창 OK 잘림 해결
CI / build (push) Failing after 12m20s
- Toast UI 기본 이미지 팝업(File/URL/Description/OK)이 작은 댓글창/하단 위치에서 잘려
  OK 를 못 누르던 문제 → 커스텀 툴바 이미지 버튼으로 교체(클릭 시 바로 파일선택→업로드→삽입)
- 서버 업로드(URL) 그대로

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 20:16:14 +09:00
ByungCheol 6b7955e098 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m19s
CI / build (push) Failing after 12m45s
2026-06-07 20:03:44 +09:00
ByungCheol f7f75b7fb1 revert: 게시판 에디터를 Toast UI 위지윅으로 통일(PC 포함)
CI / build (push) Failing after 14m53s
- PC 전용 textarea/썸네일 방식 제거 — 글 흐름(이미지 아래 설명 등)이 불편하다는 피드백 반영
- 모든 플랫폼 위지윅(이미지 인라인). PC 한글 첫 자음 중복은 감수(tui.editor Windows IME 업스트림 버그)
- 서버 이미지 업로드(addImageBlobHook)는 유지 — 화면 변화 없는 개선(base64 미사용)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 20:03:44 +09:00
ByungCheol 868f889505 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m3s
CI / build (push) Failing after 14m30s
2026-06-07 19:57:00 +09:00
ByungCheol f45014f827 feat: PC 에디터 이미지를 첨부(썸네일)로 분리 — 본문 텍스트에 URL 미노출 + 썸네일 확대
CI / build (push) Failing after 11m37s
- 이미지 마크다운(![](url))을 본문 textarea 에서 분리해 별도 첨부 목록으로 관리
  · 저장 시 본문 끝에 합쳐 emit, 편집 로드 시 본문/이미지 분리
  · 썸네일 120px 확대 + 제거(×) 버튼
- 텍스트는 깨끗하게(긴 URL 미노출), 이미지는 썸네일로 확인

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:57:00 +09:00
ByungCheol 5f23f5f0b8 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m43s
CI / build (push) Failing after 14m9s
2026-06-07 19:47:20 +09:00
ByungCheol cbc6245cc7 feat: PC 에디터 이미지 썸네일 — 편집창 안에서 올린 이미지 확인
CI / build (push) Successful in 35s
- 본문의 ![](url) 이미지를 파싱해 툴바 아래 썸네일 줄로 표시(서버URL·데이터URI 모두)
- 미리보기 탭/패널 제거(한글 IME 위해 textarea 유지), 썸네일로 업로드 확인

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:47:19 +09:00
ByungCheol 20210f6716 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m16s
CI / build (push) Failing after 13m43s
2026-06-07 19:17:44 +09:00
ByungCheol bfba296b91 feat: 에디터 이미지 = 서버 업로드(URL) + 업로드 안내 메시지
CI / build (push) Successful in 35s
- PC 에디터: 🖼 → 압축 → 서버 업로드 → 본문에 ![](/api/images/{id}) 삽입(base64 제거)
- Toast UI(웹/APK): addImageBlobHook 으로 동일하게 서버 업로드(base64 대체)
- 업로드 중/실패 안내 메시지 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:17:44 +09:00
ByungCheol 3862c33690 Merge branch 'dev'
CI / build (push) Failing after 11m44s
Deploy / deploy (push) Failing after 15m18s
2026-06-07 18:14:40 +09:00
ByungCheol 0238e0b22f feat: PC 마크다운 에디터에 이미지 추가(파일 선택 → 압축 → base64 임베드)
CI / build (push) Failing after 13m52s
- 이미지 버튼: 파일 선택 → imageToBlob(1280px·JPEG0.85)로 압축 → ![alt](data:...) 삽입
- 웹 에디터처럼 본문에 임베드(별도 서버 저장 없음), 단 압축으로 용량 절감

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:14:40 +09:00
ByungCheol 1d9a2a584e Merge branch 'dev'
Deploy / deploy (push) Failing after 11m33s
CI / build (push) Failing after 12m59s
2026-06-07 18:08:23 +09:00
ByungCheol b4aa5c2ad0 feat: PC용 툴바 마크다운 에디터 (한글 IME 정상)
CI / build (push) Successful in 34s
- Toast UI 는 Windows IME 와 충돌(업스트림 버그)하므로 PC(Electron)에선 textarea 기반
  마크다운 에디터 사용: 툴바(제목·굵게·기울임·취소선·인용·목록·체크·링크·코드·구분선) + Write/Preview 탭
- textarea(v-model)라 한글 IME 정상. 웹/APK 는 Toast UI 위지윅 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:08:23 +09:00
ByungCheol 28182cc324 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m38s
CI / build (push) Failing after 15m4s
2026-06-07 17:56:20 +09:00
ByungCheol 9c61f888a5 fix: PC(Electron) 본문 입력을 일반 textarea로 — 한글 IME 중복 완전 해결
CI / build (push) Failing after 12m11s
- Toast UI 위지윅·마크다운(CodeMirror) 둘 다 Electron 한글 IME 첫 자음 중복 발생
  (제목 등 일반 input 은 정상) → PC 에선 본문을 일반 textarea(v-model, 네이티브 IME)로 입력
- 웹/APK 는 기존 Toast UI 위지윅 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:56:20 +09:00
ByungCheol 3e1410a522 Merge branch 'dev'
CI / build (push) Failing after 10m40s
Deploy / deploy (push) Failing after 14m14s
2026-06-07 17:50:43 +09:00
ByungCheol 7f6a4c62e5 fix: PC(Electron) 위지윅 한글 IME 중복 → 에디터를 마크다운 모드로 전환
CI / build (push) Failing after 12m48s
- 진단: 제목(일반 입력)은 정상, 본문 위지윅(contenteditable)만 첫 자음 중복 → ProseMirror+Electron IME 버그
- MarkdownEditor: Electron 감지 시 initialEditType='markdown'(텍스트 기반 입력) → IME 정상. 웹/APK는 위지윅 유지
- 앞서의 불필요한 조합(composition) 처리 코드 제거(원인이 아니었음)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:50:43 +09:00
ByungCheol 8a9badb01d Merge branch 'dev'
CI / build (push) Successful in 34s
Deploy / deploy (push) Failing after 11m39s
2026-06-07 17:43:18 +09:00
ByungCheol 561c56a8e0 fix: PC(Electron) 한글 IME 첫 글자 중복 입력 수정
CI / build (push) Failing after 15m13s
- webPreferences spellcheck:false — Chromium 맞춤법 검사기가 한글 조합을 방해해
  첫 자음이 중복('ㅅ사')되던 Electron 알려진 버그 회피 (APK/모바일은 정상이라 데스크톱 전용 이슈)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:43:17 +09:00
ByungCheol ecd850f468 Merge branch 'dev'
CI / build (push) Failing after 10m58s
Deploy / deploy (push) Failing after 14m32s
2026-06-07 17:30:26 +09:00
ByungCheol f86f8343ef fix: 게시판 에디터 한글 IME 첫 자음 중복 입력 수정
CI / build (push) Failing after 13m5s
- MarkdownEditor: IME 조합(compositionstart~end) 중에는 getMarkdown/emit 보류,
  조합 완료 시 한 번만 반영 → 조합 중 직렬화로 자음이 중복되던('ㅅ사') 문제 해소
- 조합 중에는 watch 의 setMarkdown 도 건너뜀

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:30:26 +09:00
ByungCheol 9d15644180 Merge branch 'dev'
CI / build (push) Failing after 11m4s
Deploy / deploy (push) Failing after 14m39s
2026-06-07 17:25:21 +09:00
ByungCheol dc31cf4bfa fix: 데스크톱 캐시로 인한 최신 화면 미반영 해결 + HTML no-cache
CI / build (push) Failing after 13m13s
- electron/main.cjs: 시작 시 HTTP 캐시 클리어 → 라이브 사이트 최신 프론트 항상 로드
  (루트 '/' HTML 이 캐시돼 옛 번들이 뜨던 문제 — '로그인 전 사이드바' 미반영 원인)
- nginx: location / 에 Cache-Control no-cache 추가(웹/PWA 진입 HTML 항상 최신)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:25:21 +09:00
ByungCheol 3fb0e36413 Merge branch 'dev'
Deploy / deploy (push) Failing after 12m11s
CI / build (push) Failing after 13m41s
2026-06-07 17:17:42 +09:00
ByungCheol 0f9f7b4507 feat: PC 로그인 전 빈 사이드바 제거(중앙 정렬) + 데스크톱 창 크기 기억
CI / build (push) Failing after 10m45s
- App.vue: 비로그인 시 사이드바 숨김·단일 컬럼 그리드 → 콘텐츠가 창 중앙 정렬
  (프론트 변경이라 데스크톱은 재설치 없이 자동 반영)
- electron/main.cjs: 기본 창 1024x720 + 마지막 크기/위치/최대화 상태 저장·복원

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 17:17:42 +09:00
ByungCheol 32522ed512 Merge branch 'dev'
CI / build (push) Failing after 11m57s
Deploy / deploy (push) Failing after 15m30s
2026-06-07 16:54:28 +09:00
ByungCheol 786f7aae61 feat: 웹 안내 페이지에 PC 버전(Windows) 다운로드 링크 추가
CI / build (push) Failing after 14m5s
- WebOnlyNotice: PC 버전 다운로드 버튼(/download/SlimBudget-Setup.exe) + 모바일(준비 중)
- nginx: /download/ 위치 추가 — 배포(dist 교체)에 안 지워지는 /var/www/sb-downloads 에서 서빙
- 설치파일 이름 고정(SlimBudget-Setup.exe)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 16:54:28 +09:00
175 changed files with 14979 additions and 2129 deletions
+3 -2
View File
@@ -1,8 +1,9 @@
name: CI
on:
# main 은 Deploy 워크플로가 테스트+빌드를 수행하므로 CI 중복 실행 제외(배포 시간 단축).
push:
branches: [main, dev]
branches: [dev]
pull_request:
branches: [main, dev]
@@ -17,7 +18,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
# cache 제거: Gitea 캐시 서버 미연결 시 'Save cache' 후처리가 수 분 hang → '가짜 실패' 유발
- name: Install dependencies
run: npm ci
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
# cache 제거: Gitea 캐시 서버 미연결 시 'Save cache' 후처리가 수 분 hang → '가짜 실패' 유발
- name: Install
run: npm ci
+58 -31
View File
@@ -1,44 +1,71 @@
# sb_pt
# 돈돼지 가계부 — 프론트엔드 (sb_pt)
This template should help get you started developing with Vue 3 in Vite.
Vue 3 + Vite 기반의 가계부·자산·게시판 앱 프론트엔드. 웹 / 안드로이드(Capacitor) / Windows 데스크톱(Electron) 3개 플랫폼을 하나의 코드베이스로 빌드합니다.
## Recommended IDE Setup
- 서비스명 **돈돼지 가계부** (구 Slim Budget)
- 운영: [app.sblog.kr](https://app.sblog.kr) · 안내/정책 페이지 [www.sblog.kr](https://www.sblog.kr)
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Recommended Browser Setup
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
## 기술 스택
- **Vue 3** (`<script setup>` Composition API) + **Vite**
- **Pinia** (상태관리), **Vue Router** (라우팅 + 인증 가드)
- **Axios** (공통 인스턴스 + 토큰/401 인터셉터)
- **Toast UI Editor** + Prism.js (게시판 마크다운 작성/뷰어)
- **Capacitor** (안드로이드 하이브리드 앱), **Electron** (Windows 데스크톱)
- **Vitest** + `@vue/test-utils` (테스트)
## 빠른 시작
```sh
npm install
npm run dev # 개발 서버 (전체 앱 표시 — 웹 접근 제한 예외)
```
- Node: `^20.19.0 || >=22.12.0`
- API 베이스 URL: `VITE_API_BASE_URL` (기본 `/api`, Nginx 가 백엔드 8080 으로 프록시)
### Compile and Hot-Reload for Development
## 주요 스크립트
| 명령 | 설명 |
|---|---|
| `npm run dev` | 개발 서버 (HMR) |
| `npm run build` | `dist/` 정적 빌드 (웹 배포용) |
| `npm run build:app` | Capacitor 모드 빌드 (`--mode capacitor`, 앱 전용 env) |
| `npm run cap:sync` | 앱 빌드 + `cap sync` (안드로이드 동기화) |
| `npm run cap:android` | Android Studio 열기 |
| `npm run electron:dev` | Electron 데스크톱 실행 |
| `npm run electron:build` | Windows 설치파일(`DonDwaeji-Setup.exe`) 빌드 |
| `npm test` | Vitest 실행 |
| `npm run lint` | oxlint + eslint (`--fix`) |
```sh
npm run dev
```
## 플랫폼별 동작
- **웹(PC·모바일)**: 안내 페이지만 노출(앱 다운로드 유도). 실제 이용은 앱에서. (`npm run dev` 는 예외로 전체 앱 표시)
- **안드로이드**: Capacitor WebView 패키징. 빌드 전 `.env.capacitor` 의 백엔드 절대 URL 설정 필수 → [docs/ANDROID.md](docs/ANDROID.md)
- **Windows**: Electron 이 라이브 사이트(app.sblog.kr)를 직접 로드 → 프론트 변경이 재설치 없이 반영
### Compile and Minify for Production
## 인증
- 로그인 시 발급된 **Bearer 토큰**을 `localStorage` 에 저장, `http.js` 인터셉터가 `Authorization` 헤더에 주입
- **구글 로그인**(웹/PC=GIS, 앱=네이티브 플러그인) — 현재 소셜 로그인 전용(아이디/비번 폼은 `features.js` 플래그로 숨김)
- 401 응답 시 토큰 정리 후 로그인 모달, 라우트 `meta.requiresAuth` / `requiresAdmin` 가드
```sh
npm run build
```
## 주요 기능
- **가계부** — 수입·지출·이체 기록, 검색·필터, 날짜 그룹 접기/펴기, 외화 입력(환율 자동환산)
- **AI 보조** — 자연어 한 줄 입력("어제 스타벅스 5천원"), 영수증 사진 구조화, 분류·계좌 자동 추천, 통계 화면 재무 코멘트·절약 가이드 (Claude Haiku, 집계만 전송·저장은 사용자 확인 후)
- **계좌·자산** — 은행·현금·카드·대출·투자 계좌, 순자산 자동 집계
- **고정 지출** — 반복 규칙 자동 생성 + '확인' 확정 흐름
- **통계** — '이번 달 현황'(예산·수입 대비 통합), 분류별 분석, 순자산 추이, 월말 예상 지출(일회성 큰 지출 제외 결정적 계산), 지난달 대비
- **예산** — 월별 카테고리 예산, 예산 대비 진행률
- **활동 포인트** — 가계부·게시판 활동 적립, 한 달 개근(모든 날 기록) 랜덤 보너스
- **게시판** — 커뮤니티/짠테크/재테크 3개 게시판, 댓글·태그·이미지 업로드·공지
- **테마** — 시스템/라이트/다크 모드
### Lint with [ESLint](https://eslint.org/)
## 문서
| 문서 | 내용 |
|---|---|
| [docs/FRONTEND.md](docs/FRONTEND.md) | 아키텍처·디렉터리 구조·화면 명세 |
| [docs/release-2026-06-06.md](docs/release-2026-06-06.md) | 누적 변경 이력 (릴리스 노트) |
| [docs/ANDROID.md](docs/ANDROID.md) | 안드로이드(Capacitor) 빌드·배포 |
| [docs/plan-membership-tiers.md](docs/plan-membership-tiers.md) | 무료/유료 티어 설계 (미구현) |
| [deploy/README.md](deploy/README.md) | 프론트 배포(Nginx + Gitea Actions) |
| [brand/LAUNCH-PROGRESS.md](brand/LAUNCH-PROGRESS.md) | Play 스토어 출시 진행 상황·로드맵 |
```sh
npm run lint
```
## 배포
- `main` 푸시 → Gitea Actions 가 CI(빌드·테스트) + Deploy(Nginx 업로드·reload) 실행
- **테스트 게이트**: CI·배포 워크플로 모두 테스트 실패 시 배포 중단
- 상세: [deploy/README.md](deploy/README.md)
+5
View File
@@ -99,3 +99,8 @@ app/src/main/assets/public
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml
# 릴리스 서명 (절대 커밋 금지)
*.jks
*.keystore
key.properties
+22 -2
View File
@@ -1,5 +1,13 @@
apply plugin: 'com.android.application'
// 릴리스 서명: android/key.properties 가 있으면 사용, 없으면 디버그 서명으로 폴백.
def keystorePropsFile = rootProject.file("key.properties")
def keystoreProps = new Properties()
if (keystorePropsFile.exists()) {
keystoreProps.load(new FileInputStream(keystorePropsFile))
}
def hasReleaseKeystore = keystorePropsFile.exists()
android {
namespace = "kr.sblog.slimbudget"
compileSdk = rootProject.ext.compileSdkVersion
@@ -7,8 +15,8 @@ android {
applicationId "kr.sblog.slimbudget"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
versionCode 6
versionName "1.0.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -16,8 +24,20 @@ android {
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
signingConfigs {
if (hasReleaseKeystore) {
release {
storeFile rootProject.file(keystoreProps['storeFile'])
storePassword keystoreProps['storePassword']
keyAlias keystoreProps['keyAlias']
keyPassword keystoreProps['keyPassword']
}
}
}
buildTypes {
release {
// 키스토어가 있으면 릴리스 서명, 없으면 디버그 서명(로컬 테스트용)
signingConfig hasReleaseKeystore ? signingConfigs.release : signingConfigs.debug
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
+4
View File
@@ -11,7 +11,11 @@ apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-camera')
implementation project(':capacitor-filesystem')
implementation project(':capacitor-local-notifications')
implementation project(':capacitor-preferences')
implementation project(':capacitor-share')
implementation project(':capawesome-capacitor-google-sign-in')
}
+6 -1
View File
@@ -13,6 +13,7 @@
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -28,7 +29,10 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
<!-- 카드 결제 알림 자동인식: 알림 접근 리스너 서비스 -->
<!-- 카드 결제 알림 자동인식(알림 접근 리스너) — 스토어 첫 출시에서는 비활성화.
Google Play NotificationListenerService(알림 접근) 정책 심사 회피용으로 선언 제거.
추후 정당화/선언 후 재활성화하려면 아래 service 블록 주석을 해제할 것. -->
<!--
<service
android:name=".CardNotifListenerService"
android:label="@string/app_name"
@@ -38,6 +42,7 @@
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
-->
</application>
<!-- Permissions -->
@@ -50,15 +50,17 @@ public class CardNotifListenerService extends NotificationListenerService {
}
/**
* 결제 알림 후보 판별: 금액(####원) + 결제/승인/출금 키워드.
* '카드'는 광고에도 흔해 제외. 강한 승인신호(승인/출금/취소/환불) 없이 광고성 표현이 있으면 제외.
* (1차 필터 — 최종 판별 백엔드 파서)
* 결제/이체 알림 후보 판별: 금액(####원) + 승인/결제/출금/입금/이체/취소 키워드.
* '카드'는 광고에도 흔해 제외. 강한 신호 없이 광고성 표현이 있으면 제외.
* (1차 필터 — 최종 판별/분류는 백엔드 파서)
*/
private boolean looksLikePayment(String s) {
boolean money = s.matches("(?s).*\\d[\\d,]{2,}\\s*원.*");
boolean kw = s.contains("승인") || s.contains("결제") || s.contains("출금");
boolean kw = s.contains("승인") || s.contains("결제") || s.contains("출금")
|| s.contains("입금") || s.contains("이체") || s.contains("송금");
if (!money || !kw) return false;
boolean strong = s.contains("승인") || s.contains("출금") || s.contains("취소") || s.contains("환불");
boolean strong = s.contains("승인") || s.contains("출금") || s.contains("입금")
|| s.contains("이체") || s.contains("송금") || s.contains("취소") || s.contains("환불");
boolean adLike = s.matches("(?s).*(이벤트|광고|혜택|쿠폰|할인|적립|포인트|캐시백|마일리지|리워드|당첨|응모|추첨|프로모션|배너|무료|증정|모집).*");
return strong || !adLike;
}
@@ -23,7 +23,11 @@ public class MainActivity extends BridgeActivity {
final View content = findViewById(android.R.id.content);
ViewCompat.setOnApplyWindowInsetsListener(content, (v, windowInsets) -> {
Insets bars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(bars.left, bars.top, bars.right, bars.bottom);
Insets ime = windowInsets.getInsets(WindowInsetsCompat.Type.ime());
// 키보드(IME)가 올라오면 그 높이만큼 하단 패딩 → WebView가 키보드 위로 줄어든다.
// (입력 모달이 키보드에 가려지지 않고, 포커스된 입력칸이 보이도록 재배치됨)
int bottom = Math.max(bars.bottom, ime.bottom);
v.setPadding(bars.left, bars.top, bars.right, bottom);
return WindowInsetsCompat.CONSUMED;
});
ViewCompat.requestApplyInsets(content);
@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>
@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 809 B

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 720 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 B

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 474 B

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 B

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 575 B

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 778 B

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 36 KiB

+2 -2
View File
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">Slim Budget</string>
<string name="title_activity_main">Slim Budget</string>
<string name="app_name">돈돼지가계부</string>
<string name="title_activity_main">돈돼지가계부</string>
<string name="package_name">kr.sblog.slimbudget</string>
<string name="custom_url_scheme">kr.sblog.slimbudget</string>
</resources>
+12
View File
@@ -8,5 +8,17 @@ project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/
include ':capacitor-camera'
project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android')
include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')
include ':capacitor-local-notifications'
project(':capacitor-local-notifications').projectDir = new File('../node_modules/@capacitor/local-notifications/android')
include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
include ':capacitor-share'
project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android')
include ':capawesome-capacitor-google-sign-in'
project(':capawesome-capacitor-google-sign-in').projectDir = new File('../node_modules/@capawesome/capacitor-google-sign-in/android')
+5 -9
View File
@@ -1,9 +1,5 @@
import sharp from 'sharp'
import { readFileSync } from 'node:fs'
const files = ['icon-only', 'icon-foreground', 'icon-background']
for (const f of files) {
const svg = readFileSync(`assets/${f}.svg`)
await sharp(svg, { density: 384 }).resize(1024, 1024).png().toFile(`assets/${f}.png`)
console.log('wrote assets/' + f + '.png')
}
// 앱 아이콘은 이제 '돈돼지' 래스터 이미지(assets/piggy-fg.png) 기반입니다.
// 재생성: piggy-fg.png → sharp 합성으로 icon-foreground/background/only.png 만든 뒤
// `npx @capacitor/assets generate --android` 로 안드로이드 아이콘 생성.
// (구 SVG 래스터화는 더 이상 사용하지 않음 — 실행해도 PNG 를 덮어쓰지 않도록 비활성화)
console.log('skip: icons are raster (piggy). use piggy-fg.png + @capacitor/assets generate')
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

+198
View File
@@ -0,0 +1,198 @@
# 돈돼지 가계부 — Play 스토어 첫 출시 진행 상황
> 최종 업데이트: 2026-07-11. 첫 정식 출시(무료 앱) 준비 로그 + 남은 로드맵.
> 관련 문서: [play-store-listing.md](play-store-listing.md) · [production-access-draft.md](production-access-draft.md)
---
## ✅ 완료된 작업
### 출시 전략 결정
- **무료 앱**으로 첫 출시 (수익화 기능은 순차 도입)
- 첫 출시 기능 플래그 (`src/config/features.js`, 전부 `false`):
- `BILLING_ENABLED=false` — 인앱결제 스텁이라 결제 없이 프리미엄 부여되는 악용 차단
- `ADS_ENABLED=false` — 애드핏 미승인 + 데이터 안전 양식 단순화(광고 식별자 미수집)
- `CARD_NOTIF_ENABLED=false` — NotificationListenerService 정책 심사 회피
- 패키지명 `kr.sblog.slimbudget` **유지**(출시 후 영구 고정. 사용자 비노출이라 무방)
### 인프라 / 정책 페이지
- **www.sblog.kr** 도메인 + 인증서 확장 (app.sblog.kr와 동일 dist 서빙)
- 공개 URL: `/privacy`, `/terms`, `/account-deletion` (모두 200 확인)
- 개인정보처리방침 광고 문구 → "현재 광고 없음 + 향후 도입 가능" 미래형으로 정합화
### 빌드 / 서명
- **AAB**: `android/app/build/outputs/bundle/release/app-release.aab`
- targetSdk 36, versionCode 1, versionName 1.0
- 업로드 키(CN=Don Dwaeji) 서명 확인 (디버그 아님)
- 결제·광고·카드알림 전부 OFF 클린 (NotificationListener 권한 없음 검증)
- 키스토어: `android/upload-keystore.jks` + `android/key.properties` (gitignore, **이 PC에만 존재 — 분실 시 업데이트 불가, 반드시 백업**)
### 스토어 그래픽 (`brand/store/`)
- `icon-512.png` (512×512 불투명)
- `feature-1024x500.png` / `feature-1024x500-text.png` (피처그래픽, 택1)
- 생성기 `brand/gen-store-assets.mjs` + 마스터 `brand/logo-1024.png`
### 스크린샷 (`brand/screenshots/`, 7컷, 실기기 1080×2340)
- shot0 홈 대시보드(리드) / shot1 가계부 내역 / shot2 통계 / shot3 정기결제 / shot4 계좌 / shot5 게시판 / shot6 게시글 상세
### 등록 문서
- `brand/play-store-listing.md` — 등록정보(제목·설명)·데이터안전 양식·콘텐츠등급 답안
- `brand/production-access-draft.md` — 프로덕션 액세스 신청서 초안(4문항)
### 코드 수정 (배포 완료)
- 인앱결제 진입점 비활성화 (UpgradeView "곧 출시 예정", AccountInfoView 정기결제 카드 숨김)
- 광고 비활성화 (AdBanner + App.vue 게이팅, 하단 광고 여백 제거)
- 둘러보기(데모) 모드 메뉴 접근 버그 수정 (AppHeader 햄버거 `demo.on` 노출)
- 계정삭제 공개 페이지 추가
### 테스트 데이터 (캡처용, `sb_bt/scripts/`, gitignore 로컬 전용)
- 테스트 계정 `kenneth73510@gmail.com` (DB로 PREMIUM 부여)
- `seed-test-kenneth.sql`(계정+가계부), `seed-test-board.sql`(게시판 더미)
- `cleanup-test-board.sql`(게시판 더미 삭제 — **캡처 후 정리 완료**)
---
## 🚧 현재 상태 (2026-07-05)
- ✅ 개발자 계정 신원확인 완료 → 앱 생성 완료(`kr.sblog.slimbudget`)
- ✅ 필수 설정(데이터안전·콘텐츠등급(전체이용가)·앱액세스(둘러보기)·광고없음·비거래자·방침URL) 완료
- ✅ 스토어 등록정보·이미지(아이콘512·피처·스크린샷7) 완료
-**비공개 테스트 트랙에 v2(AAB) 게시 → 검토 통과**
- ✅ 구글 로그인 SHA-1 이슈 해결(Play 앱서명 SHA-1 등록) → 로그인 정상
- ✅ Google Search Console sblog.kr 도메인 소유권 인증 완료 (DNS TXT)
-**테스터 12명 opt-in 대기**(업체 의뢰) → 14일 → 프로덕션 신청
-**v4 AAB 빌드 완료** (versionCode 4, 1.0.3) → 비공개 트랙 업로드 대기
- ⏳ 개인사업자 계정 전환 → DUNS 신청 완료(D&B 발급 대기) → 발급 후 Play Console에서 마저 진행
- Play Console 웹사이트 인증(www.sblog.kr) 완료
### 🍎 iOS(App Store) 트랙 — 2026-07-11
-**애플 개발자 등록 완료** (Team ID `G9PDM2C4L6`)
-**애플 로그인(Sign in with Apple) 실기기 동작 확인** — App Store 4.8 요건 충족
-**iOS 번들ID `kr.sblog.slimbudget.app`** (기존 `kr.sblog.slimbudget`는 타 계정 선점 → iOS 전용 새 ID, 안드로이드는 그대로)
- App ID + Sign in with Apple/Push capability, `CODE_SIGN_ENTITLEMENTS` 연결, `ITSAppUsesNonExemptEncryption=false`
-**App Store Connect 앱 생성 + v1.0 (build 1) 심사 제출 완료** — 연령 15+, App Privacy(이메일·사용자콘텐츠·사용자ID / 추적없음 / 앱기능), 무료, 콘텐츠권한 없음, 스크린샷(6.5" 1242×2688 iPhone + iPad 13")
- 로그인은 구글/애플 소셜 전용 → 심사 메모에 **둘러보기(데모)** 안내
-**심사 결과 대기** (승인 시 출시 / 거부 시 Resolution Center 대응)
---
## 📋 남은 로드맵 (프로덕션까지)
1.**테스터 12명 opt-in** (업체 의뢰) — 14일 카운트 시작 조건
2.**14일 연속** 12명 유지 → **프로덕션 액세스 신청** (자료: production-access-draft.md)
3.**v4 AAB 비공개 트랙 업로드** — 빌드 완료(versionCode 4, 1.0.3), Play Console 업로드 후 검토 제출 (14일 카운트 리셋 안 됨)
4. 승인 → **프로덕션 출시**
### ⚠️ 폐쇄 테스트 의무 (개인 계정 + 2023-11-13 이후 개설)
- **테스터 12명 이상 · 14일 연속** 폐쇄 테스트 후 프로덕션 신청 가능
- 실기기·실계정 필수 (에뮬·봇·중복 불인정)
- 14일 카운트 = "릴리스 승인 + 12명 opt-in" 둘 다 충족 시점부터
- 테스터 확보: 지인 비공개 방침 → 품앗이 커뮤니티(무료·안전) 또는 평판 좋은 유료 서비스(실사용 보장형만)
### 익명 셋업 체크리스트
- [ ] 전용 Gmail 생성 (개발자 연락처 + 품앗이용)
- [ ] 공개 개발자 이름 → 브랜드명 (법적이름은 구글 내부 전용, 비공개)
- [ ] 연락처 이메일(계정+앱) → 전용 메일
- [ ] 거래자 상태 → **비거래자(Non-trader)** (주소 비공개)
- [ ] 품앗이 노출은 전용 Gmail로만
---
## ✅ 적용 완료 (전부 웹/소스 배포됨)
**v2(versionCode 2, 1.0.1, 06-29)에 포함 — 비공개 테스트 게시·검토통과**
- 신규 유저 빈 상태 CTA(홈 '시작하기'·내역·계좌) / 오프라인·네트워크 오류 배너
- 앱 잠금(PIN, 옵트인) + 카메라/갤러리 복귀 오작동 수정
- 전역 에러 폴백(흰 화면 방지) / 안드로이드 뒤로가기 통합(오버레이→뒤로→종료토스트)
- 분류 순서변경 보호('순서변경' 토글)
**v2 이후 추가 — 웹 배포됨, v3 빌드 대기**
- 앱 잠금 **1시간 유예**(잦은 PIN 입력 해소)
- **외화 입/지출** — 통화+환율 자동조회(open.er-api.com 무료)→원화 환산, 원본 보존. `amount`는 환산 원화라 통계·예산 무변경
- **외화 환율 갱신 시각** — 환율 자동조회 후 '방금 / N분 전 / N시간 전' 레이블(30초 ticker), FxController `updatedAt` 응답 포함
- **월별 예산 + 전월/다음달 복사** — budget에 year/month 추가, 기존 예산은 현재 월로 1회 이관
- 내역추가 **전체화면 모달**(바깥 닫힘 방지) / 분류 **아코디언**(대분류 삼각형) / 계좌 셀렉트 정리(종류별 안내·이름만)
- 구글 로그인 **Play 서명 SHA-1 등록**(Google Cloud Android OAuth 클라 추가)
- **UI 전면 개편 (2026-07-04)** — 모든 입력 모달 전체화면 슬라이드업(AppModal) 적용:
- 공통 `CategoryPicker.vue` 컴포넌트 — 대분류/소분류 아코디언 칩 + 인라인 추가 (가계부·고정지출·예산 공통)
- `AccountView` 계좌종류·구분 셀렉트→뱃지, 분류→CategoryPicker
- `RecurringView` 구분·주기 셀렉트→뱃지, 계좌/카드→뱃지, 분류→CategoryPicker
- `AccountWalletView` 카드종류·상환방식 셀렉트→뱃지
- `BudgetView` 비고정/고정 라디오→뱃지, 카테고리→CategoryPicker
- `CategoryView` 리스트 UI → **칩 그리드 관리 모드** (CategoryPicker admin prop): ✏ 이름수정·✕ 삭제·⠿ 순서변경(토글 후 드래그)
- **게시판 포인트 정책** — 작성 10P(일 5회·50P 한도), 스팸 키워드 포함 시 미지급; 추천 +5P(작성자·일 50P)/비추 -1P; 남글추천 +1P(투표자·일 10P)/취소 -1P
- **가계부 작성 포인트** — 건당 10P, 일 5회(50P) 한도
- **스팸 키워드 자동 블라인드** — 14개 키워드 포함 시 글/댓글 즉시 블라인드, 포인트 미지급
- **관리자 신고글 목록 고도화** — 신고 누적 + 스팸 감지 통합; 🤖 스팸감지 뱃지·blockReason 표시; 스팸 항목 신고 무시 버튼 숨김
**2026-07-09~11 추가 (웹·서버 배포 완료 / iOS는 재빌드 시 반영)**
- **애플 로그인(Sign in with Apple)** — 프런트(`native/appleAuth`·LoginModal)·백엔드(`/auth/apple`, 애플 JWKS 검증·aud=번들ID)·DB(`member.apple_id`)·iOS entitlements 전 구간. iOS 번들ID `kr.sblog.slimbudget.app`로 변경, 서버 `apple-client-id` 정합. 실기기 로그인 확인
- **입력 UI 모달 → 페이지 전환 + 바텀시트** (톤 통일)
- **계좌**(`WalletFormView`)·**정기결제**(`RecurringFormView`) → 별도 라우트 페이지, 생성 시 계좌 종류 선택 복원
- **내역·예산** → 콘텐츠 영역 인라인 페이지(`?entry`/`?budget` 라우트 바인딩, 뒤로가기로 닫힘) — 핵심 로직(AI·OCR·상환 등) 이관 없이 프레젠테이션만 변경(회귀 위험 최소화)
- 선택 항목 **바텀시트**: `BottomSheet`·`SheetSelect`·**`AccountPicker`(종류→계좌 아코디언)** 공통 컴포넌트. 계좌·분류·검색 필터에 적용(분류는 CategoryPicker `hideAdd`)
- **화면 헤더 뒤로가기 버튼 제거** → 하단 네비 뒤로가기로 유도
- **계좌 관리 개편** — 탭 제거→종류별 그룹 목록, 계좌명 클릭 시 **상세 화면**(`AccountWalletDetailView`), 순서변경 잠금 토글, 투자계좌는 투자금만
- **대출·카드할부 상환 조회(유료)** `RepaymentScheduleView`+`utils/repayment.js` — 상환방식별 상각+카드할부(무이자), 회차별 원금·이자·합계·잔액. **대출 월 상환금(loanPayment) 필드**로 은행 조회액과 정확 일치. 이자율 소수점 3자리. 홈 **'이번 달 상환 예정' 카드(유료)**
- **예산 단순화** — '월 예산'만 등록, 고정/비고정·일/주/년·**1회성 예산 제거**
- **레이아웃** — 하단 고정 내비에 가리던 문제 수정(본문 `padding-bottom`)
- **정기결제 등록 자동세팅** — 원리금균등은 저장된 월 상환금 우선
- **연락처 이메일** → `dondwaeji.app@gmail.com` (약관·계정삭제·스토어 문의)
**2026-07-07 추가 (웹·서버 배포 완료)**
- **정기결제 리네이밍** — 전체 화면 '고정지출' → '정기결제' (백업 엑셀 시트명은 기존 백업 복원 호환 위해 유지)
- **정기결제 주기 월/년만** — DAILY·WEEKLY 제거, 요일선택 UI 제거 (`RecurringView`)
- **계좌 관리 화면 개편** — 탭 제거 → **종류별 그룹 목록**, 계좌명 클릭 시 **상세 화면 분리**(`/account/wallets/:id` `AccountWalletDetailView`), 순서변경 **잠금 토글**, 투자계좌는 투자금만 표기·상세 비활성
- **태그관리 순서변경 토글** — 기본 비활성, '순서변경' 버튼 클릭 시 드래그 활성 (`AccountTagView`)
- **대출·카드할부 상환 조회 (유료)** — `RepaymentScheduleView.vue` + `utils/repayment.js`. 대출 상환방식별 상각(원리금균등·원금균등·만기일시) + 카드 할부(무이자) **남은 스케줄**, 회차별 **원금·이자·합계·남은잔액**, 이번 달/남은 총액. 사이드바 유료 메뉴
- **대출 월 상환금(loanPayment) 필드** — 백엔드 컬럼(`wallet.loan_payment`)+DTO/매퍼, 계좌 폼 선택 입력. 원리금균등을 **은행 조회액과 정확히 일치**(이자=잔액×이율, 원금=월상환금−이자), **마지막 회차 잔여 반올림 정산**. 미입력 시 잔액·개월수로 PMT 추정
- **대출 이자율 소수점 3자리** 입력(step 0.001, `loan_rate` DECIMAL(7,4))
- **가계부 대출 상환 자동세팅** — 원리금균등 선택 시 저장된 **월 상환금 우선** 반영(옛 PMT 추정 대체), 마지막 회차 초과상환 방지 (`AccountView`)
- **홈 대시보드 '이번 달 상환 예정' 카드 (유료)** — 현재일 기준 대출·카드할부 상환 예정 합계, **가계부에 상환 기록 있으면 제외**, 모두 기록 시 '상환 완료' 표시
- **AI 교통 절약 팁 제외** — 버스·지하철·대중교통 분류는 정기권 최저비용 사용 중으로 tips에서 제외 (`AiEntryParser`)
**2026-07-05 추가 (웹 배포됨, v3 빌드 대기)**
- **AI 편의 기능** — 자연어 입력·영수증 비전·분류/계좌 추천·재무 코멘트·절약 가이드 (상세는 아래 🤖 섹션)
- **홈 대시보드 오늘 기록 넛지** — '안녕하세요 OO님' → 오늘 기록 상태로 대체(없으면 '지금 기록하기' CTA / 있으면 'N건'·연속 2일+ 'N일 연속 🔥')
- **통계 화면 정리** — '예산 대비'+'수입 대비' 두 패널 → '이번 달 현황' 단일 패널 통합; 예상 지출은 **결정적 계산**(일회성 큰 지출 제외, 매 접속 동일값); AI 코멘트 로딩 인디케이터
- **한 달 개근 보너스 포인트** — 현재 월 모든 날 기록 시 **랜덤 1~100P**(월 1회, 전월 소급 제외, 화면 안내 없음, `MONTH_COMPLETE_yyyyMM`로 중복 차단)
- **iOS 안전영역·클립보드 수정** — 사이드바 상단 상태바 겹침 해결; 모달 열 때 클립보드 자동읽기 제거(iOS '붙여넣기' 토스트 방지). 음성 입력=iOS 키보드 받아쓰기(실기기 확인)
---
## 📌 남은 백로그 (출시 후 업데이트)
1. **앱 업데이트 안내** — 새 버전 알림 + 구글플레이 바로가기
2. (선택) 첫 실행 온보딩 / Play 인앱 리뷰 / 생체 인증
3. **조직 계정 전환** — 사업자등록→DUNS→앱이전
4.**포인트 상점** (2026-07-10 구현 완료)
- **계좌 슬롯**: 500P/개, 최대 3개, 영구. `extra_wallet_slots` (member 테이블)
- **AI 통계 분석**: 1000P → 10회 이용권. `ai_stat_credits` (member 테이블)
- FREE 회원이 크레딧 없이 AI 코멘트 호출 시 402 → 통계 화면에서 상점 안내
- PREMIUM 회원은 AI 코멘트 무제한 (크레딧 불필요)
### 🤖 AI 기능 — ✅ 구현·배포 완료 (2026-07-05)
- 백엔드: `AiEntryParser`(Claude Haiku 4.5, raw HTTP `/v1/messages`), 키는 서버 `/opt/sb-backend/.env`(`ANTHROPIC_API_KEY`), 실패/미설정 시 규칙기반·Google Vision OCR 폴백.
-**① 자연어 한 줄 입력** — "어제 스타벅스 5천원" → 폼 자동 채움. 내역 화면 '✨ 빠른 입력' 바 진입점. (음성=iOS 키보드 받아쓰기, 실기기 확인)
-**② 영수증 사진 → AI 구조화** — Claude Vision이 금액·상호·날짜·분류·계좌 추출(`/ocr/receipt-ai`), 실패 시 Google Vision 폴백.
-**③ 분류·계좌 자동 추천** — 사용자 실제 분류·계좌 목록 중에서 선택(할루시네이션 방지 검증).
-**AI 재무 코멘트 + 절약 가이드** — 통계 화면, **집계 수치만** 전송(거래 원문 미전송), 정중체. 예상 지출은 비결정성 때문에 **결정적 계산**(일회성 큰 지출 제외)으로 분리.
-**개인정보처리방침·데이터안전 갱신** — 처리자 Anthropic(Claude) 명시, 전송 데이터 범위·학습 미사용 고지 (terms.js §2·§3, play-store-listing.md 데이터안전).
- 원칙 준수: AI는 폼 prefill·참고 코멘트만, **저장은 사용자 확인 후**.
---
## 🔨 빌드 상태
- **v2** (versionCode 2, 1.0.1): 비공개 테스트 게시·검토통과
- **v3** (versionCode 3): 위 'v2 이후 추가' + **AI 기능(자연어·영수증·분류/계좌·재무코멘트)** 전부 반영 — 빌드 후 비공개 트랙 업로드 (14일 카운트 리셋 안 됨)
- ⚠️ 빌드 전 서버 `.env``ANTHROPIC_API_KEY`(AI) 확인
- ✅ 가계부 내역 오류 해결됨 (2026-07-05)
- ✅ 1회성 예산(BudgetOnetime) 백엔드 배포 완료 (2026-07-05)
---
## 다음 세션 재개 지점
0.**[진행중] iOS App Store 심사 결과 대기** — v1.0 제출 완료. 승인 시 출시(자동/수동), 거부 시 Resolution Center 대응(주로 데모 접근 안내)
1. **테스터 12명 opt-in 진행상황 확인**(업체 의뢰) → 12명 + 14일 연속 충족되면 **프로덕션 액세스 신청**
2. **v5 AAB 비공개 트랙 업로드** — 이번 작업분(애플로그인·페이지개편·상환조회 등) 반영해 AAB 재빌드·업로드
3. **DUNS 발급 대기** → 발급 시 Play Console 개인사업자 계정 전환
4. **수익화(출시 후)** — 구독(iOS StoreKit 미구현 → 결제코드+ASC상품+영수증검증 필요) / 광고(광고SDK+ATT+App Privacy 갱신). 둘 다 무료 출시 후 업데이트로
5. (선택) **음성 전용 🎤 버튼**`@capacitor-community/speech-recognition`
6. **CI 러너 안정화** — Gitea act_runner 멈춤(재부팅 후 대기)로 배포 지연된 적 있음 → 자동재시작/헬스체크
+93
View File
@@ -0,0 +1,93 @@
// Play Console 스토어 그래픽 생성기.
// 입력: brand/logo-1024.png (사용자가 저장한 1024x1024 돈돼지 마스터 아트)
// 출력: brand/store/icon-512.png (스토어 고해상도 아이콘 512x512, 불투명)
// brand/store/feature-1024x500.png (피처 그래픽 — 텍스트 없음, 항상 안정)
// brand/store/feature-1024x500-text.png(피처 그래픽 — 한글 텍스트 포함, best-effort)
//
// 실행: node brand/gen-store-assets.mjs [입력경로]
import sharp from 'sharp'
import { mkdirSync, existsSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, join, resolve } from 'node:path'
const __dirname = dirname(fileURLToPath(import.meta.url))
const SRC = resolve(process.argv[2] || join(__dirname, 'logo-1024.png'))
const OUT = join(__dirname, 'store')
// 브랜드 색 (아이콘 배경의 크림/옐로 계열)
const CREAM = '#FBF7E9'
const YELLOW = '#F2C94C'
const BROWN = '#B8860B'
if (!existsSync(SRC)) {
console.error(`\n[!] 마스터 아트가 없습니다: ${SRC}`)
console.error(` 1024x1024 돈돼지 PNG 를 위 경로에 저장한 뒤 다시 실행하세요.\n`)
process.exit(1)
}
mkdirSync(OUT, { recursive: true })
const log = (f) => console.log(' ✓', f)
// 1) 스토어 아이콘 512x512 — 불투명 크림 배경 위에 마스터를 합성(투명 PNG 대비)
async function icon512() {
const logo = await sharp(SRC).resize(512, 512, { fit: 'contain', background: CREAM }).png().toBuffer()
const out = join(OUT, 'icon-512.png')
await sharp({ create: { width: 512, height: 512, channels: 4, background: CREAM } })
.composite([{ input: logo }])
.flatten({ background: CREAM })
.png()
.toFile(out)
log('store/icon-512.png (512x512, 불투명)')
}
// 공통: 1024x500 배경 — 피그가 놓일 우측(크림)에서 좌측(옐로 살짝)으로.
// 중심을 피그 쪽 크림으로 두어 마스터의 크림 배경 사각이 자연스럽게 녹도록 한다.
function bgSvg(cx = '80%') {
return Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="500">
<defs>
<radialGradient id="g" cx="${cx}" cy="48%" r="95%">
<stop offset="0%" stop-color="${CREAM}"/>
<stop offset="62%" stop-color="${CREAM}"/>
<stop offset="100%" stop-color="${YELLOW}" stop-opacity="0.55"/>
</radialGradient>
</defs>
<rect width="1024" height="500" fill="url(#g)"/>
</svg>`)
}
// 2) 피처 그래픽 (텍스트 없음) — 가운데 큼직하게, 박스 없이 녹아들게
async function featurePlain() {
const bg = await sharp(bgSvg('50%')).png().toBuffer()
const pig = await sharp(SRC).resize(420, 420, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toBuffer()
const out = join(OUT, 'feature-1024x500.png')
await sharp(bg).composite([{ input: pig, top: 40, left: 302 }]).png().toFile(out)
log('store/feature-1024x500.png (텍스트 없음)')
}
// 3) 피처 그래픽 (한글 텍스트) — 좌측 텍스트 / 우측 피그, 겹침 없도록 간격 조정.
async function featureWithText() {
const bg = await sharp(bgSvg('82%')).png().toBuffer()
const pig = await sharp(SRC).resize(380, 380, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toBuffer()
const text = Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="500">
<style>
.t { font-family: 'Malgun Gothic','맑은 고딕',sans-serif; fill:${BROWN}; }
.title { font-size: 80px; font-weight: 800; }
.sub { font-size: 29px; font-weight: 500; fill:#8a6d10; }
</style>
<text x="64" y="232" class="t title">돈돼지 가계부</text>
<text x="68" y="290" class="t sub">슬림하게 관리하는</text>
<text x="68" y="332" class="t sub">가계부 · 자산 · 예산</text>
</svg>`)
const out = join(OUT, 'feature-1024x500-text.png')
await sharp(bg)
.composite([{ input: pig, top: 60, left: 630 }, { input: text }])
.png()
.toFile(out)
log('store/feature-1024x500-text.png (한글 텍스트, 폰트 렌더 확인 필요)')
}
console.log(`\n[스토어 에셋 생성] 입력: ${SRC}\n`)
await icon512()
await featurePlain()
await featureWithText()
console.log(`\n완료 → ${OUT}\n`)
Binary file not shown.

After

Width:  |  Height:  |  Size: 908 KiB

+136
View File
@@ -0,0 +1,136 @@
# Play Console 등록 자료 — 돈돼지 가계부
> 첫 출시(무료 앱, 인앱결제·광고 모두 비활성화) 기준. Play Console 각 항목에 그대로 붙여넣기.
> 광고(카카오 애드핏)를 껐으므로 광고 식별자 수집/제3자 공유가 없어 데이터 안전이 단순함.
---
## 1. 스토어 등록정보 (Main store listing)
### 앱 이름 (30자 이내)
```
돈돼지 가계부
```
### 간단한 설명 / Short description (80자 이내)
```
가계부·자산·예산을 슬림하게. 고정지출 자동, 통계 차트, 영수증 인식까지.
```
### 자세한 설명 / Full description (4000자 이내)
```
🐷 돈돼지 가계부 — 슬림하게 관리하는 가계부 · 자산 · 예산
복잡한 가계부는 그만! 수입·지출을 빠르게 기록하고, 자산과 예산을 한눈에 관리하세요.
군더더기 없는 깔끔한 화면으로 매일 쓰기 부담 없는 가계부입니다.
▪ 간편한 가계부
· 수입·지출·이체를 몇 번의 터치로 기록
· 자주 쓰는 내역을 칩으로 등록해 1초 입력
· 월별 수입/지출/잔액 요약과 일별 내역 정리
▪ 자산 · 부채 관리
· 은행·현금·신용/체크카드·대출·투자 계좌를 한 곳에서
· 총자산 · 총부채 · 순자산을 자동 집계
▪ 고정 지출 자동 반영 (프리미엄)
· 월세·구독료·급여 등 반복 거래를 등록해두면 주기에 맞춰 자동 생성
· 실제 결제만 '확인'으로 확정 — 빠뜨림 없는 가계부
▪ 통계와 예산 (프리미엄)
· 분류별 지출, 수입 대비 지출, 지난달 대비, 월말 예상까지 차트로 분석
· 카테고리별 예산을 정하고 예산 대비 지출을 한눈에
▪ 영수증 인식 (프리미엄)
· 영수증을 촬영하면 금액·내역을 자동 인식해 입력 시간을 단축
▪ 커뮤니티
· 짠테크·재테크 노하우를 나누는 게시판
· 추천·댓글로 소통하고 좋은 글을 발견
▪ 그 외
· 다크 모드 지원
· 엑셀 백업/복구로 내 데이터를 안전하게 (프리미엄)
· 로그인 없이 '둘러보기'로 먼저 체험
무료로 핵심 가계부 기능을 모두 사용할 수 있으며, 통계·예산·고정지출 자동화·영수증 인식·백업 등은 프리미엄에서 제공됩니다. (구독 기능은 순차적으로 제공될 예정입니다.)
오늘부터 돈돼지와 함께 슬림한 가계 관리를 시작해보세요!
문의: dondwaeji.app@gmail.com
```
### 기타 등록정보 필드
- **앱 카테고리**: 금융 (Finance)
- **태그**: 가계부, 예산, 자산관리
- **연락처 이메일**: dondwaeji.app@gmail.com
- **개인정보처리방침 URL**: https://www.sblog.kr/privacy
- **그래픽 에셋**:
- 앱 아이콘: brand/store/icon-512.png
- 그래픽 이미지(피처): brand/store/feature-1024x500-text.png (또는 -text 없는 버전)
- 휴대전화 스크린샷: brand/screenshots/shot1~6 (6장)
---
## 2. 데이터 안전 (Data safety) 양식
> "앱 콘텐츠 → 데이터 보안" 에서 작성. 아래 기준으로 답하면 개인정보처리방침과 일치함.
### 개요 질문
- 데이터를 수집하나요? → **예**
- 데이터를 제3자와 공유하나요? → **아니요** (광고 끔. OCR·AI(Anthropic)는 대행 처리(처리 위탁)이며 Google Play 기준 '공유'에 해당하지 않음)
- 전송 중 데이터가 암호화되나요? → **예** (HTTPS)
- 사용자가 데이터 삭제를 요청할 수 있나요? → **예** (앱 내 '회원 탈퇴' + 이메일 요청, https://www.sblog.kr/account-deletion)
### 수집하는 데이터 유형 (각 항목: 수집됨 / 목적 / 공유 여부)
| 데이터 유형 | 수집 | 공유 | 목적 |
|---|---|---|---|
| **개인 정보 → 이름** | 예 | 아니요 | 앱 기능, 계정 관리 |
| **개인 정보 → 이메일 주소** | 예 | 아니요 | 앱 기능, 계정 관리 |
| **금융 정보 → 기타 금융 정보** (사용자가 입력한 수입·지출·자산 등 가계부 데이터) | 예 | 아니요 | 앱 기능 |
| **사진 및 동영상 → 사진** (프로필·영수증·게시판 이미지) | 예 | 아니요 | 앱 기능 |
| **앱 활동 → 사용자가 생성한 콘텐츠** (게시판 글·댓글) | 예 | 아니요 | 앱 기능 |
| **앱 활동 → 앱 상호작용** | 예 | 아니요 | 앱 기능, 부정 이용 방지 |
각 항목 공통:
- **수집이 필수인가요?** → "데이터 수집이 필수임" (서비스 제공에 필요)
- **데이터가 암호화되나요(전송 중)?** → 예
- **삭제 요청 가능?** → 예
> ✅ 광고를 껐으므로 **광고 식별자(기기 ID) 수집/공유가 없습니다** — 데이터 안전 양식이 단순해지고 심사 불일치 리스크가 사라졌습니다.
### AI 편의 기능의 데이터 처리 (처리 위탁, 공유 아님)
> 아래는 개인정보처리방침 3항과 정합. 회원이 해당 기능을 쓸 때에만, 기능 수행 목적으로 Anthropic(Claude) API에 전송됨. Anthropic은 학습에 사용하지 않고, 서비스도 별도 저장하지 않음(폼 자동 채움용). API 키는 서버 보관.
> - 자연어 한 줄 입력: 입력 텍스트 → 금액·분류 등 구조화
> - 영수증 사진 구조화: 영수증 이미지 → 금액·상호·날짜·분류
> - 분류·계좌 자동 추천: 분류·계좌 '이름' 목록
> - 재무 코멘트: 이번 달 **집계 수치만**(개별 거래 원문 미전송)
>
> Play 데이터 안전에서 위 데이터 유형(금융 정보·사진)의 **공유 여부는 계속 '아니요'** (대행 처리는 공유 아님). 다만 방침에 처리자(Anthropic)를 명시했으므로 심사자 교차확인에도 일치함.
---
## 3. 콘텐츠 등급 설문 (IARC)
> "앱 콘텐츠 → 콘텐츠 등급" 설문. 핵심 답안:
- 앱 카테고리: **유틸리티/생산성/기타** (또는 금융)
- 폭력/성적/혐오/약물/도박 콘텐츠: **모두 없음**
- **사용자 간 상호작용/사용자 생성 콘텐츠(게시판)**: **있음** → "사용자가 콘텐츠를 만들고 공유할 수 있음" 으로 정직하게 표시
- 신고 기능(5건 누적 블라인드)·관리자 차단 등 **중재 수단 보유** → 관련 질문에 "콘텐츠 신고/차단 기능 있음"으로 답변
- 위치 공유 / 개인정보 공유 기능: **없음**
- 디지털 구매(인앱결제): 첫 출시는 **비활성화** → "없음" (추후 활성화 시 등급 갱신)
- **광고 포함 여부**: **아니요** (광고 껐음 — 추후 활성화 시 갱신)
- 예상 등급: 대체로 **만 3세 이상(전체이용가)** 수준, UGC로 인해 일부 지역에서 Teen 가능 — 설문 결과 따름
---
## 4. 기타 출시 전 체크
- **앱 액세스 권한**: 로그인 필요 → 심사용 테스트 계정 제공 (Play Console "앱 액세스 권한"에 kenneth73510 계정 또는 데모 안내). 데모(둘러보기)로도 핵심 화면 확인 가능함을 메모.
- **타겟 출시 국가**: 대한민국 (필요 시 확대)
- **데이터 삭제 URL**: https://www.sblog.kr/account-deletion (데이터 안전 양식 + 앱 콘텐츠)
- **앱 카테고리/연락처/방침 URL** 위 1번 참고
- 광고 포함 여부 질문: **아니요(광고 없음)** — 광고를 껐으므로 "광고 없음"으로 표시
- ⚠️ **개인정보처리방침 정합성**: 현재 방침 4항이 "무료 회원에게 광고가 표시되며…"로 적혀 있어 광고 OFF 와 불일치. 심사자 교차확인 대비해 방침의 광고 문구를 "향후 광고가 도입될 수 있으며…"(미래형)로 완화 권장. (원하면 수정·배포 도와드림)
```
+57
View File
@@ -0,0 +1,57 @@
# 프로덕션 액세스 신청서 초안 — 돈돼지 가계부
> 14일 폐쇄 테스트 완료 후 Play Console "프로덕션 액세스 신청" 폼에 입력.
> ⚠️ [대괄호] 부분은 실제 테스트 후 본인 경험으로 교체하세요. 거짓·과장 금지 — 실제로 테스트한 내용을 적어야 합니다.
---
## Q1. 테스터를 어떻게 모집했나요? (How did you recruit your testers?)
```
실제 Android 기기와 Google 계정을 보유한 [12]명의 테스터를 모집해 비공개 테스트를 진행했습니다.
테스터는 안드로이드 사용자 지인 및 개발자 커뮤니티의 베타 테스터 모임을 통해 모았으며,
모두 실제 기기에 앱을 설치하고 14일간 사용했습니다.
```
> 유료/품앗이 서비스를 쓴 경우에도 "실제 기기·실제 계정 테스터"라는 사실 위주로 적되, 허위로 쓰지 마세요.
## Q2. 테스터와 어떻게 소통하고 피드백을 수집했나요?
```
이메일과 메신저(오픈채팅)로 테스터에게 설치 안내와 점검 항목을 전달했습니다.
점검 항목: 회원가입/구글 로그인, 가계부 수입·지출 기록, 계좌·자산 관리, 예산·통계 화면,
고정지출 자동 반영, 커뮤니티 게시판(글·댓글·추천), 다크 모드.
테스터가 사용 중 발견한 버그·불편 사항을 회신받아 정리했습니다.
```
## Q3. 어떤 피드백을 받았고, 무엇을 개선했나요?
```
주요 피드백과 개선:
- 둘러보기(로그인 없이 체험) 모드에서 일부 화면의 메뉴 접근이 불편 → 메뉴 진입 동선 수정
- 특정 기기에서 한글 입력 시 자동완성/맞춤법 검사로 입력이 끊김 → 입력 필드 설정 수정으로 해결
- 무료/유료 기능 구분 및 화면 흐름이 헷갈린다는 의견 → 안내 문구·접근 동선 정리
- [본인 테스트 중 실제로 발견한 버그/불편과 그에 대한 수정 내용을 추가 기재]
크래시나 데이터 유실 등 치명적 문제는 발견되지 않았습니다.
```
## Q4. 앱이 프로덕션에 준비되었다고 보는 이유는?
```
돈돼지 가계부는 웹·Windows 데스크톱에서 이미 운영 중인 개인 가계부 서비스이며,
이번 비공개 테스트로 Android 실기기에서 핵심 기능(가계부 기록, 계좌·자산, 예산·통계,
고정지출 자동 반영, 커뮤니티, 구글 로그인)의 안정성을 14일간 검증했습니다.
다양한 기기에서 크래시 없이 정상 동작하며, 테스트 중 제기된 개선 사항을 반영했습니다.
개인정보처리방침·데이터 삭제 안내 등 정책 요건도 갖추어 정식 출시 준비가 되었다고 판단합니다.
```
---
## (선택) 영어 버전 — 폼이 영어로 표시될 경우
**Q1 Recruit:** I recruited [12] testers who own real Android devices with genuine Google accounts, through acquaintances and a developer beta-tester community. All installed the app and used it over the 14-day period.
**Q2 Feedback collection:** I communicated via email and messenger, sharing install instructions and a checklist (Google sign-in, expense/income tracking, accounts & assets, budget & statistics, recurring transactions, community board, dark mode), and collected bug reports and usability feedback.
**Q3 Findings & changes:** Key fixes included improving menu navigation in the guest "explore" mode, resolving a Korean-input (IME) issue on certain devices, and clarifying free/premium feature flow. [Add the real bugs you found.] No crashes or data-loss issues were found.
**Q4 Readiness:** Don Dwaeji budget app already runs in production on web and Windows desktop. The closed test validated the Android build's core features on real devices over 14 days with no crashes, and feedback was addressed. Privacy policy and data-deletion pages are in place, so the app is ready for production.
Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

+9
View File
@@ -0,0 +1,9 @@
; 기본 설치 경로 지정 (사용자 폴더 = 관리자 권한 불필요 → UAC 프롬프트 없음).
; productName 이 한글이라 electron-builder 가 기본 경로를 package.json name(sb_pt)으로 폴백하는 문제도 함께 회피.
; → %LOCALAPPDATA%\Programs\DonDwaeji 로 고정 (사용자는 설치 시 변경 가능).
!macro preInit
SetRegView 64
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LOCALAPPDATA\Programs\DonDwaeji"
SetRegView 32
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LOCALAPPDATA\Programs\DonDwaeji"
!macroend
+1 -1
View File
@@ -1,6 +1,6 @@
{
"appId": "kr.sblog.slimbudget",
"appName": "Slim Budget",
"appName": "돈돼지 가계부",
"webDir": "dist",
"server": {
"cleartext": true
+12
View File
@@ -7,8 +7,11 @@ server {
index index.html;
# SPA: 새로고침/직접 진입 시 index.html 로 폴백 (vue-router history 모드)
# 진입 HTML(루트·폴백)은 캐시 금지 — 배포로 청크 해시가 바뀌어도 항상 최신 진입점을 받도록.
# (루트 '/' 는 아래 '= /index.html' 매칭을 안 타므로 여기에 헤더를 둬야 함)
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache";
}
# index.html 은 캐시 금지 — 배포로 청크 해시가 바뀌어도 항상 최신 진입점을 받도록.
@@ -24,6 +27,15 @@ server {
add_header Cache-Control "public, immutable";
}
# 설치파일 다운로드 — 배포(dist 교체)에 지워지지 않도록 root 밖의 영속 디렉터리에서 서빙.
# 서버에 /var/www/sb-downloads/ 를 만들고 설치파일(SlimBudget-Setup.exe)을 올려두세요.
# 예) sudo mkdir -p /var/www/sb-downloads && sudo cp SlimBudget-Setup.exe /var/www/sb-downloads/
location /download/ {
alias /var/www/sb-downloads/;
add_header Content-Disposition "attachment";
autoindex off;
}
# API 프록시 → 백엔드(8080)
location /api/ {
proxy_pass http://127.0.0.1:8080;
+49 -31
View File
@@ -1,16 +1,23 @@
# Slim Budget — 프론트엔드 (sb_pt)
# 돈돼지 가계부 — 프론트엔드 (sb_pt)
Vue 3 + Vite 기반의 가계부·게시판 웹 애플리케이션 프론트엔드.
Vue 3 + Vite 기반의 가계부·자산·게시판 앱 프론트엔드. 하나의 코드베이스로 **웹 / 안드로이드(Capacitor) / Windows 데스크톱(Electron)** 3개 플랫폼을 빌드합니다.
## 기술 스택
- **Vue 3** (`<script setup>` Composition API) + **Vite**
- **Pinia** (상태관리), **Vue Router** (라우팅)
- **Axios** (HTTP, 공통 인스턴스 + 토큰/401 인터셉터)
- **Toast UI Editor** (`@toast-ui/editor`) + Prism.js (게시판 마크다운 작성/뷰어·코드 하이라이트)
- **Capacitor**(안드로이드 하이브리드 앱) · **Electron**(Windows 데스크톱) · **Vitest**(테스트)
## 브랜딩
- 서비스명 **Slim Budget** (헤더 로고/타이틀/푸터)
- 로고: `public/logo.svg` (브랜드 그린 `#00bc7e` 배지 + 슬림 막대 차트), 파비콘으로도 사용
- 서비스명 **돈돼지 가계부** (구 Slim Budget)
- 로고: 돈돼지(투명 배경) — 헤더/사이드바 로고·파비콘·앱 런처 아이콘. 브랜드 그린 `#00bc7e`
## 플랫폼별 동작
- **웹(PC·모바일)**: 안내 페이지([WebOnlyNotice.vue](../src/components/WebOnlyNotice.vue))만 노출(앱 다운로드 유도). 실제 이용은 앱에서. `npm run dev` 는 예외로 전체 앱 표시
- **안드로이드**: Capacitor WebView 패키징 → [ANDROID.md](ANDROID.md)
- **Windows**: Electron 이 라이브 사이트(app.sblog.kr)를 직접 로드 → 프론트 변경이 재설치 없이 반영
- 기능 플래그 [src/config/features.js](../src/config/features.js): `ID_LOGIN_ENABLED`(아이디 로그인)·`BILLING_ENABLED`(인앱결제)·`ADS_ENABLED`(광고)·`CARD_NOTIF_ENABLED`(카드알림) — 첫 출시 기준 전부 `false`
## 디렉터리 구조
```
@@ -21,65 +28,76 @@ src/
│ ├── boardApi.js # 게시판
│ ├── adminApi.js # 관리자(태그 카테고리)
│ └── accountApi.js # 가계부 전체
│ ├── billingApi.js # 인앱결제/구독
├── stores/ # Pinia (auth, ui, user)
├── config/features.js # 기능 플래그(아이디로그인·결제·광고·카드알림)
├── router/index.js # 라우트 + 인증 가드
├── components/
│ ├── layout/ # AppHeader / AppSidebar / AppFooter
│ ├── ui/IconBtn.vue # 공통 SVG 아이콘 버튼
│ ├── layout/ # AppHeader / AppSidebar / AppFooter / AppBottomNav
│ ├── ui/ # IconBtn / AppModal(전체화면 슬라이드업) / AppDialog / CategoryPicker
│ ├── editor/ # MarkdownEditor / MarkdownViewer
│ ├── LoginModal.vue / SignupModal.vue
│ ├── LoginModal.vue / SignupModal.vue / AppLockScreen.vue(PIN) / AdBanner.vue / WebOnlyNotice.vue
├── views/
│ ├── HomeView.vue # 홈(대시보드 자리, 현재 빈 공간)
│ ├── UsersView.vue # 사용자 관리
│ ├── HomeView.vue # 홈 대시보드(요약·일별 캘린더)
│ ├── board/ # 게시판 (목록/상세/작성)
│ ├── account/ # 가계부 (아래 참조)
│ └── admin/ # 태그 관리(관리자)
└── assets/main.css # 전역 스타일
│ └── admin/ # 회원/태그 관리(관리자)
└── assets/ # base.css(data-theme 다크모드) 등 전역 스타일
```
## 인증
- 로그인 시 발급된 **Bearer 토큰**을 `localStorage`에 저장, `http.js` 요청 인터셉터가 `Authorization` 헤더에 주입
- **구글 로그인**(웹/PC=Google Identity Services, 앱=네이티브 플러그인) — 현재 소셜 로그인 전용(`ID_LOGIN_ENABLED=false` 로 아이디/비번 폼·회원가입 UI 숨김)
- 401 응답 시 토큰 정리 후 `auth:unauthorized` 이벤트 → 로그인 모달 표시
- 라우트 `meta.requiresAuth` / `requiresAdmin` 가드
## 레이아웃 / 반응형
- 그리드 레이아웃(헤더·사이드바·본문·푸터)
## 레이아웃 / 반응형 / 테마
- 그리드 레이아웃 — 사이드바가 좌측 상단(헤더 행)까지, 헤더는 현재 화면 타이틀 표시. 본문 폭 `--page-max`(760px)로 통일
- **하단 고정 내비**([AppBottomNav.vue](../src/components/layout/AppBottomNav.vue)): 뒤로·앞으로·홈·새로고침·설정 (안드로이드 소프트키 위 안전영역)
- **모바일(≤768px)**: 사이드바가 **오프캔버스 드로어**(햄버거 토글 + 백드롭, 라우트 이동 시 자동 닫힘)
- 모든 액션 버튼은 **아이콘화**(`IconBtn`: 등록/수정/삭제/검색/페이징 등), `title`로 접근성·툴팁 제공
- 한 줄에 담기 어려운 목록은 **2줄 표기**(게시판: 제목/메타, 가계부 내역: 분류·금액/날짜·메모·액션 등)
- **입력 모달**: 전체화면 슬라이드업([AppModal.vue](../src/components/ui/AppModal.vue), 바깥 닫힘 방지). 셀렉트 대신 **뱃지/칩** 선택, 분류는 공통 [CategoryPicker.vue](../src/components/ui/CategoryPicker.vue)(대/소분류 아코디언)
- **테마**: 시스템 / 라이트 / 다크 (설정 화면, `data-theme` 기반)
- 모든 액션 버튼은 **아이콘화**(`IconBtn`), `title`로 접근성·툴팁 제공
## 게시판 (`/board`)
- 목록(페이징·태그 필터·검색[제목/본문/댓글]), 상세, 작성/수정(마크다운)
- 댓글, 태그, 관리자 **열람 제한/해제**
- **커뮤니티 / 짠테크 수다방 / 재테크 팁** 3개 게시판(카테고리)
- 목록(페이징·태그 필터·검색[제목/본문/댓글]·공지 최상단), 상세, 작성/수정(마크다운·이미지 업로드)
- 댓글, 추천(vote), 태그, **공지**(관리자), 관리자 **열람 제한/해제**, 신고
## 가계부 (`/account`)
사용자별 데이터 격리(본인 데이터만). 주요 화면(사이드바 하위 메뉴):
| 경로 | 화면 | 설명 |
|------|------|------|
| `/account` | 대시보드 | 예산 대비 지출 도넛, **분류별 파이차트**, 기간별 예산대비지출 막대(툴팁), 수입대비지출, 월별 총액, **순자산 추이** 라인차트 |
| `/account/entries` | 가계부 내역 | 월별 내역 CRUD, **검색·필터**(키워드/구분/분류/계좌/태그), 분류 인라인 추가 |
| `/account/recurrings` | 정기 거래 | 일/주/월/년 반복 규칙, 진입 시 자동 반영(중복 없이) |
| `/account/wallets` | 계좌 관리 | 은행/카드/대출/**투자** 계좌, 잔액·순자산, 상환/납부, **투자 포트폴리오** |
| `/account/categories` | 분류 관리 | 수입/지출 분류 CRUD, 이름 변경 전파, 기존 내역에서 가져오기 |
| `/account/budget` | 예산 설정 | 고정(일수 기준 자동 환산)/비고정, 예산 대비 진행률 |
| `/account` | 대시보드 | 예산 대비 지출 도넛, **분류별 파이차트**(대분류 집계), 수입대비지출(월말 예상·지난달 대비), 월별 총액, **순자산 추이**(주간) 라인차트, 일별 캘린더 |
| `/account/entries` | 가계부 내역 | 월별 내역 CRUD, **검색·필터**(키워드/구분/분류/계좌/태그), 날짜 그룹 접기/펴기, **외화 입력**(환율 자동환산), 내역→고정지출 바로 등록 |
| `/account/recurrings` | 고정 지출 | 일/주/월/년 반복 규칙, 진입 시 자동 반영(중복 없이·'확인 필요'로 생성) |
| `/account/wallets` | 계좌 관리 | 은행/**현금**/카드/대출/**투자** 계좌, 잔액·순자산, 상환/납부(원금·이자·연회비 분리), 월별 이용내역 |
| `/account/categories` | 분류 관리 | 수입/지출 분류 CRUD(**대/소분류 2단계**), 칩 그리드 관리, 순서변경, 기존 내역에서 가져오기 |
| `/account/budget` | 예산 설정 | **월별 예산**(전월/다음달 복사), 고정(일수 기준 자동 환산)/비고정, 예산 대비 진행률 |
| `/account/tags` | 태그 관리 | 사용자별 가계부 태그 |
### 잔액·순자산 모델 (표시)
- 자산(+) / 부채(−) 부호 모델. 지갑 잔액 = 개시잔액 + 수입 − 지출 − 이체출금 + 이체입금
- 자산(+) / 부채(−) 부호 모델. 현금은 은행과 동일하게 자산 합산. 지갑 잔액 = 개시잔액 + 수입 − 지출 − 이체출금 + 이체입금
- 순자산 = 총자산 − 총부채 (투자계좌는 평가금액 반영)
### 투자 포트폴리오 (계좌 관리 > 투자 탭)
### 투자 계좌 (계좌 관리 > 투자 탭)
- 투자계좌를 펼치면 [InvestPortfolio.vue](../src/views/account/InvestPortfolio.vue) 패널
- **종목별** 보유: 수량·평균매입단가·현재가(수동)·평가금액·**평가손익·수익률**·실현손익
- **매수/매도** 건별 기록(이동평균 평단 자동 산출, 매도수량 초과 검증)
- 입금은 가계부 **이체**(은행→투자)로, 예수금·주식평가는 자동 계산되어 순자산·추이에 반영
- 원화·정수 수량 기준 (해외주식 소수점/환율·자동 시세는 범위 외)
- **단순형 UI**: **투자금(투입원금) + 현재 평가액**만 입력 → 손익·수익률 자동 계산(퇴직연금·연금 등 포함). 계좌 펼침 시 투자금/평가액/손익(%) 요약 카드
- 입금은 가계부 **이체**(은행→투자)로, 평가액은 순자산·추이에 반영
- ℹ️ 백엔드는 종목 단위(매수·매도·시세 자동조회) API 를 유지하나 현재 UI 에는 노출하지 않음. 원화 기준
## 설정 화면
- 하단 내비 → 설정: 계정정보(이름·이메일·비밀번호 변경) · 테마(시스템/라이트/다크) · 앱 잠금(PIN, 1시간 유예) · 앱 버전 · App Data 삭제 · (PC)창 크기 프리셋
## 개발/빌드
```bash
npm install
npm run dev # 개발 서버
npm run build # dist/ 정적 빌드
npm run dev # 개발 서버(전체 앱 표시)
npm run build # dist/ 정적 빌드(웹 배포)
npm run cap:sync # 앱 빌드 + 안드로이드 동기화
npm run electron:build # Windows 설치파일
npm test # Vitest
```
- API 베이스 URL: `VITE_API_BASE_URL` (기본 `/api`)
- 상세: [../README.md](../README.md) · 앱 [ANDROID.md](ANDROID.md) · 배포 [../deploy/README.md](../deploy/README.md)
+81
View File
@@ -0,0 +1,81 @@
# iOS 하이브리드 앱 (Capacitor)
Vue `dist` 를 네이티브 WebView로 패키징해 iOS 앱으로 빌드합니다. 안드로이드와 **동일 코드베이스**이며, 플랫폼 분기는 `Capacitor.isNativePlatform()` / `getPlatform() === 'ios'` 로 처리합니다.
## 구성
- `capacitor.config.json` — appId `kr.sblog.slimbudget`(안드와 동일), appName `돈돼지 가계부`, webDir `dist`
- `ios/` — 네이티브 프로젝트(커밋됨, 빌드 산출물 `ios/App/build`·`Pods` 는 .gitignore)
- **Capacitor 8 = Swift Package Manager(SPM)** 사용 (CocoaPods 아님 — Podfile 불필요)
- 웹 게이팅: iOS 는 `isNativePlatform()` 이 true → 안내 페이지가 아닌 전체 앱 표시
## 사전 준비 (Mac)
- **Xcode**(App Store, 대용량) + iOS SDK/시뮬레이터 런타임
```bash
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -license accept
xcodebuild -downloadPlatform iOS # 시뮬레이터 런타임(별도 온디맨드 다운로드)
```
- **Apple Developer Program($99/년)** — 실기기 배포·App Store·Apple 로그인 Capability 에 필요. (시뮬레이터 UI 테스트는 불필요)
## 최초 셋업 (1회)
```bash
npm i @capacitor/ios
npx cap add ios # ios/ 생성
npm run build:app && npx cap sync ios
npx capacitor-assets generate --ios # 브랜드 아이콘 생성(assets/ 기준)
```
## 빌드 & 실행
```bash
npm run build:app && npx cap sync ios # 코드 변경 시마다
npx cap open ios # Xcode 열기
# 또는 CLI 로 시뮬레이터에 바로:
npx cap run ios --target <시뮬레이터-UDID>
```
시뮬레이터 목록: `xcrun simctl list devices available | grep iPhone`
### 실기기 실행 (Xcode)
1. **데이터 지원 케이블**로 아이폰 연결 → "이 컴퓨터 신뢰" (충전 전용 케이블은 인식 안 됨)
2. Xcode → **App 타겟 → Signing & Capabilities → Automatically manage signing → Team 선택**(무료 Apple ID 도 7일 한정 가능)
3. 상단 툴바 중앙 **기기 선택**(스킴 `App` 옆) → 연결된 아이폰 → **▶ Run**
4. 최초엔 아이폰이 **개발자 모드**를 요구 → Run 시도 후 **설정 → 개인정보 보호 및 보안 → 개발자 모드 → 켬 → 재시동**
5. "신뢰할 수 없는 개발자" → **설정 → 일반 → VPN 및 기기 관리 → 개발자 앱 → 신뢰**
## 필수 설정 (반영 완료)
- **번들 ID** `kr.sblog.slimbudget` (안드로이드와 동일 — App Store Connect 에서 그대로 사용)
- **Info.plist 권한 문구** (없으면 카메라/사진 접근 시 크래시):
- `NSCameraUsageDescription`(영수증·프로필·게시판 촬영)
- `NSPhotoLibraryUsageDescription` / `NSPhotoLibraryAddUsageDescription`
- **safe-area**: `index.html` 의 `viewport-fit=cover` + CSS `env(safe-area-inset-*)`.
⚠️ 전역 `box-sizing: border-box` 에서 고정 `height` 를 쓰면 safe-area 패딩이 높이에 포함돼 콘텐츠가 눌린다 → 헤더는 `min-height`+세로패딩, 하단 내비는 `height: calc(56px + env(safe-area-inset-bottom))` 로 처리(적용됨).
## Apple 로그인 (Sign in with Apple)
> App Store 심사 가이드라인 **4.8**: 제3자 소셜 로그인(구글)을 제공하면 **Apple 로그인도 동등하게 제공**해야 함. iOS 출시에 **사실상 필수**.
**구현 상태(코드 완료):**
- 프론트: `@capacitor-community/apple-sign-in` + [LoginModal.vue](../src/components/LoginModal.vue) Apple 버튼(iOS 전용) + [appleAuth.js](../src/native/appleAuth.js)
- ⚠️ 이 플러그인은 Capacitor 7 기준(`capacitor-swift-pm 7.x`) → **patch-package** 로 8.x 로 패치(`patches/`). `npm ci` 후 `postinstall` 이 자동 적용. @capawesome 구글 플러그인과 iOS 공존.
- 백엔드: `POST /api/auth/apple` — **nimbus-jose-jwt 로 Apple JWKS(공개키) 서명 검증** + iss/aud(번들ID)/exp 확인, `member.apple_id` 로 구글과 동일하게 이메일 자동연결. `app.apple-client-id`(기본 `kr.sblog.slimbudget`).
- `ios/App/App/App.entitlements` — `applesignin` 엔틀먼트 준비됨.
**작동시키려면(유료 승인 후):**
1. Apple Developer 포털: App ID `kr.sblog.slimbudget` 에 **Sign in with Apple 활성화**
2. Xcode → Signing & Capabilities → **+ Capability → "Sign in with Apple"** (준비된 `App.entitlements` 연결 + 프로비저닝)
3. 테스트: 시뮬레이터 **설정 → Apple ID 로그인** 후 `cap run ios` → Apple 버튼
- Sign in with Apple 은 **시뮬레이터에서도 작동**(카메라/푸시와 달리). 단 1~2 가 갖춰져야 함.
## 배포 (App Store)
- **의무 테스트 기간 없음** — 안드로이드(Play)의 "테스터 12명·14일"과 달리, iOS 는 **바로 심사 제출 가능**. TestFlight 은 **선택**.
- 절차: App Store Connect 앱 생성 → 아이콘·스크린샷(6.x" iPhone 규격)·App Privacy 작성 → Xcode Archive → 업로드 → 심사(보통 1~3일)
- **TestFlight**(선택): 유료 승인 후 빌드 업로드 → **케이블 없이 아이폰에 무선 설치**(데이터 케이블 없을 때 유용). 내부 100명 즉시 / 외부 10,000명(가벼운 베타 심사).
## 자주 막히는 부분
- **SPM 의존성 충돌**: 플러그인이 서로 다른 `capacitor-swift-pm`/`googlesignin-ios` 버전을 요구하면 빌드 실패. Capacitor 8 전용 플러그인을 쓰거나(patch-package) 버전 정렬 필요.
- **USB 연결이 충전만 됨**: 충전 전용 케이블 → **데이터 케이블**로 교체. 무선 디버깅도 최초 1회 유선 페어링 필요.
- **개발자 모드 메뉴가 없음**: Xcode 로 기기 설치를 **한 번 시도한 뒤** 설정에 항목이 생김(iOS 16+).
- **Developer 등록 "보류"**: 개인 등록은 Apple 이 추가 본인확인을 요청할 수 있음 → **Apple ID 이메일 확인 후 회신**해야 진행됨(자동으로 안 풀림).
- **카메라/푸시는 시뮬레이터 제한** → 실기기 필요. Apple 로그인은 시뮬레이터 가능.
## 참고
- 안드로이드 빌드: [ANDROID.md](ANDROID.md) · 아키텍처: [FRONTEND.md](FRONTEND.md)
- 앱 전용 백엔드 URL: `.env.capacitor` 의 `VITE_API_BASE_URL`(HTTPS 필수)
+51
View File
@@ -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 의 기반으로 재사용할 수 있음.*
+144 -1
View File
@@ -68,12 +68,155 @@
## 13. 안드로이드
- 에지투에지(`targetSdk 36`) 대응: **콘텐츠 루트에 시스템 바 인셋 패딩** 적용 → 헤더는 상태바 아래, 하단 내비는 소프트키 위.
## 14. PC 데스크톱 클라이언트 (Electron)
- **Windows 설치형 `.exe`** — 번들 dist 대신 **라이브 사이트(app.sblog.kr) 직접 로드** → 프론트 변경이 **재설치 없이** 앱 재시작만으로 반영. 시작 시 `clearCache()`로 옛 HTML 캐시 방지.
- 창 크기/위치/최대화 상태 **기억**(다음 실행 복원). 로그인 전 빈 사이드바 숨김(콘텐츠 중앙 정렬).
- 웹 안내페이지에 **PC 버전 다운로드 링크**(`/download/SlimBudget-Setup.exe`).
- ⚠️ 게시판 위지윅 에디터는 **Windows IME 첫 자음 중복**(tui.editor 업스트림 버그) — PC에선 감수(일반 입력칸은 정상).
## 15. 브랜드 앱 아이콘
- 기본 Capacitor 아이콘 → **브랜드(초록 #00bc7e + 흰 막대 차트) 적응형 아이콘**(전 해상도, `@capacitor/assets`).
## 16. 홈 캘린더 · 계좌 · 카드
- **홈 일별 캘린더**: 하단 인라인 패널 → **탭 시 팝업(모달)** 표시(시인성·단일 탭).
- **계좌 이용내역 월별 표시**: 계좌 펼치면 `◀ YYYY.MM ▶` + 그 달 합계, 데이터 있는 달만 이동(긴 스크롤 해소).
- **카드 알림 계좌 자동매칭 유연화**: 카드사 표기 차이 흡수(`KB/국민/국민카드/KB카드`, `농협/NH`, `비씨/BC`, `씨티/시티`) — 어떻게 등록해도 매칭.
## 17. 게시판 이미지 / 에디터
- **인라인 이미지 = base64 → 서버 업로드(DB 저장)**: `POST /api/board/images`(로그인) → `GET /api/images/{id}`(공개). **절대 URL** 반환(앱은 출처가 localhost 라 상대경로 불가). 업로드 전 1280px·JPEG 압축.
- **에디터 이미지 버튼**: Toast UI 기본 팝업 제거 → 클릭 시 **바로 파일선택→업로드**(작은 댓글창에서 팝업 OK 잘림 회피).
- **모바일 에디터 팝업(표·링크 등)**: 화면 **중앙 모달**로(Toast UI 의 `margin-left:-150px` 로 좌측 잘리던 문제 해결).
---
# (이어진 작업 2) 알림 분류 · 투자 · 상환 (2026-06-12)
## 18. 투자 계좌 단순화 (종목 관리 제거)
- 투자 계좌를 **퇴직연금식으로 통일**: 종목(매수·매도·현재가) 관리 UI 제거, **투자금(투입원금) + 현재 평가액**만 입력 → 손익·수익률 자동 계산. (위 2번의 시세 자동조회·보유 종목 표기는 본 변경으로 대체)
- 계좌 펼침: **투자금 / 평가액 / 손익(%)** 요약 카드. 투자 탭 진입 시 자동 시세갱신 제거(불필요).
## 19. 알림 자동인식 — 입금·출금 분류
- 카드: **승인=지출 / 취소·환불=수입**, 은행: **입금=수입 / 출금·이체=지출** 로 정리(은행 이체 알림이 지출로만 잡히던 문제 해결). 네이티브 1차 필터에 입금/이체/송금 키워드 추가.
- 미확인(pending) 기본 계좌종류: 수입(입금)=은행, 지출(카드결제)=카드.
## 20. 광고 푸시·문자 오등록 차단 강화
- 기존 광고 차단이 **죽은 코드**(강신호가 이미 보장된 지점이라 한 번도 실행 안 됨)였던 것 수정 → 승인/입금 신호가 있으면 광고어가 섞여도 등록되던 원인 제거.
- 광고어 2단계 분리:
- **무조건 차단**(이벤트·쿠폰·혜택·당첨·응모·프로모션·배너·세일·수신거부·바로가기 등): 거래신호가 있어도 제외.
- **약한 광고어**(적립·포인트·할인·캐시백·마일리지 등): `결제`만 있고 명확한 거래신호(승인/입출금/취소)가 **없을 때만** 제외 → 실거래 영수증의 '포인트 적립'은 보존. `결제`는 광고 문구에도 흔해 **약신호로 강등**.
## 21. 자동인식 분류 학습
- 알림 자동등록 시 **메모(가맹점) 키워드가 과거 확정 내역과 일치하면 그 분류를 자동 선반영**(가장 많이 쓴 분류, `스타벅스``스타벅스 강남점` 포함관계 매칭, 같은 수입/지출만). 안전상 **'확인 필요' 상태는 유지**(무검토 오확정 방지) — 확정이 쌓일수록 정확도 상승.
## 22. 상환/납부 수정 가능
- '상환/납부'(원금=이체 · 이자=지출 분리)를 **신규 등록뿐 아니라 수정 시에도** 선택 가능. 수정 시 상환 2건 생성 후 원본 1건 삭제(생성 성공 후 삭제 → 중간 실패 시 원본 보존). 잘못 넣은 단순 이체/지출을 **원금·이자 분리 상환으로 정정** 가능.
---
# (이어진 작업 3) 현금 계좌 · 통계 · 가계부 UX (2026-06-22)
## 23. 상환/납부 — 연회비(카드)
- 카드 상환 시 **연회비**도 분리 입력(대상이 카드일 때만 노출). 원금=이체 · 이자=지출(분류 '이자') · **연회비=지출(분류 '연회비')**. 대출엔 연회비 입력 불가.
## 24. 현금 계좌
- **계좌 관리에 '현금' 유형 추가**(이름·초기 잔액만). 순자산 계산에 **자산으로 합산**(BANK와 동일 취급).
- **가계부 입력**: 현금 선택 시 등록된 현금 계좌를 고를 수 있고, 없으면 종전처럼 '미지정'으로 기록(하위호환).
- **이체에도 현금 지원**: 출금/입금 종류에 현금 포함 → **ATM 출금(은행→현금)·현금 입금(현금→은행)** 등 현금 계좌 이체 가능. 중복되던 별도 '현금' 라디오 제거(일반 계좌 종류로 통합).
## 25. 고정 지출 — '확인 필요(pending)'로 생성
- 자동 생성되는 고정지출 내역을 **'확인 필요'**로 만들어, 실제 결제·이체를 확인한 뒤 **'확인'으로 확정**(카드 알림과 동일 흐름). 메모 없으면 제목으로 채워 식별 쉽게. → "실제 처리 여부"를 사용자가 판단 가능.
## 26. 가계부 내역 → 고정지출 바로 등록
- 내역 **수정 모달에 「🔁 이 내역을 고정지출로 등록」** 버튼. 현재 항목의 종류·금액·분류·메모·계좌로 **매월** 고정지출 생성(일자=내역 날짜, 시작=다음날로 이번 회차 중복 방지). 주기는 고정지출 화면에서 변경.
## 27. 통계 — 순자산 추이 주간화
- 순자산 추이를 **최근 3개월·주간(월요일 기준 13주)**으로 변경. 백엔드 `weeklyWalletFlow` + `netWorthTrendWeekly` 추가, `/networth/trend?unit=WEEK&weeks=N`. x축 라벨은 격주 표기(겹침 방지).
## 28. 통계 — 이번달 예상 지출(월말 추정)
- 대시보드 '수입 대비 지출'에 **예상 지출** 표시: `현재까지 지출 ÷ 경과일 × 그 달 총일수`(run-rate). 이번 달에만 표시, 예산 대비 %도 함께.
## 29. 통계 — 지난달 대비 이번달 지출
- 같은 패널에 **「지난달 대비」** 행: `±차액 (±%)`(늘면 빨강·줄면 초록), 기준 '지난달 N원' 표기. 선택 월 기준으로 직전 달과 비교.
## 30. 가계부 — 날짜별 접기/펴기
- 내역이 많을 때를 위해 **날짜 그룹 접기/펴기**. 기본은 **접힘 + 오늘만 펼침**(날짜 요약 = 날짜·건수·일 합계), 헤더 탭으로 개별 토글, 상단 **「모두 펴기/접기」** 토글. 월 이동 시 초기화, 내역 편집 시엔 펼침 상태 유지.
## 31. 앱(안드로이드) — 키보드가 입력폼 가리던 문제
- 에지투에지 환경에서 **IME(키보드) 인셋을 콘텐츠 패딩에 반영** → 키보드가 뜨면 WebView가 키보드 위로 줄어들어 입력 모달이 가려지지 않음(`windowSoftInputMode=adjustResize` + 인셋 리스너). 모든 모달 입력 공통.
## 32. 계좌 관리 — 탭 한 줄
- 계좌 종류 탭(은행/현금/카드/대출/투자)이 두 줄로 줄바꿈되던 것 → **한 줄(넘치면 가로 스크롤)**. 버튼 글자 줄바꿈 방지.
## 33. 분류 — 대분류/소분류(2단계)
- **분류를 대분류 아래 소분류로 묶기**(2단계). `account_category.parent_id` 추가(멱등 마이그레이션). 기존 분류는 모두 대분류로 시작 → 분류 관리에서 소분류로 재배치.
- **소분류 이름 기반 유지**(내역·예산·고정지출·통계 무변경) — 같은 소분류 이름 중복은 불가, 예산·통계는 **소분류 기준**.
- 분류 관리: 행별 대분류 선택·소분류 들여쓰기, 드래그 정렬 유지. 자식 있는 대분류는 소분류화·삭제 차단.
- 입력/예산/고정지출 드롭다운: **대분류 optgroup** 그룹핑(값은 소분류명).
- 대시보드 통계: 파이는 소분류 기준 + 아래 **대분류 합계** 롤업 표시.
---
# (이어진 작업 4) 구글 로그인 · 다크모드 · UI 개편 (2026-06-28)
## 34. 구글 로그인/가입 (소셜 로그인)
- **웹/PC/앱 3플랫폼 구글 로그인**: 웹·PC(Electron)는 **Google Identity Services(GIS)** ID 토큰, 앱(안드로이드)은 **네이티브 플러그인 `@capawesome/capacitor-google-sign-in`**(Credential Manager). 모두 ID 토큰을 백엔드 `POST /api/auth/google` 로 전달 → **tokeninfo 로 검증(aud=웹 클라이언트 ID)** 후 세션 발급.
- **PC(Electron) 팝업 수정**: `accounts.google.com` 팝업을 외부 브라우저로 보내 결과가 안 돌아오던 문제 → **앱 내부 자식 창**으로 열어 opener 로 credential 전달.
- **같은 이메일 자동 연결**: 구글 로그인 시 `member.google_id`(신규 컬럼)로 조회, 없으면 **검증된 동일 이메일의 기존 계정에 연결**(provider 유지 → 아이디 로그인/프로필 편집 보존), 그래도 없으면 신규 생성.
- 설정 필요: Google Cloud **웹 + Android OAuth 클라이언트**(패키지 `kr.sblog.slimbudget` + 서명 SHA-1), 서버 `.env` `GOOGLE_CLIENT_ID`.
## 35. 구글 전용 로그인 (아이디 로그인/회원가입 숨김)
- `src/config/features.js` `ID_LOGIN_ENABLED=false` 플래그로 **아이디/비번 폼·회원가입 UI 숨김**(로그인 모달·홈 랜딩). 백엔드 `/auth/login`·`/signup` 은 비상용으로 유지(복구 가능).
## 36. 브랜딩 — 돈돼지 가계부
- 앱/파비콘/PC 아이콘을 **돈돼지(투명 배경)** 로 교체, 헤더 로고·런처 라벨 정리. 런처 라벨 `돈돼지가계부`(한 줄), 앱 표시명 `돈돼지 가계부`.
## 37. PC 설치파일 (Electron)
- 산출물명 `SlimBudget-Setup.exe`**`DonDwaeji-Setup.exe`**, 다운로드 URL `/download/DonDwaeji-Setup.exe`.
- **사용자 폴더 설치**(`%LOCALAPPDATA%\Programs\DonDwaeji`, `build/installer.nsh`)로 변경 → **관리자 권한(UAC) 프롬프트 제거**. (게시자 미서명 경고/SmartScreen 은 인증서 없이는 잔존)
## 38. 게시판 공지 기능 (관리자)
- `post.notice` 컬럼(멱등 ALTER), 목록 **정렬 `notice DESC, id DESC`(공지 최상단)**, 목록·상세 **공지 배지**.
- 공지 설정은 **글 작성/수정 시 체크박스**로 일원화(커뮤니티+관리자). 상세의 공지 토글 버튼 제거, **목록 이동 아이콘** 추가.
## 39. 라이트/다크 테마
- 설정 화면에 **시스템 / 라이트 / 다크** 선택. `base.css`**`data-theme` 기반 수동 다크 + 시스템 폴백**으로 전환, `theme.js` 유틸 + ui 스토어 상태, `main.js` 시작 시 즉시 적용(깜빡임 방지).
## 40. 사이드바/헤더 개편 · 레이아웃 정리
- 사이드바 상단에 **돼지 로고 + '돈돼지 가계부'**(메뉴 텍스트 대체), 사이드바 **메뉴 아이콘**(lucide) 추가. **헤더는 현재 화면 타이틀** 표시(돼지 로고 제거).
- **PC 레이아웃**: 그리드 `'left top'` → 사이드바가 **좌측 상단(헤더 행)까지** 차지, 헤더는 우측 콘텐츠 영역만.
- **화면 내 페이지 타이틀(`<h1>`) 제거 → 헤더로 일원화**, **화면 내 뒤로가기 버튼 제거**(하단 내비 뒤로가기만 유지).
- **전 화면 본문 폭 통일**: `--page-max`(760px) + `.layout-body > *` 전역 규칙(뷰별 제각각 max-width 대체).
## 41. 화면별 액션 아이콘 위치 정리
- 가계부/예산 **month-nav**: 좌 spacer + 우 액션으로 **월 변경은 가운데, 추가·검색/필터는 우측**. 고정지출: 안내문구 아래. 커뮤니티 글쓰기: 태그 필터와 같은 줄. 분류: 지출/수입 탭과 같은 줄.
- **커뮤니티 검색 모달**: input `min-width:0` 으로 **검색 버튼이 모달 밖으로 밀리던 문제** 수정.
## 42. 자주 쓰는 내역 UX 정리
- 등록 모달의 **'자주 쓰는 내역 불러오기' 버튼·피커 모달 제거**(번거로운 3단계 해소, 저장 기능은 유지). 가계부 quick-bar 에 **'⭐ 자주 쓰는 내역' 라벨** 추가.
- 빠른등록(`useQuick`) **등록 성공과 목록 갱신 분리** — 갱신 오류가 '등록 실패'로 오인되던 케이스 방지.
## 43. 계정정보 단순화 · 구글 계정 이름 변경
- 계정정보에서 **아이디·가입유형 행 제거**(이름/이메일/권한만). **소셜(구글) 계정도 비밀번호 재인증 없이 이름만 변경** 가능(ProfileEdit), 백엔드 `updateProfile` 그대로.
## 44. PC 화면 해상도 변경
- Electron **preload(`window.desktop`) + IPC(`window:setSize`)** 추가, 설정 화면(PC)에 **창 크기 프리셋**(1024×720 ~ 1920×1080). 선택 크기는 Electron 창 상태 저장으로 다음 실행에도 유지.
## 45. 기타
- 통계 '분류별 지출/수입'을 **대분류 기준 집계**(소분류 펼침 제거로 스크롤 단축).
- 게시판 작성일: 1시간 이내 'N분 전' / 1일 이내 'N시간 전' / 그 이상 **`YYYY.MM.DD`**.
- 웹 안내 페이지 한국어 줄바꿈 **절(어절) 단위**(`word-break: keep-all` + inline-block 묶음).
---
## 운영 반영 시 체크
- 서버 `/opt/sb-backend/.env`
- `ACCOUNT_CRYPTO_KEY` — 계좌번호 암호화를 켤 때만(미설정 시 평문, 깨지지 않음).
- `app_setting` · `auth_session` 테이블 — 배포 시 `member.sql` 초기화로 자동 생성(가입 허용 플래그 / 세션 DB 백업).
- `GOOGLE_CLIENT_ID` — 구글 로그인 웹 클라이언트 ID(미설정 시 구글 버튼 미노출). **앱 구글 로그인**은 Google Cloud 에 **Android OAuth 클라이언트**(패키지 `kr.sblog.slimbudget` + 서명 SHA-1) 등록 필요.
- `member.google_id`(계정 연결) · `post.notice`(공지) 컬럼 — 기동 시 **멱등 ALTER** 자동 적용.
- `app_setting` · `auth_session` · `board_image` 테이블 — 배포 시 `member.sql`/`board.sql` 초기화로 자동 생성(가입 허용 / 세션 DB 백업 / 게시글 이미지).
- Redis `appendonly yes` — 세션 영속(이미 적용). 단, 세션 DB 이중 저장으로 Redis 유실에도 로그인 유지.
- 서버 `.env`/환경변수 `PUBLIC_URL`(기본 `https://app.sblog.kr`) — 업로드 이미지 절대 URL 베이스(앱에서 이미지 로드에 필요).
- **nginx**(`deploy/nginx-sb-front.conf` 참고): `location /``Cache-Control no-cache`(진입 HTML 최신화), `location /download/``/var/www/sb-downloads`(설치파일, 배포에 안 지워지는 영속 경로).
- 앱 버전 표기는 `package.json``version`(현재 `1.0.0`)을 단일 출처로 사용.
- **PC 설치파일**: `npm run electron:build``release-desktop/DonDwaeji-Setup.exe`(**사용자 폴더 설치=UAC 없음**, 미서명 → SmartScreen 경고 정상). 서버는 `/var/www/sb-downloads/DonDwaeji-Setup.exe` 로 업로드.
- 배포는 `main` 푸시 → CI·Deploy 워크플로 모두 **테스트 통과해야 진행**(Gitea Actions).
+142 -8
View File
@@ -1,13 +1,84 @@
// Slim Budget 데스크톱(Electron) 메인 프로세스.
// 돈돼지 가계부 데스크톱(Electron) 메인 프로세스.
// 라이브 사이트(https://app.sblog.kr)를 직접 로드한다 → 프론트 변경이 자동 반영(재설치 불필요),
// 같은 출처라 CORS 우회도 불필요. 배포된 웹 게이트가 Electron 을 감지해 안내페이지 대신 전체 앱을 띄운다.
const { app, BrowserWindow, shell } = require('electron')
//
// 로딩 UX: 예전에는 시작 시 HTTP 캐시를 통째로 비워 매번 전체 자산을 재다운로드 → 흰 화면이 오래 노출됐다.
// 이제 index.html 만 캐시버스트(?_=t)로 항상 최신을 받고, 해시된 정적 자산(JS/CSS)은 캐시를 유지해
// 재시작이 빠르다. 로드되는 동안에는 스플래시(로딩) 창을 띄워 빈 화면을 가린다.
// 또한 단일 인스턴스 잠금으로 중복 실행을 막아(두 번째 실행 시 사이트를 다시 로드하며 빈 화면이 보이던 문제),
// 이미 실행 중이면 기존 창을 활성화한다.
const { app, BrowserWindow, shell, ipcMain, Menu } = require('electron')
const fs = require('fs')
const path = require('path')
// 기본 애플리케이션 메뉴(File/Edit/View/Window) 제거 — 메인 창뿐 아니라
// 구글 로그인 팝업 등 자식 창에도 메뉴바가 안 붙도록 전역으로 비활성화.
Menu.setApplicationMenu(null)
// 네이티브 대화상자(alert/confirm) 제목은 app.getName() 을 쓴다 → package.json name('sb_pt') 대신 표기.
app.setName('돈돼지 가계부')
const APP_URL = 'https://app.sblog.kr'
// 현재 메인 창 참조(중복 실행 시 활성화 대상).
let mainWindow = null
// 중복 실행 방지: 잠금을 못 얻으면 이미 실행 중 → 새 인스턴스를 즉시 종료한다.
// (두 번째 실행에서 라이브 사이트를 다시 로드하느라 빈 화면이 보이던 현상 해결)
if (!app.requestSingleInstanceLock()) {
app.quit()
} else {
// 두 번째 실행 시도가 들어오면 기존 창을 복원/활성화.
app.on('second-instance', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
if (mainWindow.isMinimized()) mainWindow.restore()
if (!mainWindow.isVisible()) mainWindow.show()
mainWindow.focus()
}
})
}
// 창 크기/위치를 userData 에 저장해 다음 실행 때 복원 (기본 해상도 고정 대신 사용자가 맞춘 크기 유지)
function stateFile() {
return path.join(app.getPath('userData'), 'window-state.json')
}
function loadState() {
try {
return JSON.parse(fs.readFileSync(stateFile(), 'utf-8'))
} catch {
return null
}
}
function saveState(win) {
try {
if (win.isMinimized()) return
const b = win.getBounds()
fs.writeFileSync(stateFile(), JSON.stringify({ ...b, maximized: win.isMaximized() }))
} catch {
/* 저장 실패 무시 */
}
}
// 스플래시(로딩) 화면 — 라이브 사이트가 첫 렌더될 때까지 빈 흰 화면 대신 표시.
function splashHtml() {
const html = `<!doctype html><html><head><meta charset="utf-8"><title>돈돼지 가계부</title>
<style>html,body{height:100%;margin:0;display:flex;align-items:center;justify-content:center;
background:#1a1a1a;color:#eee;font-family:system-ui,'Segoe UI',sans-serif;-webkit-user-select:none;user-select:none}
.box{text-align:center}
.pig{font-size:46px;line-height:1}
.name{margin-top:10px;font-size:17px;font-weight:700;letter-spacing:-0.01em}
.spin{margin:18px auto 0;width:26px;height:26px;border:3px solid rgba(255,255,255,.2);
border-top-color:#00bc7e;border-radius:50%;animation:r .8s linear infinite}
@keyframes r{to{transform:rotate(360deg)}}
.tip{margin-top:12px;font-size:12px;opacity:.5}</style></head>
<body><div class="box"><div class="pig">🐷</div><div class="name">돈돼지 가계부</div>
<div class="spin"></div><div class="tip">불러오는 중…</div></div></body></html>`
return 'data:text/html;charset=utf-8,' + encodeURIComponent(html)
}
// 오프라인/로드 실패 시 안내 화면 (다시 시도 버튼)
function offlineHtml() {
const html = `<!doctype html><html><head><meta charset="utf-8"><title>Slim Budget</title>
const html = `<!doctype html><html><head><meta charset="utf-8"><title>돈돼지 가계부</title>
<style>html,body{height:100%;margin:0;display:flex;align-items:center;justify-content:center;
background:#1a1a1a;color:#eee;font-family:system-ui,'Segoe UI',sans-serif}
.box{text-align:center}h2{margin:0 0 8px}p{opacity:.7;margin:0}
@@ -19,30 +90,78 @@ font-size:15px;font-weight:600;cursor:pointer}</style></head>
}
function createWindow() {
const s = loadState()
const win = new BrowserWindow({
width: 1200,
height: 820,
width: s?.width || 1024,
height: s?.height || 720,
x: s?.x,
y: s?.y,
minWidth: 360,
minHeight: 560,
show: false, // 첫 렌더(ready-to-show) 전까지 숨김 → 빈 흰 화면 노출 방지(스플래시가 대신 보임)
backgroundColor: '#1a1a1a',
title: 'Slim Budget',
title: '돈돼지 가계부',
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
preload: path.join(__dirname, 'preload.cjs'),
// 한글 IME 첫 글자 중복 입력 방지 — Chromium 맞춤법 검사기가 조합을 방해하는 Electron 알려진 버그 회피
spellcheck: false,
},
})
if (s?.maximized) win.maximize()
mainWindow = win
// 로딩 스플래시(프레임 없는 작은 창) — 메인이 준비되면 닫는다.
const splash = new BrowserWindow({
width: 300,
height: 300,
frame: false,
resizable: false,
movable: false,
skipTaskbar: true,
alwaysOnTop: true,
backgroundColor: '#1a1a1a',
title: '돈돼지 가계부',
})
splash.loadURL(splashHtml())
splash.center()
// 메인 창을 한 번만 노출하고 스플래시를 정리(이벤트 중복/지연 대비).
let revealed = false
const reveal = () => {
if (revealed) return
revealed = true
if (!win.isDestroyed()) win.show()
if (!splash.isDestroyed()) splash.destroy()
}
win.once('ready-to-show', reveal)
// 안전장치: 로드가 비정상적으로 지연돼도 12초 후엔 메인 창을 띄운다.
setTimeout(reveal, 12000)
// 기본 메뉴바 제거(앱 자체 내비게이션 사용)
win.removeMenu()
win.loadURL(APP_URL)
// index.html 만 캐시버스트(?_=t) → 항상 최신 프론트. 해시 자산은 캐시 유지 → 빠른 재시작.
win.loadURL(APP_URL + '/?_=' + Date.now())
// 닫을 때 창 크기/위치 저장 → 다음 실행에 복원
win.on('close', () => saveState(win))
// 메인 프레임 로드 실패(오프라인 등) 시 안내 화면. -3(aborted, 리다이렉트 등)은 무시.
win.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame) => {
if (isMainFrame && errorCode !== -3) win.loadURL(offlineHtml())
if (isMainFrame && errorCode !== -3) {
win.loadURL(offlineHtml())
reveal() // 오프라인 안내도 보이도록 메인 창 노출
}
})
// 외부(앱 외 도메인) 링크는 기본 브라우저로, 앱 내 링크는 창에서 처리
win.webContents.setWindowOpenHandler(({ url }) => {
// 구글 로그인(GIS) 팝업은 앱 내부 자식 창으로 열어야 opener 로 로그인 결과(postMessage)가 돌아온다.
// 외부 브라우저로 보내면 계정 선택 후 credential 이 앱으로 전달되지 않아 로그인이 끊긴다.
if (/^https:\/\/accounts\.google\.com\//i.test(url)) {
return { action: 'allow' }
}
if (/^https?:\/\//i.test(url) && !url.startsWith(APP_URL)) {
shell.openExternal(url)
return { action: 'deny' }
@@ -51,7 +170,22 @@ function createWindow() {
})
}
// 데스크톱 창 크기(해상도) 변경 — 렌더러(설정 화면)에서 호출
ipcMain.handle('window:setSize', (e, w, h) => {
const win = BrowserWindow.fromWebContents(e.sender)
if (!win) return false
if (win.isMaximized()) win.unmaximize()
win.setSize(Math.max(360, Math.round(w)), Math.max(560, Math.round(h)))
win.center()
return true
})
ipcMain.handle('window:getSize', (e) => {
const win = BrowserWindow.fromWebContents(e.sender)
return win ? win.getSize() : [0, 0]
})
app.whenReady().then(() => {
// 캐시는 비우지 않는다(빠른 재시작). 최신 프론트는 index.html 캐시버스트로 보장.
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
+8
View File
@@ -0,0 +1,8 @@
// 렌더러(웹)에서 안전하게 호출할 데스크톱 전용 API 노출 (contextIsolation 유지).
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('desktop', {
// 데스크톱 창 크기(해상도) 변경
setWindowSize: (w, h) => ipcRenderer.invoke('window:setSize', w, h),
getWindowSize: () => ipcRenderer.invoke('window:getSize'),
})
+4 -5
View File
@@ -2,11 +2,10 @@
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/svg+xml" href="/logo.svg">
<link rel="alternate icon" href="/favicon.ico">
<link rel="apple-touch-icon" href="/logo.svg">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Slim Budget</title>
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>돈돼지 가계부</title>
</head>
<body>
<div id="app"></div>
+13
View File
@@ -0,0 +1,13 @@
App/build
App/Pods
App/output
App/App/public
DerivedData
xcuserdata
# Cordova plugins for Capacitor
capacitor-cordova-ios-plugins
# Generated Config files
App/App/capacitor.config.json
App/App/config.xml
+393
View File
@@ -0,0 +1,393 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
4D22ABE92AF431CB00220026 /* CapApp-SPM in Frameworks */ = {isa = PBXBuildFile; productRef = 4D22ABE82AF431CB00220026 /* CapApp-SPM */; };
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; };
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
327E84D03001463F00AB0467 /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
504EC3011FED79650016851F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4D22ABE92AF431CB00220026 /* CapApp-SPM in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
504EC2FB1FED79650016851F = {
isa = PBXGroup;
children = (
958DCC722DB07C7200EA8C5F /* debug.xcconfig */,
504EC3061FED79650016851F /* App */,
504EC3051FED79650016851F /* Products */,
);
sourceTree = "<group>";
};
504EC3051FED79650016851F /* Products */ = {
isa = PBXGroup;
children = (
504EC3041FED79650016851F /* App.app */,
);
name = Products;
sourceTree = "<group>";
};
504EC3061FED79650016851F /* App */ = {
isa = PBXGroup;
children = (
327E84D03001463F00AB0467 /* App.entitlements */,
50379B222058CBB4000EE86E /* capacitor.config.json */,
504EC3071FED79650016851F /* AppDelegate.swift */,
504EC30B1FED79650016851F /* Main.storyboard */,
504EC30E1FED79650016851F /* Assets.xcassets */,
504EC3101FED79650016851F /* LaunchScreen.storyboard */,
504EC3131FED79650016851F /* Info.plist */,
2FAD9762203C412B000D30F8 /* config.xml */,
50B271D01FEDC1A000F3C39B /* public */,
);
path = App;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
504EC3031FED79650016851F /* App */ = {
isa = PBXNativeTarget;
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */;
buildPhases = (
504EC3001FED79650016851F /* Sources */,
504EC3011FED79650016851F /* Frameworks */,
504EC3021FED79650016851F /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = App;
packageProductDependencies = (
4D22ABE82AF431CB00220026 /* CapApp-SPM */,
);
productName = App;
productReference = 504EC3041FED79650016851F /* App.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
504EC2FC1FED79650016851F /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 2660;
TargetAttributes = {
504EC3031FED79650016851F = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 504EC2FB1FED79650016851F;
packageReferences = (
D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */,
);
productRefGroup = 504EC3051FED79650016851F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
504EC3031FED79650016851F /* App */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
504EC3021FED79650016851F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */,
50B271D11FEDC1A000F3C39B /* public in Resources */,
504EC30F1FED79650016851F /* Assets.xcassets in Resources */,
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */,
504EC30D1FED79650016851F /* Main.storyboard in Resources */,
2FAD9763203C412B000D30F8 /* config.xml in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
504EC3001FED79650016851F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
504EC30B1FED79650016851F /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
504EC30C1FED79650016851F /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
504EC3101FED79650016851F /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
504EC3111FED79650016851F /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
504EC3141FED79650016851F /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = G9PDM2C4L6;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
504EC3151FED79650016851F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = G9PDM2C4L6;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
504EC3171FED79650016851F /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = kr.sblog.slimbudget.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
504EC3181FED79650016851F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = kr.sblog.slimbudget.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
504EC3141FED79650016851F /* Debug */,
504EC3151FED79650016851F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
504EC3171FED79650016851F /* Debug */,
504EC3181FED79650016851F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = "CapApp-SPM";
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
4D22ABE82AF431CB00220026 /* CapApp-SPM */ = {
isa = XCSwiftPackageProductDependency;
package = D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */;
productName = "CapApp-SPM";
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 504EC2FC1FED79650016851F /* Project object */;
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,105 @@
{
"originHash" : "275f5197fc45ecbed402538938c9f019518bacb33270bc32cf5549af9b2891e6",
"pins" : [
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "bb4002485ff867768dec13bf904a2ddb050bd1b1",
"version" : "11.3.0"
}
},
{
"identity" : "appauth-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/openid/AppAuth-iOS.git",
"state" : {
"revision" : "2781038865a80e2c425a1da12cc1327bcd56501f",
"version" : "1.7.6"
}
},
{
"identity" : "capacitor-swift-pm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ionic-team/capacitor-swift-pm.git",
"state" : {
"revision" : "2231987d85b8b0b289320b1d0947b4ae8345cde4",
"version" : "8.4.1"
}
},
{
"identity" : "googlesignin-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleSignIn-iOS",
"state" : {
"revision" : "65fb3f1aa6ffbfdc79c4e22178a55cd91561f5e9",
"version" : "8.0.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "9f183ae842be978784f2963a343682e0c46d8fb3",
"version" : "8.1.2"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b",
"version" : "3.5.0"
}
},
{
"identity" : "gtmappauth",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GTMAppAuth.git",
"state" : {
"revision" : "5d7d66f647400952b1758b230e019b07c0b4b22a",
"version" : "4.1.1"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
"identity" : "ion-ios-camera",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ionic-team/ion-ios-camera.git",
"state" : {
"revision" : "2cd4f06b0ece21b6458e23357280c51f94bfceb8",
"version" : "1.0.4"
}
},
{
"identity" : "ion-ios-filesystem",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ionic-team/ion-ios-filesystem.git",
"state" : {
"revision" : "0d81e26e828ff9582807e2339112cedf2e0fab85",
"version" : "1.1.2"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "f4a19a3c313dc2616c70bb49d29a799fb16be837",
"version" : "2.4.1"
}
}
],
"version" : 3
}
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>
+49
View File
@@ -0,0 +1,49 @@
import UIKit
import Capacitor
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
// Called when the app was launched with a url. Feel free to add additional processing here,
// but if you want the App API to support tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Called when the app was launched with an activity, including Universal Links.
// Feel free to add additional processing here, but if you want the App API to support
// tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 507 KiB

@@ -0,0 +1,14 @@
{
"images": [
{
"idiom": "universal",
"size": "1024x1024",
"filename": "AppIcon-512@2x.png",
"platform": "ios"
}
],
"info": {
"author": "xcode",
"version": 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "splash-2732x2732-2.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "splash-2732x2732-1.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "splash-2732x2732.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17105"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<imageView key="view" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" id="snD-IY-ifK">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</imageView>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="Splash" width="1366" height="1366"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14111" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
</dependencies>
<scenes>
<!--Bridge View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="CAPBridgeViewController" customModule="Capacitor" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
+70
View File
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CAPACITOR_DEBUG</key>
<string>$(CAPACITOR_DEBUG)</string>
<key>NSCameraUsageDescription</key>
<string>영수증·프로필·게시판 사진을 촬영할 때 카메라를 사용합니다.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>영수증·프로필·게시판 사진을 첨부할 때 사진 보관함에서 이미지를 불러옵니다.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>촬영한 사진을 사진 보관함에 저장할 때 사용합니다.</string>
<key>GIDClientID</key>
<string>127753308411-9617kn17bjfjmenbscfgvobjgc0l8vne.apps.googleusercontent.com</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.127753308411-9617kn17bjfjmenbscfgvobjgc0l8vne</string>
</array>
</dict>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>돈돼지 가계부</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
</plist>
+9
View File
@@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
+41
View File
@@ -0,0 +1,41 @@
// swift-tools-version: 5.9
import PackageDescription
// DO NOT MODIFY THIS FILE - managed by Capacitor CLI commands
let package = Package(
name: "CapApp-SPM",
platforms: [.iOS(.v15)],
products: [
.library(
name: "CapApp-SPM",
targets: ["CapApp-SPM"])
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.4.1"),
.package(name: "CapacitorCommunityAppleSignIn", path: "../../../node_modules/@capacitor-community/apple-sign-in"),
.package(name: "CapacitorApp", path: "../../../node_modules/@capacitor/app"),
.package(name: "CapacitorCamera", path: "../../../node_modules/@capacitor/camera"),
.package(name: "CapacitorFilesystem", path: "../../../node_modules/@capacitor/filesystem"),
.package(name: "CapacitorLocalNotifications", path: "../../../node_modules/@capacitor/local-notifications"),
.package(name: "CapacitorPreferences", path: "../../../node_modules/@capacitor/preferences"),
.package(name: "CapacitorShare", path: "../../../node_modules/@capacitor/share"),
.package(name: "CapawesomeCapacitorGoogleSignIn", path: "../../../node_modules/@capawesome/capacitor-google-sign-in")
],
targets: [
.target(
name: "CapApp-SPM",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm"),
.product(name: "CapacitorCommunityAppleSignIn", package: "CapacitorCommunityAppleSignIn"),
.product(name: "CapacitorApp", package: "CapacitorApp"),
.product(name: "CapacitorCamera", package: "CapacitorCamera"),
.product(name: "CapacitorFilesystem", package: "CapacitorFilesystem"),
.product(name: "CapacitorLocalNotifications", package: "CapacitorLocalNotifications"),
.product(name: "CapacitorPreferences", package: "CapacitorPreferences"),
.product(name: "CapacitorShare", package: "CapacitorShare"),
.product(name: "CapawesomeCapacitorGoogleSignIn", package: "CapawesomeCapacitorGoogleSignIn")
]
)
]
)
+5
View File
@@ -0,0 +1,5 @@
# CapApp-SPM
This package is used to host SPM dependencies for your Capacitor project
Do not modify the contents of it or there may be unintended consequences.
@@ -0,0 +1 @@
public let isCapacitorApp = true
+1
View File
@@ -0,0 +1 @@
CAPACITOR_DEBUG = true
+571 -7
View File
@@ -8,12 +8,18 @@
"name": "sb_pt",
"version": "1.0.0",
"dependencies": {
"@capacitor-community/apple-sign-in": "^7.1.0",
"@capacitor/android": "^8.3.4",
"@capacitor/app": "^8.1.0",
"@capacitor/camera": "^8.2.0",
"@capacitor/cli": "^8.3.4",
"@capacitor/core": "^8.3.4",
"@capacitor/filesystem": "^8.1.2",
"@capacitor/ios": "^8.4.1",
"@capacitor/local-notifications": "^8.2.0",
"@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",
"axios": "^1.16.1",
@@ -21,7 +27,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",
@@ -38,6 +45,7 @@
"jsdom": "^29.1.1",
"npm-run-all2": "^8.0.4",
"oxlint": "~1.60.0",
"patch-package": "^8.0.1",
"vite": "^8.0.8",
"vite-plugin-vue-devtools": "^8.1.1",
"vitest": "^4.1.8"
@@ -595,6 +603,19 @@
"specificity": "bin/cli.js"
}
},
"node_modules/@capacitor-community/apple-sign-in": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@capacitor-community/apple-sign-in/-/apple-sign-in-7.1.0.tgz",
"integrity": "sha512-df7cA7vfvvvilTtpJKeVvxO31W+py5t3HK9233GBD5MvgAgtGvCqeQ3pI0noNkVsKPF9B1g/po8Mph6s4bVSmw==",
"license": "MIT",
"dependencies": {
"@types/scriptjs": "0.0.2",
"scriptjs": "^2.5.9"
},
"peerDependencies": {
"@capacitor/core": ">=7.0.0"
}
},
"node_modules/@capacitor/android": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@capacitor/android/-/android-8.3.4.tgz",
@@ -1178,14 +1199,44 @@
}
},
"node_modules/@capacitor/core": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.3.4.tgz",
"integrity": "sha512-CqRQCkb6HXxcx/N7s+hHTN6ef2CmamFiRMITwm4qB840ph56mS42bzUgn6tKCP+RZjdDweiRHj9ytDDeN6jFag==",
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.4.1.tgz",
"integrity": "sha512-xqhOGLbTAYeOWK+IDUNSjQJAPapQjRHrIcgk9PYp52or9zFTaaMko31uNi16N6W+CRJ8VrRram6fOYILkBG2Hg==",
"license": "MIT",
"dependencies": {
"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/ios": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/@capacitor/ios/-/ios-8.4.1.tgz",
"integrity": "sha512-EgcAk7NYheHMTyP3CUrA65qKs4D2UEAKgw44HI6Uk3dTw6KjLQkdLOQWvbeRncHaZ2gklfOojUoc5DlSw2lhYg==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": "^8.4.0"
}
},
"node_modules/@capacitor/local-notifications": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/@capacitor/local-notifications/-/local-notifications-8.2.0.tgz",
"integrity": "sha512-fvLY0w2w4MiX+DD4+Wv4DOwOLdzKZsMDwAcRv/Juudd+QbKbn69s6cM3xVqPwAiDqfnqsY4/S8xtQD6M73wY2A==",
"license": "MIT",
"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",
@@ -1195,6 +1246,40 @@
"@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",
"integrity": "sha512-41Z5LBVZJQ/20lEbzQJUtjfm7rCUhiq0EsfKmytTfKjqUjQB4zy0zlDMowe/z5CtetL8u7WGvERtyRYil2exww==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/capawesome-team/"
},
{
"type": "opencollective",
"url": "https://opencollective.com/capawesome"
}
],
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -3492,6 +3577,12 @@
"@types/node": "*"
}
},
"node_modules/@types/scriptjs": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/@types/scriptjs/-/scriptjs-0.0.2.tgz",
"integrity": "sha512-GFrUgzGYfNX3VWZwMyzr0JrBwzLv5Kes4BQBvo6hXdRy14/GBbpCiVwwB7v1o2xIEvFf+9GyznmYhuesQQjSag==",
"license": "MIT"
},
"node_modules/@types/slice-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz",
@@ -3943,6 +4034,13 @@
"node": ">=14.6"
}
},
"node_modules/@yarnpkg/lockfile": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/abbrev": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
@@ -3995,6 +4093,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",
@@ -4709,6 +4816,19 @@
"node": "18 || 20 || >=22"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/browserslist": {
"version": "4.28.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
@@ -5100,6 +5220,25 @@
"node": ">=8"
}
},
"node_modules/call-bind": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
"integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"get-intrinsic": "^1.3.0",
"set-function-length": "^1.2.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -5113,6 +5252,23 @@
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
@@ -5162,6 +5318,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",
@@ -5358,6 +5527,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",
@@ -5762,6 +5940,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",
@@ -6071,7 +6261,6 @@
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
@@ -7355,6 +7544,19 @@
"node": ">=10"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -7372,6 +7574,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/find-yarn-workspace-root": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"micromatch": "^4.0.2"
}
},
"node_modules/flat-cache": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
@@ -7459,6 +7671,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",
@@ -7992,7 +8213,6 @@
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"es-define-property": "^1.0.0"
},
@@ -8387,6 +8607,16 @@
"node": ">=8"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
@@ -8793,6 +9023,26 @@
"dev": true,
"license": "MIT"
},
"node_modules/json-stable-stringify": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz",
"integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.8",
"call-bound": "^1.0.4",
"isarray": "^2.0.5",
"jsonify": "^0.0.1",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
@@ -8800,6 +9050,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/json-stable-stringify/node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
"dev": true,
"license": "MIT"
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -8838,6 +9095,16 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonify": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
"integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
"dev": true,
"license": "Public Domain",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/jsonparse": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
@@ -8885,6 +9152,16 @@
"node": ">=0.10.0"
}
},
"node_modules/klaw-sync": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.1.11"
}
},
"node_modules/kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@@ -9650,6 +9927,33 @@
"semver": "bin/semver"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/micromatch/node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
@@ -10377,7 +10681,6 @@
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.4"
}
@@ -10707,6 +11010,169 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/patch-package": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz",
"integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@yarnpkg/lockfile": "^1.1.0",
"chalk": "^4.1.2",
"ci-info": "^3.7.0",
"cross-spawn": "^7.0.3",
"find-yarn-workspace-root": "^2.0.0",
"fs-extra": "^10.0.0",
"json-stable-stringify": "^1.0.2",
"klaw-sync": "^6.0.0",
"minimist": "^1.2.6",
"open": "^7.4.2",
"semver": "^7.5.3",
"slash": "^2.0.0",
"tmp": "^0.2.4",
"yaml": "^2.2.2"
},
"bin": {
"patch-package": "index.js"
},
"engines": {
"node": ">=14",
"npm": ">5"
}
},
"node_modules/patch-package/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/patch-package/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/patch-package/node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/patch-package/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/patch-package/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/patch-package/node_modules/is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true,
"license": "MIT",
"bin": {
"is-docker": "cli.js"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/patch-package/node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/patch-package/node_modules/open": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0",
"is-wsl": "^2.1.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/patch-package/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -12068,6 +12534,12 @@
"node": ">=v12.22.7"
}
},
"node_modules/scriptjs": {
"version": "2.5.9",
"resolved": "https://registry.npmjs.org/scriptjs/-/scriptjs-2.5.9.tgz",
"integrity": "sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg==",
"license": "MIT"
},
"node_modules/scule": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
@@ -12132,6 +12604,24 @@
"dev": true,
"license": "ISC"
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dev": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/sharp": {
"version": "0.32.6",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz",
@@ -12326,6 +12816,16 @@
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT"
},
"node_modules/slash": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/slice-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
@@ -12520,6 +13020,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",
@@ -13106,6 +13618,19 @@
"tmp": "^0.2.0"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
@@ -14155,6 +14680,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",
@@ -14277,6 +14820,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",
+15 -4
View File
@@ -5,6 +5,7 @@
"type": "module",
"main": "electron/main.cjs",
"scripts": {
"postinstall": "patch-package",
"dev": "vite",
"build": "vite build",
"build:app": "vite build --mode capacitor",
@@ -21,7 +22,7 @@
},
"build": {
"appId": "kr.sblog.slimbudget",
"productName": "Slim Budget",
"productName": "돈돼지 가계부",
"directories": {
"output": "release-desktop"
},
@@ -30,21 +31,29 @@
],
"win": {
"target": "nsis",
"icon": "assets/icon-only.png"
"icon": "build/icon.ico",
"artifactName": "DonDwaeji-Setup.${ext}"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true
"allowToChangeInstallationDirectory": true,
"include": "build/installer.nsh"
}
},
"dependencies": {
"@capacitor-community/apple-sign-in": "^7.1.0",
"@capacitor/android": "^8.3.4",
"@capacitor/app": "^8.1.0",
"@capacitor/camera": "^8.2.0",
"@capacitor/cli": "^8.3.4",
"@capacitor/core": "^8.3.4",
"@capacitor/filesystem": "^8.1.2",
"@capacitor/ios": "^8.4.1",
"@capacitor/local-notifications": "^8.2.0",
"@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",
"axios": "^1.16.1",
@@ -52,7 +61,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",
@@ -69,6 +79,7 @@
"jsdom": "^29.1.1",
"npm-run-all2": "^8.0.4",
"oxlint": "~1.60.0",
"patch-package": "^8.0.1",
"vite": "^8.0.8",
"vite-plugin-vue-devtools": "^8.1.1",
"vitest": "^4.1.8"
@@ -0,0 +1,13 @@
diff --git a/node_modules/@capacitor-community/apple-sign-in/Package.swift b/node_modules/@capacitor-community/apple-sign-in/Package.swift
index 94246df..7d9d007 100644
--- a/node_modules/@capacitor-community/apple-sign-in/Package.swift
+++ b/node_modules/@capacitor-community/apple-sign-in/Package.swift
@@ -10,7 +10,7 @@ let package = Package(
targets: ["SignInWithApple"])
],
dependencies: [
- .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0")
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
],
targets: [
.target(
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+355 -10
View File
@@ -1,19 +1,40 @@
<script setup>
import { onMounted, onUnmounted, watch } from 'vue'
import { RouterView, useRoute } from 'vue-router'
import { computed, onErrorCaptured, onMounted, onUnmounted, ref, watch } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
import AppHeader from '@/components/layout/AppHeader.vue'
import AppSidebar from '@/components/layout/AppSidebar.vue'
import AppBottomNav from '@/components/layout/AppBottomNav.vue'
import AdBanner from '@/components/AdBanner.vue'
import LoginModal from '@/components/LoginModal.vue'
import SignupModal from '@/components/SignupModal.vue'
import WebOnlyNotice from '@/components/WebOnlyNotice.vue'
import AppDialog from '@/components/ui/AppDialog.vue'
import AppLockScreen from '@/components/AppLockScreen.vue'
import { appLock } from '@/composables/appLock'
import { appError, reportFatal } from '@/composables/appError'
import { Capacitor } from '@capacitor/core'
import { reminders } from '@/native/reminders'
import { useAuthStore } from '@/stores/auth'
import { useUiStore } from '@/stores/ui'
import { demo, exitDemo } from '@/demo'
import { ADS_ENABLED } from '@/config/features'
const auth = useAuthStore()
const ui = useUiStore()
const route = useRoute()
const router = useRouter()
// 로그인 또는 둘러보기(데모) 상태면 사이드바/전체 셸 표시
const authed = computed(() => auth.isAuthenticated || demo.on)
// 광고: 광고 기능이 켜져 있고 유료(PREMIUM) 회원이 아닐 때만 표시 (레이아웃 하단 여백도 함께 제어)
const showAds = computed(() => ADS_ENABLED && !auth.isPremium)
// 공개 법적 고지(개인정보처리방침·약관)는 웹 브라우저에서도 접근 허용 (앱 마켓 정책 URL)
const isPublicLegal = computed(() => !!route.meta.public)
function leaveDemo() {
exitDemo()
router.push('/')
}
// 앱(Capacitor 네이티브) 또는 데스크톱(Electron) 클라이언트에서 전체 이용.
// 웹(브라우저)은 안내 페이지만 노출. 개발 모드(npm run dev)는 예외로 전체 앱 표시.
@@ -25,35 +46,178 @@ function onUnauthorized() {
auth.clear()
ui.openLogin()
}
// http.js 의 403 PREMIUM_REQUIRED 처리 → 업그레이드 안내 화면으로
function onPremiumRequired() {
if (route.name !== 'upgrade') router.push({ name: 'upgrade' })
}
// ===== 네트워크 상태 배너 =====
// 오프라인이거나(서버 연결 끊김) 서버 응답이 없을 때 상단에 안내 배너를 띄운다.
const online = ref(typeof navigator !== 'undefined' ? navigator.onLine : true)
const netTrouble = ref(false) // 온라인인데 서버 응답이 없는 경우(타임아웃/서버다운) 잠깐 표시
let netTimer = null
function onOnline() { online.value = true }
function onOffline() { online.value = false }
function onNetError() {
if (typeof navigator !== 'undefined' && !navigator.onLine) { online.value = false; return }
netTrouble.value = true
clearTimeout(netTimer)
netTimer = setTimeout(() => { netTrouble.value = false }, 5000)
}
const showNetBanner = computed(() => !online.value || netTrouble.value)
const netBannerText = computed(() =>
!online.value
? '📡 인터넷 연결이 끊겼어요. 연결 상태를 확인해 주세요.'
: '⚠️ 서버에 연결할 수 없어요. 잠시 후 다시 시도해 주세요.',
)
function retryReload() {
window.location.reload()
}
// ===== 앱 잠금(PIN) =====
// 잠깐 다른 앱 다녀온 정도로는 잠그지 않고, 일정 시간 이상 백그라운드였을 때만 잠근다.
// (완전 종료/재시작은 onMounted 에서 잠금) — 카메라/갤러리/공유 등 인앱 동작은 suppress 로 제외.
const LOCK_GRACE_MS = 60 * 60 * 1000 // 1시간 미만 이탈은 잠그지 않음(잦은 PIN 입력 방지)
let backgroundedAt = 0
function onVisibility() {
if (document.hidden) {
backgroundedAt = Date.now()
} else {
const away = backgroundedAt ? Date.now() - backgroundedAt : 0
backgroundedAt = 0
if (away >= LOCK_GRACE_MS) appLock.lock() // lock() 은 suppress 창이면 건너뜀
appLock.clearSuppress() // 복귀 — 일시중지 창 해제
}
}
// 파일 선택(input[type=file]) 클릭 시 다음 백그라운드 전환을 잠금에서 제외
function onFileInputClick(e) {
const t = e.target
if (t && t.tagName === 'INPUT' && t.type === 'file') appLock.suppressLock()
}
// ===== 토스트 (뒤로가기 종료 안내 등) =====
const toastMsg = ref('')
let toastTimer = null
function onToast(e) {
toastMsg.value = e.detail || ''
clearTimeout(toastTimer)
toastTimer = setTimeout(() => { toastMsg.value = '' }, 1800)
}
onMounted(() => {
if (!isApp) return // 웹은 안내만 — 앱 전용 초기화 생략
window.addEventListener('auth:unauthorized', onUnauthorized)
window.addEventListener('premium:required', onPremiumRequired)
window.addEventListener('online', onOnline)
window.addEventListener('offline', onOffline)
window.addEventListener('net:error', onNetError)
window.addEventListener('app:toast', onToast)
// 앱 잠금: 저장된 세션으로 재진입하는 경우 시작 시 잠금(새 로그인 직후는 잠그지 않음)
if (appLock.state.enabled && appLock.hasSession()) appLock.lock()
document.addEventListener('visibilitychange', onVisibility)
document.addEventListener('click', onFileInputClick, true)
ui.loadSignupEnabled() // 회원가입 허용 여부 선로딩(랜딩/로그인 가입 버튼에 반영)
// 로그인 상태면 전체 프로필(아바타·포인트) 최신화 — 재로그인 없이 헤더 아바타 반영
if (auth.isAuthenticated) {
auth.refreshProfile().then(() => {
if (reminders.isNative()) {
reminders.scheduleExpiryReminder({
premium: auth.isPremium,
autoRenew: !!auth.user?.planAutoRenew,
expiresAt: auth.user?.planExpiresAt,
})
}
})
if (reminders.isNative()) reminders.scheduleEntryReminder(false) // 오늘 기록 여부는 홈/내역에서 보정
}
})
onUnmounted(() => {
window.removeEventListener('auth:unauthorized', onUnauthorized)
window.removeEventListener('premium:required', onPremiumRequired)
window.removeEventListener('online', onOnline)
window.removeEventListener('offline', onOffline)
window.removeEventListener('net:error', onNetError)
window.removeEventListener('app:toast', onToast)
document.removeEventListener('visibilitychange', onVisibility)
document.removeEventListener('click', onFileInputClick, true)
clearTimeout(netTimer)
clearTimeout(toastTimer)
})
// 자식 컴포넌트의 렌더/라이프사이클 오류를 잡아 흰 화면 대신 폴백 표시
onErrorCaptured((err, _instance, info) => {
reportFatal({ err, info })
return false // 상위로 전파 중단
})
onUnmounted(() => window.removeEventListener('auth:unauthorized', onUnauthorized))
// 페이지 이동 시 모바일 사이드바 자동 닫힘
watch(() => route.fullPath, () => ui.closeSidebar())
</script>
<template>
<!-- (브라우저): 안내 페이지만 -->
<WebOnlyNotice v-if="!isApp" />
<!-- 치명적 오류 폴백 ( 화면 방지) -->
<div v-if="appError.hasError" class="fatal-screen">
<div class="fatal-inner">
<div class="fatal-emoji">😵</div>
<h1 class="fatal-title">문제가 발생했어요</h1>
<p class="fatal-sub">일시적인 오류일 있어요. 새로고침 다시 시도해 주세요.</p>
<button type="button" class="fatal-btn" @click="retryReload">새로고침</button>
</div>
</div>
<template v-else>
<!-- (브라우저): 공개 법적 고지는 노출, 외엔 안내 페이지 -->
<template v-if="!isApp">
<RouterView v-if="isPublicLegal" />
<WebOnlyNotice v-else />
</template>
<!-- (Capacitor): 전체 기능 -->
<template v-else>
<div class="layout" :class="{ 'sidebar-open': ui.sidebarOpen }">
<!-- 잠금 화면 (로그인 상태 + 잠금 활성 ) -->
<AppLockScreen v-if="appLock.state.locked && auth.isAuthenticated" />
<!-- 네트워크 오류/오프라인 안내 배너 -->
<Transition name="netbar">
<div v-if="showNetBanner" class="net-banner" role="alert">
<span class="net-text">{{ netBannerText }}</span>
<button type="button" class="net-retry" @click="retryReload">새로고침</button>
</div>
</Transition>
<div class="layout" :class="{ 'sidebar-open': ui.sidebarOpen, 'no-sidebar': !authed, 'has-ad': showAds }">
<AppHeader class="layout-top" />
<!-- 로그인/둘러보기 전에는 메뉴가 없어 사이드바를 숨김( 제거·중앙 정렬) -->
<template v-if="authed">
<AppSidebar class="layout-left" />
<div class="sidebar-backdrop" @click="ui.closeSidebar()"></div>
</template>
<main class="layout-body">
<div v-if="demo.on" class="demo-bar">
<span>👀 <b>둘러보기 모드</b> · 샘플 데이터입니다.</span>
<span class="demo-actions">
<button type="button" class="db-login" @click="ui.openLogin('/account')">로그인</button>
<button type="button" class="db-exit" @click="leaveDemo">나가기</button>
</span>
</div>
<RouterView />
</main>
<!-- 하단 내비 광고 (유료 회원 제외). 앱은 320x50 모바일 배너(임시) -->
<AdBanner v-if="showAds" class="app-ad" unit="DAN-qd8As3nqJjXTQKa6" :width="320" :height="50" />
<AppBottomNav class="layout-bottom" />
</div>
<LoginModal />
<SignupModal />
<!-- 토스트 (뒤로가기 종료 안내 ) -->
<Transition name="toast">
<div v-if="toastMsg" class="app-toast" role="status">{{ toastMsg }}</div>
</Transition>
</template>
<!-- 공용 인앱 다이얼로그 (네이티브 alert/confirm/prompt 대체) -->
<AppDialog />
</template>
</template>
@@ -61,12 +225,15 @@ watch(() => route.fullPath, () => ui.closeSidebar())
.layout {
display: grid;
grid-template-areas:
'top top'
'left top'
'left body'
'bottom bottom';
grid-template-columns: 220px 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
/* 앱 셸: 뷰포트 높이에 고정하고 본문(layout-body)만 스크롤 → 상단 헤더·하단 내비 고정, 페이지 전체 스크롤 방지 */
height: 100vh; /* 폴백 */
height: 100dvh; /* 동적 뷰포트(주소창·안전영역 반영) */
overflow: hidden;
}
.layout-top {
@@ -77,18 +244,196 @@ watch(() => route.fullPath, () => ui.closeSidebar())
grid-area: left;
}
.demo-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
flex-wrap: wrap;
margin-bottom: 1rem;
padding: 0.55rem 0.9rem;
border: 1px solid hsla(160, 100%, 37%, 0.4);
border-radius: 8px;
background: hsla(160, 100%, 37%, 0.08);
font-size: 0.88rem;
}
/* 네트워크 오류/오프라인 배너 — 화면 최상단 고정 */
.net-banner {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
padding: calc(env(safe-area-inset-top, 0) + 0.5rem) 0.9rem 0.5rem;
background: #c0392b;
color: #fff;
font-size: 0.85rem;
font-weight: 600;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
}
.net-text {
word-break: keep-all;
}
.net-retry {
flex: none;
padding: 0.25rem 0.7rem;
border: 1px solid rgba(255, 255, 255, 0.7);
border-radius: 6px;
background: transparent;
color: #fff;
font-size: 0.8rem;
font-weight: 700;
cursor: pointer;
}
.net-retry:hover {
background: rgba(255, 255, 255, 0.15);
}
.netbar-enter-active,
.netbar-leave-active {
transition: transform 0.2s ease, opacity 0.2s ease;
}
.netbar-enter-from,
.netbar-leave-to {
transform: translateY(-100%);
opacity: 0;
}
/* 토스트 */
.app-toast {
position: fixed;
left: 50%;
bottom: calc(72px + env(safe-area-inset-bottom, 0));
transform: translateX(-50%);
z-index: 2600;
max-width: 80vw;
padding: 0.6rem 1.1rem;
border-radius: 999px;
background: rgba(0, 0, 0, 0.82);
color: #fff;
font-size: 0.85rem;
white-space: nowrap;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.toast-enter-active,
.toast-leave-active {
transition: opacity 0.2s ease, transform 0.2s ease;
}
.toast-enter-from,
.toast-leave-to {
opacity: 0;
transform: translateX(-50%) translateY(8px);
}
/* 치명적 오류 폴백 화면 */
.fatal-screen {
position: fixed;
inset: 0;
z-index: 4000;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
background: var(--color-background);
}
.fatal-inner {
text-align: center;
max-width: 360px;
}
.fatal-emoji {
font-size: 2.8rem;
}
.fatal-title {
margin-top: 0.6rem;
font-size: 1.2rem;
color: var(--color-heading);
}
.fatal-sub {
margin-top: 0.5rem;
font-size: 0.9rem;
opacity: 0.7;
line-height: 1.6;
word-break: keep-all;
}
.fatal-btn {
margin-top: 1.4rem;
padding: 0.7rem 1.8rem;
border: 0;
border-radius: 10px;
background: hsla(160, 100%, 37%, 1);
color: #fff;
font-size: 0.95rem;
font-weight: 700;
cursor: pointer;
}
.fatal-btn:hover {
background: hsla(160, 100%, 32%, 1);
}
.demo-actions {
display: flex;
gap: 0.4rem;
}
.demo-bar button {
padding: 0.35rem 0.8rem;
border-radius: 6px;
font-weight: 600;
font-size: 0.85rem;
cursor: pointer;
}
.db-login {
border: 1px solid hsla(160, 100%, 37%, 1);
background: hsla(160, 100%, 37%, 1);
color: #fff;
}
.db-exit {
border: 1px solid var(--color-border);
background: var(--color-background);
color: var(--color-text);
}
.layout-body {
grid-area: body;
padding: 1.5rem 2rem;
/* 하단 고정 내비(56px)+소프트키 인셋만큼 비워 콘텐츠가 가리지 않게 */
/* 하단 고정 내비(56px + 안전영역) 뒤로 마지막 내용이 가려지지 않도록 항상 여백 확보 */
padding-bottom: calc(56px + env(safe-area-inset-bottom, 0) + 1rem);
overflow: auto;
/* 그리드 1fr 트랙 안에서만 스크롤(콘텐츠로 인해 트랙이 늘어나 페이지 전체가 스크롤되지 않도록 min-height:0) */
min-height: 0;
overflow-y: auto;
overflow-x: hidden; /* 페이지 가로 스크롤 방지 — 넓은 표/차트는 각자 컨테이너에서 스크롤 */
-webkit-overflow-scrolling: touch;
}
.layout-bottom {
grid-area: bottom;
}
/* 하단 내비(56px) 바로 위 고정 광고 */
.app-ad {
position: fixed;
left: 0;
right: 0;
bottom: calc(56px + env(safe-area-inset-bottom, 0));
z-index: 999;
background: var(--color-background);
border-top: 1px solid var(--color-border);
}
/* 광고가 있을 땐 본문 하단 여백을 광고 높이(320x50 배너)만큼 더 확보 */
.layout.has-ad .layout-body {
padding-bottom: calc(56px + 62px + env(safe-area-inset-bottom, 0) + 1rem);
}
/* 로그인 전: 사이드바 없이 단일 컬럼 — 콘텐츠가 창 중앙에 오도록 */
.layout.no-sidebar {
grid-template-columns: 1fr;
grid-template-areas:
'top'
'body'
'bottom';
}
.sidebar-backdrop {
display: none;
}
+119 -8
View File
@@ -1,7 +1,8 @@
import http from './http'
import { demo, demoApi } from '@/demo'
// 백엔드 /api/account 엔드포인트와 매핑 (본인 데이터만)
export const accountApi = {
const realApi = {
list({ year, month, type, category, walletId, keyword, tagId } = {}) {
return http.get('/account/entries', { params: { year, month, type, category, walletId, keyword, tagId } })
},
@@ -14,8 +15,12 @@ export const accountApi = {
categoryStats({ type, year, month } = {}) {
return http.get('/account/category-stats', { params: { type, year, month } })
},
netWorthTrend({ months } = {}) {
return http.get('/account/networth/trend', { params: { months } })
// 통계 화면 집계 → AI 재무 코멘트(유료). 실패해도 화면을 막지 않도록 전역 배너 억제
aiComment(payload) {
return http.post('/account/ai-comment', payload, { timeout: 30000, suppressNetError: true })
},
netWorthTrend({ months, weeks, unit } = {}) {
return http.get('/account/networth/trend', { params: { months, weeks, unit } })
},
create(payload) {
return http.post('/account/entries', payload)
@@ -26,6 +31,26 @@ export const accountApi = {
remove(id) {
return http.delete(`/account/entries/${id}`)
},
// 문자/푸시 텍스트 파싱(저장 안 함) → 추가 폼 자동채움
// categoryHints/walletHints: AI가 이 후보들 중에서 분류·결제수단을 고르도록 사용자 목록 전달
parseText({ title, text, categoryHints, walletHints } = {}) {
return http.post('/account/entries/parse', { title, text, categoryHints, walletHints })
},
// 환율 조회 — 외화 결제 입력 시 통화→원화 환율 자동 채움
fxRate(from, to = 'KRW') {
return http.get('/fx/rate', { params: { from, to } })
},
// 자주 쓰는 내역(빠른 등록 템플릿)
quickEntries() {
return http.get('/account/quick-entries')
},
createQuickEntry(payload) {
return http.post('/account/quick-entries', payload)
},
removeQuickEntry(id) {
return http.delete(`/account/quick-entries/${id}`)
},
// 계좌/카드 (사용자별)
wallets() {
@@ -56,8 +81,12 @@ export const accountApi = {
},
// 예산 (사용자별)
budgets() {
return http.get('/account/budgets')
budgets({ year, month } = {}) {
return http.get('/account/budgets', { params: { year, month } })
},
// 다른 월 예산을 이 달로 복사(전월 복사 등)
copyBudget({ fromYear, fromMonth, toYear, toMonth }) {
return http.post('/account/budgets/copy', null, { params: { fromYear, fromMonth, toYear, toMonth } })
},
budgetStatus({ year, month }) {
return http.get('/account/budgets/status', { params: { year, month } })
@@ -71,8 +100,8 @@ export const accountApi = {
setExpectedIncome({ year, month, expectedIncome }) {
return http.put('/account/budgets/income', { expectedIncome }, { params: { year, month } })
},
createBudget(payload) {
return http.post('/account/budgets', payload)
createBudget(payload, { year, month } = {}) {
return http.post('/account/budgets', payload, { params: { year, month } })
},
updateBudget(id, payload) {
return http.put(`/account/budgets/${id}`, payload)
@@ -80,6 +109,19 @@ export const accountApi = {
removeBudget(id) {
return http.delete(`/account/budgets/${id}`)
},
// 1회성 예산
onetimeBudgets() {
return http.get('/account/budgets/onetime')
},
createOnetimeBudget(payload) {
return http.post('/account/budgets/onetime', payload)
},
updateOnetimeBudget(id, payload) {
return http.put(`/account/budgets/onetime/${id}`, payload)
},
removeOnetimeBudget(id) {
return http.delete(`/account/budgets/onetime/${id}`)
},
createWallet(payload) {
return http.post('/account/wallets', payload)
},
@@ -173,13 +215,82 @@ export const accountApi = {
return http.post(`/account/entries/${id}/confirm`, payload || {})
},
// 영수증 OCR (백엔드가 Google Vision 호출 → 전체 텍스트 반환)
// 데이터 복구(가져오기) — 전체 데이터 덮어쓰기
restore(payload) {
return http.post('/account/restore', payload)
},
// 가계부 데이터 전체 삭제 (내역·계좌·분류·태그·예산·고정지출·투자 전부)
deleteAllData() {
return http.delete('/account/all')
},
// 포인트 상점 — 현황 조회 / 아이템 구매
shopStatus() {
return http.get('/account/shop/status')
},
buyShopItem(item) {
return http.post('/account/shop/buy', { item })
},
// 영수증 OCR (백엔드가 Google Vision 호출 → 전체 텍스트 반환) — AI 폴백용
// suppressNetError: runOcr 이 자체 formError 로 안내하므로 전역 배너 억제
ocrReceipt(blob) {
const fd = new FormData()
fd.append('image', blob, 'receipt.jpg')
return http.post('/account/ocr/receipt', fd, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 30000,
suppressNetError: true,
})
},
// 영수증 AI 구조화 (Claude Vision) — 금액·상호·날짜·분류·계좌를 한 번에. 사용자 후보 목록을 함께 전달
// 비전 처리가 느릴 수 있어 타임아웃 상향, 실패 시 OCR 폴백이 있으므로 전역 배너 억제
parseReceiptAi(blob, { categoryHints = [], walletHints = [] } = {}) {
const fd = new FormData()
fd.append('image', blob, 'receipt.jpg')
// 멀티파트 파트 수 제한(Tomcat 기본 10) 회피: 후보를 각각 한 파트에 줄바꿈으로 합쳐 전송
if (categoryHints.length) fd.append('categoryHints', categoryHints.join('\n'))
if (walletHints.length) fd.append('walletHints', walletHints.join('\n'))
return http.post('/account/ocr/receipt-ai', fd, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 60000,
suppressNetError: true,
})
},
}
// ===== 둘러보기(데모) 모드 =====
// demo.on 이면 읽기는 더미 데이터, 쓰기는 차단(안내 메시지)으로 응답한다.
const DEMO_READS = {
list: demoApi.list,
summary: demoApi.summary,
wallets: demoApi.wallets,
netWorth: demoApi.netWorth,
categories: demoApi.categories,
recurrings: demoApi.recurrings,
quickEntries: demoApi.quickEntries,
pendingCount: demoApi.pendingCount,
tags: demoApi.tags,
walletEntries: demoApi.walletEntries,
}
const DEMO_WRITES = new Set([
'create', 'update', 'remove', 'parseText', 'createQuickEntry', 'removeQuickEntry', 'repayment',
'createRecurring', 'updateRecurring', 'removeRecurring', 'runRecurrings',
'createWallet', 'updateWallet', 'removeWallet', 'reorderWallets',
'createCategory', 'updateCategory', 'removeCategory', 'reorderCategories', 'importCategories',
'createTag', 'updateTag', 'removeTag', 'reorderTags', 'confirmEntry', 'ocrReceipt', 'parseReceiptAi', 'aiComment', 'restore', 'deleteAllData', 'buyShopItem',
'createBudget', 'updateBudget', 'removeBudget', 'setExpectedIncome',
'createHolding', 'updateHolding', 'removeHolding', 'addTrade', 'updateTrade', 'removeTrade',
'refreshPrices', 'refreshAllPrices',
])
export const accountApi = new Proxy(realApi, {
get(target, prop) {
if (demo.on) {
if (DEMO_READS[prop]) return DEMO_READS[prop]
if (DEMO_WRITES.has(prop)) return () => demoApi.block()
}
return target[prop]
},
})
+18 -4
View File
@@ -23,11 +23,22 @@ export const adminApi = {
removeTag(id) {
return http.delete(`/admin/tags/${id}`)
},
getBoardSetting() {
return http.get('/admin/board-setting')
// 기본(디폴트) 분류 관리 (관리자) — 사용자가 '기본 분류 불러오기'로 가져감
defaultCategories() {
return http.get('/admin/default-categories')
},
setBoardSetting(tagCategoryId) {
return http.put('/admin/board-setting', { tagCategoryId })
createDefaultCategory(payload) {
return http.post('/admin/default-categories', payload)
},
updateDefaultCategory(id, payload) {
return http.put(`/admin/default-categories/${id}`, payload)
},
removeDefaultCategory(id) {
return http.delete(`/admin/default-categories/${id}`)
},
reorderDefaultCategories(type, ids) {
return http.put('/admin/default-categories/reorder', { type, ids })
},
// 앱 설정 (관리자)
@@ -45,6 +56,9 @@ export const adminApi = {
updateMemberRole(id, role) {
return http.put(`/admin/members/${id}/role`, { role })
},
updateMemberPlan(id, plan) {
return http.put(`/admin/members/${id}/plan`, { plan })
},
updateMemberStatus(id, status) {
return http.put(`/admin/members/${id}/status`, { status })
},
+32
View File
@@ -11,6 +11,17 @@ export const authApi = {
login(payload) {
return http.post('/auth/login', payload)
},
// 구글 로그인/가입 — GIS에서 받은 ID 토큰 전달
googleLogin(payload) {
return http.post('/auth/google', payload)
},
googleClientId() {
return http.get('/auth/google-client-id')
},
// 애플 로그인/가입 — Sign in with Apple 에서 받은 identity token 전달
appleLogin(payload) {
return http.post('/auth/apple', payload)
},
logout() {
return http.post('/auth/logout')
},
@@ -28,4 +39,25 @@ export const authApi = {
updateProfile(payload) {
return http.put('/auth/profile', payload)
},
// 프로필 사진 변경/해제 — image 가 null/빈값이면 해제(구글 사진으로 폴백)
updateProfileImage(image) {
return http.put('/auth/profile-image', { image })
},
// 현재 사용자 활동 포인트 (최신값)
points() {
return http.get('/auth/points')
},
// 포인트 적립/차감 내역 — year+month 지정 시 해당 월, 미지정 시 최근 100건
pointHistory(year, month) {
const params = year && month ? { year, month } : {}
return http.get('/auth/point-history', { params })
},
// 현재 사용자 전체 프로필(아바타·포인트 포함) — 재로그인 없이 최신값 동기화
profile() {
return http.get('/auth/profile')
},
// 회원 탈퇴 — 계정·데이터 전체 삭제
withdraw() {
return http.delete('/auth/me')
},
}

Some files were not shown because too many files have changed in this diff Show More