feat: 백엔드 API 연동 (HTTP 클라이언트 + 홈 화면 실데이터)
- VITE_API_BASE_URL 기반 fetch 클라이언트 및 ApiError 처리 - api/spots, api/matches 타입드 모듈 - HomePage: 스팟/한줄평/메이트 실제 로드, 체크인·매칭 신청 API 연동 - 임시 세션 스토어(인증 도입 전, userId/dogId) - 로딩/에러/빈 상태 처리 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
// 공통 HTTP 클라이언트 — VITE_API_BASE_URL 기준
|
||||
const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8080'
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
public status: number,
|
||||
public code: string,
|
||||
message: string,
|
||||
) {
|
||||
super(message)
|
||||
this.name = 'ApiError'
|
||||
}
|
||||
}
|
||||
|
||||
async function request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
|
||||
const text = await res.text()
|
||||
const data = text ? JSON.parse(text) : null
|
||||
|
||||
if (!res.ok) {
|
||||
const code = (data && data.code) || 'ERROR'
|
||||
const message = (data && data.message) || `요청 실패 (${res.status})`
|
||||
throw new ApiError(res.status, code, message)
|
||||
}
|
||||
|
||||
return data as T
|
||||
}
|
||||
|
||||
export const http = {
|
||||
get: <T>(path: string) => request<T>('GET', path),
|
||||
post: <T>(path: string, body?: unknown) => request<T>('POST', path, body),
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { http } from './http'
|
||||
|
||||
export interface Match {
|
||||
id: number
|
||||
requesterDogId: number
|
||||
targetDogId: number
|
||||
status: string
|
||||
createdAt: string
|
||||
respondedAt: string | null
|
||||
}
|
||||
|
||||
export const matchesApi = {
|
||||
create: (requesterDogId: number, targetDogId: number) =>
|
||||
http.post<Match>('/api/matches', { requesterDogId, targetDogId }),
|
||||
accept: (id: number) => http.post<Match>(`/api/matches/${id}/accept`),
|
||||
reject: (id: number) => http.post<Match>(`/api/matches/${id}/reject`),
|
||||
received: (dogId: number) => http.get<Match[]>(`/api/matches/received/${dogId}`),
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { http } from './http'
|
||||
|
||||
export interface Spot {
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
latitude: number | null
|
||||
longitude: number | null
|
||||
}
|
||||
|
||||
export interface Mate {
|
||||
dogId: number
|
||||
name: string
|
||||
breed: string | null
|
||||
}
|
||||
|
||||
export interface Review {
|
||||
id: number
|
||||
tag: string | null
|
||||
content: string | null
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface CheckInResult {
|
||||
id: number
|
||||
spotId: number
|
||||
dogId: number
|
||||
expiresAt: string
|
||||
}
|
||||
|
||||
export const spotsApi = {
|
||||
list: () => http.get<Spot[]>('/api/spots'),
|
||||
mates: (spotId: number) => http.get<Mate[]>(`/api/spots/${spotId}/mates`),
|
||||
reviews: (spotId: number) => http.get<Review[]>(`/api/spots/${spotId}/reviews`),
|
||||
checkIn: (spotId: number, userId: number, dogId: number) =>
|
||||
http.post<CheckInResult>(`/api/spots/${spotId}/checkins`, { userId, dogId }),
|
||||
addReview: (spotId: number, userId: number, tag: string) =>
|
||||
http.post<Review>(`/api/spots/${spotId}/reviews`, { userId, tag }),
|
||||
}
|
||||
Reference in New Issue
Block a user