- 매칭 버튼: 신청 후 "진행중" 표시, 무료 소진(429) 안내
- 헤더 알림 벨: 받은 매칭 신청 목록 + 수락/거절
- 실시간 알림: STOMP /topic/users/{id} 구독(신청/수락/거절/만료 토스트)
- 수락 시 1:1 매칭 채팅(MatchChatDialog, /topic/matches/{id})
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,37 @@
|
||||
<q-chip dense color="white" text-color="primary" icon="bolt">
|
||||
{{ tierLabel }}
|
||||
</q-chip>
|
||||
|
||||
<!-- 받은 매칭 알림 -->
|
||||
<q-btn flat round dense icon="notifications" class="q-mr-xs">
|
||||
<q-badge v-if="matching.received.length" color="red" floating>
|
||||
{{ matching.received.length }}
|
||||
</q-badge>
|
||||
<q-menu anchor="bottom right" self="top right">
|
||||
<q-list style="min-width: 260px">
|
||||
<q-item-label header>받은 매칭 신청</q-item-label>
|
||||
<q-item v-if="!matching.received.length">
|
||||
<q-item-section class="text-grey-6">받은 신청이 없어요.</q-item-section>
|
||||
</q-item>
|
||||
<q-item v-for="r in matching.received" :key="r.id">
|
||||
<q-item-section avatar>
|
||||
<q-avatar color="secondary" text-color="accent" icon="pets" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label class="text-weight-medium">{{ r.requesterDogName }}</q-item-label>
|
||||
<q-item-label caption>{{ r.requesterDogBreed || '견종 미상' }} · 매칭 신청</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<div class="row q-gutter-xs">
|
||||
<q-btn dense unelevated color="primary" label="수락" @click="onAccept(r.id)" />
|
||||
<q-btn dense flat color="grey-7" label="거절" @click="onReject(r.id)" />
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
|
||||
<q-btn flat round dense icon="account_circle">
|
||||
<q-menu>
|
||||
<q-list style="min-width: 160px">
|
||||
@@ -46,22 +77,73 @@
|
||||
<q-route-tab name="profile" :to="{ name: 'profile' }" icon="pets" label="우리 개" />
|
||||
</q-tabs>
|
||||
</q-footer>
|
||||
|
||||
<!-- 매칭 수락 시 1:1 채팅 -->
|
||||
<MatchChatDialog v-if="matchChatId != null" v-model="matchChatOpen" :match-id="matchChatId" />
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useQuasar } from 'quasar'
|
||||
import AdBanner from '@/components/AdBanner.vue'
|
||||
import MatchChatDialog from '@/components/MatchChatDialog.vue'
|
||||
import { useSubscriptionStore } from '@/stores/subscription'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useDogsStore } from '@/stores/dogs'
|
||||
import { useMatchingStore } from '@/stores/matching'
|
||||
import { ApiError } from '@/api/http'
|
||||
|
||||
const tab = ref('home')
|
||||
const subscription = useSubscriptionStore()
|
||||
const auth = useAuthStore()
|
||||
const dogs = useDogsStore()
|
||||
const matching = useMatchingStore()
|
||||
const router = useRouter()
|
||||
const $q = useQuasar()
|
||||
|
||||
const matchChatOpen = ref(false)
|
||||
const matchChatId = ref<number | null>(null)
|
||||
|
||||
onMounted(async () => {
|
||||
await dogs.loadMyDogs()
|
||||
if (auth.member && dogs.currentDogId != null) {
|
||||
matching.connect(auth.member.id, dogs.currentDogId)
|
||||
}
|
||||
})
|
||||
onBeforeUnmount(() => matching.disconnect())
|
||||
|
||||
// 수락(내가/상대가) → 1:1 채팅 열기
|
||||
watch(
|
||||
() => matching.openChatMatchId,
|
||||
(id) => {
|
||||
if (id != null) {
|
||||
matchChatId.value = id
|
||||
matchChatOpen.value = true
|
||||
matching.openChatMatchId = null
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
async function onAccept(id: number) {
|
||||
try {
|
||||
await matching.accept(id)
|
||||
} catch (e) {
|
||||
notifyError(e)
|
||||
}
|
||||
}
|
||||
async function onReject(id: number) {
|
||||
try {
|
||||
await matching.reject(id)
|
||||
} catch (e) {
|
||||
notifyError(e)
|
||||
}
|
||||
}
|
||||
function notifyError(e: unknown) {
|
||||
const message = e instanceof ApiError ? e.message : '처리 중 오류가 발생했습니다.'
|
||||
$q.notify({ color: 'negative', message, icon: 'error', position: 'top' })
|
||||
}
|
||||
|
||||
const tierLabel = computed(() => {
|
||||
switch (subscription.tier) {
|
||||
@@ -75,6 +157,7 @@ const tierLabel = computed(() => {
|
||||
})
|
||||
|
||||
async function onLogout() {
|
||||
matching.disconnect()
|
||||
await auth.logout()
|
||||
dogs.reset()
|
||||
router.replace({ name: 'login' })
|
||||
|
||||
Reference in New Issue
Block a user