1- "use client" ;
2-
3- import { useEffect , useState } from "react" ;
1+ import React , { useState } from "react" ;
2+ import { useRouter } from "next/router" ;
43import { Input } from "@/components/ui/input" ;
5- import { Label } from "@/components/ui/label" ;
64import { Button } from "@/components/ui/button" ;
7- import {
8- Select ,
9- SelectContent ,
10- SelectItem ,
11- SelectTrigger ,
12- SelectValue ,
13- } from "@/components/ui/select" ;
145import { 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