diff --git a/app/matching-result/_components/ScreenMatchingResult.tsx b/app/matching-result/_components/ScreenMatchingResult.tsx index 1775f4f..5484509 100644 --- a/app/matching-result/_components/ScreenMatchingResult.tsx +++ b/app/matching-result/_components/ScreenMatchingResult.tsx @@ -47,6 +47,7 @@ const ScreenMatchingResult = () => { return (
router.push("/main")} text={
))}
diff --git a/app/matching/_components/MatchingOptionCard.tsx b/app/matching/_components/MatchingOptionCard.tsx index f0325f8..b26b700 100644 --- a/app/matching/_components/MatchingOptionCard.tsx +++ b/app/matching/_components/MatchingOptionCard.tsx @@ -30,9 +30,8 @@ export default function MatchingOptionCard({ onPointerDown={(e) => onPointerDown?.(e, value)} onPointerMove={onPointerMove} onPointerUp={onPointerUp} - onClick={() => onClick?.(value)} className={cn( - "flex h-[75px] w-full cursor-grab touch-none items-center justify-between gap-1 rounded-lg border px-[17px] transition-all outline-none focus-visible:ring-2 focus-visible:ring-offset-2 active:scale-[0.98] active:cursor-grabbing", + "flex h-[75px] w-full cursor-grab touch-pan-y items-center justify-between gap-1 rounded-lg border px-[17px] transition-all outline-none focus-visible:ring-2 focus-visible:ring-offset-2 active:scale-[0.98] active:cursor-grabbing", isSelected ? "border-color-main-700 ring-color-main-700 bg-white ring-1" : "border-color-gray-64 bg-color-gray-50", diff --git a/app/matching/_components/ScreenMatching.tsx b/app/matching/_components/ScreenMatching.tsx index e76042f..2123eb3 100644 --- a/app/matching/_components/ScreenMatching.tsx +++ b/app/matching/_components/ScreenMatching.tsx @@ -26,7 +26,7 @@ import { MatchingInterestCategory } from "@/lib/constants/matchingInterests"; const hobbyMapping: Record = { 스포츠: "SPORTS", 문화: "CULTURE", - 예술: "MUSIC", + 음악: "MUSIC", 여행: "TRAVEL", 자기계발: "DAILY", 게임: "GAME", @@ -195,7 +195,6 @@ const ScreenMatching = () => { selectedOption={importantOption} selections={{ MBTI: selectedMBTI || "", - AGE: selectedAgeGroup || "", HOBBY: selectedHobbyCategory || "", CONTACT: selectedFrequency || "", }} diff --git a/app/mypage/_components/HobbyEditDrawer.tsx b/app/mypage/_components/HobbyEditDrawer.tsx index f8df8a2..c2e4e7f 100644 --- a/app/mypage/_components/HobbyEditDrawer.tsx +++ b/app/mypage/_components/HobbyEditDrawer.tsx @@ -173,7 +173,7 @@ const HobbyEditDrawer = ({ getHobbiesByCategory(cat); return (
- {/* 스포츠, 문화예술 등 */} + {/* 스포츠, 문화, 음악 등 */}

{cat}

{predefined.map((hobby) => ( diff --git a/app/profile-image/_components/TermsDrawer.tsx b/app/profile-image/_components/TermsDrawer.tsx index e4f946d..9fb6716 100644 --- a/app/profile-image/_components/TermsDrawer.tsx +++ b/app/profile-image/_components/TermsDrawer.tsx @@ -67,7 +67,7 @@ const TermsDrawer = ({ children }: TermsDrawerProps) => { const categoryMap: Record = { 스포츠: "SPORTS", 문화: "CULTURE", - 예술: "MUSIC", + 음악: "MUSIC", 여행: "LEISURE", 자기계발: "DAILY", 게임: "GAME", diff --git a/components/common/charge-confirm/ConfirmChargeDrawer.tsx b/components/common/charge-confirm/ConfirmChargeDrawer.tsx index 1d0bdac..9b32294 100644 --- a/components/common/charge-confirm/ConfirmChargeDrawer.tsx +++ b/components/common/charge-confirm/ConfirmChargeDrawer.tsx @@ -42,6 +42,7 @@ export default function ConfirmChargeDrawer({ const queryClient = useQueryClient(); const drawerContext = React.useContext(ChargeDrawerContext); const { mutate: purchase, isPending } = usePurchaseProduct(); + const [open, setOpen] = React.useState(false); const [agreed, setAgreed] = React.useState(false); const [name, setName] = React.useState(depositorName); @@ -246,7 +247,6 @@ export default function ConfirmChargeDrawer({ {/* ── 하단 버튼 영역 ── */}
- {/* CTA 버튼 */} diff --git a/components/common/charge/ChargeInventoryCard.tsx b/components/common/charge/ChargeInventoryCard.tsx index 4a3fa75..912e9e4 100644 --- a/components/common/charge/ChargeInventoryCard.tsx +++ b/components/common/charge/ChargeInventoryCard.tsx @@ -9,46 +9,66 @@ import { } from "@/lib/constants/charge"; import { useItems } from "@/hooks/useItems"; +import { usePurchaseLimits } from "@/hooks/usePurchaseLimits"; export default function ChargeInventoryCard() { - const { data, isLoading } = useItems(); + const { data: itemData } = useItems(); + const { data: limitData } = usePurchaseLimits(); + + const limits = limitData?.data.limits || []; + const matchingLimit = limits.find((l) => l.itemType === "MATCHING_TICKET"); + const optionLimit = limits.find((l) => l.itemType === "OPTION_TICKET"); const ticketCounts = { - matching: data?.data.matchingTicketCount ?? 0, - option: data?.data.optionTicketCount ?? 0, + matching: itemData?.data.matchingTicketCount ?? 0, + option: itemData?.data.optionTicketCount ?? 0, }; return (
{/* 보유 수량 */}
- {INVENTORY_ROWS.map((row) => ( -
- {row.alt} -
- - {row.label} - - - {ticketCounts[row.key as keyof typeof ticketCounts]}개 - + {INVENTORY_ROWS.map((row) => { + const limit = row.key === "matching" ? matchingLimit : optionLimit; + return ( +
+ {row.alt} +
+ + {row.label} + +
+ + {limit?.ownedQuantity ?? 0}개 + + {limit?.pendingQuantity ? ( + + (+{limit.pendingQuantity} 대기중) + + ) : null} +
+
-
- ))} + ); + })}
{/* 구매 가능 한도 */}
구매 가능 한도 -
- {Object.values(PURCHASE_LIMITS).map((limit) => ( - - {limit.label} {limit.current}/{limit.max} +
+ {limits.map((limit) => ( + + {limit.itemName} {limit.ownedQuantity + limit.pendingQuantity}/ + {limit.maxQuantity} ))}
diff --git a/components/common/charge/DepositorNameContent.tsx b/components/common/charge/DepositorNameContent.tsx index 2857fa1..e28ce06 100644 --- a/components/common/charge/DepositorNameContent.tsx +++ b/components/common/charge/DepositorNameContent.tsx @@ -1,17 +1,28 @@ "use client"; -import React from "react"; +import React, { useState, useEffect } from "react"; import Image from "next/image"; import { AxiosError } from "axios"; import { cn } from "@/lib/utils"; import { isValidDepositorName } from "@/lib/validators"; import Button from "@/components/ui/Button"; import { useUpdateRealName } from "@/hooks/useUpdateRealName"; +import { useRealName } from "@/hooks/useRealName"; export default function DepositorNameContent() { - const [name, setName] = React.useState(""); + const [name, setName] = useState(""); + const { data: realNameData } = useRealName(); const { mutate: updateName, isPending } = useUpdateRealName(); + const isInitialized = React.useRef(false); + useEffect(() => { + if (realNameData?.data?.realName && !isInitialized.current) { + // eslint-disable-next-line react-hooks/set-state-in-effect + setName(realNameData.data.realName); + isInitialized.current = true; + } + }, [realNameData]); + const handleNameChange = (e: React.ChangeEvent) => { const value = e.target.value; if (isValidDepositorName(value)) { @@ -32,7 +43,7 @@ export default function DepositorNameContent() { }; return ( -
+
{/* ── 입금자명 입력 ── */}
입금자명 diff --git a/hooks/usePurchaseLimits.ts b/hooks/usePurchaseLimits.ts new file mode 100644 index 0000000..d50f8af --- /dev/null +++ b/hooks/usePurchaseLimits.ts @@ -0,0 +1,44 @@ +import { api } from "@/lib/axios"; +import { useQuery } from "@tanstack/react-query"; + +export type ItemType = "MATCHING_TICKET" | "OPTION_TICKET"; + +export interface PurchaseLimit { + itemType: ItemType; + itemName: string; + ownedQuantity: number; + pendingQuantity: number; + maxQuantity: number; + remainingQuantity: number; + purchasable: boolean; +} + +export interface PurchaseLimitsResponse { + code: string; + status: number; + message: string; + data: { + limits: PurchaseLimit[]; + }; +} + +/** + * 아이템 구매 한도 조회 API 호출 함수 + */ +export const getPurchaseLimits = async (): Promise => { + const { data } = await api.get( + "/api/v1/shop/purchase/limits", + ); + return data; +}; + +/** + * 아이템 구매 한도 조회 훅 + */ +export const usePurchaseLimits = () => { + return useQuery({ + queryKey: ["purchaseLimits"], + queryFn: getPurchaseLimits, + staleTime: 1000 * 60, // 1분 유지 + }); +}; diff --git a/hooks/useRealName.ts b/hooks/useRealName.ts new file mode 100644 index 0000000..8db2ee9 --- /dev/null +++ b/hooks/useRealName.ts @@ -0,0 +1,31 @@ +import { api } from "@/lib/axios"; +import { useQuery } from "@tanstack/react-query"; + +interface RealNameResponse { + code: string; + status: number; + message: string; + data: { + realName: string | null; + }; +} + +/** + * 사용자 실명 조회 API 호출 함수 + */ +export const getRealName = async (): Promise => { + const { data } = await api.get("/api/members/real-name"); + console.log("📡 [GET] /api/members/real-name 응답 데이터:", data); + return data; +}; + +/** + * 사용자 실명 조회 훅 + */ +export const useRealName = () => { + return useQuery({ + queryKey: ["realName"], + queryFn: getRealName, + staleTime: 1000 * 60 * 5, // 5분 + }); +}; diff --git a/hooks/useUpdateRealName.ts b/hooks/useUpdateRealName.ts index 437d25e..2081183 100644 --- a/hooks/useUpdateRealName.ts +++ b/hooks/useUpdateRealName.ts @@ -32,8 +32,7 @@ export const useUpdateRealName = () => { mutationFn: (realName: string) => postRealName(realName), onSuccess: (data) => { console.log("✅ 실명 수정 성공:", data); - // 실명이 바뀌었으므로 관련 쿼리 무효화 (필요시) - // queryClient.invalidateQueries({ queryKey: ["userProfile"] }); + queryClient.invalidateQueries({ queryKey: ["realName"] }); }, onError: (error: AxiosError<{ message: string }>) => { const errorData = error.response?.data; diff --git a/lib/constants/hobbies.ts b/lib/constants/hobbies.ts index d53a8e6..860481d 100644 --- a/lib/constants/hobbies.ts +++ b/lib/constants/hobbies.ts @@ -32,7 +32,7 @@ export const HOBBIES = { "📱 웹툰", "💃 댄스", ], - 예술: [ + 음악: [ "🎤 K-POP", "🎶 팝", "🧢 힙합", diff --git a/lib/constants/matchingInterests.ts b/lib/constants/matchingInterests.ts index 518c7e0..db00f3c 100644 --- a/lib/constants/matchingInterests.ts +++ b/lib/constants/matchingInterests.ts @@ -1,7 +1,7 @@ export const MATCHING_INTEREST_CATEGORIES = [ "스포츠", "문화", - "예술", + "음악", "여행", "자기계발", "게임", @@ -18,7 +18,7 @@ export interface MatchingInterestItem { export const MATCHING_INTEREST_ITEMS: MatchingInterestItem[] = [ { category: "스포츠", imageSrc: "/interest/스포츠.png" }, { category: "문화", imageSrc: "/interest/문화.png" }, - { category: "예술", imageSrc: "/interest/예술.png" }, + { category: "음악", imageSrc: "/interest/예술.png" }, { category: "여행", imageSrc: "/interest/여행.png" }, { category: "자기계발", imageSrc: "/interest/자기계발.png" }, { category: "게임", imageSrc: "/interest/게임.png" }, diff --git a/lib/types/matching.ts b/lib/types/matching.ts index 14bc1bf..3e5828a 100644 --- a/lib/types/matching.ts +++ b/lib/types/matching.ts @@ -10,7 +10,7 @@ export type HobbyOption = | "DAILY" | "GAME"; -export type ImportantOption = "AGE" | "MBTI" | "HOBBY" | "CONTACT"; +export type ImportantOption = "MBTI" | "HOBBY" | "CONTACT"; export interface MatchingRequest { ageOption?: AgeOption | null;