diff --git a/src/api/Visaclient.js b/src/api/Visaclient.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/api/visa.js b/src/api/visa.js index a17ec23..f8d27b4 100644 --- a/src/api/visa.js +++ b/src/api/visa.js @@ -1,50 +1,6 @@ -// src/api/visa.js -const BASE = "https://crossbiz.store"; // or "https://www.crossbiz.store" +import axiosInstance from "@/lib/axiosInstance"; -export async function fetchVisaRecommendWith() { - const payload = { - basicInfo: { - userId: 2, - age: 35, - bizStatus: "창업예정", - nationality: "미국", - status: "D-10", - bizCategory: "음식점업", - estimatePeriod: 24, - workExperience: 5, - degree: "석사", - koreanLevel: "TOPIK 5급", - }, - withVisaInfo: { - stayPeriod: "2년", - visaType: "D-10", - issuedDate: "2023-06-01", - expiryDate: "2025-06-01", - businessRegNumber: "123-45-67890", - annualRevenue: 80000000, - employeeCount: 3, - }, - }; - - try { - const res = await fetch(`${BASE}/api/visa/recommend/without`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - // 상태코드 확인 - if (!res.ok) { - const text = await res.text().catch(() => ""); - throw new Error(`HTTP ${res.status} ${res.statusText} :: ${text}`); - } - - const data = await res.json(); - console.log("✅ /api/visa/recommend/with 응답:", data); - return data; - } catch (err) { - // CORS / 네트워크 오류 로그 - console.error("❌ API 호출 실패:", err); - return null; - } -} \ No newline at end of file +export const fetchVisaRecommendWith = async (payload)=> { + const response = await axiosInstance.post('/visa/recommend/with',payload); + return response.data; +}; diff --git a/src/components/CardList.jsx b/src/components/CardList.jsx index 397403a..aeb9c3e 100644 --- a/src/components/CardList.jsx +++ b/src/components/CardList.jsx @@ -1,4 +1,3 @@ -// src/components/CardList.jsx import React, { useEffect, useState, useCallback } from "react"; import { useNavigate, useLocation } from "react-router-dom"; import GoodIcon from "../assets/good.svg"; @@ -6,7 +5,6 @@ import WarnIcon from "../assets/warning.svg"; import HotBadge from "../assets/Hot.svg"; import PurpleIcon from "../assets/purpleIcon.svg"; - export default function CardList({ items = [], from: fromProp }) { const [selectedIdx, setSelectedIdx] = useState(null); const [open, setOpen] = useState(false); @@ -19,7 +17,6 @@ export default function CardList({ items = [], from: fromProp }) { setOpen(true); }, []); - // ESC 로 닫기 useEffect(() => { const onKey = (e) => e.key === "Escape" && setOpen(false); window.addEventListener("keydown", onKey); @@ -28,13 +25,10 @@ export default function CardList({ items = [], from: fromProp }) { const selectedItem = selectedIdx != null ? items[selectedIdx] : null; - // 상세로 이동 (from을 함께 전달) const goDetail = (item) => { const name = item?.code || item?.name || ""; const encoded = encodeURIComponent(name); nav(`/visa-info?name=${encoded}`, { state: { from } }); - // 필요하면 모달도 닫기 - // setOpen(false); }; return ( @@ -45,7 +39,7 @@ export default function CardList({ items = [], from: fromProp }) { return (
onSelect(idx)} className={[ "cursor-pointer rounded-2xl shadow-[0_6px_24px_rgba(0,0,0,0.08)] overflow-visible border-2 transition-colors", @@ -55,18 +49,15 @@ export default function CardList({ items = [], from: fromProp }) { backgroundColor: isSelected ? "rgba(101,78,255,0.10)" : "#FFFFFF", }} > - {/* 헤더 + 배지 기준 컨테이너 */}
- {/* 🔥 인덱스 0일 때만, 헤더 왼쪽 ‘밖’ 중앙에 배치 */} {idx === 0 && ( 가장 유력한 후보 + /> )} - {/* 헤더: 1번 파랑, 나머지 회색 */}
- {/* 본문 */}
- {/* 추천이유 */} {item.reasons?.length > 0 && (
@@ -95,7 +84,6 @@ export default function CardList({ items = [], from: fromProp }) {
)} - {/* 주의 */} {item.warnings?.length > 0 && (
@@ -117,7 +105,7 @@ export default function CardList({ items = [], from: fromProp }) { })}
- {/* --- 하단 모달 (fixed) --- */} + {/* 하단 모달 */}
{selectedItem ? ( <> - {/* 상단: 왼쪽 텍스트 / 오른쪽 아이콘 */}

{selectedItem.code || selectedItem.name}

- {/* 아이콘 (클릭 시 상세로 이동 + from 전달) */}
- {/* 링크 */}

- {/* 요약 있으면 출력 */} {selectedItem.summary && (

{selectedItem.summary} diff --git a/src/pages/Visa/ConfirmCheck.jsx b/src/pages/Visa/ConfirmCheck.jsx index 3847069..2b0621d 100644 --- a/src/pages/Visa/ConfirmCheck.jsx +++ b/src/pages/Visa/ConfirmCheck.jsx @@ -3,8 +3,9 @@ import React, { useEffect, useRef, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import visaUser from "../../data/visaUser.json"; import commonUser from "../../data/commonUser.json"; +import { getUserProfile, updateUserProfile } from "@/api/auth/Auth"; -const LS_KEY = "confirm_check_form_issued_v1"; +const LS_KEY = "confirm_check_form_issued_v1"; export default function ConfirmCheck() { const nav = useNavigate(); @@ -12,46 +13,78 @@ export default function ConfirmCheck() { const from = state?.from; // "issued" | "match" | undefined const [isEditing, setIsEditing] = useState(false); + const [loading, setLoading] = useState(true); + const [loadErr, setLoadErr] = useState(null); + const [profile, setProfile] = useState(null); // ★ 조회 응답 보관(userId 등) const firstInputRef = useRef(null); - // ✅ JSON 데이터에서 form 빌드하는 함수 (공통화) + const toStr = (v) => (v == null ? "" : String(v)); + const onlyNum = (s = "") => String(s).replace(/\D/g, ""); + const toNumOrNull = (v) => (v === "" || v == null ? null : Number(v)); + + // ---------- JSON fallback ---------- const buildFormFromJson = (source) => { const basic = source?.request?.basicInfo ?? {}; const visa = source?.request?.withVisaInfo ?? {}; return { - nationality: basic.nationality ?? "", - bizInfo: basic.bizStatus ?? "", - status: basic.status || visa.visaType || "기업투자", + nationality: toStr(basic.nationality), + bizInfo: toStr(basic.status), + status: toStr(visa.visaType || basic.status), estimatePeriod: basic.estimatePeriod != null - ? `${basic.estimatePeriod}개월` - : visa.stayPeriod ?? "", - workExperience: - basic.workExperience != null ? String(basic.workExperience) : "", - degree: basic.degree ?? "", - koreanLevel: basic.koreanLevel ?? "", + ? `${toStr(basic.estimatePeriod)}개월` + : toStr(visa.stayPeriod), + workExperience: toStr(basic.workExperience), + degree: toStr(basic.degree), + koreanLevel: toStr(basic.koreanLevel), }; }; - // ✅ 초기값 분기 - const [form, setForm] = useState(() => { - if (from === "issued") return buildFormFromJson(visaUser); - if (from === "match") return buildFormFromJson(commonUser); - return { - nationality: "", - bizInfo: "", - status: "기업투자", - estimatePeriod: "", - workExperience: "", - degree: "", - koreanLevel: "", - }; + // ---------- form state ---------- + const [form, setForm] = useState({ + nationality: "", + bizInfo: "", + status: "기업투자", + estimatePeriod: "", + workExperience: "", + degree: "", + koreanLevel: "", }); - // 라우팅 state가 늦게 들어올 경우 대비 + // ---------- fetch profile & map ---------- useEffect(() => { - if (from === "issued") setForm(buildFormFromJson(visaUser)); - if (from === "match") setForm(buildFormFromJson(commonUser)); + let mounted = true; + + const mapProfileToForm = (p) => ({ + nationality: toStr(p?.nationality), + bizInfo: toStr(p?.bizCategory), // 사업자 정보 ← bizCategory ("구직") + status: toStr(p?.status), // 체류 자격 ← status ("D-10") + estimatePeriod: p?.estimatePeriod != null ? `${toStr(p.estimatePeriod)}개월` : "", + workExperience: toStr(p?.workExperience), + degree: toStr(p?.degree), + koreanLevel: toStr(p?.koreanLevel), + }); + + (async () => { + try { + setLoading(true); + setLoadErr(null); + const res = await getUserProfile(); // 반드시 res.data만 반환 + if (!mounted) return; + setProfile(res); // ★ userId 등 저장 + setForm(mapProfileToForm(res)); + } catch (e) { + setLoadErr(e?.message ?? "회원정보 조회에 실패했어요."); + if (from === "issued") setForm(buildFormFromJson(visaUser)); + else if (from === "match") setForm(buildFormFromJson(commonUser)); + } finally { + if (mounted) setLoading(false); + } + })(); + + return () => { + mounted = false; + }; }, [from]); // 편집 모드 전환 시 포커스 @@ -59,30 +92,76 @@ export default function ConfirmCheck() { if (isEditing && firstInputRef.current) firstInputRef.current.focus(); }, [isEditing]); - // 변경 시 저장 (issued일 때만 저장) + // 변경 시 저장(issued일 때만) useEffect(() => { - if (from === "issued") - localStorage.setItem(LS_KEY, JSON.stringify(form)); + if (from === "issued") localStorage.setItem(LS_KEY, JSON.stringify(form)); }, [form, from]); const handleChange = (key) => (e) => setForm((prev) => ({ ...prev, [key]: e.target.value })); - const handleEditToggle = () => { + // 저장 토글 (PATCH) + const handleEditToggle = async () => { if (!isEditing) { setIsEditing(true); - } else { - if (from === "issued") - localStorage.setItem(LS_KEY, JSON.stringify(form)); - setIsEditing(false); + return; + } + try { + const payload = { + nationality: form.nationality || null, + bizCategory: form.bizInfo || null, // 사업자 정보 + status: form.status || null, // 체류 자격 + estimatePeriod: form.estimatePeriod + ? Number(onlyNum(form.estimatePeriod)) // "4개월" → 4 + : null, + workExperience: form.workExperience !== "" ? Number(form.workExperience) : null, + degree: form.degree || null, + koreanLevel: form.koreanLevel || null, + }; + await updateUserProfile?.(payload); + // 재조회 반영 + const refreshed = await getUserProfile(); + setProfile(refreshed); + setForm({ + nationality: toStr(refreshed?.nationality), + bizInfo: toStr(refreshed?.bizCategory), + status: toStr(refreshed?.status), + estimatePeriod: + refreshed?.estimatePeriod != null ? `${toStr(refreshed.estimatePeriod)}개월` : "", + workExperience: toStr(refreshed?.workExperience), + degree: toStr(refreshed?.degree), + koreanLevel: toStr(refreshed?.koreanLevel), + }); + if (from === "issued") localStorage.setItem(LS_KEY, JSON.stringify(form)); alert("수정한 내용을 저장했어요."); + setIsEditing(false); + } catch (e) { + console.error("[ConfirmCheck] update failed:", e); + alert("저장에 실패했어요. 잠시 후 다시 시도해주세요."); } }; + // 확인 → 다음 페이지로 basicInfo(state) 전달 (userId 포함) const handleConfirm = () => { - if (from === "match") nav("/confirm-more"); - else if (from === "issued") nav("/confirm-visa"); - else nav("/confirm-visa"); + const basicInfo = { + userId: toNumOrNull(profile?.userId), // ★ 필수 + age: toNumOrNull(profile?.age), + bizStatus: form.bizInfo || profile?.bizStatus || null, // 예: "창업예정" + nationality: form.nationality || profile?.nationality || null, + status: form.status || profile?.status || null, // 예: "D-10" + bizCategory: profile?.bizCategory || null, // 예: "음식점업"/"구직" + estimatePeriod: form.estimatePeriod + ? toNumOrNull(onlyNum(form.estimatePeriod)) + : toNumOrNull(profile?.estimatePeriod), + workExperience: + form.workExperience !== "" ? toNumOrNull(form.workExperience) : toNumOrNull(profile?.workExperience), + degree: form.degree || profile?.degree || null, + koreanLevel: form.koreanLevel || profile?.koreanLevel || null, + }; + + const nextState = { payload: { basicInfo }, from: "confirm-check" }; + if (from === "match") nav("/confirm-more", { state: nextState }); + else nav("/confirm-visa", { state: nextState }); }; const inputCls = @@ -94,12 +173,12 @@ export default function ConfirmCheck() { return (

-

- 아래 정보가 맞나요? -

+

아래 정보가 맞나요?

+ + {loading &&

회원정보를 불러오는 중…

} + {loadErr && !loading &&

{loadErr}

}
- {/* 입력 필드들 (동일) */}