Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e411560
refactor: 사업자 인증 단계 리팩토링 (스키마 분리 및 개업일자 검증 추가)
dew102938 Feb 8, 2026
0d6b599
feat: 사업자 인증 API 연동
dew102938 Feb 8, 2026
601ac82
refactor: 스웨거 맞춰 스키마, ui 수정 / 이미 인증된 사업자도 식당 등록 단계 진행되도록 개선
dew102938 Feb 9, 2026
5d2c2b9
refactor: 스웨거 맞춰 메뉴 등록 스키마, ui 수정
dew102938 Feb 9, 2026
27132a9
feat: 식당 등록 API 연동, 데이터 변환 로직 구현
dew102938 Feb 10, 2026
48468e9
feat: 식당 대표 이미지 등록 API 연동
dew102938 Feb 10, 2026
5c40104
feat: 메뉴 등록 API 연동, 사장 인증 후 권한 갱신을 위한 로그아웃 로직 추가
dew102938 Feb 11, 2026
772fa14
Merge branch 'develop' into feat/StoreRegistration-api
dew102938 Feb 11, 2026
d339ee3
Merge branch 'develop' into feat/StoreRegistration-api
dew102938 Feb 11, 2026
9d8143e
refactor: CategoryEnum 명칭 중복 방지를 위해 Menu/Store용으로 각각 분리
dew102938 Feb 11, 2026
e0f0ccf
refactor: getValues() 사용, input id 연결
dew102938 Feb 11, 2026
f7bfdb3
refactor: 영업 시간 변환 유틸 함수의 암묵적 기본값 제거, 필수값 검증 로직 추가
dew102938 Feb 11, 2026
ba24842
refactor: 가게 등록 로직을 mutateAsync, try-catch 패턴으로 변경하여 에러 처리 강화
dew102938 Feb 11, 2026
6a04145
fix: 메뉴 가격 유효성 검사 정규식 수정 (0 허용, 불필요한 선행 0 차단)
dew102938 Feb 11, 2026
06d24e7
fix: 가게 대표 이미지 유효성 검사 강화
dew102938 Feb 11, 2026
d3ba4d4
docs: TODO 주석 추가
dew102938 Feb 11, 2026
0273c11
refactor: 지오코딩 실패 시 등록 차단 로직 구현
dew102938 Feb 11, 2026
bf6266f
refactor: 정기 휴무일 접근성 개선
dew102938 Feb 11, 2026
bf8a781
feat: ESC키 핸들러 추가, 모달에 role, aria-modal 등 속성 추가
dew102938 Feb 11, 2026
27579a1
fix: 주소 데이터 변환 시 발생할 수 있는 문자열 결합 오류 방지
dew102938 Feb 11, 2026
532842c
refactor: 불필요한 any 타입 캐스팅 제거
dew102938 Feb 11, 2026
f6bddc9
Merge branch 'develop' into feat/StoreRegistration-api
dew102938 Feb 11, 2026
0edc8c0
fix: 코드래빗 수정사항 반영
dew102938 Feb 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"immer": "^11.1.3",
"lucide-react": "^0.562.0",
"react": "^19.2.0",
"react-daum-postcode": "^3.2.0",
"react-day-picker": "^9.13.0",
"react-dom": "^19.2.0",
"react-hook-form": "^7.69.0",
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type {
ResponseLoginDto,
ResponseLogoutDto,
ResponseRefreshDto,
RequestVerifyOwnerDto,
ResponseVerifyOwnerDto,
} from "@/types/auth";
import { api } from "./axios";
import { useAuthStore } from "@/stores/useAuthStore";
Expand Down Expand Up @@ -65,3 +67,13 @@ export const postRefresh = async (): Promise<ResponseRefreshDto> => {
}
return data.result;
};

export const patchVerifyOwner = async (
body: RequestVerifyOwnerDto,
): Promise<ResponseVerifyOwnerDto> => {
const { data } = await api.patch<ApiResponse<ResponseVerifyOwnerDto>>(
"/api/users/role/owner",
body,
);
return data.result;
};
34 changes: 34 additions & 0 deletions src/api/menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { ApiResponse } from "@/types/api";
import type {
RequestMenuCreateDto,
RequestMenuImageDto,
ResponseMenuCreateDto,
ResponseMenuImageDto,
} from "@/types/menus";
import { api } from "./axios";

export const postMenuCreate = async (
storeId: number,
body: RequestMenuCreateDto,
): Promise<ResponseMenuCreateDto> => {
const { data } = await api.post<ApiResponse<ResponseMenuCreateDto>>(
`/api/v1/stores/${storeId}/menus`,
body,
);
return data.result;
};

export const postMenuImage = async (
storeId: number,
body: RequestMenuImageDto,
): Promise<ResponseMenuImageDto> => {
const formData = new FormData();

formData.append("image", body.image);

const { data } = await api.post<ApiResponse<ResponseMenuImageDto>>(
`/api/v1/stores/${storeId}/menus/images`,
formData,
);
return data.result;
};
33 changes: 33 additions & 0 deletions src/api/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type {
RequestMainImageDto,
RequestStoreCreateDto,
ResponseMainImageDto,
ResponseStoreCreateDto,
} from "@/types/store";
import { api } from "./axios";
import type { ApiResponse } from "@/types/api";

export const postRegisterStore = async (
body: RequestStoreCreateDto,
): Promise<ResponseStoreCreateDto> => {
const { data } = await api.post<ApiResponse<ResponseStoreCreateDto>>(
"/api/v1/stores",
body,
);
return data.result;
};

export const postMainImage = async (
storeId: number,
body: RequestMainImageDto,
): Promise<ResponseMainImageDto> => {
const formData = new FormData();

formData.append("mainImage", body.mainImage);

const { data } = await api.post<ApiResponse<ResponseMainImageDto>>(
`/api/v1/stores/${storeId}/main-image`,
formData,
);
return data.result;
};
Comment thread
dew102938 marked this conversation as resolved.
38 changes: 38 additions & 0 deletions src/components/store-registration/BusinessAuth.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import z from "zod";

export const BusinessAuthSchema = z.object({
businessNumber: z
.string()
.regex(/^[0-9]+$/, "숫자만 입력해주세요.")
.length(10, "1234567890 형식의 10자리 숫자를 입력해주세요."),
startDate: z
.string()
.regex(/^[0-9]+$/, "숫자만 입력해주세요.")
.length(8, "YYYYMMDD 형식의 8자리 숫자를 입력해주세요.")
.refine(
(val) => {
const year = parseInt(val.substring(0, 4));
const month = parseInt(val.substring(4, 6));
const day = parseInt(val.substring(6, 8));

if (month < 1 || month > 12) return false;

const date = new Date(year, month - 1, day);

if (
date.getFullYear() !== year ||
date.getMonth() !== month - 1 ||
date.getDate() !== day
) {
return false;
}

if (date > new Date()) return false;

return true;
},
{ message: "유효하지 않은 날짜입니다. (예: 20240101)" },
),
});

export type BusinessAuthFormValues = z.infer<typeof BusinessAuthSchema>;
9 changes: 7 additions & 2 deletions src/components/store-registration/CompleteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { useEffect } from "react";
import type { StoreInfoFormValues } from "./StoreInfo.schema";
import { Check } from "lucide-react";
import { categoryLabel } from "@/types/store";

interface CompleteModalProps {
isOpen: boolean;
Expand Down Expand Up @@ -50,7 +51,9 @@ export default function CompleteModal({
</div>
<div className="flex justify-between items-start gap-4">
<span className="text-gray-500 shrink-0">음식 종류</span>
<span className="text-gray-900 text-right">{data.category || "-"}</span>
<span className="text-gray-900 text-right">
{data.category ? categoryLabel[data.category] : "-"}
</span>
</div>
<div className="flex justify-between items-start gap-4">
<span className="text-gray-500 shrink-0">주소</span>
Expand All @@ -60,7 +63,9 @@ export default function CompleteModal({
</div>
<div className="flex justify-between items-start gap-4">
<span className="text-gray-500 shrink-0">전화번호</span>
<span className="text-gray-900 text-right">{data.phone || "-"}</span>
<span className="text-gray-900 text-right">
{data.phoneNumber || "-"}
</span>
</div>
<div className="flex justify-between items-start gap-4">
<span className="text-gray-500 shrink-0">운영 시간</span>
Expand Down
53 changes: 40 additions & 13 deletions src/components/store-registration/Menu.schema.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
import { z } from "zod";

export const MenuCategoryEnum = z.enum(["MAIN", "SIDE", "BEVERAGE", "ALCOHOL"]);

const MAX_FILE_SIZE = 1 * 1024 * 1024;

const ACCEPTED_IMAGE_TYPES = ["image/jpeg", "image/png"];

export const MenuSchema = z.object({
menus: z
.array(
z.object({
menuName: z.string().min(1, "메뉴명을 입력하세요."),
price: z
.string()
.min(1, "가격을 입력하세요.")
.regex(/^\d+$/, "숫자만 입력해주세요."),
description: z.string().optional(),
image: z.any().optional(),
}),
)
.optional(),
menus: z.array(
z.object({
name: z.string().min(1, "메뉴명을 입력하세요."),
description: z.string().max(500, "500자 이내로 입력하세요.").optional(),
price: z
.string()
.min(1, "가격을 입력하세요.")
.regex(/^(0|[1-9]\d*)$/, "0 이상의 올바른 숫자를 입력하세요."),
category: MenuCategoryEnum,
imageKey: z
.any()
.optional()
.refine(
(file) => {
if (!file || typeof file === "string") return true;
return file instanceof File ? file.size <= MAX_FILE_SIZE : true;
},
{
message: "이미지 크기는 1MB 이하여야 합니다.",
},
)
.refine(
(file) => {
if (!file || typeof file === "string") return true;
return file instanceof File
? ACCEPTED_IMAGE_TYPES.includes(file.type)
: true;
},
{
message: ".jpg, .png 형식의 이미지만 업로드 가능합니다.",
},
),
}),
),
});

export type MenuFormValues = z.infer<typeof MenuSchema>;
Loading