사진 첨부
@@ -41,12 +49,23 @@ export default function PhotoUploadSection({
className="relative flex h-[18rem] w-full items-center justify-center overflow-hidden rounded-[10px] bg-background"
>
{previewUrl ? (
-
+ <>
+
+
+
+ >
) : (
)}
diff --git a/apps/owner/src/app/signup/register/_components/sections/RandomBoxSection.tsx b/apps/owner/src/app/signup/register/_components/sections/RandomBoxSection.tsx
index 57b3957..225a64e 100644
--- a/apps/owner/src/app/signup/register/_components/sections/RandomBoxSection.tsx
+++ b/apps/owner/src/app/signup/register/_components/sections/RandomBoxSection.tsx
@@ -1,10 +1,10 @@
"use client";
import { Button, Card } from "@compasser/design-system";
-import type { RandomBoxItem } from "../../_types/register";
+import type { RandomBoxRespDTO } from "@compasser/api";
interface RandomBoxSectionProps {
- randomBoxes: RandomBoxItem[];
+ randomBoxes: RandomBoxRespDTO[];
selectedRandomBoxIds: number[];
onToggleRandomBoxSelection: (id: number) => void;
onDeleteRandomBoxes: () => void;
@@ -65,23 +65,23 @@ export default function RandomBoxSection({
}
>
{randomBoxes.map((item) => {
- const isSelected = selectedRandomBoxIds.includes(item.id);
+ const isSelected = selectedRandomBoxIds.includes(item.boxId);
return (
);
})}
diff --git a/apps/owner/src/app/signup/register/_components/sections/TagSection.tsx b/apps/owner/src/app/signup/register/_components/sections/TagSection.tsx
index e00296c..e6a4fb7 100644
--- a/apps/owner/src/app/signup/register/_components/sections/TagSection.tsx
+++ b/apps/owner/src/app/signup/register/_components/sections/TagSection.tsx
@@ -2,16 +2,18 @@
import { Tag } from "@compasser/design-system";
+type FixedTag = "카페" | "베이커리" | "식당";
+
interface TagSectionProps {
- tagOptions: string[];
- selectedTags: string[];
- onToggleTag: (tag: string) => void;
+ tagOptions: FixedTag[];
+ selectedTag: FixedTag | "";
+ onSelectTag: (tag: FixedTag) => void;
}
export default function TagSection({
tagOptions,
- selectedTags,
- onToggleTag,
+ selectedTag,
+ onSelectTag,
}: TagSectionProps) {
return (
@@ -23,8 +25,8 @@ export default function TagSection({
onToggleTag(tag)}
+ selected={selectedTag === tag}
+ onClick={() => onSelectTag(tag)}
>
{tag}
diff --git a/apps/owner/src/app/signup/register/_constants/register.ts b/apps/owner/src/app/signup/register/_constants/register.ts
deleted file mode 100644
index 890e3fc..0000000
--- a/apps/owner/src/app/signup/register/_constants/register.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import type {
- BusinessHours,
- RandomBoxItem,
- DayKey,
- BusinessHourFormValue,
-} from "../_types/register";
-
-export const businessHoursData: BusinessHours = {
- fri: "09:00-21:00",
- mon: "09:00-21:00",
- sat: "10:00-18:00",
- sun: "closed",
- thu: "09:00-21:00",
- tue: "09:00-21:00",
- wed: "09:00-21:00",
-};
-
-export const dayLabelMap: Record
= {
- mon: "월",
- tue: "화",
- wed: "수",
- thu: "목",
- fri: "금",
- sat: "토",
- sun: "일",
-};
-
-export const orderedDays: (keyof BusinessHours)[] = [
- "mon",
- "tue",
- "wed",
- "thu",
- "fri",
- "sat",
- "sun",
-];
-
-export const initialRandomBoxes: RandomBoxItem[] = [
- { id: 1, name: "Level.1", quantity: 5, price: 6000, limit: 1 },
- { id: 2, name: "Level.2", quantity: 3, price: 10000, limit: 1 },
- { id: 3, name: "Level.3", quantity: 1, price: 15000, limit: 1 },
-];
-
-export const tagOptions = ["카페", "베이커리", "식당"];
-
-export const dayOptions: { key: DayKey; label: string }[] = [
- { key: "mon", label: "월" },
- { key: "tue", label: "화" },
- { key: "wed", label: "수" },
- { key: "thu", label: "목" },
- { key: "fri", label: "금" },
- { key: "sat", label: "토" },
- { key: "sun", label: "일" },
-];
-
-export const initialBusinessHourFormValue: BusinessHourFormValue = {
- openTime: "",
- closeTime: "",
- hasBreakTime: null,
- breakStartTime: "",
- breakEndTime: "",
-};
\ No newline at end of file
diff --git a/apps/owner/src/app/signup/register/_types/register.ts b/apps/owner/src/app/signup/register/_types/register.ts
deleted file mode 100644
index c0ba67c..0000000
--- a/apps/owner/src/app/signup/register/_types/register.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-export type BusinessHours = {
- mon: string;
- tue: string;
- wed: string;
- thu: string;
- fri: string;
- sat: string;
- sun: string;
-};
-
-export type RandomBoxItem = {
- id: number;
- name: string;
- quantity: number;
- price: number;
- limit: number;
-};
-
-export interface RandomBoxFormValue {
- name: string;
- quantity: number;
- limit: number;
- pickupStartTime: string;
- pickupEndTime: string;
- description: string;
-}
-
-export type AccountType = "bank" | "holder";
-
-export type DayKey =
- | "mon"
- | "tue"
- | "wed"
- | "thu"
- | "fri"
- | "sat"
- | "sun";
-
-export type BreakTimeOption = "yes" | "no";
-
-export interface BusinessHourFormValue {
- openTime: string;
- closeTime: string;
- hasBreakTime: BreakTimeOption | null;
- breakStartTime: string;
- breakEndTime: string;
-}
\ No newline at end of file
diff --git a/apps/owner/src/app/signup/register/_utils/business-hours.ts b/apps/owner/src/app/signup/register/_utils/business-hours.ts
new file mode 100644
index 0000000..3cd536a
--- /dev/null
+++ b/apps/owner/src/app/signup/register/_utils/business-hours.ts
@@ -0,0 +1,29 @@
+export type DayKey = "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun";
+
+export type BusinessHoursValue = Record;
+
+export const EMPTY_BUSINESS_HOURS: BusinessHoursValue = {
+ mon: "",
+ tue: "",
+ wed: "",
+ thu: "",
+ fri: "",
+ sat: "",
+ sun: "",
+};
+
+export const parseBusinessHours = (value: unknown): BusinessHoursValue => {
+ if (!value || typeof value !== "object") return EMPTY_BUSINESS_HOURS;
+
+ const obj = value as Partial>;
+
+ return {
+ mon: typeof obj.mon === "string" ? obj.mon : "",
+ tue: typeof obj.tue === "string" ? obj.tue : "",
+ wed: typeof obj.wed === "string" ? obj.wed : "",
+ thu: typeof obj.thu === "string" ? obj.thu : "",
+ fri: typeof obj.fri === "string" ? obj.fri : "",
+ sat: typeof obj.sat === "string" ? obj.sat : "",
+ sun: typeof obj.sun === "string" ? obj.sun : "",
+ };
+};
\ No newline at end of file
diff --git a/apps/owner/src/app/signup/register/page.tsx b/apps/owner/src/app/signup/register/page.tsx
index 7ab9cf1..0ce9159 100644
--- a/apps/owner/src/app/signup/register/page.tsx
+++ b/apps/owner/src/app/signup/register/page.tsx
@@ -1,7 +1,13 @@
"use client";
-import { useMemo, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
import { useRouter } from "next/navigation";
+
+import type {
+ StoreUpdateReqDTO,
+ StoreLocationUpdateReqDTO,
+} from "@compasser/api";
+
import StoreNameField from "./_components/fields/StoreNameField";
import EmailField from "./_components/fields/EmailField";
import StoreAddressField from "./_components/fields/StoreAddressField";
@@ -14,144 +20,222 @@ import RegisterSubmitButton from "./_components/RegisterSubmitButton";
import BusinessHoursModal from "./_components/modals/BusinessHoursModal";
import RandomBoxModal from "./_components/modals/RandomBoxModal";
-import {
- businessHoursData,
- dayLabelMap,
- orderedDays,
- initialRandomBoxes,
- tagOptions,
-} from "./_constants/register";
+import { useMyStoreQuery } from "@/shared/queries/query/useMyStoreQuery";
+import { usePatchMyStoreMutation } from "@/shared/queries/mutation/auth/usePatchMyStoreMutation";
+import { usePatchMyStoreLocationMutation } from "@/shared/queries/mutation/auth/usePatchMyStoreLocationMutation";
+import { useRandomBoxListQuery } from "@/shared/queries/query/useRandomBoxListQuery";
+import { useCreateRandomBoxMutation } from "@/shared/queries/mutation/auth/useCreateRandomBoxMutation";
+import { useUpdateRandomBoxMutation } from "@/shared/queries/mutation/auth/useUpdateRandomBoxMutation";
+import { useDeleteRandomBoxMutation } from "@/shared/queries/mutation/auth/useDeleteRandomBoxMutation";
+import { useStoreImageQuery } from "@/shared/queries/query/useStoreImageQuery";
+import { useUploadStoreImageMutation } from "@/shared/queries/mutation/auth/useUploadStoreImageMutation";
+import { useRemoveStoreImageMutation } from "@/shared/queries/mutation/auth/useRemoveStoreImageMutation";
-import type {
- AccountType,
- RandomBoxItem,
- RandomBoxFormValue,
-} from "./_types/register";
+import { parseBusinessHours, EMPTY_BUSINESS_HOURS } from "./_utils/business-hours";
export default function StoreRegisterPage() {
const router = useRouter();
- const [selectedAccountType, setSelectedAccountType] =
- useState(null);
- const [selectedTags, setSelectedTags] = useState([]);
- const [randomBoxes, setRandomBoxes] =
- useState(initialRandomBoxes);
+ const { data: myStore, isLoading: isMyStoreLoading } = useMyStoreQuery();
+ const storeId = myStore?.storeId;
+
+ const { data: randomBoxes = [] } = useRandomBoxListQuery(storeId);
+ const { data: storeImage } = useStoreImageQuery();
+
+ const patchMyStoreMutation = usePatchMyStoreMutation();
+ const patchMyStoreLocationMutation = usePatchMyStoreLocationMutation();
+ const createRandomBoxMutation = useCreateRandomBoxMutation();
+ const updateRandomBoxMutation = useUpdateRandomBoxMutation();
+ const deleteRandomBoxMutation = useDeleteRandomBoxMutation();
+ const uploadStoreImageMutation = useUploadStoreImageMutation();
+ const removeStoreImageMutation = useRemoveStoreImageMutation();
+
+ const [storeName, setStoreName] = useState("");
+ const [storeEmail, setStoreEmail] = useState("");
+ const [inputAddress, setInputAddress] = useState("");
+ const [bankName, setBankName] = useState("");
+ const [depositor, setDepositor] = useState("");
+ const [bankAccount, setBankAccount] = useState("");
+ const [businessHours, setBusinessHours] = useState(EMPTY_BUSINESS_HOURS);
+
+ const tagOptions = ["카페", "베이커리", "식당"] as const;
+ const [selectedTag, setSelectedTag] = useState<"" | "카페" | "베이커리" | "식당">("");
const [selectedRandomBoxIds, setSelectedRandomBoxIds] = useState([]);
- const [isBusinessHoursModalOpen, setIsBusinessHoursModalOpen] =
- useState(false);
+ const [isBusinessHoursModalOpen, setIsBusinessHoursModalOpen] = useState(false);
const [isRandomBoxModalOpen, setIsRandomBoxModalOpen] = useState(false);
const [photoFile, setPhotoFile] = useState(null);
- const [photoPreviewUrl, setPhotoPreviewUrl] = useState("");
+ const [photoPreviewUrl, setPhotoPreviewUrl] = useState("");
+ const [imageRemoved, setImageRemoved] = useState(false);
+
+ useEffect(() => {
+ if (!myStore) return;
+
+ setStoreName(myStore.storeName ?? "");
+ setStoreEmail("");
+ setInputAddress(myStore.inputAddress ?? "");
+ setBusinessHours(parseBusinessHours(myStore.businessHours));
+ }, [myStore]);
+
+ useEffect(() => {
+ if (!storeImage || imageRemoved) return;
+ setPhotoPreviewUrl(storeImage.imageUrl);
+ }, [storeImage, imageRemoved]);
const businessHoursRows = useMemo(() => {
+ const dayLabelMap = {
+ mon: "월",
+ tue: "화",
+ wed: "수",
+ thu: "목",
+ fri: "금",
+ sat: "토",
+ sun: "일",
+ } as const;
+
+ const orderedDays = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] as const;
+
return orderedDays.map((day) => {
- const value = businessHoursData[day];
- const formatted = value === "closed" ? "휴무" : value.replace("-", " ~ ");
+ const value = businessHours[day];
+ const formatted = value === "closed" ? "휴무" : value || "-";
return {
dayLabel: dayLabelMap[day],
time: formatted,
};
});
- }, []);
-
- const toggleTag = (tag: string) => {
- setSelectedTags((prev) =>
- prev.includes(tag)
- ? prev.filter((item) => item !== tag)
- : [...prev, tag]
- );
- };
+ }, [businessHours]);
const toggleRandomBoxSelection = (id: number) => {
setSelectedRandomBoxIds((prev) =>
- prev.includes(id)
- ? prev.filter((item) => item !== id)
- : [...prev, id]
+ prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
};
- const handleDeleteRandomBoxes = () => {
- setRandomBoxes((prev) =>
- prev.filter((item) => !selectedRandomBoxIds.includes(item.id))
+ const handleDeleteRandomBoxes = async () => {
+ if (!storeId || selectedRandomBoxIds.length === 0) return;
+
+ await Promise.all(
+ selectedRandomBoxIds.map((boxId) =>
+ deleteRandomBoxMutation.mutateAsync({ storeId, boxId })
+ )
);
+
setSelectedRandomBoxIds([]);
};
- const handleOpenRandomBoxModal = () => {
- setIsRandomBoxModalOpen(true);
- };
+ const handleSubmitRandomBox = async (form: {
+ boxName: string;
+ stock: number;
+ price: number;
+ buyLimit: number;
+ content: string;
+ boxId?: number;
+ }) => {
+ if (!storeId) return;
- const handleCloseRandomBoxModal = () => {
- setIsRandomBoxModalOpen(false);
- };
+ if (form.boxId) {
+ await updateRandomBoxMutation.mutateAsync({
+ storeId,
+ boxId: form.boxId,
+ body: {
+ boxName: form.boxName,
+ stock: form.stock,
+ price: form.price,
+ buyLimit: form.buyLimit,
+ content: form.content,
+ },
+ });
+ return;
+ }
- const handleSubmitRandomBox = (data: RandomBoxFormValue) => {
- const nextId =
- randomBoxes.length > 0
- ? Math.max(...randomBoxes.map((item) => item.id)) + 1
- : 1;
-
- setRandomBoxes((prev) => [
- ...prev,
- {
- id: nextId,
- name: data.name,
- quantity: data.quantity,
- price: 0,
- limit: data.limit,
- pickupStartTime: data.pickupStartTime,
- pickupEndTime: data.pickupEndTime,
- description: data.description,
+ await createRandomBoxMutation.mutateAsync({
+ storeId,
+ body: {
+ boxName: form.boxName,
+ stock: form.stock,
+ price: form.price,
+ buyLimit: form.buyLimit,
+ content: form.content,
},
- ]);
- };
-
- const handleOpenBusinessHoursModal = () => {
- setIsBusinessHoursModalOpen(true);
+ });
};
- const handleCloseBusinessHoursModal = () => {
+ const handleSubmitBusinessHours = (value: typeof businessHours) => {
+ setBusinessHours(value);
setIsBusinessHoursModalOpen(false);
};
- const handleSubmitBusinessHours = (data: any) => {
- console.log("영업시간 등록", data);
+ const handleChangePhoto = (file: File) => {
+ setPhotoFile(file);
+ setImageRemoved(false);
+ setPhotoPreviewUrl(URL.createObjectURL(file));
};
- const handleSearchAddress = () => {
- console.log("주소 검색");
+ const handleRemovePhoto = async () => {
+ setPhotoFile(null);
+ setPhotoPreviewUrl("");
+ setImageRemoved(true);
+
+ if (storeImage) {
+ await removeStoreImageMutation.mutateAsync(undefined);
+ }
};
- const handleChangePhoto = (file: File) => {
- setPhotoFile(file);
+ const handleCompleteRegister = async () => {
+ if (!myStore) return;
- const objectUrl = URL.createObjectURL(file);
- setPhotoPreviewUrl(objectUrl);
- };
+ const storePayload: StoreUpdateReqDTO = {
+ storeName,
+ storeEmail,
+ bankName,
+ depositor,
+ bankAccount,
+ businessHours,
+ };
+
+ const locationPayload: StoreLocationUpdateReqDTO = {
+ inputAddress,
+ };
+
+ await patchMyStoreMutation.mutateAsync(storePayload);
+ await patchMyStoreLocationMutation.mutateAsync(locationPayload);
+
+ if (photoFile) {
+ await uploadStoreImageMutation.mutateAsync(photoFile);
+ }
- const handleCompleteRegister = () => {
- console.log("업로드 파일", photoFile);
router.push("/main");
};
+ if (isMyStoreLoading) {
+ return 불러오는 중...
;
+ }
+
return (
<>
-
-
-
+
+
+
{}}
+ />
setIsBusinessHoursModalOpen(true)}
/>
setIsRandomBoxModalOpen(true)}
/>
@@ -180,13 +265,14 @@ export default function StoreRegisterPage() {
setIsBusinessHoursModalOpen(false)}
+ initialValue={businessHours}
onSubmit={handleSubmitBusinessHours}
/>
setIsRandomBoxModalOpen(false)}
onSubmit={handleSubmitRandomBox}
/>
>
diff --git a/apps/owner/src/shared/api/api.ts b/apps/owner/src/shared/api/api.ts
new file mode 100644
index 0000000..37e2264
--- /dev/null
+++ b/apps/owner/src/shared/api/api.ts
@@ -0,0 +1,47 @@
+"use client";
+
+import {
+ createCompasserApi,
+ createAuthModule,
+ type TokenPair,
+ type TokenStore,
+ createOwnerModule,
+ createStoreModule,
+ createRandomBoxModule,
+ createStoreImageModule,
+} from "@compasser/api";
+
+const tokenStore: TokenStore = {
+ getAccessToken: () =>
+ typeof window === "undefined" ? null : localStorage.getItem("ownerAccessToken"),
+
+ getRefreshToken: () =>
+ typeof window === "undefined" ? null : localStorage.getItem("ownerRefreshToken"),
+
+ setTokens: ({ accessToken, refreshToken }: TokenPair) => {
+ if (typeof window === "undefined") return;
+
+ localStorage.setItem("ownerAccessToken", accessToken);
+
+ if (refreshToken) {
+ localStorage.setItem("ownerRefreshToken", refreshToken);
+ }
+ },
+
+ clearTokens: () => {
+ if (typeof window === "undefined") return;
+ localStorage.removeItem("ownerAccessToken");
+ localStorage.removeItem("ownerRefreshToken");
+ },
+};
+
+export const compasserApi = createCompasserApi({
+ baseURL: process.env.NEXT_PUBLIC_API_BASE_URL ?? "",
+ tokenStore,
+});
+
+export const authModule = createAuthModule(compasserApi);
+export const ownerModule = createOwnerModule(compasserApi);
+export const storeModule = createStoreModule(compasserApi);
+export const randomBoxModule = createRandomBoxModule(compasserApi);
+export const storeImageModule = createStoreImageModule(compasserApi);
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/mutation/auth/useCreateRandomBoxMutation.ts b/apps/owner/src/shared/queries/mutation/auth/useCreateRandomBoxMutation.ts
new file mode 100644
index 0000000..f20982c
--- /dev/null
+++ b/apps/owner/src/shared/queries/mutation/auth/useCreateRandomBoxMutation.ts
@@ -0,0 +1,9 @@
+"use client";
+
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { randomBoxModule } from "@/shared/api/api";
+
+export const useCreateRandomBoxMutation = () => {
+ const queryClient = useQueryClient();
+ return useMutation(randomBoxModule.mutations.create(queryClient));
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/mutation/auth/useDeleteRandomBoxMutation.ts b/apps/owner/src/shared/queries/mutation/auth/useDeleteRandomBoxMutation.ts
new file mode 100644
index 0000000..01dcf75
--- /dev/null
+++ b/apps/owner/src/shared/queries/mutation/auth/useDeleteRandomBoxMutation.ts
@@ -0,0 +1,9 @@
+"use client";
+
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { randomBoxModule } from "@/shared/api/api";
+
+export const useDeleteRandomBoxMutation = () => {
+ const queryClient = useQueryClient();
+ return useMutation(randomBoxModule.mutations.remove(queryClient));
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/mutation/auth/usePatchMyStoreLocationMutation.ts b/apps/owner/src/shared/queries/mutation/auth/usePatchMyStoreLocationMutation.ts
new file mode 100644
index 0000000..9e98019
--- /dev/null
+++ b/apps/owner/src/shared/queries/mutation/auth/usePatchMyStoreLocationMutation.ts
@@ -0,0 +1,9 @@
+"use client";
+
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { storeModule } from "@/shared/api/api";
+
+export const usePatchMyStoreLocationMutation = () => {
+ const queryClient = useQueryClient();
+ return useMutation(storeModule.mutations.patchMyStoreLocation(queryClient));
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/mutation/auth/usePatchMyStoreMutation.ts b/apps/owner/src/shared/queries/mutation/auth/usePatchMyStoreMutation.ts
new file mode 100644
index 0000000..395f9be
--- /dev/null
+++ b/apps/owner/src/shared/queries/mutation/auth/usePatchMyStoreMutation.ts
@@ -0,0 +1,9 @@
+"use client";
+
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { storeModule } from "@/shared/api/api";
+
+export const usePatchMyStoreMutation = () => {
+ const queryClient = useQueryClient();
+ return useMutation(storeModule.mutations.patchMyStore(queryClient));
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/mutation/auth/useRemoveStoreImageMutation.ts b/apps/owner/src/shared/queries/mutation/auth/useRemoveStoreImageMutation.ts
new file mode 100644
index 0000000..5725c14
--- /dev/null
+++ b/apps/owner/src/shared/queries/mutation/auth/useRemoveStoreImageMutation.ts
@@ -0,0 +1,9 @@
+"use client";
+
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { storeImageModule } from "@/shared/api/api";
+
+export const useRemoveStoreImageMutation = () => {
+ const queryClient = useQueryClient();
+ return useMutation(storeImageModule.mutations.remove(queryClient));
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/mutation/auth/useSignupMutation.ts b/apps/owner/src/shared/queries/mutation/auth/useSignupMutation.ts
new file mode 100644
index 0000000..82ae4c9
--- /dev/null
+++ b/apps/owner/src/shared/queries/mutation/auth/useSignupMutation.ts
@@ -0,0 +1,10 @@
+"use client";
+
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { authModule } from "@/shared/api/api";
+
+export const useSignupMutation = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation(authModule.mutations.signUp(queryClient));
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/mutation/auth/useUpdateRandomBoxMutation.ts b/apps/owner/src/shared/queries/mutation/auth/useUpdateRandomBoxMutation.ts
new file mode 100644
index 0000000..189959f
--- /dev/null
+++ b/apps/owner/src/shared/queries/mutation/auth/useUpdateRandomBoxMutation.ts
@@ -0,0 +1,9 @@
+"use client";
+
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { randomBoxModule } from "@/shared/api/api";
+
+export const useUpdateRandomBoxMutation = () => {
+ const queryClient = useQueryClient();
+ return useMutation(randomBoxModule.mutations.update(queryClient));
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/mutation/auth/useUploadStoreImageMutation.ts b/apps/owner/src/shared/queries/mutation/auth/useUploadStoreImageMutation.ts
new file mode 100644
index 0000000..9021e19
--- /dev/null
+++ b/apps/owner/src/shared/queries/mutation/auth/useUploadStoreImageMutation.ts
@@ -0,0 +1,9 @@
+"use client";
+
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { storeImageModule } from "@/shared/api/api";
+
+export const useUploadStoreImageMutation = () => {
+ const queryClient = useQueryClient();
+ return useMutation(storeImageModule.mutations.upload(queryClient));
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/mutation/auth/useVerifyBusinessMutation.ts b/apps/owner/src/shared/queries/mutation/auth/useVerifyBusinessMutation.ts
new file mode 100644
index 0000000..7112468
--- /dev/null
+++ b/apps/owner/src/shared/queries/mutation/auth/useVerifyBusinessMutation.ts
@@ -0,0 +1,12 @@
+"use client";
+
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { ownerModule } from "@/shared/api/api";
+
+export const useVerifyBusinessMutation = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation(
+ ownerModule.mutations.verifyBizAndUpgrade(queryClient),
+ );
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/query/useMyStoreQuery.ts b/apps/owner/src/shared/queries/query/useMyStoreQuery.ts
new file mode 100644
index 0000000..cbd0da5
--- /dev/null
+++ b/apps/owner/src/shared/queries/query/useMyStoreQuery.ts
@@ -0,0 +1,8 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { storeModule } from "@/shared/api/api";
+
+export const useMyStoreQuery = () => {
+ return useQuery(storeModule.queries.myStore());
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/query/useRandomBoxListQuery.ts b/apps/owner/src/shared/queries/query/useRandomBoxListQuery.ts
new file mode 100644
index 0000000..de7b45c
--- /dev/null
+++ b/apps/owner/src/shared/queries/query/useRandomBoxListQuery.ts
@@ -0,0 +1,11 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { randomBoxModule } from "@/shared/api/api";
+
+export const useRandomBoxListQuery = (storeId?: number) => {
+ return useQuery({
+ ...randomBoxModule.queries.list({ storeId: storeId ?? 0 }),
+ enabled: Boolean(storeId),
+ });
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/queries/query/useStoreImageQuery.ts b/apps/owner/src/shared/queries/query/useStoreImageQuery.ts
new file mode 100644
index 0000000..e01ca55
--- /dev/null
+++ b/apps/owner/src/shared/queries/query/useStoreImageQuery.ts
@@ -0,0 +1,8 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { storeImageModule } from "@/shared/api/api";
+
+export const useStoreImageQuery = () => {
+ return useQuery(storeImageModule.queries.get());
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/stores/ownerSignup.store.ts b/apps/owner/src/shared/stores/ownerSignup.store.ts
new file mode 100644
index 0000000..f98b39d
--- /dev/null
+++ b/apps/owner/src/shared/stores/ownerSignup.store.ts
@@ -0,0 +1,65 @@
+"use client";
+
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+
+type OwnerSignupStep = "signup" | "business" | "register" | "done";
+
+interface OwnerSignupState {
+ step: OwnerSignupStep;
+ signupCompleted: boolean;
+ businessCompleted: boolean;
+ registerCompleted: boolean;
+ email: string | null;
+ setSignupCompleted: (email: string) => void;
+ setBusinessCompleted: () => void;
+ setRegisterCompleted: () => void;
+ resetSignupFlow: () => void;
+}
+
+export const useOwnerSignupStore = create()(
+ persist(
+ (set) => ({
+ step: "signup",
+ signupCompleted: false,
+ businessCompleted: false,
+ registerCompleted: false,
+ email: null,
+
+ setSignupCompleted: (email) =>
+ set({
+ step: "business",
+ signupCompleted: true,
+ businessCompleted: false,
+ registerCompleted: false,
+ email,
+ }),
+
+ setBusinessCompleted: () =>
+ set((state) => ({
+ ...state,
+ step: "register",
+ businessCompleted: true,
+ })),
+
+ setRegisterCompleted: () =>
+ set((state) => ({
+ ...state,
+ step: "done",
+ registerCompleted: true,
+ })),
+
+ resetSignupFlow: () =>
+ set({
+ step: "signup",
+ signupCompleted: false,
+ businessCompleted: false,
+ registerCompleted: false,
+ email: null,
+ }),
+ }),
+ {
+ name: "owner-signup-flow",
+ },
+ ),
+);
\ No newline at end of file
diff --git a/apps/owner/src/shared/utils/bank.ts b/apps/owner/src/shared/utils/bank.ts
new file mode 100644
index 0000000..209edc4
--- /dev/null
+++ b/apps/owner/src/shared/utils/bank.ts
@@ -0,0 +1,28 @@
+export const bankNameOptions = [
+ "국민은행",
+ "신한은행",
+ "우리은행",
+ "하나은행",
+ "농협은행",
+ "기업은행",
+ "카카오뱅크",
+ "토스뱅크",
+ "케이뱅크",
+ "새마을금고",
+ "수협은행",
+ "부산은행",
+ "대구은행",
+ "광주은행",
+ "전북은행",
+ "경남은행",
+] as const;
+
+export const filterBankNames = (keyword: string) => {
+ const normalized = keyword.trim();
+ if (!normalized) return bankNameOptions;
+ return bankNameOptions.filter((bank) => bank.includes(normalized));
+};
+
+export const normalizeAccountNumber = (value: string) => {
+ return value.replace(/[^\d-]/g, "");
+};
\ No newline at end of file
diff --git a/apps/owner/src/shared/utils/businessLicense.ts b/apps/owner/src/shared/utils/businessLicense.ts
new file mode 100644
index 0000000..303ec8f
--- /dev/null
+++ b/apps/owner/src/shared/utils/businessLicense.ts
@@ -0,0 +1,11 @@
+export const normalizeBusinessNumber = (value: string) => {
+ return value.replace(/\D/g, "");
+};
+
+export const isValidBusinessNumberFormat = (value: string) => {
+ return /^\d{10}$/.test(normalizeBusinessNumber(value));
+};
+
+export const isValidBusinessNumber = (value: string) => {
+ return /^\d{10}$/.test(normalizeBusinessNumber(value));
+};
\ No newline at end of file
diff --git a/package.json b/package.json
index 48c2603..db1d1af 100644
--- a/package.json
+++ b/package.json
@@ -22,5 +22,8 @@
"turbo": "^2.0.0",
"typescript": "^5",
"typescript-eslint": "^8"
+ },
+ "dependencies": {
+ "zustand": "^5.0.12"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 04cf6ad..d314d93 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,6 +7,10 @@ settings:
importers:
.:
+ dependencies:
+ zustand:
+ specifier: ^5.0.12
+ version: 5.0.12(@types/react@19.2.14)(react@19.2.3)
devDependencies:
'@eslint/js':
specifier: ^9
@@ -3127,6 +3131,24 @@ packages:
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
+ zustand@5.0.12:
+ resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
snapshots:
'@adobe/css-tools@4.4.4': {}
@@ -6175,3 +6197,8 @@ snapshots:
zod: 4.3.6
zod@4.3.6: {}
+
+ zustand@5.0.12(@types/react@19.2.14)(react@19.2.3):
+ optionalDependencies:
+ '@types/react': 19.2.14
+ react: 19.2.3