Skip to content
Merged
13 changes: 9 additions & 4 deletions src/api/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,20 @@ api.interceptors.request.use((config: InternalAxiosRequestConfig) => {

let refreshPromise: ReturnType<typeof postRefresh> | null = null;

type ApiResponseWithFlags = {
code?: string;
message?: string;
success?: boolean;
isSuccess?: boolean;
};

api.interceptors.response.use(
(res) => {
const data = res.data;
if (isApiResponse(data)) {
const responseData: ApiResponseWithFlags = data;
const failed =
(typeof (data as any).success === "boolean" &&
(data as any).success === false) ||
(typeof (data as any).isSuccess === "boolean" &&
(data as any).isSuccess === false);
responseData.success === false || responseData.isSuccess === false;

if (failed) {
return Promise.reject({
Expand Down
12 changes: 9 additions & 3 deletions src/api/bookings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { api } from "./axios";

type ApiBookingStatus = "CONFIRMED" | "COMPLETED" | "CANCELED";
type ApiBookingStatus = "PENDING" | "CONFIRMED" | "COMPLETED" | "CANCELED";

interface Booking {
bookingId: number;
Expand All @@ -9,7 +9,8 @@ interface Booking {
bookingDate: string;
bookingTime: string;
partySize: number;
amount: number;
tableNumbers: string;
amount: number | null;
paymentMethod: string;
status: ApiBookingStatus;
}
Expand All @@ -23,11 +24,16 @@ interface BookingResponse {
isLast: boolean;
}

type GetBookingParams = {
page: number;
status?: ApiBookingStatus;
};

export const getBookings = async (
status?: ApiBookingStatus,
page: number = 1,
): Promise<BookingResponse> => {
const params: any = { page };
const params: GetBookingParams = { page };
if (status) params.status = status;

const response = await api.get<{ result: BookingResponse }>(
Expand Down
13 changes: 11 additions & 2 deletions src/api/owner/menus.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import axios from "axios";
import { api } from "../axios";
import type { ApiResponse } from "@/types/api";

Expand Down Expand Up @@ -96,12 +97,20 @@ export const deleteMenuImage = async (
`/api/v1/stores/${storeId}/menus/${menuId}/image`,
);
return res.data;
} catch (err: any) {
} catch (err: unknown) {
console.error("deleteMenuImage error", err);
if (axios.isAxiosError(err)) {
return {
isSuccess: false,
code: "_MENU_IMAGE_DELETE_FAILED",
message: err?.response?.data?.message || "이미지 삭제 실패",
result: { deletedImageKey: "" },
};
}
return {
isSuccess: false,
code: "_MENU_IMAGE_DELETE_FAILED",
message: err?.response?.data?.message || "이미지 삭제 실패",
message: "이미지 삭제 실패",
result: { deletedImageKey: "" },
};
}
Expand Down
14 changes: 10 additions & 4 deletions src/api/owner/storeLayout.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ApiResponse } from "@/types/api";
import { api } from "../axios";
import type { SeatsType } from "@/types/table";
import axios from "axios";

export interface LayoutTable {
tableId: number;
Expand Down Expand Up @@ -53,8 +54,8 @@ export const getActiveLayout = async (
return null;
}
return null;
} catch (e: any) {
if (e.response?.status === 404) {
} catch (e: unknown) {
if (axios.isAxiosError(e) && e.response?.status === 404) {
console.error("가게를 찾을 수 없음");
} else {
console.error(e);
Expand Down Expand Up @@ -97,8 +98,13 @@ export const createTable = async (
}
console.error("테이블 생성 실패 응답:", res.data);
return null;
} catch (e: any) {
console.error("테이블 생성 실패:", e?.response?.data ?? e);
} catch (e: unknown) {
if (axios.isAxiosError(e)) {
console.error("테이블 생성 실패:", e?.response?.data ?? e);
} else {
console.error("테이블 생성 실패:", e);
}

return null;
}
};
Expand Down
6 changes: 5 additions & 1 deletion src/api/owner/stores.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { api } from "@/api/axios";
import type { ApiResponse } from "@/types/api";
import type { UpdateStoreResponse } from "@/types/store";

interface StoreDetail {
storeId: number;
Expand Down Expand Up @@ -70,7 +71,10 @@ export function updateStore(
phoneNumber: string;
},
) {
return api.patch<ApiResponse<any>>(`/api/v1/stores/${storeId}`, body);
return api.patch<ApiResponse<UpdateStoreResponse>>(
`/api/v1/stores/${storeId}`,
body,
);
}

export function updateBusinessHours(
Expand Down
2 changes: 1 addition & 1 deletion src/api/owner/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface DeleteTableImageResult {
tableId: number;
}

interface PatchTableRequest {
export interface PatchTableRequest {
tableNumber?: string;
minSeatCount?: number;
maxSeatCount?: number;
Expand Down
9 changes: 7 additions & 2 deletions src/components/auth/ChangePasswordDiaLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "../ui/button";
import { X } from "lucide-react";
import axios from "axios";

const schema = z
.object({
Expand Down Expand Up @@ -43,8 +44,12 @@ export function ChangePasswordDialog({
form.reset();
onOpenChange(false);
},
onError: (e: any) => {
const msg = e?.response?.data?.message ?? "비밀번호 변경에 실패했습니다.";
onError: (e: unknown) => {
let msg = "비밀번호 변경에 실패했습니다.";

if (axios.isAxiosError(e)) {
msg = e.response?.data?.message ?? msg;
}
if (typeof msg === "string" && /현재|기존|일치|틀렸/.test(msg)) {
form.setError("currentPassword", { type: "server", message: msg });
return;
Expand Down
2 changes: 1 addition & 1 deletion src/components/auth/SignupDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function SignupDialog({

return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="sm:max-w-[500px] max-h-[90vh] overflow-y-auto">
<DialogContent className="sm:max-w-125 max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-center text-2xl font-bold">
회원가입
Expand Down
12 changes: 9 additions & 3 deletions src/components/auth/WithdrawDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { Button } from "../ui/button";
import { X } from "lucide-react";
import { useState } from "react";
import { logout as performLogout } from "@/api/auth";
import axios from "axios";

function isWithdrawBlockByBookings(e: any) {
function isWithdrawBlockByBookings(e: unknown) {
if (!axios.isAxiosError(e)) return false;
const msg = e?.response?.data?.message;
const result = e?.response?.data?.result;
const code = e?.response?.data?.code;
Expand Down Expand Up @@ -56,12 +58,16 @@ export function WithdrawDialog({
onOpenChange(false);
nav("/", { replace: true });
},
onError: (e: any) => {
onError: (e: unknown) => {
if (isWithdrawBlockByBookings(e)) {
setBlocked(true);
return;
}
alert(e?.response?.data?.message ?? "회원 탈퇴에 실패했습니다.");
let msg = "회원 탈퇴에 실패했습니다";
if (axios.isAxiosError(e)) {
msg = e?.response?.data?.message ?? msg;
}
alert(msg);
},
});

Expand Down
2 changes: 1 addition & 1 deletion src/components/customer-support/SupportHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function SupportHero() {

return (
<>
<section className="bg-gradient-to-r from-blue-600 to-blue-700 text-white">
<section className="bg-linear-to-r from-blue-600 to-blue-700 text-white">
<div className="max-w-[1920px] mx-auto p-8 md:p-16 text-center">
<h2 className="text-white mb-4">무엇을 도와드릴까요?</h2>
<p className="text-blue-100 max-w-2xl mx-auto mb-6 break-keep">
Expand Down
77 changes: 40 additions & 37 deletions src/components/map/KakaoMap.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { loadKakaoMapSdk } from "@/lib/kakao";
import type {
KakaoInfoWindowInstance,
KaKaoMapInstance,
KakaoMarkerInstance,
LatLng,
MarkerWithLocation,
} from "@/types/map";
import type { RestaurantSummary } from "@/types/store";
import { useEffect, useMemo, useRef, useState } from "react";

type LatLng = { lat: number; lng: number };
type MarkerWithLocation = RestaurantSummary & { location: LatLng };

type Props = {
center: LatLng;
markers: RestaurantSummary[];
Expand All @@ -14,22 +18,17 @@ type Props = {
defaultLevel?: number;
selectedLevel?: number;
};
declare global {
interface Window {
kakao: any;
}
}

const toNum = (v: unknown) => {
const n = typeof v === "string" ? parseFloat(v) : Number(v);
return Number.isFinite(n) ? n : null;
};

const normalizeLatLng = (loc: any): LatLng | null => {
if (!loc) return null;
const normalizeLatLng = (loc: unknown): LatLng | null => {
if (!loc || typeof loc !== "object") return null;
const maybeLoc = loc as { lat?: unknown; lng?: unknown };

let lat = toNum(loc.lat);
let lng = toNum(loc.lng);
let lat = toNum(maybeLoc.lat);
let lng = toNum(maybeLoc.lng);

if (lat == null || lng == null) return null;

Expand All @@ -52,17 +51,17 @@ export default function KakaoMap({
selectedLevel,
}: Props) {
const containerRef = useRef<HTMLDivElement | null>(null);
const mapRef = useRef<any>(null);
const markersRef = useRef<Map<number, any>>(new Map());
const infoRef = useRef<any>(null);
const mapRef = useRef<KaKaoMapInstance | null>(null);
const markersRef = useRef<Map<number, KakaoMarkerInstance>>(new Map());
const infoRef = useRef<KakaoInfoWindowInstance | null>(null);
const prevSelectedIdRef = useRef<number | null>(null);

const safeMarkers = useMemo<MarkerWithLocation[]>(() => {
return markers
.map((m) => {
const norm = normalizeLatLng((m as any).location);
.map((marker) => {
const norm = normalizeLatLng((marker as MarkerWithLocation).location);
if (!norm) return null;
return { ...m, location: norm } as MarkerWithLocation;
return { ...marker, location: norm } as MarkerWithLocation;
})
.filter(Boolean) as MarkerWithLocation[];
}, [markers]);
Expand All @@ -76,7 +75,9 @@ export default function KakaoMap({

try {
mapRef.current.relayout();
} catch {}
} catch {
//지도 초기화 타이밍에서 발생 가능
}
};

//1. 지도 최초 1회 생성
Expand All @@ -87,10 +88,11 @@ export default function KakaoMap({
try {
await loadKakaoMapSdk();
if (cancelled) return;

setSdkReady(true);

const kakao = window.kakao;
if (!containerRef.current) return;
if (!kakao?.maps || !containerRef.current) return;
if (mapRef.current) return;

const options = {
Expand All @@ -112,7 +114,7 @@ export default function KakaoMap({
return () => {
cancelled = true;
};
}, [defaultLevel]);
}, [defaultLevel, center.lat, center.lng]);

// 2. 컨테이너 사이즈 변하면 relayout
useEffect(() => {
Expand Down Expand Up @@ -160,21 +162,22 @@ export default function KakaoMap({
//5. 마커 바뀌면 마커 재생성
useEffect(() => {
const kakao = window.kakao;
if (!kakao?.maps || !mapRef.current) return;
const maps = kakao?.maps;
if (!maps || !mapRef.current) return;

markersRef.current.forEach((mk) => mk.setMap(null));
markersRef.current.clear();

safeMarkers.forEach((store) => {
const pos = new kakao.maps.LatLng(store.location.lat, store.location.lng);
const marker = new kakao.maps.Marker({
const pos = new maps.LatLng(store.location.lat, store.location.lng);
const marker = new maps.Marker({
map: mapRef.current,
position: pos,
clickable: true,
zIndex: 1,
});

kakao.maps.event.addListener(marker, "click", () => {
maps.event.addListener(marker, "click", () => {
mapRef.current?.panTo(pos);
if (selectedLevel != null) {
mapRef.current?.setLevel(selectedLevel);
Expand All @@ -192,7 +195,7 @@ export default function KakaoMap({
markersRef.current.set(store.id, marker);
});
relayout();
}, [safeMarkers, onSelectMarker]);
}, [safeMarkers, onSelectMarker, selectedLevel]);

//6. 선택변경시 zIndex 처리
useEffect(() => {
Expand All @@ -212,22 +215,22 @@ export default function KakaoMap({
//7. 선택 없으면 bounds 맞추기
useEffect(() => {
const kakao = window.kakao;
if (!kakao?.maps || !mapRef.current) return;
if (selectedId != null) return;
if (safeMarkers.length === 0) return;
const bounds = new kakao.maps.LatLngBounds();
const maps = kakao?.maps;
if (!maps || !mapRef.current) return;
if (selectedId != null || safeMarkers.length === 0) return;

const bounds = new maps.LatLngBounds();
safeMarkers.forEach((store) => {
bounds.extend(
new kakao.maps.LatLng(store.location.lat, store.location.lng),
);
bounds.extend(new maps.LatLng(store.location.lat, store.location.lng));
});

requestAnimationFrame(() => {
try {
mapRef.current.relayout();
mapRef.current.setBounds(bounds);
} catch {}
mapRef.current?.relayout();
mapRef.current?.setBounds(bounds);
} catch {
//지도 초기화 타이밍 이슈무시
}
});

if (safeMarkers.length === 1 && defaultLevel != null) {
Expand Down
Loading
Loading