From 866abee8602105a9b6166e89b7307793d78a34d1 Mon Sep 17 00:00:00 2001 From: ByungCheol Date: Sun, 28 Jun 2026 11:24:59 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=EB=AC=B4=EB=A3=8C=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EA=B0=80=EA=B3=84=EB=B6=80=20=EB=82=B4=EC=97=AD=20?= =?UTF-8?q?=EC=A7=84=EC=9E=85=20=EC=8B=9C=20=EC=9C=A0=EB=A3=8C=20=EC=95=88?= =?UTF-8?q?=EB=82=B4=EB=A1=9C=20=ED=8A=95=EA=B8=B0=EB=8A=94=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - onMounted 의 runRecurrings(고정지출 자동반영)는 유료 전용 경로(/recurrings/run)라 무료 회원이 403 → premium:required → /upgrade 로 리다이렉트되던 문제 - isPremium 일 때만 runRecurrings 호출, '이 내역을 고정지출로 등록' 버튼도 유료에만 노출 Co-Authored-By: Claude Opus 4.8 --- src/views/account/AccountView.vue | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/views/account/AccountView.vue b/src/views/account/AccountView.vue index 7219e33..92e98d4 100644 --- a/src/views/account/AccountView.vue +++ b/src/views/account/AccountView.vue @@ -803,10 +803,13 @@ async function analyzePasted() { onMounted(async () => { // 진입 시 밀린 정기 거래를 먼저 반영한 뒤 목록을 불러온다 (중복 없이) - try { - await accountApi.runRecurrings() - } catch { - // 정기 거래 반영 실패는 가계부 조회를 막지 않는다 + // 고정지출은 유료 전용 — 무료 회원은 호출 자체를 건너뛴다(403/업그레이드 리다이렉트 방지) + if (isPremium.value) { + try { + await accountApi.runRecurrings() + } catch { + // 정기 거래 반영 실패는 가계부 조회를 막지 않는다 + } } load() loadTagOptions() @@ -1102,7 +1105,7 @@ onMounted(async () => {

{{ formInfo }}

+ + + + +
+
+ +
+ + + + +
+
{{ t }} @@ -162,6 +239,12 @@ onMounted(load)
  • + {{ c.authorName }} {{ formatRelative(c.createdAt) }} @@ -169,6 +252,16 @@ onMounted(load)
    +
    + + +

첫 댓글을 남겨보세요.

@@ -186,6 +279,29 @@ onMounted(load) + + + +
+ +
+
@@ -208,11 +324,74 @@ h1 { .meta { font-size: 0.85rem; color: var(--color-text); - opacity: 0.75; + opacity: 0.85; display: flex; + align-items: center; gap: 0.4rem; flex-wrap: wrap; } +.meta-author { + font-weight: 600; +} +/* 추천/비추천 */ +.vote-bar { + display: flex; + justify-content: center; + gap: 0.75rem; + padding: 0.5rem 0 1rem; +} +.vote-btn { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.5rem 1.4rem; + border-radius: 999px; + font-size: 0.92rem; +} +.vote-btn b { + font-weight: 700; +} +.vote-btn.up.active { + border-color: hsla(160, 100%, 37%, 1); + color: hsla(160, 100%, 37%, 1); + background: hsla(160, 100%, 37%, 0.08); +} +.vote-btn.down.active { + border-color: #e67e22; + color: #e67e22; + background: rgba(230, 126, 34, 0.08); +} +/* 추천자 */ +.recommenders { + display: flex; + align-items: center; + gap: 0.6rem; + flex-wrap: wrap; + padding: 0.6rem 0.8rem; + border: 1px solid var(--color-border); + border-radius: 8px; + background: var(--color-background-soft); + cursor: pointer; + margin-bottom: 0.5rem; +} +.recommenders:hover { + background: var(--color-background-mute); +} +.rec-avatars { + display: flex; +} +.rec-avatar { + margin-left: -6px; + box-shadow: 0 0 0 2px var(--color-background-soft); +} +.rec-avatar:first-child { + margin-left: 0; +} +.rec-label { + font-size: 0.85rem; + color: var(--color-text); + opacity: 0.85; +} .tags { display: flex; gap: 0.4rem; @@ -323,6 +502,68 @@ button.notice-btn { .comment-body { margin-top: 0.3rem; } +.comment-votes { + display: flex; + gap: 0.4rem; + margin-top: 0.4rem; +} +.cvote { + padding: 0.2rem 0.6rem; + font-size: 0.8rem; + border-radius: 999px; +} +.cvote.up.active { + border-color: hsla(160, 100%, 37%, 1); + color: hsla(160, 100%, 37%, 1); +} +.cvote.down.active { + border-color: #e67e22; + color: #e67e22; +} +/* 추천자 모달 */ +.rec-modal-backdrop { + position: fixed; + inset: 0; + z-index: 2000; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; +} +.rec-modal { + width: 100%; + max-width: 360px; + max-height: 70vh; + display: flex; + flex-direction: column; + background: var(--color-background); + border: 1px solid var(--color-border); + border-radius: 10px; + overflow: hidden; +} +.rec-modal-head { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.85rem 1rem; + border-bottom: 1px solid var(--color-border); +} +.rec-modal-list { + list-style: none; + margin: 0; + padding: 0.5rem 0; + overflow-y: auto; +} +.rec-modal-list li { + display: flex; + align-items: center; + gap: 0.6rem; + padding: 0.5rem 1rem; +} +.rec-name { + font-size: 0.95rem; +} .comment-form { margin-top: 1rem; } diff --git a/src/views/board/BoardListView.vue b/src/views/board/BoardListView.vue index 3b1f83d..2e857ff 100644 --- a/src/views/board/BoardListView.vue +++ b/src/views/board/BoardListView.vue @@ -6,6 +6,7 @@ import { formatRelative } from '@/utils/datetime' import { useAuthStore } from '@/stores/auth' import { DEFAULT_BOARD } from '@/constants/boards' import IconBtn from '@/components/ui/IconBtn.vue' +import UserAvatar from '@/components/UserAvatar.vue' const route = useRoute() const router = useRouter() @@ -170,9 +171,20 @@ onMounted(loadTags) {{ p.title }} 제한 [{{ p.commentCount }}] + 👍 {{ p.upCount }} - {{ p.authorName }} + + + + {{ p.authorName }} + + {{ p.viewCount }} {{ formatRelative(p.createdAt) }} @@ -307,7 +319,18 @@ button:disabled { text-align: center; } .col-author { - width: 100px; + width: 130px; +} +.author-cell { + display: inline-flex; + align-items: center; + gap: 0.4rem; + min-width: 0; +} +.author-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .col-date { width: 110px; @@ -318,6 +341,12 @@ button:disabled { color: hsla(160, 100%, 37%, 1); font-size: 0.85rem; } +.recommend-count { + margin-left: 0.35rem; + color: var(--color-text); + opacity: 0.7; + font-size: 0.82rem; +} .blocked-msg { color: #c0392b; font-size: 0.9rem; diff --git a/src/views/settings/AccountInfoView.vue b/src/views/settings/AccountInfoView.vue index 03754bf..e252a5e 100644 --- a/src/views/settings/AccountInfoView.vue +++ b/src/views/settings/AccountInfoView.vue @@ -1,5 +1,5 @@