diff --git a/apps/customer/next-env.d.ts b/apps/customer/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/apps/customer/next-env.d.ts +++ b/apps/customer/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/owner/src/app/(tabs)/main/_components/CafeIntro.tsx b/apps/owner/src/app/(tabs)/main/_components/CafeIntro.tsx index a481d94..69e0835 100644 --- a/apps/owner/src/app/(tabs)/main/_components/CafeIntro.tsx +++ b/apps/owner/src/app/(tabs)/main/_components/CafeIntro.tsx @@ -8,15 +8,19 @@ import QRRewardConfirmModal from "./QRRewardConfirmModal"; import { useWritingRewardMutation } from "@/shared/queries/mutation/store-manager/useQrMutation"; interface CafeIntroProps { - cafeName: string; + cafeName?: string | null; } -const formatCafeName = (name: string) => { - if (name.length <= 10) { - return { firstLine: name, secondLine: "" }; +const DEFAULT_CAFE_NAME = "등록된 가게"; + +const formatCafeName = (name?: string | null) => { + const safeName = name?.trim() || DEFAULT_CAFE_NAME; + + if (safeName.length <= 10) { + return { firstLine: safeName, secondLine: "" }; } - const firstTen = name.slice(0, 10); + const firstTen = safeName.slice(0, 10); const spaceIndexes = [...firstTen] .map((char, index) => (char === " " ? index : -1)) .filter((index) => index !== -1); @@ -25,14 +29,14 @@ const formatCafeName = (name: string) => { const breakIndex = spaceIndexes[1]; return { - firstLine: name.slice(0, breakIndex).trim(), - secondLine: name.slice(breakIndex + 1).trim(), + firstLine: safeName.slice(0, breakIndex).trim(), + secondLine: safeName.slice(breakIndex + 1).trim(), }; } return { - firstLine: name.slice(0, 10).trim(), - secondLine: name.slice(10).trim(), + firstLine: safeName.slice(0, 10).trim(), + secondLine: safeName.slice(10).trim(), }; }; diff --git a/apps/owner/src/app/(tabs)/main/page.tsx b/apps/owner/src/app/(tabs)/main/page.tsx index a888f00..3920f71 100644 --- a/apps/owner/src/app/(tabs)/main/page.tsx +++ b/apps/owner/src/app/(tabs)/main/page.tsx @@ -14,7 +14,7 @@ export default function OwnerMainPage() { id: reservation.reservationId, customerName: `회원 ${reservation.memberId}`, randomBoxName: `예약 #${reservation.reservationId}`, - price: reservation.totalPrice, + price: reservation.totalPrice ?? 0, })) ?? []; if (isLoading) { @@ -42,8 +42,8 @@ export default function OwnerMainPage() { ); diff --git a/apps/owner/src/app/(tabs)/mypage/store-account/page.tsx b/apps/owner/src/app/(tabs)/mypage/store-account/page.tsx index f9c2488..9174e6c 100644 --- a/apps/owner/src/app/(tabs)/mypage/store-account/page.tsx +++ b/apps/owner/src/app/(tabs)/mypage/store-account/page.tsx @@ -29,4 +29,4 @@ export default function StoreAccountPage() { ); -} \ No newline at end of file +} diff --git a/apps/owner/src/app/signup/page.tsx b/apps/owner/src/app/signup/page.tsx index 9d25c2d..d1bee3a 100644 --- a/apps/owner/src/app/signup/page.tsx +++ b/apps/owner/src/app/signup/page.tsx @@ -59,10 +59,15 @@ export default function SignupPage() { passwordConfirm, }, { - onSuccess: () => { + onSuccess: (res) => { + const accessToken = res.data.accessToken; + + localStorage.setItem("ownerAccessToken", accessToken); + setSignupCompleted(email); router.push("/signup/business"); }, + onError: (error) => { console.log("회원가입 실패", error); }, diff --git a/apps/owner/src/app/signup/register/_apis/loadDaumPostcode.ts b/apps/owner/src/app/signup/register/_apis/loadDaumPostcode.ts new file mode 100644 index 0000000..2d0cd0c --- /dev/null +++ b/apps/owner/src/app/signup/register/_apis/loadDaumPostcode.ts @@ -0,0 +1,52 @@ +export function loadDaumPostcode(): Promise { + return new Promise((resolve, reject) => { + if (typeof window === "undefined") { + reject(new Error("window is undefined")); + return; + } + + if (window.daum?.Postcode) { + resolve(); + return; + } + + const existingScript = document.querySelector( + 'script[src*="postcode.v2.js"]', + ); + + if (existingScript) { + existingScript.addEventListener( + "load", + () => { + if (window.daum?.Postcode) resolve(); + else reject(new Error("Daum Postcode is not loaded.")); + }, + { once: true }, + ); + + existingScript.addEventListener( + "error", + () => reject(new Error("Failed to load Daum Postcode script.")), + { once: true }, + ); + + return; + } + + const script = document.createElement("script"); + script.src = + "https://t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"; + script.async = true; + + script.onload = () => { + if (window.daum?.Postcode) resolve(); + else reject(new Error("Daum Postcode is not loaded.")); + }; + + script.onerror = () => { + reject(new Error("Failed to load Daum Postcode script.")); + }; + + document.head.appendChild(script); + }); +} \ No newline at end of file diff --git a/apps/owner/src/app/signup/register/_apis/openDaumPostcode.ts b/apps/owner/src/app/signup/register/_apis/openDaumPostcode.ts new file mode 100644 index 0000000..84224b3 --- /dev/null +++ b/apps/owner/src/app/signup/register/_apis/openDaumPostcode.ts @@ -0,0 +1,31 @@ +import { loadDaumPostcode } from "./loadDaumPostcode"; +import type { + AddressSearchItem, + DaumPostcodeData, +} from "../_types/address-search"; + +export async function openDaumPostcode(): Promise { + await loadDaumPostcode(); + + return new Promise((resolve, reject) => { + if (!window.daum?.Postcode) { + reject(new Error("Daum Postcode is not loaded.")); + return; + } + + new window.daum.Postcode({ + oncomplete: (data: DaumPostcodeData) => { + const roadAddress = data.roadAddress || data.address || ""; + const lotNumberAddress = data.jibunAddress || ""; + + resolve({ + id: `${data.zonecode}-${roadAddress || lotNumberAddress}`, + label: roadAddress || lotNumberAddress, + roadAddress, + lotNumberAddress, + zonecode: data.zonecode, + }); + }, + }).open(); + }); +} \ No newline at end of file diff --git a/apps/owner/src/app/signup/register/_apis/searchAddressBykakao.ts b/apps/owner/src/app/signup/register/_apis/searchAddressBykakao.ts deleted file mode 100644 index 3da000e..0000000 --- a/apps/owner/src/app/signup/register/_apis/searchAddressBykakao.ts +++ /dev/null @@ -1,68 +0,0 @@ -"use client"; - -import type { AddressSearchItem } from "../_types/address-search"; - -export function searchAddressByKakao( - keyword: string, - size = 10, -): Promise { - return new Promise((resolve, reject) => { - const trimmedKeyword = keyword.trim(); - - if (!trimmedKeyword) { - resolve([]); - return; - } - - if ( - typeof window === "undefined" || - !window.kakao?.maps?.services - ) { - reject(new Error("Kakao services library is not loaded.")); - return; - } - - const geocoder = new window.kakao.maps.services.Geocoder(); - - geocoder.addressSearch( - trimmedKeyword, - (result: KakaoAddressSearchResult[], status: string) => { - const { kakao } = window; - - if (status === kakao.maps.services.Status.ZERO_RESULT) { - resolve([]); - return; - } - - if (status !== kakao.maps.services.Status.OK) { - reject(new Error("주소 검색 중 오류가 발생했습니다.")); - return; - } - - const mapped = result.slice(0, size).map((item, index) => { - const lotNumberAddress = - item.address?.address_name || item.address_name || ""; - - const roadAddress = - item.road_address?.address_name || item.address_name || ""; - - return { - id: `${item.x}-${item.y}-${index}`, - label: roadAddress || lotNumberAddress, - lotNumberAddress, - roadAddress, - longitude: Number(item.x), - latitude: Number(item.y), - }; - }); - - resolve(mapped); - }, - { - page: 1, - size, - analyze_type: window.kakao.maps.services.AnalyzeType.SIMILAR, - }, - ); - }); -} \ No newline at end of file diff --git a/apps/owner/src/app/signup/register/_components/AddressSearchBottomSheet.tsx b/apps/owner/src/app/signup/register/_components/AddressSearchBottomSheet.tsx index b5c5586..0b6b0c7 100644 --- a/apps/owner/src/app/signup/register/_components/AddressSearchBottomSheet.tsx +++ b/apps/owner/src/app/signup/register/_components/AddressSearchBottomSheet.tsx @@ -1,9 +1,9 @@ "use client"; import { useEffect, useRef, useState } from "react"; -import { BottomSheet, Input } from "@compasser/design-system"; +import { BottomSheet, Button } from "@compasser/design-system"; import type { AddressSearchItem } from "../_types/address-search"; -import { searchAddressByKakao } from "../_apis/searchAddressBykakao"; +import { openDaumPostcode } from "../_apis/openDaumPostcode"; interface AddressSearchBottomSheetProps { open: boolean; @@ -16,59 +16,36 @@ export default function AddressSearchBottomSheet({ onClose, onSelectAddress, }: AddressSearchBottomSheetProps) { - const [keyword, setKeyword] = useState(""); - const [results, setResults] = useState([]); - const [isSearching, setIsSearching] = useState(false); + const [isOpening, setIsOpening] = useState(false); + const hasOpenedRef = useRef(false); - const debounceRef = useRef(null); + const handleOpenPostcode = async () => { + try { + setIsOpening(true); - useEffect(() => { - if (!open) { - setKeyword(""); - setResults([]); - setIsSearching(false); + const selectedAddress = await openDaumPostcode(); + + onSelectAddress(selectedAddress); + onClose(); + } catch (error) { + console.error("주소 검색 실패", error); + } finally { + setIsOpening(false); } - }, [open]); + }; useEffect(() => { - if (!open) return; - - const trimmedKeyword = keyword.trim(); - - if (!trimmedKeyword) { - setResults([]); - setIsSearching(false); + if (!open) { + hasOpenedRef.current = false; + setIsOpening(false); return; } - if (debounceRef.current) { - window.clearTimeout(debounceRef.current); - } - - debounceRef.current = window.setTimeout(async () => { - try { - setIsSearching(true); - const searched = await searchAddressByKakao(trimmedKeyword, 10); - setResults(searched); - } catch (error) { - console.error(error); - setResults([]); - } finally { - setIsSearching(false); - } - }, 250); + if (hasOpenedRef.current) return; - return () => { - if (debounceRef.current) { - window.clearTimeout(debounceRef.current); - } - }; - }, [keyword, open]); - - const handleSelectItem = (item: AddressSearchItem) => { - onSelectAddress(item); - onClose(); - }; + hasOpenedRef.current = true; + void handleOpenPostcode(); + }, [open]); return ( - setKeyword(event.target.value)} - placeholder="주소를 입력해주세요" - /> - - {results.length > 0 && ( - - {results.map((item) => ( - handleSelectItem(item)} - className="block w-full border-b border-gray-100 px-[0.6rem] pt-[0.8rem] pb-[1rem] text-left" - > - - {item.roadAddress || item.lotNumberAddress} - + + 주소를 검색해주세요 - {item.roadAddress && item.lotNumberAddress && ( - - {item.lotNumberAddress} - - )} - - ))} - - )} - - {keyword.trim() && isSearching && ( - - 검색 중... + + 주소 검색 창에서 주소를 선택하면 상점 주소에 자동으로 입력됩니다. - )} - {keyword.trim() && !isSearching && results.length === 0 && ( - - 검색 결과가 없습니다. - - )} + + {isOpening ? "주소 검색 여는 중..." : "주소 검색 다시 열기"} + + ); } \ No newline at end of file diff --git a/apps/owner/src/app/signup/register/_types/address-search.ts b/apps/owner/src/app/signup/register/_types/address-search.ts index 6f8a069..73c83c5 100644 --- a/apps/owner/src/app/signup/register/_types/address-search.ts +++ b/apps/owner/src/app/signup/register/_types/address-search.ts @@ -1,8 +1,31 @@ export interface AddressSearchItem { id: string; label: string; + roadAddress: string; lotNumberAddress: string; + zonecode: string; +} + +export interface DaumPostcodeData { + zonecode: string; roadAddress: string; - longitude: number; - latitude: number; -} \ No newline at end of file + jibunAddress: string; + address: string; + buildingName: string; + bname: string; +} + +declare global { + interface Window { + daum?: { + Postcode: new (options: { + oncomplete: (data: DaumPostcodeData) => void; + onclose?: () => void; + }) => { + open: () => void; + }; + }; + } +} + +export {}; \ No newline at end of file diff --git a/packages/api/src/models/auth.ts b/packages/api/src/models/auth.ts index 4d7278a..1ee43f3 100644 --- a/packages/api/src/models/auth.ts +++ b/packages/api/src/models/auth.ts @@ -9,6 +9,7 @@ export interface JoinReqDTO { } export interface JoinRespDTO { + accessToken: string; memberName: string; email: string; }
- {item.roadAddress || item.lotNumberAddress} -
주소를 검색해주세요
- {item.lotNumberAddress} -
- 검색 중... +
+ 주소 검색 창에서 주소를 선택하면 상점 주소에 자동으로 입력됩니다.
- 검색 결과가 없습니다. -