feat: 강아지 관리 화면 (프로필 CRUD + 댕비티아이 편집)
- dogs 스토어: 내 강아지 로드/등록/수정/삭제, 대표견(currentDog) - ProfilePage: 강아지 등록/선택/삭제, 성향 태그 편집·저장, 회원 티어 표시 - HomePage 체크인/매칭 주체를 대표견으로 전환(미등록 시 안내) - http 클라이언트 put/del 추가, 임시 session 스토어 제거 - 로그아웃 시 강아지 상태 리셋, Dialog 플러그인 등록 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
import { http } from './http'
|
||||||
|
|
||||||
|
export interface TraitTag {
|
||||||
|
id: number
|
||||||
|
code: string
|
||||||
|
label: string
|
||||||
|
category: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Dog {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
breed: string | null
|
||||||
|
birthDate: string | null
|
||||||
|
size: string | null
|
||||||
|
gender: string | null
|
||||||
|
neutered: boolean
|
||||||
|
imageUrl: string | null
|
||||||
|
traits: TraitTag[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DogSaveRequest {
|
||||||
|
name: string
|
||||||
|
breed?: string | null
|
||||||
|
birthDate?: string | null
|
||||||
|
size?: string | null
|
||||||
|
gender?: string | null
|
||||||
|
neutered?: boolean
|
||||||
|
imageUrl?: string | null
|
||||||
|
traitIds?: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dogsApi = {
|
||||||
|
list: () => http.get<Dog[]>('/api/dogs'),
|
||||||
|
create: (body: DogSaveRequest) => http.post<Dog>('/api/dogs', body),
|
||||||
|
update: (id: number, body: DogSaveRequest) => http.put<Dog>(`/api/dogs/${id}`, body),
|
||||||
|
remove: (id: number) => http.del<void>(`/api/dogs/${id}`),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const traitTagsApi = {
|
||||||
|
list: () => http.get<TraitTag[]>('/api/trait-tags'),
|
||||||
|
}
|
||||||
@@ -55,10 +55,12 @@ import { useRouter } from 'vue-router'
|
|||||||
import AdBanner from '@/components/AdBanner.vue'
|
import AdBanner from '@/components/AdBanner.vue'
|
||||||
import { useSubscriptionStore } from '@/stores/subscription'
|
import { useSubscriptionStore } from '@/stores/subscription'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { useDogsStore } from '@/stores/dogs'
|
||||||
|
|
||||||
const tab = ref('home')
|
const tab = ref('home')
|
||||||
const subscription = useSubscriptionStore()
|
const subscription = useSubscriptionStore()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
const dogs = useDogsStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const tierLabel = computed(() => {
|
const tierLabel = computed(() => {
|
||||||
@@ -74,6 +76,7 @@ const tierLabel = computed(() => {
|
|||||||
|
|
||||||
async function onLogout() {
|
async function onLogout() {
|
||||||
await auth.logout()
|
await auth.logout()
|
||||||
|
dogs.reset()
|
||||||
router.replace({ name: 'login' })
|
router.replace({ name: 'login' })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+15
-6
@@ -74,16 +74,16 @@ import { useQuasar } from 'quasar'
|
|||||||
import { spotsApi, type Mate, type Review, type Spot } from '@/api/spots'
|
import { spotsApi, type Mate, type Review, type Spot } from '@/api/spots'
|
||||||
import { matchesApi } from '@/api/matches'
|
import { matchesApi } from '@/api/matches'
|
||||||
import { ApiError } from '@/api/http'
|
import { ApiError } from '@/api/http'
|
||||||
import { useSessionStore } from '@/stores/session'
|
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { useDogsStore } from '@/stores/dogs'
|
||||||
import NaverMap from '@/components/NaverMap.vue'
|
import NaverMap from '@/components/NaverMap.vue'
|
||||||
|
|
||||||
const $q = useQuasar()
|
const $q = useQuasar()
|
||||||
const session = useSessionStore()
|
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
const dogs = useDogsStore()
|
||||||
|
|
||||||
// 체크인/매칭 주체: 로그인한 회원. 강아지 ID 는 아직 견 관리 API 전이라 세션 기본값 사용.
|
// 체크인/매칭 주체: 로그인 회원 + 대표 강아지
|
||||||
const currentUserId = () => auth.member?.id ?? session.userId
|
const currentUserId = () => auth.member?.id ?? 0
|
||||||
|
|
||||||
const spots = ref<Spot[]>([])
|
const spots = ref<Spot[]>([])
|
||||||
const currentSpot = ref<Spot | null>(null)
|
const currentSpot = ref<Spot | null>(null)
|
||||||
@@ -99,6 +99,7 @@ onMounted(load)
|
|||||||
async function load() {
|
async function load() {
|
||||||
loadError.value = false
|
loadError.value = false
|
||||||
try {
|
try {
|
||||||
|
await dogs.loadMyDogs()
|
||||||
spots.value = await spotsApi.list()
|
spots.value = await spotsApi.list()
|
||||||
currentSpot.value = spots.value[0] ?? null
|
currentSpot.value = spots.value[0] ?? null
|
||||||
if (currentSpot.value) {
|
if (currentSpot.value) {
|
||||||
@@ -126,9 +127,13 @@ async function refreshSpot(spotId: number) {
|
|||||||
|
|
||||||
async function onCheckIn() {
|
async function onCheckIn() {
|
||||||
if (!currentSpot.value) return
|
if (!currentSpot.value) return
|
||||||
|
if (dogs.currentDogId == null) {
|
||||||
|
$q.notify({ color: 'warning', message: '먼저 우리 개를 등록해 주세요.', icon: 'pets', position: 'top' })
|
||||||
|
return
|
||||||
|
}
|
||||||
checkingIn.value = true
|
checkingIn.value = true
|
||||||
try {
|
try {
|
||||||
await spotsApi.checkIn(currentSpot.value.id, currentUserId(), session.dogId)
|
await spotsApi.checkIn(currentSpot.value.id, currentUserId(), dogs.currentDogId)
|
||||||
checkedIn.value = true
|
checkedIn.value = true
|
||||||
await refreshSpot(currentSpot.value.id)
|
await refreshSpot(currentSpot.value.id)
|
||||||
$q.notify({ color: 'primary', message: '체크인 완료! 스팟 채팅이 열렸어요.', icon: 'place', position: 'top' })
|
$q.notify({ color: 'primary', message: '체크인 완료! 스팟 채팅이 열렸어요.', icon: 'place', position: 'top' })
|
||||||
@@ -140,9 +145,13 @@ async function onCheckIn() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onMatch(mate: Mate) {
|
async function onMatch(mate: Mate) {
|
||||||
|
if (dogs.currentDogId == null) {
|
||||||
|
$q.notify({ color: 'warning', message: '먼저 우리 개를 등록해 주세요.', icon: 'pets', position: 'top' })
|
||||||
|
return
|
||||||
|
}
|
||||||
matchingDogId.value = mate.dogId
|
matchingDogId.value = mate.dogId
|
||||||
try {
|
try {
|
||||||
await matchesApi.create(session.dogId, mate.dogId)
|
await matchesApi.create(dogs.currentDogId, mate.dogId)
|
||||||
$q.notify({ color: 'positive', message: `${mate.name}에게 매칭을 신청했어요!`, icon: 'favorite', position: 'top' })
|
$q.notify({ color: 'positive', message: `${mate.name}에게 매칭을 신청했어요!`, icon: 'favorite', position: 'top' })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notifyError(e)
|
notifyError(e)
|
||||||
|
|||||||
+225
-50
@@ -1,73 +1,248 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-page class="q-pa-md">
|
<q-page class="q-pa-md">
|
||||||
<!-- 강아지 프로필 카드 -->
|
<!-- 회원 + 구독 티어 (실데이터) -->
|
||||||
<q-card flat bordered class="q-mb-md">
|
<q-card flat bordered class="q-mb-md">
|
||||||
<q-card-section class="row items-center">
|
<q-item>
|
||||||
<q-avatar size="64px" color="secondary" text-color="accent" icon="pets" />
|
<q-item-section avatar>
|
||||||
<div class="q-ml-md">
|
<q-avatar color="primary" text-color="white" icon="person" />
|
||||||
<div class="text-h6">몽이</div>
|
</q-item-section>
|
||||||
<div class="text-caption text-grey-7">푸들 · 3살 · 소형견</div>
|
<q-item-section>
|
||||||
</div>
|
<q-item-label class="text-weight-medium">{{ auth.member?.nickname }}</q-item-label>
|
||||||
</q-card-section>
|
<q-item-label caption>{{ auth.member?.email }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-chip dense color="primary" text-color="white" icon="bolt">
|
||||||
|
{{ auth.member?.subscriptionTier }}
|
||||||
|
</q-chip>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
<!-- 댕비티아이(성향 태그) -->
|
<!-- 강아지 없을 때 -->
|
||||||
<div class="text-subtitle1 text-weight-bold q-mb-sm">댕비티아이 · 성향 태그</div>
|
<q-card v-if="!dogs.myDogs.length" flat bordered class="q-pa-lg text-center text-grey-6">
|
||||||
<div class="row q-gutter-sm q-mb-lg">
|
<q-icon name="pets" size="40px" color="secondary" />
|
||||||
|
<div class="q-mt-sm q-mb-md">아직 등록된 강아지가 없어요.</div>
|
||||||
|
<q-btn color="primary" unelevated icon="add" label="우리 개 등록" @click="openCreate" />
|
||||||
|
</q-card>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<!-- 강아지 선택 (여러 마리) -->
|
||||||
|
<div class="row items-center q-mb-sm">
|
||||||
|
<div class="text-subtitle1 text-weight-bold">우리 개</div>
|
||||||
|
<q-space />
|
||||||
|
<q-btn dense flat color="primary" icon="add" label="추가" @click="openCreate" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="dogs.myDogs.length > 1" class="row q-gutter-sm q-mb-md">
|
||||||
<q-chip
|
<q-chip
|
||||||
v-for="trait in traits"
|
v-for="d in dogs.myDogs"
|
||||||
:key="trait.code"
|
:key="d.id"
|
||||||
:color="trait.selected ? 'primary' : 'grey-3'"
|
|
||||||
:text-color="trait.selected ? 'white' : 'grey-7'"
|
|
||||||
clickable
|
clickable
|
||||||
@click="trait.selected = !trait.selected"
|
:color="d.id === dogs.currentDogId ? 'primary' : 'grey-3'"
|
||||||
|
:text-color="d.id === dogs.currentDogId ? 'white' : 'grey-8'"
|
||||||
|
@click="dogs.currentDogId = d.id"
|
||||||
>
|
>
|
||||||
{{ trait.label }}
|
{{ d.name }}
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 구독 티어 전환 (개발용 데모) -->
|
<!-- 대표 강아지 프로필 -->
|
||||||
<q-card flat bordered>
|
<q-card v-if="current" flat bordered class="q-mb-md">
|
||||||
<q-card-section>
|
<q-card-section class="row items-center">
|
||||||
<div class="text-subtitle2 q-mb-sm">구독 티어 (데모)</div>
|
<q-avatar size="64px" color="secondary" text-color="accent" icon="pets" />
|
||||||
<q-btn-toggle
|
<div class="q-ml-md">
|
||||||
v-model="tier"
|
<div class="text-h6">{{ current.name }}</div>
|
||||||
spread
|
<div class="text-caption text-grey-7">
|
||||||
no-caps
|
{{ current.breed || '견종 미상' }}<span v-if="current.size"> · {{ sizeLabel(current.size) }}</span>
|
||||||
toggle-color="primary"
|
|
||||||
color="grey-3"
|
|
||||||
text-color="grey-8"
|
|
||||||
:options="[
|
|
||||||
{ label: 'FREE', value: 'FREE' },
|
|
||||||
{ label: '광고제거', value: 'AD_FREE' },
|
|
||||||
{ label: 'PREMIUM', value: 'PREMIUM' },
|
|
||||||
]"
|
|
||||||
@update:model-value="onTierChange"
|
|
||||||
/>
|
|
||||||
<div class="text-caption text-grey-6 q-mt-sm">
|
|
||||||
FREE 에서만 하단 배너 광고가 노출됩니다.
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-space />
|
||||||
|
<q-btn dense flat round color="grey-6" icon="delete_outline" @click="onDelete" />
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
|
<!-- 댕비티아이 성향 태그 편집 -->
|
||||||
|
<div class="text-subtitle1 text-weight-bold q-mb-sm">댕비티아이 · 성향 태그</div>
|
||||||
|
<div class="row q-gutter-sm q-mb-md">
|
||||||
|
<q-chip
|
||||||
|
v-for="tag in dogs.traitTags"
|
||||||
|
:key="tag.id"
|
||||||
|
clickable
|
||||||
|
:color="selected.has(tag.id) ? 'primary' : 'grey-3'"
|
||||||
|
:text-color="selected.has(tag.id) ? 'white' : 'grey-7'"
|
||||||
|
@click="toggle(tag.id)"
|
||||||
|
>
|
||||||
|
{{ tag.label }}
|
||||||
|
</q-chip>
|
||||||
|
</div>
|
||||||
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
unelevated
|
||||||
|
icon="save"
|
||||||
|
label="성향 저장"
|
||||||
|
:loading="saving"
|
||||||
|
:disable="!dirty"
|
||||||
|
@click="saveTraits"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 강아지 등록 다이얼로그 -->
|
||||||
|
<q-dialog v-model="createOpen">
|
||||||
|
<q-card style="min-width: 300px">
|
||||||
|
<q-card-section class="text-subtitle1 text-weight-bold">우리 개 등록</q-card-section>
|
||||||
|
<q-card-section class="q-gutter-sm">
|
||||||
|
<q-input v-model="form.name" label="이름 *" dense outlined />
|
||||||
|
<q-input v-model="form.breed" label="견종" dense outlined />
|
||||||
|
<q-select
|
||||||
|
v-model="form.size"
|
||||||
|
:options="sizeOptions"
|
||||||
|
label="크기"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn flat label="취소" v-close-popup />
|
||||||
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
unelevated
|
||||||
|
label="등록"
|
||||||
|
:loading="creating"
|
||||||
|
:disable="!form.name"
|
||||||
|
@click="onCreate"
|
||||||
|
/>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import { useSubscriptionStore, type SubscriptionTier } from '@/stores/subscription'
|
import { useQuasar } from 'quasar'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { useDogsStore } from '@/stores/dogs'
|
||||||
|
import { ApiError } from '@/api/http'
|
||||||
|
|
||||||
const subscription = useSubscriptionStore()
|
const $q = useQuasar()
|
||||||
const tier = ref<SubscriptionTier>(subscription.tier)
|
const auth = useAuthStore()
|
||||||
|
const dogs = useDogsStore()
|
||||||
|
|
||||||
const traits = ref([
|
const current = computed(() => dogs.currentDog)
|
||||||
{ code: 'FEAR_BIG_DOG', label: '대형견 무서워함', selected: true },
|
const selected = ref<Set<number>>(new Set())
|
||||||
{ code: 'TUG_LOVER', label: '터그놀이 매니아', selected: true },
|
const saving = ref(false)
|
||||||
{ code: 'SOCIALIZING', label: '사회화 훈련 중', selected: false },
|
const createOpen = ref(false)
|
||||||
{ code: 'LOVE_PEOPLE', label: '사람 좋아함', selected: true },
|
const creating = ref(false)
|
||||||
{ code: 'SHY', label: '낯가림 있음', selected: false },
|
|
||||||
])
|
|
||||||
|
|
||||||
function onTierChange(next: SubscriptionTier) {
|
const sizeOptions = [
|
||||||
subscription.setTier(next)
|
{ label: '소형견', value: 'SMALL' },
|
||||||
|
{ label: '중형견', value: 'MEDIUM' },
|
||||||
|
{ label: '대형견', value: 'LARGE' },
|
||||||
|
]
|
||||||
|
const form = reactive<{ name: string; breed: string; size: string | null }>({
|
||||||
|
name: '',
|
||||||
|
breed: '',
|
||||||
|
size: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
await Promise.all([dogs.loadMyDogs(), dogs.loadTraitTags()])
|
||||||
|
} catch (e) {
|
||||||
|
notifyError(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 대표 강아지가 바뀌면 선택된 성향 동기화
|
||||||
|
watch(
|
||||||
|
current,
|
||||||
|
(d) => {
|
||||||
|
selected.value = new Set(d?.traits.map((t) => t.id) ?? [])
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
// 변경 감지(저장 버튼 활성화)
|
||||||
|
const dirty = computed(() => {
|
||||||
|
const orig = new Set(current.value?.traits.map((t) => t.id) ?? [])
|
||||||
|
if (orig.size !== selected.value.size) return true
|
||||||
|
for (const id of selected.value) if (!orig.has(id)) return true
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
function toggle(id: number) {
|
||||||
|
const next = new Set(selected.value)
|
||||||
|
next.has(id) ? next.delete(id) : next.add(id)
|
||||||
|
selected.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
function sizeLabel(size: string) {
|
||||||
|
return sizeOptions.find((o) => o.value === size)?.label ?? size
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveTraits() {
|
||||||
|
if (!current.value) return
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
await dogs.updateDog(current.value.id, {
|
||||||
|
name: current.value.name,
|
||||||
|
breed: current.value.breed,
|
||||||
|
birthDate: current.value.birthDate,
|
||||||
|
size: current.value.size,
|
||||||
|
gender: current.value.gender,
|
||||||
|
neutered: current.value.neutered,
|
||||||
|
imageUrl: current.value.imageUrl,
|
||||||
|
traitIds: [...selected.value],
|
||||||
|
})
|
||||||
|
$q.notify({ color: 'primary', message: '성향을 저장했어요.', icon: 'check', position: 'top' })
|
||||||
|
} catch (e) {
|
||||||
|
notifyError(e)
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCreate() {
|
||||||
|
form.name = ''
|
||||||
|
form.breed = ''
|
||||||
|
form.size = null
|
||||||
|
createOpen.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onCreate() {
|
||||||
|
creating.value = true
|
||||||
|
try {
|
||||||
|
await dogs.createDog({ name: form.name, breed: form.breed || null, size: form.size })
|
||||||
|
createOpen.value = false
|
||||||
|
$q.notify({ color: 'primary', message: `${form.name} 등록 완료!`, icon: 'pets', position: 'top' })
|
||||||
|
} catch (e) {
|
||||||
|
notifyError(e)
|
||||||
|
} finally {
|
||||||
|
creating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDelete() {
|
||||||
|
if (!current.value) return
|
||||||
|
$q.dialog({
|
||||||
|
title: '삭제',
|
||||||
|
message: `${current.value.name}을(를) 삭제할까요?`,
|
||||||
|
cancel: true,
|
||||||
|
persistent: true,
|
||||||
|
}).onOk(async () => {
|
||||||
|
try {
|
||||||
|
await dogs.removeDog(current.value!.id)
|
||||||
|
$q.notify({ color: 'grey-8', message: '삭제되었습니다.', position: 'top' })
|
||||||
|
} catch (e) {
|
||||||
|
notifyError(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyError(e: unknown) {
|
||||||
|
const message = e instanceof ApiError ? e.message : '요청 중 오류가 발생했습니다.'
|
||||||
|
$q.notify({ color: 'negative', message, icon: 'error', position: 'top' })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { dogsApi, traitTagsApi, type Dog, type DogSaveRequest, type TraitTag } from '@/api/dogs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 내 강아지 상태. 로그인 후 loadMyDogs 로 채운다.
|
||||||
|
* currentDogId 는 체크인/매칭의 주체(대표 강아지).
|
||||||
|
*/
|
||||||
|
export const useDogsStore = defineStore('dogs', () => {
|
||||||
|
const myDogs = ref<Dog[]>([])
|
||||||
|
const traitTags = ref<TraitTag[]>([])
|
||||||
|
const currentDogId = ref<number | null>(null)
|
||||||
|
|
||||||
|
const currentDog = computed(() => myDogs.value.find((d) => d.id === currentDogId.value) ?? null)
|
||||||
|
|
||||||
|
async function loadMyDogs(): Promise<void> {
|
||||||
|
myDogs.value = await dogsApi.list()
|
||||||
|
if (currentDogId.value == null || !myDogs.value.some((d) => d.id === currentDogId.value)) {
|
||||||
|
currentDogId.value = myDogs.value[0]?.id ?? null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTraitTags(): Promise<void> {
|
||||||
|
if (traitTags.value.length === 0) {
|
||||||
|
traitTags.value = await traitTagsApi.list()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createDog(body: DogSaveRequest): Promise<Dog> {
|
||||||
|
const dog = await dogsApi.create(body)
|
||||||
|
await loadMyDogs()
|
||||||
|
currentDogId.value = dog.id
|
||||||
|
return dog
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateDog(id: number, body: DogSaveRequest): Promise<Dog> {
|
||||||
|
const dog = await dogsApi.update(id, body)
|
||||||
|
await loadMyDogs()
|
||||||
|
return dog
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeDog(id: number): Promise<void> {
|
||||||
|
await dogsApi.remove(id)
|
||||||
|
await loadMyDogs()
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset(): void {
|
||||||
|
myDogs.value = []
|
||||||
|
currentDogId.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
myDogs,
|
||||||
|
traitTags,
|
||||||
|
currentDogId,
|
||||||
|
currentDog,
|
||||||
|
loadMyDogs,
|
||||||
|
loadTraitTags,
|
||||||
|
createDog,
|
||||||
|
updateDog,
|
||||||
|
removeDog,
|
||||||
|
reset,
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 임시 세션 스토어 (인증 도입 전까지 사용).
|
|
||||||
* local 시드 데이터 기준: 내 유저=1, 내 강아지(몽이)=1.
|
|
||||||
* 인증(4번) 도입 시 로그인 응답으로 대체.
|
|
||||||
*/
|
|
||||||
export const useSessionStore = defineStore('session', () => {
|
|
||||||
const userId = ref<number>(1)
|
|
||||||
const dogId = ref<number>(1)
|
|
||||||
return { userId, dogId }
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user