Skip to content

Commit 6478a57

Browse files
authored
🐛 fix: KaKao Type Error 해결 (#120)
1 parent 4ae202e commit 6478a57

9 files changed

Lines changed: 96 additions & 353 deletions

File tree

app/merchant/signup/business/components/AddressSearchModal.tsx

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,6 @@ interface AddressSearchModalProps {
1818
}) => void;
1919
}
2020

21-
declare global {
22-
interface Window {
23-
kakao: any;
24-
}
25-
}
26-
2721
export default function AddressSearchModal({
2822
onClose,
2923
onSelect,
@@ -38,7 +32,7 @@ export default function AddressSearchModal({
3832
script.src = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_MAP_API_KEY}&libraries=services&autoload=false`;
3933
script.async = true;
4034
script.onload = () => {
41-
window.kakao.maps.load(() => setScriptLoaded(true));
35+
(window as any).kakao.maps.load(() => setScriptLoaded(true));
4236
};
4337
document.head.appendChild(script);
4438
return () => {
@@ -52,11 +46,11 @@ export default function AddressSearchModal({
5246
setSearchingAddress(true);
5347
setAddressResults([]);
5448

55-
const geocoder = new window.kakao.maps.services.Geocoder();
56-
const places = new window.kakao.maps.services.Places();
49+
const geocoder = new (window as any).kakao.maps.services.Geocoder();
50+
const places = new (window as any).kakao.maps.services.Places();
5751

5852
places.keywordSearch(addressKeyword, (result: any, status: any) => {
59-
if (status === window.kakao.maps.services.Status.OK) {
53+
if (status === (window as any).kakao.maps.services.Status.OK) {
6054
const filtered = result.filter((r: any) => r.address_name);
6155
setAddressResults(filtered);
6256
}
@@ -65,7 +59,7 @@ export default function AddressSearchModal({
6559
addressKeyword,
6660
(geoResult: any, geoStatus: any) => {
6761
setSearchingAddress(false);
68-
if (geoStatus === window.kakao.maps.services.Status.OK) {
62+
if (geoStatus === (window as any).kakao.maps.services.Status.OK) {
6963
setAddressResults((prev) => {
7064
const combined = [...prev];
7165
geoResult.forEach((item: any) => {
Lines changed: 26 additions & 289 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,20 @@
1-
"use client";
2-
3-
import { useEffect, useState } from "react";
1+
import React, { useState } from "react";
2+
import { useRouter } from "next/router";
43
import { Input } from "@/components/ui/input";
5-
import { Label } from "@/components/ui/label";
64
import { Button } from "@/components/ui/button";
7-
import {
8-
Select,
9-
SelectContent,
10-
SelectItem,
11-
SelectTrigger,
12-
SelectValue,
13-
} from "@/components/ui/select";
145
import { Search } from "lucide-react";
15-
import { useRouter } from "next/navigation";
16-
import {
17-
fetchSidoList,
18-
fetchSigunguList,
19-
} from "@/app/merchant/signup/business/api/region";
20-
import AddressSearchModal from "./AddressSearchModal";
21-
22-
interface InfoStepProps {}
236

24-
const STORE_CATEGORIES = [
25-
{ value: "음식점", label: "음식점" },
26-
{ value: "의료", label: "의료" },
27-
{ value: "서비스", label: "서비스" },
28-
{ value: "관광", label: "관광" },
29-
{ value: "숙박", label: "숙박" },
30-
{ value: "교육", label: "교육" },
31-
];
7+
interface InfoStepProps {
8+
onAddressSearch: () => void;
9+
roadAddress: string;
10+
setRoadAddress: React.Dispatch<React.SetStateAction<string>>;
11+
}
3212

33-
export default function InfoStep() {
13+
export default function InfoStep({ onAddressSearch, roadAddress, setRoadAddress }: InfoStepProps) {
3414
const router = useRouter();
3515
const [businessNumber, setBusinessNumber] = useState("");
3616
const [storeName, setStoreName] = useState("");
3717
const [name, setName] = useState("");
38-
const [roadAddress, setRoadAddress] = useState("");
3918
const [detailAddress, setDetailAddress] = useState("");
4019
const [selectedSido, setSelectedSido] = useState("");
4120
const [selectedSigungu, setSelectedSigungu] = useState("");
@@ -45,267 +24,25 @@ export default function InfoStep() {
4524
const [loading, setLoading] = useState(false);
4625
const [isModalOpen, setIsModalOpen] = useState(false);
4726

48-
// 주소 선택 콜백
49-
const handleAddressSelected = (data: {
50-
businessAddress: string;
51-
sido: string;
52-
sigungu: string;
53-
latitude: string;
54-
longitude: string;
55-
zipcode: string;
56-
}) => {
57-
sessionStorage.setItem("businessAddressData", JSON.stringify(data));
58-
setRoadAddress(data.businessAddress);
59-
setSelectedSido(data.sido);
60-
setSelectedSigungu(data.sigungu);
61-
};
62-
63-
// 초기화 시 OCR 세션 반영
64-
useEffect(() => {
65-
const savedBusinessNumber = sessionStorage.getItem("businessNumber") || "";
66-
const savedStoreName = sessionStorage.getItem("storeName") || "";
67-
const savedName = sessionStorage.getItem("name") || "";
68-
const savedRoadAddress = sessionStorage.getItem("roadAddress") || "";
69-
70-
setBusinessNumber(savedBusinessNumber);
71-
setStoreName(savedStoreName);
72-
setName(savedName);
73-
setRoadAddress(savedRoadAddress);
74-
75-
const savedAddressJson = sessionStorage.getItem("businessAddressData");
76-
if (savedAddressJson) {
77-
try {
78-
const parsed = JSON.parse(savedAddressJson);
79-
if (parsed.sido) setSelectedSido(parsed.sido);
80-
if (parsed.sigungu) setSelectedSigungu(parsed.sigungu);
81-
} catch (e) {
82-
console.error("주소 파싱 실패:", e);
83-
}
84-
}
85-
}, []);
86-
87-
useEffect(() => {
88-
const loadSido = async () => {
89-
try {
90-
const list = await fetchSidoList();
91-
setSidoList(list);
92-
} catch (e) {
93-
console.error("시/도 불러오기 실패:", e);
94-
}
95-
};
96-
97-
loadSido();
98-
}, []);
99-
100-
useEffect(() => {
101-
if (selectedSido) {
102-
const loadSigungu = async () => {
103-
try {
104-
const list = await fetchSigunguList(selectedSido);
105-
setSigunguList(list);
106-
} catch (e) {
107-
console.error("시/군/구 불러오기 실패:", e);
108-
}
109-
};
110-
111-
loadSigungu();
112-
} else {
113-
setSigunguList([]);
114-
}
115-
}, [selectedSido]);
116-
117-
const handleSubmit = async (e: React.FormEvent) => {
118-
e.preventDefault();
119-
120-
if (
121-
!businessNumber ||
122-
!storeName ||
123-
!name ||
124-
!roadAddress ||
125-
!selectedSido ||
126-
!selectedSigungu ||
127-
!selectedCategory
128-
) {
129-
alert("모든 필수 정보를 입력해주세요.");
130-
return;
131-
}
132-
133-
const payload = {
134-
businessNumber,
135-
storeName,
136-
name,
137-
roadAddress,
138-
detailAddress,
139-
sido: selectedSido,
140-
sigungu: selectedSigungu,
141-
category: selectedCategory,
142-
};
143-
144-
sessionStorage.setItem("businessInfo", JSON.stringify(payload));
145-
146-
setLoading(true);
147-
await new Promise((res) => setTimeout(res, 1000));
148-
setLoading(false);
149-
150-
router.push("/merchant/signup/wallet");
151-
};
152-
15327
return (
154-
<>
155-
<form
156-
onSubmit={handleSubmit}
157-
className="space-y-6 w-full max-w-md mx-auto"
28+
<div>
29+
<Input
30+
data-cy="road-address-input"
31+
value={roadAddress}
32+
readOnly
33+
onClick={onAddressSearch}
34+
placeholder="주소 검색 버튼을 클릭하세요"
35+
className="pr-10"
36+
/>
37+
<Button
38+
type="button"
39+
size="icon"
40+
variant="ghost"
41+
className="absolute right-1 top-1/2 -translate-y-1/2"
42+
onClick={onAddressSearch}
15843
>
159-
<div className="space-y-2">
160-
<Label>사업자 등록번호</Label>
161-
<Input
162-
data-cy="business-number-input"
163-
value={businessNumber}
164-
onChange={(e) => {
165-
const raw = e.target.value.replace(/\D/g, "").slice(0, 10);
166-
const formatted = raw.replace(
167-
/^(\d{3})(\d{2})(\d{0,5})$/,
168-
"$1-$2-$3"
169-
);
170-
setBusinessNumber(formatted);
171-
}}
172-
placeholder="000-00-00000"
173-
required
174-
/>
175-
</div>
176-
177-
<div className="space-y-2">
178-
<Label>상호명</Label>
179-
<Input
180-
data-cy="store-name-input"
181-
value={storeName}
182-
onChange={(e) => setStoreName(e.target.value)}
183-
placeholder="상호명"
184-
required
185-
/>
186-
</div>
187-
188-
<div className="space-y-2">
189-
<Label>대표자명</Label>
190-
<Input
191-
data-cy="representative-name-input"
192-
value={name}
193-
onChange={(e) => setName(e.target.value)}
194-
placeholder="대표자명"
195-
required
196-
/>
197-
</div>
198-
199-
<div className="space-y-2">
200-
<Label>사업장 주소</Label>
201-
<div className="relative">
202-
<Input
203-
data-cy="road-address-input"
204-
value={roadAddress}
205-
readOnly
206-
onClick={() => setIsModalOpen(true)}
207-
placeholder="주소 검색 버튼을 클릭하세요"
208-
className="pr-10"
209-
/>
210-
<Button
211-
type="button"
212-
size="icon"
213-
variant="ghost"
214-
className="absolute right-1 top-1/2 -translate-y-1/2"
215-
onClick={() => setIsModalOpen(true)}
216-
>
217-
<Search className="h-4 w-4" />
218-
</Button>
219-
</div>
220-
<Input
221-
data-cy="detail-address-input"
222-
value={detailAddress}
223-
onChange={(e) => setDetailAddress(e.target.value)}
224-
placeholder="상세 주소 입력 (선택)"
225-
className="text-sm mt-2"
226-
/>
227-
</div>
228-
229-
<div className="grid grid-cols-2 gap-4">
230-
<div className="space-y-2">
231-
<Label>시/도</Label>
232-
<Select value={selectedSido} onValueChange={setSelectedSido}>
233-
<SelectTrigger className="bg-white" data-cy="sido-select">
234-
<SelectValue placeholder="시/도 선택" />
235-
</SelectTrigger>
236-
<SelectContent className="bg-gray-100">
237-
{sidoList.map((sido) => (
238-
<SelectItem
239-
key={sido}
240-
value={sido}
241-
className="bg-white relative pl-8 py-2 rounded-md text-sm text-black hover:bg-[#FFFAEB] hover:text-[#FF9500] transition-all cursor-pointer"
242-
>
243-
{sido}
244-
</SelectItem>
245-
))}
246-
</SelectContent>
247-
</Select>
248-
</div>
249-
250-
<div className="space-y-2">
251-
<Label>시/군/구</Label>
252-
<Select
253-
value={selectedSigungu}
254-
onValueChange={setSelectedSigungu}
255-
disabled={!sigunguList.length}
256-
>
257-
<SelectTrigger className="bg-white" data-cy="sigungu-select">
258-
<SelectValue placeholder="시/군/구 선택" />
259-
</SelectTrigger>
260-
<SelectContent className="bg-gray-100">
261-
{sigunguList.map((s) => (
262-
<SelectItem
263-
key={s}
264-
value={s}
265-
className="bg-white relative pl-8 py-2 rounded-md text-sm text-black hover:bg-[#FFFAEB] hover:text-[#FF9500] transition-all cursor-pointer"
266-
>
267-
{s}
268-
</SelectItem>
269-
))}
270-
</SelectContent>
271-
</Select>
272-
</div>
273-
</div>
274-
275-
<div className="space-y-2">
276-
<Label>상점 카테고리</Label>
277-
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
278-
<SelectTrigger className="bg-white" data-cy="category-select">
279-
<SelectValue placeholder="카테고리 선택" />
280-
</SelectTrigger>
281-
<SelectContent className="bg-gray-100">
282-
{STORE_CATEGORIES.map((c) => (
283-
<SelectItem
284-
key={c.value}
285-
value={c.value}
286-
className="bg-white relative pl-8 py-2 rounded-md text-sm text-black hover:bg-[#FFFAEB] hover:text-[#FF9500] transition-all cursor-pointer"
287-
>
288-
{c.label}
289-
</SelectItem>
290-
))}
291-
</SelectContent>
292-
</Select>
293-
</div>
294-
295-
<Button
296-
type="submit"
297-
disabled={loading}
298-
className="w-full mt-4 bg-[#FFB020] hover:bg-[#FF9500] text-white"
299-
>
300-
{loading ? "처리 중..." : "다음"}
301-
</Button>
302-
</form>
303-
{isModalOpen && (
304-
<AddressSearchModal
305-
onClose={() => setIsModalOpen(false)}
306-
onSelect={handleAddressSelected}
307-
/>
308-
)}
309-
</>
44+
<Search className="h-4 w-4" />
45+
</Button>
46+
</div>
31047
);
31148
}

0 commit comments

Comments
 (0)