{/* 콘텐츠 */}
-
diff --git a/src/components/common/RealmatchHeader.tsx b/src/components/common/RealmatchHeader.tsx
index 0349591..ef15fce 100644
--- a/src/components/common/RealmatchHeader.tsx
+++ b/src/components/common/RealmatchHeader.tsx
@@ -19,7 +19,7 @@ export default function RealMatchHeader({
// 가능하면 라우터 상위로, 실패하면 브라우저 히스토리
try {
- navigate({ to: "/matching-test/matching-test/step3" });
+ navigate({ to: "/matching/test/step3" });
} catch {
window.history.back();
}
diff --git a/src/components/form/DateField.tsx b/src/components/form/DateField.tsx
new file mode 100644
index 0000000..f2327c2
--- /dev/null
+++ b/src/components/form/DateField.tsx
@@ -0,0 +1,44 @@
+interface DateFieldProps {
+ placeholder: string;
+ value?: string;
+ onClick: () => void;
+}
+
+// 날짜를 "YYYY년 M월 D일" 형식으로 변환하는 함수
+function formatDateToKorean(dateString: string): string {
+ if (!dateString) return "";
+
+ try {
+ const date = new Date(dateString);
+ if (isNaN(date.getTime())) return dateString;
+
+ const year = date.getFullYear();
+ const month = date.getMonth() + 1;
+ const day = date.getDate();
+
+ return `${year}년 ${month}월 ${day}일`;
+ } catch {
+ return dateString;
+ }
+}
+
+export default function DateField({
+ placeholder,
+ value,
+ onClick,
+}: DateFieldProps) {
+ const displayValue = value ? formatDateToKorean(value) : placeholder;
+
+ return (
+
+ );
+}
diff --git a/src/components/form/FeeInput.tsx b/src/components/form/FeeInput.tsx
new file mode 100644
index 0000000..47028df
--- /dev/null
+++ b/src/components/form/FeeInput.tsx
@@ -0,0 +1,26 @@
+interface FeeInputProps {
+ value: string;
+ onChange: (value: string) => void;
+ placeholder?: string;
+ unit?: string;
+}
+
+export default function FeeInput({
+ value,
+ onChange,
+ placeholder = "",
+ unit = "원",
+}: FeeInputProps) {
+ return (
+
+ onChange(e.target.value.replace(/[^0-9]/g, ""))}
+ className="flex-1 text-title3 text-text-black placeholder:text-text-gray3 focus:outline-none bg-transparent text-right"
+ placeholder={placeholder}
+ />
+ {unit}
+
+ );
+}
diff --git a/src/components/form/SelectField.tsx b/src/components/form/SelectField.tsx
new file mode 100644
index 0000000..0b1aacb
--- /dev/null
+++ b/src/components/form/SelectField.tsx
@@ -0,0 +1,27 @@
+interface SelectFieldProps {
+ placeholder: string;
+ value?: string;
+ onClick: () => void;
+}
+
+export default function SelectField({
+ placeholder,
+ value,
+ onClick,
+}: SelectFieldProps) {
+ return (
+
+ );
+}
diff --git a/src/components/form/TextArea.tsx b/src/components/form/TextArea.tsx
new file mode 100644
index 0000000..8daa369
--- /dev/null
+++ b/src/components/form/TextArea.tsx
@@ -0,0 +1,41 @@
+import { useRef, useEffect } from "react";
+
+interface TextAreaProps {
+ placeholder: string;
+ maxLength: number;
+ value: string;
+ onChange: (value: string) => void;
+}
+
+export default function TextArea({
+ placeholder,
+ maxLength,
+ value,
+ onChange,
+}: TextAreaProps) {
+ const textareaRef = useRef
(null);
+
+ useEffect(() => {
+ const textarea = textareaRef.current;
+ if (textarea) {
+ textarea.style.height = "auto";
+ textarea.style.height = `${textarea.scrollHeight}px`;
+ }
+ }, [value]);
+
+ return (
+
+
+ );
+}
diff --git a/src/components/form/TextInput.tsx b/src/components/form/TextInput.tsx
new file mode 100644
index 0000000..c891e7e
--- /dev/null
+++ b/src/components/form/TextInput.tsx
@@ -0,0 +1,28 @@
+interface TextInputProps {
+ placeholder: string;
+ maxLength: number;
+ value: string;
+ onChange: (value: string) => void;
+}
+
+export default function TextInput({
+ placeholder,
+ maxLength,
+ value,
+ onChange,
+}: TextInputProps) {
+ return (
+
+ onChange(e.target.value.slice(0, maxLength))}
+ className="flex-1 bg-transparent text-title3 text-text-black placeholder:text-text-gray3 focus:outline-none"
+ />
+
+ {value.length}/{maxLength}
+
+
+ );
+}
diff --git a/src/components/form/index.ts b/src/components/form/index.ts
new file mode 100644
index 0000000..76877d7
--- /dev/null
+++ b/src/components/form/index.ts
@@ -0,0 +1,5 @@
+export { default as TextInput } from "./TextInput";
+export { default as TextArea } from "./TextArea";
+export { default as SelectField } from "./SelectField";
+export { default as DateField } from "./DateField";
+export { default as FeeInput } from "./FeeInput";
diff --git a/src/data/existing-campaigns.ts b/src/data/existing-campaigns.ts
new file mode 100644
index 0000000..7526200
--- /dev/null
+++ b/src/data/existing-campaigns.ts
@@ -0,0 +1,6 @@
+// 기존 캠페인 목록 (더미 데이터)
+export const existingCampaigns = [
+ { id: 1, name: "글로우 쿠션 신제품 론칭 리뷰" },
+ { id: 2, name: "글로우 선크림' 체험단 모집" },
+ { id: 3, name: "글로우 세럼' 신제품 론팅 리뷰" },
+];
diff --git a/src/hooks/useHideBottomTab.ts b/src/hooks/useHideBottomTab.ts
new file mode 100644
index 0000000..d2b4905
--- /dev/null
+++ b/src/hooks/useHideBottomTab.ts
@@ -0,0 +1,22 @@
+import { useContext, useLayoutEffect } from "react";
+import { LayoutContext } from "../routes/_main/layout-context";
+
+/**
+ * 바텀탭을 숨기는 커스텀 훅
+ * 바텀시트가 열릴 때 바텀탭을 숨기고, 닫히거나 컴포넌트가 언마운트되면 복원
+ *
+ * @param hide - true면 바텀탭 숨김, false면 표시
+ */
+export function useHideBottomTab(hide: boolean) {
+ const layout = useContext(LayoutContext);
+
+ useLayoutEffect(() => {
+ if (!layout) return;
+
+ layout.setHideBottomTab(hide);
+
+ return () => {
+ layout.setHideBottomTab(false);
+ };
+ }, [hide, layout]);
+}
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index 7dfddd5..9f20ed2 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -26,9 +26,9 @@ import { Route as MainMypageLikesRouteImport } from './routes/_main/mypage/likes
import { Route as MainMypageInquiryRouteImport } from './routes/_main/mypage/inquiry'
import { Route as MainMypageEditRouteImport } from './routes/_main/mypage/edit'
import { Route as MainHomePreRouteImport } from './routes/_main/_home/pre'
+import { Route as MainMatchingSuggestRouteRouteImport } from './routes/_main/matching/suggest/route'
import { Route as MainMatchingCampaignRouteRouteImport } from './routes/_main/matching/campaign/route'
import { Route as MainMatchingBrandRouteRouteImport } from './routes/_main/matching/brand/route'
-import { Route as MainMatchingTestMatchingResultRouteRouteImport } from './routes/_main/matching-test/matching-result/route'
import { Route as MainBusinessRejectionRouteRouteImport } from './routes/_main/_business/rejection/route'
import { Route as MainBusinessProposalRouteRouteImport } from './routes/_main/_business/proposal/route'
import { Route as MainBusinessCampaignRouteRouteImport } from './routes/_main/_business/campaign/route'
@@ -39,9 +39,12 @@ import { Route as AuthSignupSuccessRouteRouteImport } from './routes/_auth/signu
import { Route as AuthSignupPurposeRouteRouteImport } from './routes/_auth/signup/purpose/route'
import { Route as AuthSignupInfoMoreRouteRouteImport } from './routes/_auth/signup/info-more/route'
import { Route as AuthSignupInfoRouteRouteImport } from './routes/_auth/signup/info/route'
-import { Route as MainMatchingTestMatchingTestStep3RouteRouteImport } from './routes/_main/matching-test/matching-test/step3/route'
-import { Route as MainMatchingTestMatchingTestStep2RouteRouteImport } from './routes/_main/matching-test/matching-test/step2/route'
-import { Route as MainMatchingTestMatchingTestStep1RouteRouteImport } from './routes/_main/matching-test/matching-test/step1/route'
+import { Route as MainMatchingSuggestIndexRouteImport } from './routes/_main/matching/suggest/index'
+import { Route as MainMatchingTestStep3RouteRouteImport } from './routes/_main/matching/test/step3/route'
+import { Route as MainMatchingTestStep2RouteRouteImport } from './routes/_main/matching/test/step2/route'
+import { Route as MainMatchingTestStep1RouteRouteImport } from './routes/_main/matching/test/step1/route'
+import { Route as MainMatchingTestResultRouteRouteImport } from './routes/_main/matching/test/result/route'
+import { Route as MainMatchingSuggestCreateRouteRouteImport } from './routes/_main/matching/suggest/create/route'
const MainRoute = MainRouteImport.update({
id: '/_main',
@@ -127,6 +130,12 @@ const MainHomePreRoute = MainHomePreRouteImport.update({
path: '/pre',
getParentRoute: () => MainRoute,
} as any)
+const MainMatchingSuggestRouteRoute =
+ MainMatchingSuggestRouteRouteImport.update({
+ id: '/suggest',
+ path: '/suggest',
+ getParentRoute: () => MainMatchingRouteRoute,
+ } as any)
const MainMatchingCampaignRouteRoute =
MainMatchingCampaignRouteRouteImport.update({
id: '/campaign',
@@ -138,12 +147,6 @@ const MainMatchingBrandRouteRoute = MainMatchingBrandRouteRouteImport.update({
path: '/brand',
getParentRoute: () => MainMatchingRouteRoute,
} as any)
-const MainMatchingTestMatchingResultRouteRoute =
- MainMatchingTestMatchingResultRouteRouteImport.update({
- id: '/matching-test/matching-result',
- path: '/matching-test/matching-result',
- getParentRoute: () => MainRoute,
- } as any)
const MainBusinessRejectionRouteRoute =
MainBusinessRejectionRouteRouteImport.update({
id: '/_business/rejection',
@@ -198,23 +201,41 @@ const AuthSignupInfoRouteRoute = AuthSignupInfoRouteRouteImport.update({
path: '/signup/info',
getParentRoute: () => rootRouteImport,
} as any)
-const MainMatchingTestMatchingTestStep3RouteRoute =
- MainMatchingTestMatchingTestStep3RouteRouteImport.update({
- id: '/matching-test/matching-test/step3',
- path: '/matching-test/matching-test/step3',
- getParentRoute: () => MainRoute,
+const MainMatchingSuggestIndexRoute =
+ MainMatchingSuggestIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => MainMatchingSuggestRouteRoute,
} as any)
-const MainMatchingTestMatchingTestStep2RouteRoute =
- MainMatchingTestMatchingTestStep2RouteRouteImport.update({
- id: '/matching-test/matching-test/step2',
- path: '/matching-test/matching-test/step2',
- getParentRoute: () => MainRoute,
+const MainMatchingTestStep3RouteRoute =
+ MainMatchingTestStep3RouteRouteImport.update({
+ id: '/test/step3',
+ path: '/test/step3',
+ getParentRoute: () => MainMatchingRouteRoute,
} as any)
-const MainMatchingTestMatchingTestStep1RouteRoute =
- MainMatchingTestMatchingTestStep1RouteRouteImport.update({
- id: '/matching-test/matching-test/step1',
- path: '/matching-test/matching-test/step1',
- getParentRoute: () => MainRoute,
+const MainMatchingTestStep2RouteRoute =
+ MainMatchingTestStep2RouteRouteImport.update({
+ id: '/test/step2',
+ path: '/test/step2',
+ getParentRoute: () => MainMatchingRouteRoute,
+ } as any)
+const MainMatchingTestStep1RouteRoute =
+ MainMatchingTestStep1RouteRouteImport.update({
+ id: '/test/step1',
+ path: '/test/step1',
+ getParentRoute: () => MainMatchingRouteRoute,
+ } as any)
+const MainMatchingTestResultRouteRoute =
+ MainMatchingTestResultRouteRouteImport.update({
+ id: '/test/result',
+ path: '/test/result',
+ getParentRoute: () => MainMatchingRouteRoute,
+ } as any)
+const MainMatchingSuggestCreateRouteRoute =
+ MainMatchingSuggestCreateRouteRouteImport.update({
+ id: '/create',
+ path: '/create',
+ getParentRoute: () => MainMatchingSuggestRouteRoute,
} as any)
export interface FileRoutesByFullPath {
@@ -235,9 +256,9 @@ export interface FileRoutesByFullPath {
'/campaign': typeof MainBusinessCampaignRouteRoute
'/proposal': typeof MainBusinessProposalRouteRoute
'/rejection': typeof MainBusinessRejectionRouteRoute
- '/matching-test/matching-result': typeof MainMatchingTestMatchingResultRouteRoute
'/matching/brand': typeof MainMatchingBrandRouteRoute
'/matching/campaign': typeof MainMatchingCampaignRouteRoute
+ '/matching/suggest': typeof MainMatchingSuggestRouteRouteWithChildren
'/pre': typeof MainHomePreRoute
'/mypage/edit': typeof MainMypageEditRoute
'/mypage/inquiry': typeof MainMypageInquiryRoute
@@ -247,9 +268,12 @@ export interface FileRoutesByFullPath {
'/mypage/profileCard': typeof MainMypageProfileCardRoute
'/mypage/terms': typeof MainMypageTermsRoute
'/mypage/withdraw': typeof MainMypageWithdrawRoute
- '/matching-test/matching-test/step1': typeof MainMatchingTestMatchingTestStep1RouteRoute
- '/matching-test/matching-test/step2': typeof MainMatchingTestMatchingTestStep2RouteRoute
- '/matching-test/matching-test/step3': typeof MainMatchingTestMatchingTestStep3RouteRoute
+ '/matching/suggest/create': typeof MainMatchingSuggestCreateRouteRoute
+ '/matching/test/result': typeof MainMatchingTestResultRouteRoute
+ '/matching/test/step1': typeof MainMatchingTestStep1RouteRoute
+ '/matching/test/step2': typeof MainMatchingTestStep2RouteRoute
+ '/matching/test/step3': typeof MainMatchingTestStep3RouteRoute
+ '/matching/suggest/': typeof MainMatchingSuggestIndexRoute
}
export interface FileRoutesByTo {
'/rooms': typeof RoomsRouteRouteWithChildren
@@ -268,7 +292,6 @@ export interface FileRoutesByTo {
'/campaign': typeof MainBusinessCampaignRouteRoute
'/proposal': typeof MainBusinessProposalRouteRoute
'/rejection': typeof MainBusinessRejectionRouteRoute
- '/matching-test/matching-result': typeof MainMatchingTestMatchingResultRouteRoute
'/matching/brand': typeof MainMatchingBrandRouteRoute
'/matching/campaign': typeof MainMatchingCampaignRouteRoute
'/pre': typeof MainHomePreRoute
@@ -281,9 +304,12 @@ export interface FileRoutesByTo {
'/mypage/terms': typeof MainMypageTermsRoute
'/mypage/withdraw': typeof MainMypageWithdrawRoute
'/': typeof MainHomeIndexRoute
- '/matching-test/matching-test/step1': typeof MainMatchingTestMatchingTestStep1RouteRoute
- '/matching-test/matching-test/step2': typeof MainMatchingTestMatchingTestStep2RouteRoute
- '/matching-test/matching-test/step3': typeof MainMatchingTestMatchingTestStep3RouteRoute
+ '/matching/suggest/create': typeof MainMatchingSuggestCreateRouteRoute
+ '/matching/test/result': typeof MainMatchingTestResultRouteRoute
+ '/matching/test/step1': typeof MainMatchingTestStep1RouteRoute
+ '/matching/test/step2': typeof MainMatchingTestStep2RouteRoute
+ '/matching/test/step3': typeof MainMatchingTestStep3RouteRoute
+ '/matching/suggest': typeof MainMatchingSuggestIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
@@ -304,9 +330,9 @@ export interface FileRoutesById {
'/_main/_business/campaign': typeof MainBusinessCampaignRouteRoute
'/_main/_business/proposal': typeof MainBusinessProposalRouteRoute
'/_main/_business/rejection': typeof MainBusinessRejectionRouteRoute
- '/_main/matching-test/matching-result': typeof MainMatchingTestMatchingResultRouteRoute
'/_main/matching/brand': typeof MainMatchingBrandRouteRoute
'/_main/matching/campaign': typeof MainMatchingCampaignRouteRoute
+ '/_main/matching/suggest': typeof MainMatchingSuggestRouteRouteWithChildren
'/_main/_home/pre': typeof MainHomePreRoute
'/_main/mypage/edit': typeof MainMypageEditRoute
'/_main/mypage/inquiry': typeof MainMypageInquiryRoute
@@ -317,9 +343,12 @@ export interface FileRoutesById {
'/_main/mypage/terms': typeof MainMypageTermsRoute
'/_main/mypage/withdraw': typeof MainMypageWithdrawRoute
'/_main/_home/': typeof MainHomeIndexRoute
- '/_main/matching-test/matching-test/step1': typeof MainMatchingTestMatchingTestStep1RouteRoute
- '/_main/matching-test/matching-test/step2': typeof MainMatchingTestMatchingTestStep2RouteRoute
- '/_main/matching-test/matching-test/step3': typeof MainMatchingTestMatchingTestStep3RouteRoute
+ '/_main/matching/suggest/create': typeof MainMatchingSuggestCreateRouteRoute
+ '/_main/matching/test/result': typeof MainMatchingTestResultRouteRoute
+ '/_main/matching/test/step1': typeof MainMatchingTestStep1RouteRoute
+ '/_main/matching/test/step2': typeof MainMatchingTestStep2RouteRoute
+ '/_main/matching/test/step3': typeof MainMatchingTestStep3RouteRoute
+ '/_main/matching/suggest/': typeof MainMatchingSuggestIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
@@ -341,9 +370,9 @@ export interface FileRouteTypes {
| '/campaign'
| '/proposal'
| '/rejection'
- | '/matching-test/matching-result'
| '/matching/brand'
| '/matching/campaign'
+ | '/matching/suggest'
| '/pre'
| '/mypage/edit'
| '/mypage/inquiry'
@@ -353,9 +382,12 @@ export interface FileRouteTypes {
| '/mypage/profileCard'
| '/mypage/terms'
| '/mypage/withdraw'
- | '/matching-test/matching-test/step1'
- | '/matching-test/matching-test/step2'
- | '/matching-test/matching-test/step3'
+ | '/matching/suggest/create'
+ | '/matching/test/result'
+ | '/matching/test/step1'
+ | '/matching/test/step2'
+ | '/matching/test/step3'
+ | '/matching/suggest/'
fileRoutesByTo: FileRoutesByTo
to:
| '/rooms'
@@ -374,7 +406,6 @@ export interface FileRouteTypes {
| '/campaign'
| '/proposal'
| '/rejection'
- | '/matching-test/matching-result'
| '/matching/brand'
| '/matching/campaign'
| '/pre'
@@ -387,9 +418,12 @@ export interface FileRouteTypes {
| '/mypage/terms'
| '/mypage/withdraw'
| '/'
- | '/matching-test/matching-test/step1'
- | '/matching-test/matching-test/step2'
- | '/matching-test/matching-test/step3'
+ | '/matching/suggest/create'
+ | '/matching/test/result'
+ | '/matching/test/step1'
+ | '/matching/test/step2'
+ | '/matching/test/step3'
+ | '/matching/suggest'
id:
| '__root__'
| '/rooms'
@@ -409,9 +443,9 @@ export interface FileRouteTypes {
| '/_main/_business/campaign'
| '/_main/_business/proposal'
| '/_main/_business/rejection'
- | '/_main/matching-test/matching-result'
| '/_main/matching/brand'
| '/_main/matching/campaign'
+ | '/_main/matching/suggest'
| '/_main/_home/pre'
| '/_main/mypage/edit'
| '/_main/mypage/inquiry'
@@ -422,9 +456,12 @@ export interface FileRouteTypes {
| '/_main/mypage/terms'
| '/_main/mypage/withdraw'
| '/_main/_home/'
- | '/_main/matching-test/matching-test/step1'
- | '/_main/matching-test/matching-test/step2'
- | '/_main/matching-test/matching-test/step3'
+ | '/_main/matching/suggest/create'
+ | '/_main/matching/test/result'
+ | '/_main/matching/test/step1'
+ | '/_main/matching/test/step2'
+ | '/_main/matching/test/step3'
+ | '/_main/matching/suggest/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
@@ -560,6 +597,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MainHomePreRouteImport
parentRoute: typeof MainRoute
}
+ '/_main/matching/suggest': {
+ id: '/_main/matching/suggest'
+ path: '/suggest'
+ fullPath: '/matching/suggest'
+ preLoaderRoute: typeof MainMatchingSuggestRouteRouteImport
+ parentRoute: typeof MainMatchingRouteRoute
+ }
'/_main/matching/campaign': {
id: '/_main/matching/campaign'
path: '/campaign'
@@ -574,13 +618,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MainMatchingBrandRouteRouteImport
parentRoute: typeof MainMatchingRouteRoute
}
- '/_main/matching-test/matching-result': {
- id: '/_main/matching-test/matching-result'
- path: '/matching-test/matching-result'
- fullPath: '/matching-test/matching-result'
- preLoaderRoute: typeof MainMatchingTestMatchingResultRouteRouteImport
- parentRoute: typeof MainRoute
- }
'/_main/_business/rejection': {
id: '/_main/_business/rejection'
path: '/rejection'
@@ -651,26 +688,47 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthSignupInfoRouteRouteImport
parentRoute: typeof rootRouteImport
}
- '/_main/matching-test/matching-test/step3': {
- id: '/_main/matching-test/matching-test/step3'
- path: '/matching-test/matching-test/step3'
- fullPath: '/matching-test/matching-test/step3'
- preLoaderRoute: typeof MainMatchingTestMatchingTestStep3RouteRouteImport
- parentRoute: typeof MainRoute
+ '/_main/matching/suggest/': {
+ id: '/_main/matching/suggest/'
+ path: '/'
+ fullPath: '/matching/suggest/'
+ preLoaderRoute: typeof MainMatchingSuggestIndexRouteImport
+ parentRoute: typeof MainMatchingSuggestRouteRoute
+ }
+ '/_main/matching/test/step3': {
+ id: '/_main/matching/test/step3'
+ path: '/test/step3'
+ fullPath: '/matching/test/step3'
+ preLoaderRoute: typeof MainMatchingTestStep3RouteRouteImport
+ parentRoute: typeof MainMatchingRouteRoute
}
- '/_main/matching-test/matching-test/step2': {
- id: '/_main/matching-test/matching-test/step2'
- path: '/matching-test/matching-test/step2'
- fullPath: '/matching-test/matching-test/step2'
- preLoaderRoute: typeof MainMatchingTestMatchingTestStep2RouteRouteImport
- parentRoute: typeof MainRoute
+ '/_main/matching/test/step2': {
+ id: '/_main/matching/test/step2'
+ path: '/test/step2'
+ fullPath: '/matching/test/step2'
+ preLoaderRoute: typeof MainMatchingTestStep2RouteRouteImport
+ parentRoute: typeof MainMatchingRouteRoute
}
- '/_main/matching-test/matching-test/step1': {
- id: '/_main/matching-test/matching-test/step1'
- path: '/matching-test/matching-test/step1'
- fullPath: '/matching-test/matching-test/step1'
- preLoaderRoute: typeof MainMatchingTestMatchingTestStep1RouteRouteImport
- parentRoute: typeof MainRoute
+ '/_main/matching/test/step1': {
+ id: '/_main/matching/test/step1'
+ path: '/test/step1'
+ fullPath: '/matching/test/step1'
+ preLoaderRoute: typeof MainMatchingTestStep1RouteRouteImport
+ parentRoute: typeof MainMatchingRouteRoute
+ }
+ '/_main/matching/test/result': {
+ id: '/_main/matching/test/result'
+ path: '/test/result'
+ fullPath: '/matching/test/result'
+ preLoaderRoute: typeof MainMatchingTestResultRouteRouteImport
+ parentRoute: typeof MainMatchingRouteRoute
+ }
+ '/_main/matching/suggest/create': {
+ id: '/_main/matching/suggest/create'
+ path: '/create'
+ fullPath: '/matching/suggest/create'
+ preLoaderRoute: typeof MainMatchingSuggestCreateRouteRouteImport
+ parentRoute: typeof MainMatchingSuggestRouteRoute
}
}
}
@@ -687,14 +745,40 @@ const RoomsRouteRouteWithChildren = RoomsRouteRoute._addFileChildren(
RoomsRouteRouteChildren,
)
+interface MainMatchingSuggestRouteRouteChildren {
+ MainMatchingSuggestCreateRouteRoute: typeof MainMatchingSuggestCreateRouteRoute
+ MainMatchingSuggestIndexRoute: typeof MainMatchingSuggestIndexRoute
+}
+
+const MainMatchingSuggestRouteRouteChildren: MainMatchingSuggestRouteRouteChildren =
+ {
+ MainMatchingSuggestCreateRouteRoute: MainMatchingSuggestCreateRouteRoute,
+ MainMatchingSuggestIndexRoute: MainMatchingSuggestIndexRoute,
+ }
+
+const MainMatchingSuggestRouteRouteWithChildren =
+ MainMatchingSuggestRouteRoute._addFileChildren(
+ MainMatchingSuggestRouteRouteChildren,
+ )
+
interface MainMatchingRouteRouteChildren {
MainMatchingBrandRouteRoute: typeof MainMatchingBrandRouteRoute
MainMatchingCampaignRouteRoute: typeof MainMatchingCampaignRouteRoute
+ MainMatchingSuggestRouteRoute: typeof MainMatchingSuggestRouteRouteWithChildren
+ MainMatchingTestResultRouteRoute: typeof MainMatchingTestResultRouteRoute
+ MainMatchingTestStep1RouteRoute: typeof MainMatchingTestStep1RouteRoute
+ MainMatchingTestStep2RouteRoute: typeof MainMatchingTestStep2RouteRoute
+ MainMatchingTestStep3RouteRoute: typeof MainMatchingTestStep3RouteRoute
}
const MainMatchingRouteRouteChildren: MainMatchingRouteRouteChildren = {
MainMatchingBrandRouteRoute: MainMatchingBrandRouteRoute,
MainMatchingCampaignRouteRoute: MainMatchingCampaignRouteRoute,
+ MainMatchingSuggestRouteRoute: MainMatchingSuggestRouteRouteWithChildren,
+ MainMatchingTestResultRouteRoute: MainMatchingTestResultRouteRoute,
+ MainMatchingTestStep1RouteRoute: MainMatchingTestStep1RouteRoute,
+ MainMatchingTestStep2RouteRoute: MainMatchingTestStep2RouteRoute,
+ MainMatchingTestStep3RouteRoute: MainMatchingTestStep3RouteRoute,
}
const MainMatchingRouteRouteWithChildren =
@@ -734,12 +818,8 @@ interface MainRouteChildren {
MainBusinessCampaignRouteRoute: typeof MainBusinessCampaignRouteRoute
MainBusinessProposalRouteRoute: typeof MainBusinessProposalRouteRoute
MainBusinessRejectionRouteRoute: typeof MainBusinessRejectionRouteRoute
- MainMatchingTestMatchingResultRouteRoute: typeof MainMatchingTestMatchingResultRouteRoute
MainHomePreRoute: typeof MainHomePreRoute
MainHomeIndexRoute: typeof MainHomeIndexRoute
- MainMatchingTestMatchingTestStep1RouteRoute: typeof MainMatchingTestMatchingTestStep1RouteRoute
- MainMatchingTestMatchingTestStep2RouteRoute: typeof MainMatchingTestMatchingTestStep2RouteRoute
- MainMatchingTestMatchingTestStep3RouteRoute: typeof MainMatchingTestMatchingTestStep3RouteRoute
}
const MainRouteChildren: MainRouteChildren = {
@@ -750,16 +830,8 @@ const MainRouteChildren: MainRouteChildren = {
MainBusinessCampaignRouteRoute: MainBusinessCampaignRouteRoute,
MainBusinessProposalRouteRoute: MainBusinessProposalRouteRoute,
MainBusinessRejectionRouteRoute: MainBusinessRejectionRouteRoute,
- MainMatchingTestMatchingResultRouteRoute:
- MainMatchingTestMatchingResultRouteRoute,
MainHomePreRoute: MainHomePreRoute,
MainHomeIndexRoute: MainHomeIndexRoute,
- MainMatchingTestMatchingTestStep1RouteRoute:
- MainMatchingTestMatchingTestStep1RouteRoute,
- MainMatchingTestMatchingTestStep2RouteRoute:
- MainMatchingTestMatchingTestStep2RouteRoute,
- MainMatchingTestMatchingTestStep3RouteRoute:
- MainMatchingTestMatchingTestStep3RouteRoute,
}
const MainRouteWithChildren = MainRoute._addFileChildren(MainRouteChildren)
diff --git a/src/routes/_auth/login/login-content.tsx b/src/routes/_auth/login/login-content.tsx
index e54c324..f3e5024 100644
--- a/src/routes/_auth/login/login-content.tsx
+++ b/src/routes/_auth/login/login-content.tsx
@@ -6,11 +6,15 @@ import { SocialLoginSection } from "./components/SocialLoginSection";
function LoginContent() {
const navigate = useNavigate();
const [lastProvider] = useState<"kakao" | "naver" | "google" | null>(() => {
- return localStorage.getItem("lastLoginProvider") as "kakao" | "naver" | "google" | null;
+ if (typeof window !== "undefined") {
+ return localStorage.getItem("lastLoginProvider") as "kakao" | "naver" | "google" | null;
+ }
+ return null;
});
const openSocialSignUp = (provider: "kakao" | "naver" | "google") => {
localStorage.setItem("lastLoginProvider", provider);
+ // 소셜 로그인/회원가입 시 provider 정보 전달
navigate({ to: "/signup/terms", search: { provider } });
};
diff --git a/src/routes/_auth/signup/info-more/signup-info-more-content.tsx b/src/routes/_auth/signup/info-more/signup-info-more-content.tsx
index 371ff62..a10c3bf 100644
--- a/src/routes/_auth/signup/info-more/signup-info-more-content.tsx
+++ b/src/routes/_auth/signup/info-more/signup-info-more-content.tsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { useNavigate } from "@tanstack/react-router";
+import { useNavigate, useSearch } from "@tanstack/react-router";
import { useForm, useWatch } from "react-hook-form";
import Button from "../../../../components/common/Button";
import { FlowNavigation } from "../../components/FlowNavigation";
@@ -15,7 +15,9 @@ interface InfoMoreFormData {
function SignUpInfoMoreContent() {
const navigate = useNavigate();
- const totalSteps = 4;
+ const { type } = useSearch({ from: "/_auth/signup/info-more" });
+ const isEmail = type === "email";
+ const totalSteps = isEmail ? 4 : 3;
const form = useForm();
const genderValue = useWatch({ control: form.control, name: "gender" });
@@ -30,8 +32,8 @@ function SignUpInfoMoreContent() {
};
const handleNext = () => {
- // 목적 선택 페이지로 이동 (소셜 회원가입이 아니므로 provider는 전달하지 않음)
- navigate({ to: "/signup/purpose" });
+ // 목적 선택 페이지로 이동
+ navigate({ to: "/signup/purpose", search: { type } });
};
return (
diff --git a/src/routes/_auth/signup/purpose/route.tsx b/src/routes/_auth/signup/purpose/route.tsx
index 6ff5169..f9e0da8 100644
--- a/src/routes/_auth/signup/purpose/route.tsx
+++ b/src/routes/_auth/signup/purpose/route.tsx
@@ -3,6 +3,7 @@ import SignUpPurposeContent from "./signup-purpose-content";
type SignUpPurposeSearch = {
provider?: "kakao" | "naver" | "google";
+ type?: "email" | "social";
};
export const Route = createFileRoute("/_auth/signup/purpose")({
@@ -10,6 +11,7 @@ export const Route = createFileRoute("/_auth/signup/purpose")({
validateSearch: (search: Record): SignUpPurposeSearch => {
return {
provider: search.provider as "kakao" | "naver" | "google" | undefined,
+ type: search.type as "email" | "social" | undefined,
};
},
});
diff --git a/src/routes/_auth/signup/purpose/signup-purpose-content.tsx b/src/routes/_auth/signup/purpose/signup-purpose-content.tsx
index 58a47ff..2d7a108 100644
--- a/src/routes/_auth/signup/purpose/signup-purpose-content.tsx
+++ b/src/routes/_auth/signup/purpose/signup-purpose-content.tsx
@@ -6,7 +6,7 @@ import { PurposeSection } from "./components/PurposeSection";
function SignUpPurposeContent() {
const navigate = useNavigate();
- const { provider, } = useSearch({ from: "/_auth/signup/purpose" });
+ const { provider } = useSearch({ from: "/_auth/signup/purpose" });
const totalSteps = 3;
const currentStep = 3;
diff --git a/src/routes/_auth/signup/success/signup-success-content.tsx b/src/routes/_auth/signup/success/signup-success-content.tsx
index 1d9f406..2ed91fa 100644
--- a/src/routes/_auth/signup/success/signup-success-content.tsx
+++ b/src/routes/_auth/signup/success/signup-success-content.tsx
@@ -1,15 +1,7 @@
-import { useNavigate } from "@tanstack/react-router";
-import Button from "../../../../components/common/Button";
import { CheckCircleIcon } from "../components/icons/CheckCircleIcon";
-import MiniLogo from "../../../../assets/logo/mini-logo.svg";
+import Button from "../../../../components/common/Button";
function SignUpSuccessContent() {
- const navigate = useNavigate();
-
- const handleStart = () => {
- // TODO: 홈 라우트 구현 후 "/" 로 변경
- navigate({ to: "/login" });
- };
return (
@@ -29,15 +21,7 @@ function SignUpSuccessContent() {
{/* 고정 하단 버튼 */}
-
diff --git a/src/routes/_main.tsx b/src/routes/_main.tsx
index a082075..84eed7d 100644
--- a/src/routes/_main.tsx
+++ b/src/routes/_main.tsx
@@ -20,18 +20,21 @@ export const Route = createFileRoute("/_main")({
function MainLayout() {
const [hideBottomTab, setHideBottomTab] = useState(false);
+ const [hideHeader, setHideHeader] = useState(false);
return (
-
+
-
-
-
-
-

+ {!hideHeader && (
+
+
+
+
+

+
-
-
+
+ )}
diff --git a/src/routes/_main/_home/components/StartRMButton.tsx b/src/routes/_main/_home/components/StartRMButton.tsx
index 647482a..eb96590 100644
--- a/src/routes/_main/_home/components/StartRMButton.tsx
+++ b/src/routes/_main/_home/components/StartRMButton.tsx
@@ -1,22 +1,9 @@
-import { Link } from "@tanstack/react-router";
-import WhiteLogo from "../../../../assets/logo/whitelogo.svg";
+import Button from "../../../../components/common/Button";
export default function StartMatchingTestButton() {
return (
-
-
-
- 매칭률 검사하기
-
+
+ 매칭률 검사하기
+
);
}
diff --git a/src/routes/_main/chat/chat-content.tsx b/src/routes/_main/chat/chat-content.tsx
index 04f9e77..ef066b5 100644
--- a/src/routes/_main/chat/chat-content.tsx
+++ b/src/routes/_main/chat/chat-content.tsx
@@ -1,29 +1,20 @@
-
-import { useState, useMemo, useContext, useEffect } from "react";
-import { LayoutContext } from "../layout-context";
+import { useState, useMemo } from "react";
import { type SortOption, SORT_LABEL } from "./types/SortOption";
import { rooms } from "../../../data/chat-room";
import ChatListHeader from "./components/ChatListHeader";
import SortFilterSheet from "./components/SortingSheet";
import ChatList from "./components/ChatList";
import { EmptyChatState } from "./components/EmptyState";
+import { useHideBottomTab } from "../../../hooks/useHideBottomTab";
function ChatPage() {
const [activeTab, setActiveTab] = useState<"sent" | "received">("sent"); // 보낸 제안 / 받은 제안 탭
const [isSortOpen, setIsSortOpen] = useState(false); // 정렬 바텀시트
const [sort, setSort] = useState("latest"); // 현재 선택된 정렬 옵션
const [pendingSort, setPendingSort] = useState(sort); // 바텀시트에서 고른 값
- const layout = useContext(LayoutContext); // 레이아웃
-
- useEffect(() => {
- if (!layout) return;
-
- layout.setHideBottomTab(isSortOpen);
- return () => {
- layout.setHideBottomTab(false);
- };
- }, [isSortOpen, layout]);
+ // 바텀탭 숨기기 (바텀시트 열렸을 때)
+ useHideBottomTab(isSortOpen);
// 받은제안/보낸제안 필터
const filteredRooms = useMemo(() => {
diff --git a/src/routes/_main/layout-context.tsx b/src/routes/_main/layout-context.tsx
index bf70feb..67421c1 100644
--- a/src/routes/_main/layout-context.tsx
+++ b/src/routes/_main/layout-context.tsx
@@ -3,6 +3,8 @@ import { createContext } from "react";
type LayoutContextType = {
hideBottomTab: boolean;
setHideBottomTab: (v: boolean) => void;
+ hideHeader: boolean;
+ setHideHeader: (v: boolean) => void;
};
export const LayoutContext = createContext(null);
diff --git a/src/routes/_main/matching/brand/brand-content.tsx b/src/routes/_main/matching/brand/brand-content.tsx
index 609a0a8..959c3fd 100644
--- a/src/routes/_main/matching/brand/brand-content.tsx
+++ b/src/routes/_main/matching/brand/brand-content.tsx
@@ -7,6 +7,7 @@ import BrandFilterBar from "./components/BrandFilterBar";
import { Route } from "./route";
import FilterBottomSheet from "../../../../components/common/FilterBottomSheet";
import MatchingFilter from "../components/MatchingFilter";
+import { useHideBottomTab } from "../../../../hooks/useHideBottomTab";
export default function BrandContent() {
@@ -17,6 +18,9 @@ export default function BrandContent() {
const [sortOption, setSortOption] = useState("정렬 필터");
const [selectedTags, setSelectedTags] = useState([]);
+ // 바텀탭 숨기기 (바텀시트 열렸을 때)
+ useHideBottomTab(isFilterOpen);
+
const handleCategoryChange = (newCategory: BrandCategory) => {
navigate({
to: "/matching/brand",
diff --git a/src/routes/_main/matching/campaign/campaign-content.tsx b/src/routes/_main/matching/campaign/campaign-content.tsx
index 4be75e8..b83fb6a 100644
--- a/src/routes/_main/matching/campaign/campaign-content.tsx
+++ b/src/routes/_main/matching/campaign/campaign-content.tsx
@@ -7,6 +7,7 @@ import CampaignFilterBar from "./components/CampaignFilterBar";
import { Route } from "./route";
import FilterBottomSheet from "../../../../components/common/FilterBottomSheet";
import MatchingFilter from "../components/MatchingFilter";
+import { useHideBottomTab } from "../../../../hooks/useHideBottomTab";
export default function CampaignContent() {
@@ -17,6 +18,9 @@ export default function CampaignContent() {
const [sortOption, setSortOption] = useState("정렬 필터");
const [selectedTags, setSelectedTags] = useState([]);
+ // 바텀탭 숨기기 (바텀시트 열렸을 때)
+ useHideBottomTab(isFilterOpen);
+
const handleCategoryChange = (newCategory: CampaignCategory) => {
navigate({
to: "/matching/campaign",
diff --git a/src/routes/_main/matching/suggest/components/ProfileSelector.tsx b/src/routes/_main/matching/suggest/components/ProfileSelector.tsx
new file mode 100644
index 0000000..775b598
--- /dev/null
+++ b/src/routes/_main/matching/suggest/components/ProfileSelector.tsx
@@ -0,0 +1,49 @@
+interface ProfileSelectorProps {
+ username?: string;
+ onClick?: () => void;
+}
+
+export default function ProfileSelector({
+ username = "@ivveeee",
+ onClick,
+}: ProfileSelectorProps) {
+ return (
+
+
+
+
+ );
+}
diff --git a/src/routes/_main/matching/suggest/components/SuggestHeader.tsx b/src/routes/_main/matching/suggest/components/SuggestHeader.tsx
new file mode 100644
index 0000000..1211168
--- /dev/null
+++ b/src/routes/_main/matching/suggest/components/SuggestHeader.tsx
@@ -0,0 +1,46 @@
+import { useNavigate } from "@tanstack/react-router";
+
+interface SuggestHeaderProps {
+ title: string;
+ onBack?: () => void;
+}
+
+export default function SuggestHeader({ title, onBack }: SuggestHeaderProps) {
+ const navigate = useNavigate();
+
+ const handleBack = () => {
+ if (onBack) {
+ onBack();
+ } else {
+ navigate({ to: "/" });
+ }
+ };
+
+ return (
+
+
+
+
+
+ {title}
+
+
+ );
+}
diff --git a/src/routes/_main/matching/suggest/create/campaignOptions.ts b/src/routes/_main/matching/suggest/create/campaignOptions.ts
new file mode 100644
index 0000000..d6a5592
--- /dev/null
+++ b/src/routes/_main/matching/suggest/create/campaignOptions.ts
@@ -0,0 +1,40 @@
+export const formatOptions = [
+ { value: "인스타 릴스", label: "인스타 릴스" },
+ { value: "인스타 게시물", label: "인스타 게시물" },
+ { value: "인스타 스토리", label: "인스타 스토리" },
+];
+
+export const categoryOptions = [
+ { value: "브이로그", label: "브이로그" },
+ { value: "리뷰", label: "리뷰" },
+ { value: "갓생디웻미", label: "갓생디웻미" },
+ { value: "비포&애프터", label: "비포&애프터" },
+ { value: "스토리/썰", label: "스토리/썰" },
+ { value: "챌린지", label: "챌린지" },
+];
+
+export const toneOptions = [
+ { value: "수다적인", label: "수다적인" },
+ { value: "일상적인", label: "일상적인" },
+ { value: "유쾌/재있는", label: "유쾌/재있는" },
+ { value: "감성적인", label: "감성적인" },
+ { value: "트렌디한", label: "트렌디한" },
+ { value: "전문적인", label: "전문적인" },
+];
+
+export const involvementOptions = [
+ { value: "관여 안함", label: "관여 안함" },
+ { value: "가이드만 제공", label: "가이드만 제공" },
+ { value: "대본 일부 제공", label: "대본 일부 제공" },
+ { value: "모든 연출 관여", label: "모든 연출 관여" },
+];
+
+export const usageScopeOptions = [
+ { value: "크리에이터 1차 활용", label: "크리에이터 1차 활용" },
+ { value: "브랜드 2차 활용", label: "브랜드 2차 활용" },
+];
+
+export const sponsorProductOptions = [
+ { value: "글로우 선크림 1개", label: "글로우 선크림 1개" },
+ { value: "글로우 크림 1개", label: "글로우 크림 1개" },
+];
diff --git a/src/routes/_main/matching/suggest/create/components/DatePickerBottomSheet.tsx b/src/routes/_main/matching/suggest/create/components/DatePickerBottomSheet.tsx
new file mode 100644
index 0000000..815c0c0
--- /dev/null
+++ b/src/routes/_main/matching/suggest/create/components/DatePickerBottomSheet.tsx
@@ -0,0 +1,160 @@
+import { useState, useMemo } from "react";
+import FilterBottomSheet from "../../../../../../components/common/FilterBottomSheet";
+import Button from "../../../../../../components/common/Button";
+import ArrowLeftIcon from "../../../../../../assets/icon/arrow-left.svg";
+import ArrowRightIcon from "../../../../../../assets/icon/arrow-right.svg";
+
+const WEEK_DAYS = ["일", "월", "화", "수", "목", "금", "토"];
+
+interface DatePickerBottomSheetProps {
+ isOpen: boolean;
+ onClose: () => void;
+ onSelect: (dateString: string) => void;
+ initialValue?: string; // "YYYY-MM-DD" 형식
+}
+
+export default function DatePickerBottomSheet({
+ isOpen,
+ onClose,
+ onSelect,
+ initialValue,
+}: DatePickerBottomSheetProps) {
+ // initialValue 파싱
+ const parseInitialDate = () => {
+ if (initialValue) {
+ const date = new Date(initialValue);
+ if (!isNaN(date.getTime())) {
+ return date;
+ }
+ }
+ return new Date();
+ };
+
+ const initialDate = parseInitialDate();
+ const [currentYear, setCurrentYear] = useState(initialDate.getFullYear());
+ const [currentMonth, setCurrentMonth] = useState(initialDate.getMonth());
+ const [selectedDay, setSelectedDay] = useState(
+ initialValue ? initialDate.getDate() : null
+ );
+
+ // 해당 월의 첫 날 요일 (0: 일요일)
+ const firstDayOfMonth = useMemo(
+ () => new Date(currentYear, currentMonth, 1).getDay(),
+ [currentYear, currentMonth]
+ );
+
+ // 해당 월의 마지막 날짜
+ const daysInMonth = useMemo(
+ () => new Date(currentYear, currentMonth + 1, 0).getDate(),
+ [currentYear, currentMonth]
+ );
+
+ // 캘린더 그리드 생성
+ const calendarDays = useMemo(() => {
+ const emptyDays = Array(firstDayOfMonth).fill(null);
+ const days = Array.from({ length: daysInMonth }, (_, i) => i + 1);
+ return [...emptyDays, ...days];
+ }, [firstDayOfMonth, daysInMonth]);
+
+ const handlePrevMonth = () => {
+ if (currentMonth === 0) {
+ setCurrentYear((y) => y - 1);
+ setCurrentMonth(11);
+ } else {
+ setCurrentMonth((m) => m - 1);
+ }
+ setSelectedDay(null);
+ };
+
+ const handleNextMonth = () => {
+ if (currentMonth === 11) {
+ setCurrentYear((y) => y + 1);
+ setCurrentMonth(0);
+ } else {
+ setCurrentMonth((m) => m + 1);
+ }
+ setSelectedDay(null);
+ };
+
+ const handleSelectDay = (day: number) => {
+ setSelectedDay(day);
+ };
+
+ const handleSubmit = () => {
+ if (selectedDay) {
+ const month = String(currentMonth + 1).padStart(2, "0");
+ const day = String(selectedDay).padStart(2, "0");
+ onSelect(`${currentYear}-${month}-${day}`);
+ onClose();
+ }
+ };
+
+ return (
+
+
+ {/* 월 네비게이션 */}
+
+
+
+
+
+ {currentYear}년 {String(currentMonth + 1).padStart(2, "0")}월
+
+
+
+
+
+
+ {/* 요일 헤더 */}
+
+ {WEEK_DAYS.map((day) => (
+
+ {day}
+
+ ))}
+
+
+ {/* 날짜 그리드 */}
+
+ {calendarDays.map((day, index) => (
+
+ {day && (
+ handleSelectDay(day)}
+ className={`w-10 h-10 rounded-full text-[15px] flex items-center justify-center transition-colors ${
+ selectedDay === day
+ ? "bg-core-1 text-white"
+ : "text-text-black hover:bg-bluegray-1"
+ }`}
+ >
+ {day}
+
+ )}
+
+ ))}
+
+
+ {/* 선택 버튼 */}
+
+ 선택 완료
+
+
+
+ );
+}
diff --git a/src/routes/_main/matching/suggest/create/components/SelectBottomSheet.tsx b/src/routes/_main/matching/suggest/create/components/SelectBottomSheet.tsx
new file mode 100644
index 0000000..cb747fb
--- /dev/null
+++ b/src/routes/_main/matching/suggest/create/components/SelectBottomSheet.tsx
@@ -0,0 +1,122 @@
+import { useState } from "react";
+import FilterBottomSheet from "../../../../../../components/common/FilterBottomSheet";
+import Button from "../../../../../../components/common/Button";
+import { CheckIcon } from "../../../../../_auth/components/CheckIcon";
+
+interface SelectOption {
+ value: string;
+ label: string;
+}
+
+interface SelectBottomSheetProps {
+ isOpen: boolean;
+ onClose: () => void;
+ title: string;
+ options: SelectOption[];
+ selectedValues: string[];
+ onSubmit: (values: string[]) => void;
+ multiSelect?: boolean;
+ hasCustomInput?: boolean;
+}
+
+export default function SelectBottomSheet({
+ isOpen,
+ onClose,
+ title,
+ options,
+ selectedValues,
+ onSubmit,
+ multiSelect = false,
+ hasCustomInput = true,
+}: SelectBottomSheetProps) {
+ const [selected, setSelected] = useState(selectedValues);
+ const [customInput, setCustomInput] = useState("");
+
+ const handleToggle = (value: string) => {
+ if (multiSelect) {
+ setSelected((prev) =>
+ prev.includes(value)
+ ? prev.filter((v) => v !== value)
+ : [...prev, value]
+ );
+ } else {
+ setSelected([value]);
+ }
+ };
+
+ const handleSubmit = () => {
+ const finalValues = customInput
+ ? [...selected.filter((v) => v !== "custom"), customInput]
+ : selected;
+ onSubmit(finalValues);
+ onClose();
+ };
+
+ const handleClose = () => {
+ setSelected(selectedValues);
+ setCustomInput("");
+ onClose();
+ };
+
+ return (
+
+ {/* 헤더 */}
+
+
{title}
+
+
+
+ {/* 옵션 목록 */}
+
+ {options.map((option) => (
+
+ ))}
+
+ {/* 그 외 입력 */}
+ {hasCustomInput && (
+
+
+ {selected.includes("custom") && (
+
setCustomInput(e.target.value)}
+ className="w-full mt-2 px-4 py-3 rounded-md border border-core-2 bg-white/80 text-title3 text-text-black placeholder:text-text-gray3 focus:outline-none"
+ />
+ )}
+
+ )}
+
+
+ {/* 선택 완료 버튼 */}
+
+
+ 선택 완료
+
+
+
+ );
+}
diff --git a/src/routes/_main/matching/suggest/create/create-campaign-content.tsx b/src/routes/_main/matching/suggest/create/create-campaign-content.tsx
new file mode 100644
index 0000000..016e5ef
--- /dev/null
+++ b/src/routes/_main/matching/suggest/create/create-campaign-content.tsx
@@ -0,0 +1,394 @@
+import { useState } from "react";
+import { useNavigate, useSearch } from "@tanstack/react-router";
+import { useForm, useWatch } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import Button from "../../../../../components/common/Button";
+import FilterBottomSheet from "../../../../../components/common/FilterBottomSheet";
+import {
+ TextInput,
+ TextArea,
+ SelectField,
+ DateField,
+ FeeInput,
+} from "../../../../../components/form";
+import { useHideBottomTab } from "../../../../../hooks/useHideBottomTab";
+import { CheckIcon } from "../../../../_auth/components/CheckIcon";
+import ExistSuggestIcon from "../../../../../assets/icon/exist-suggest.svg";
+import { existingCampaigns } from "../../../../../data/existing-campaigns";
+import ProfileSelector from "../components/ProfileSelector";
+import SelectBottomSheet from "./components/SelectBottomSheet";
+import DatePickerBottomSheet from "./components/DatePickerBottomSheet";
+import {
+ formatOptions,
+ categoryOptions,
+ toneOptions,
+ involvementOptions,
+ usageScopeOptions,
+ sponsorProductOptions,
+} from "./campaignOptions";
+import {
+ campaignFormSchema,
+ defaultCampaignFormValues,
+ type CampaignFormData,
+} from "./schema";
+
+export default function CreateCampaignContent() {
+ const navigate = useNavigate();
+ const { type } = useSearch({ from: "/_main/matching/suggest/create" });
+
+ // 바텀시트 상태 (기존 캠페인일 때만 열림)
+ const [isSheetOpen, setIsSheetOpen] = useState(type === "existing");
+ const [selectedCampaignIds, setSelectedCampaignIds] = useState([1]);
+
+ // 각 필드별 바텀시트 상태
+ const [isFormatSheetOpen, setIsFormatSheetOpen] = useState(false);
+ const [isCategorySheetOpen, setIsCategorySheetOpen] = useState(false);
+ const [isToneSheetOpen, setIsToneSheetOpen] = useState(false);
+ const [isInvolvementSheetOpen, setIsInvolvementSheetOpen] = useState(false);
+ const [isUsageScopeSheetOpen, setIsUsageScopeSheetOpen] = useState(false);
+ const [isSponsorProductSheetOpen, setIsSponsorProductSheetOpen] = useState(false);
+ const [isStartDateSheetOpen, setIsStartDateSheetOpen] = useState(false);
+ const [isEndDateSheetOpen, setIsEndDateSheetOpen] = useState(false);
+
+ // 바텀탭 숨기기 (바텀시트 열렸을 때)
+ const anySheetOpen = isSheetOpen || isFormatSheetOpen || isCategorySheetOpen ||
+ isToneSheetOpen || isInvolvementSheetOpen || isUsageScopeSheetOpen || isSponsorProductSheetOpen ||
+ isStartDateSheetOpen || isEndDateSheetOpen;
+ useHideBottomTab(anySheetOpen);
+
+ // react-hook-form + zod
+ const {
+ control,
+ setValue,
+ handleSubmit,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(campaignFormSchema),
+ defaultValues: defaultCampaignFormValues,
+ });
+
+ const formValues = useWatch({ control, defaultValue: defaultCampaignFormValues });
+
+ const handleToggleCampaign = (id: number) => {
+ setSelectedCampaignIds((prev) =>
+ prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]
+ );
+ };
+
+ const handleSheetSubmit = () => {
+ console.log("선택된 캠페인:", selectedCampaignIds);
+ setIsSheetOpen(false);
+ };
+
+ const handleSheetClose = () => {
+ setIsSheetOpen(false);
+ navigate({ to: "/matching/suggest" });
+ };
+
+ const onSubmit = (data: CampaignFormData) => {
+ console.log("캠페인 제안하기", { type, selectedCampaignIds, data });
+ };
+
+ // 선택된 캠페인 이름 가져오기
+ const selectedCampaignName = existingCampaigns.find(
+ (c) => selectedCampaignIds.includes(c.id)
+ )?.name;
+
+ const title =
+ type === "existing" && selectedCampaignName
+ ? selectedCampaignName
+ : type === "existing"
+ ? "기존 캠페인 제안하기"
+ : "신규 캠페인 제안하기";
+
+ return (
+
+ {/* 스크롤 영역 */}
+
+
+ {/* 하단 버튼 */}
+
+
+ 캠페인 제안하기
+
+
+
+ {/* 바텀시트 */}
+ {/* 기존 캠페인 선택 바텀시트 */}
+ {type === "existing" && (
+
+ {/* 헤더 */}
+
+
+

+
기존 캠페인 제안
+
+
+
+
+ {/* 캠페인 목록 */}
+
+ {existingCampaigns.map((campaign) => (
+
+ ))}
+
+
+ {/* 선택 완료 버튼 */}
+
+
+ 선택 완료
+
+
+
+ )}
+
+ {/* 형식 선택 바텀시트 */}
+
setIsFormatSheetOpen(false)}
+ title="형식"
+ options={formatOptions}
+ selectedValues={formValues.format ? [formValues.format] : []}
+ onSubmit={(values) => setValue("format", values[0] || "")}
+ multiSelect={false}
+ />
+
+ {/* 종류 선택 바텀시트 */}
+ setIsCategorySheetOpen(false)}
+ title="종류"
+ options={categoryOptions}
+ selectedValues={formValues.category ? [formValues.category] : []}
+ onSubmit={(values) => setValue("category", values[0] || "")}
+ multiSelect={false}
+ />
+
+ {/* 톤 선택 바텀시트 */}
+ setIsToneSheetOpen(false)}
+ title="톤"
+ options={toneOptions}
+ selectedValues={formValues.tone ? [formValues.tone] : []}
+ onSubmit={(values) => setValue("tone", values[0] || "")}
+ multiSelect={false}
+ />
+
+ {/* 관여도 선택 바텀시트 */}
+ setIsInvolvementSheetOpen(false)}
+ title="관여도"
+ options={involvementOptions}
+ selectedValues={formValues.involvement ? [formValues.involvement] : []}
+ onSubmit={(values) => setValue("involvement", values[0] || "")}
+ multiSelect={false}
+ />
+
+ {/* 활용 범위 선택 바텀시트 */}
+ setIsUsageScopeSheetOpen(false)}
+ title="활용 범위"
+ options={usageScopeOptions}
+ selectedValues={formValues.usageScope ? [formValues.usageScope] : []}
+ onSubmit={(values) => setValue("usageScope", values[0] || "")}
+ multiSelect={false}
+ />
+
+ {/* 협찬품 선택 바텀시트 */}
+ setIsSponsorProductSheetOpen(false)}
+ title="협찬품 선택"
+ options={sponsorProductOptions}
+ selectedValues={formValues.sponsorProduct ? [formValues.sponsorProduct] : []}
+ onSubmit={(values) => setValue("sponsorProduct", values[0] || "")}
+ multiSelect={false}
+ />
+
+ {/* 시작 날짜 선택 바텀시트 */}
+ setIsStartDateSheetOpen(false)}
+ initialValue={formValues.startDate}
+ onSelect={(date) => setValue("startDate", date)}
+ />
+
+ {/* 끝 날짜 선택 바텀시트 */}
+ setIsEndDateSheetOpen(false)}
+ initialValue={formValues.endDate}
+ onSelect={(date) => setValue("endDate", date)}
+ />
+
+ );
+}
diff --git a/src/routes/_main/matching/suggest/create/route.tsx b/src/routes/_main/matching/suggest/create/route.tsx
new file mode 100644
index 0000000..25650ae
--- /dev/null
+++ b/src/routes/_main/matching/suggest/create/route.tsx
@@ -0,0 +1,11 @@
+import { createFileRoute } from "@tanstack/react-router";
+import CreateCampaignContent from "./create-campaign-content";
+
+export const Route = createFileRoute("/_main/matching/suggest/create")({
+ validateSearch: (search: Record) => {
+ return {
+ type: (search.type as "new" | "existing") || "new",
+ };
+ },
+ component: CreateCampaignContent,
+});
diff --git a/src/routes/_main/matching/suggest/create/schema.ts b/src/routes/_main/matching/suggest/create/schema.ts
new file mode 100644
index 0000000..6f715e0
--- /dev/null
+++ b/src/routes/_main/matching/suggest/create/schema.ts
@@ -0,0 +1,37 @@
+import { z } from "zod";
+
+export const campaignFormSchema = z.object({
+ campaignName: z
+ .string()
+ .min(1, "캠페인명을 입력해주세요")
+ .max(30, "캠페인명은 30자 이내로 입력해주세요"),
+ description: z
+ .string()
+ .min(1, "설명을 입력해주세요")
+ .max(300, "설명은 300자 이내로 입력해주세요"),
+ format: z.string().min(1, "형식을 선택해주세요"),
+ category: z.string().min(1, "종류를 선택해주세요"),
+ tone: z.string().min(1, "톤을 선택해주세요"),
+ involvement: z.string().min(1, "관여도를 선택해주세요"),
+ usageScope: z.string().min(1, "활용 범위를 선택해주세요"),
+ sponsorProduct: z.string().min(1, "협찬품을 선택해주세요"),
+ fee: z.string().min(1, "원고료를 입력해주세요"),
+ startDate: z.string().min(1, "시작 날짜를 선택해주세요"),
+ endDate: z.string().min(1, "끝 날짜를 선택해주세요"),
+});
+
+export type CampaignFormData = z.infer;
+
+export const defaultCampaignFormValues: CampaignFormData = {
+ campaignName: "",
+ description: "",
+ format: "",
+ category: "",
+ tone: "",
+ involvement: "",
+ usageScope: "",
+ sponsorProduct: "",
+ fee: "",
+ startDate: "",
+ endDate: "",
+};
diff --git a/src/routes/_main/matching/suggest/index.tsx b/src/routes/_main/matching/suggest/index.tsx
new file mode 100644
index 0000000..0ee3251
--- /dev/null
+++ b/src/routes/_main/matching/suggest/index.tsx
@@ -0,0 +1,6 @@
+import { createFileRoute } from "@tanstack/react-router";
+import MatchingSuggestContent from "./matching-suggest-content";
+
+export const Route = createFileRoute("/_main/matching/suggest/")({
+ component: MatchingSuggestContent,
+});
diff --git a/src/routes/_main/matching/suggest/matching-suggest-content.tsx b/src/routes/_main/matching/suggest/matching-suggest-content.tsx
new file mode 100644
index 0000000..33432bf
--- /dev/null
+++ b/src/routes/_main/matching/suggest/matching-suggest-content.tsx
@@ -0,0 +1,42 @@
+import { useNavigate } from "@tanstack/react-router";
+import Button from "../../../../components/common/Button";
+import NewSuggestIcon from "../../../../assets/icon/new-suggest.svg";
+import ExistSuggestIcon from "../../../../assets/icon/exist-suggest.svg";
+
+export default function MatchingSuggestContent() {
+ const navigate = useNavigate();
+
+ const handleNewCampaign = () => {
+ navigate({ to: "/matching/suggest/create", search: { type: "new" } });
+ };
+
+ const handleExistingCampaign = () => {
+ navigate({ to: "/matching/suggest/create", search: { type: "existing" } });
+ };
+
+ return (
+
+
+
+ 신규 캠페인 제안
+
+
+
+
+ 기존 캠페인 제안
+
+
+ );
+}
diff --git a/src/routes/_main/matching/suggest/route.tsx b/src/routes/_main/matching/suggest/route.tsx
new file mode 100644
index 0000000..7741f41
--- /dev/null
+++ b/src/routes/_main/matching/suggest/route.tsx
@@ -0,0 +1,28 @@
+import { createFileRoute, Outlet } from "@tanstack/react-router";
+import { useContext, useEffect } from "react";
+import { LayoutContext } from "../../layout-context";
+import SuggestHeader from "./components/SuggestHeader";
+
+export const Route = createFileRoute("/_main/matching/suggest")({
+ component: MatchingSuggestLayout,
+});
+
+function MatchingSuggestLayout() {
+ const layout = useContext(LayoutContext);
+
+ useEffect(() => {
+ if (!layout) return;
+ layout.setHideHeader(true);
+
+ return () => {
+ layout.setHideHeader(false);
+ };
+ }, [layout]);
+
+ return (
+
+
+
+
+ );
+}
diff --git a/src/routes/_main/matching-test/components/BottomSheet.tsx b/src/routes/_main/matching/test/components/BottomSheet.tsx
similarity index 100%
rename from src/routes/_main/matching-test/components/BottomSheet.tsx
rename to src/routes/_main/matching/test/components/BottomSheet.tsx
diff --git a/src/routes/_main/matching-test/components/CheckDropdown.tsx b/src/routes/_main/matching/test/components/CheckDropdown.tsx
similarity index 100%
rename from src/routes/_main/matching-test/components/CheckDropdown.tsx
rename to src/routes/_main/matching/test/components/CheckDropdown.tsx
diff --git a/src/routes/_main/matching-test/components/FormField.tsx b/src/routes/_main/matching/test/components/FormField.tsx
similarity index 100%
rename from src/routes/_main/matching-test/components/FormField.tsx
rename to src/routes/_main/matching/test/components/FormField.tsx
diff --git a/src/routes/_main/matching-test/components/InputSheet.tsx b/src/routes/_main/matching/test/components/InputSheet.tsx
similarity index 100%
rename from src/routes/_main/matching-test/components/InputSheet.tsx
rename to src/routes/_main/matching/test/components/InputSheet.tsx
diff --git a/src/routes/_main/matching-test/components/MatchingTestHeader.tsx b/src/routes/_main/matching/test/components/MatchingTestHeader.tsx
similarity index 100%
rename from src/routes/_main/matching-test/components/MatchingTestHeader.tsx
rename to src/routes/_main/matching/test/components/MatchingTestHeader.tsx
diff --git a/src/routes/_main/matching-test/components/SelectChip.tsx b/src/routes/_main/matching/test/components/SelectChip.tsx
similarity index 100%
rename from src/routes/_main/matching-test/components/SelectChip.tsx
rename to src/routes/_main/matching/test/components/SelectChip.tsx
diff --git a/src/routes/_main/matching-test/components/SelectContents.tsx b/src/routes/_main/matching/test/components/SelectContents.tsx
similarity index 100%
rename from src/routes/_main/matching-test/components/SelectContents.tsx
rename to src/routes/_main/matching/test/components/SelectContents.tsx
diff --git a/src/routes/_main/matching-test/components/SelectSheet.tsx b/src/routes/_main/matching/test/components/SelectSheet.tsx
similarity index 100%
rename from src/routes/_main/matching-test/components/SelectSheet.tsx
rename to src/routes/_main/matching/test/components/SelectSheet.tsx
diff --git a/src/routes/_main/matching-test/components/Selectfield.tsx b/src/routes/_main/matching/test/components/Selectfield.tsx
similarity index 100%
rename from src/routes/_main/matching-test/components/Selectfield.tsx
rename to src/routes/_main/matching/test/components/Selectfield.tsx
diff --git a/src/routes/_main/matching-test/matching-result/matching-result-content.tsx b/src/routes/_main/matching/test/result/matching-result-content.tsx
similarity index 73%
rename from src/routes/_main/matching-test/matching-result/matching-result-content.tsx
rename to src/routes/_main/matching/test/result/matching-result-content.tsx
index eb7dbde..681575d 100644
--- a/src/routes/_main/matching-test/matching-result/matching-result-content.tsx
+++ b/src/routes/_main/matching/test/result/matching-result-content.tsx
@@ -1,11 +1,9 @@
-import { useNavigate } from "@tanstack/react-router";
import { Route } from "./route";
-import MatchResultHeader from "../../../../components/common/RealmatchHeader";
-import MainIcon from "../../../../assets/MainIcon.svg";
-import WhiteLogo from "../../../../assets/logo/whitelogo.svg";
+import MatchResultHeader from "../../../../../components/common/RealmatchHeader";
+import MainIcon from "../../../../../assets/MainIcon.svg"
+import Button from "../../../../../components/common/Button";
export default function MatchingResultContent() {
- const navigate = useNavigate();
const search = Route.useSearch();
const resultData = {
@@ -17,7 +15,7 @@ export default function MatchingResultContent() {
};
return (
-
+
@@ -27,7 +25,7 @@ export default function MatchingResultContent() {
text-[24px]
font-extrabold
tracking-[-0.02em]
- bg-[radial-gradient(circle_at_top,_#5D5DFF_0%,_#382FE4_45%,_#3915DA_100%)]
+ bg-[radial-gradient(circle_at_top,#5D5DFF_0%,#382FE4_45%,#3915DA_100%)]
bg-clip-text
text-transparent">
{resultData.userName}
@@ -76,18 +74,9 @@ export default function MatchingResultContent() {
-
navigate({ to: "/" })}
- >
-
+
RealMatch 시작하기
-
+
);
diff --git a/src/routes/_main/matching-test/matching-result/route.tsx b/src/routes/_main/matching/test/result/route.tsx
similarity index 92%
rename from src/routes/_main/matching-test/matching-result/route.tsx
rename to src/routes/_main/matching/test/result/route.tsx
index 2f38863..0f4f1ab 100644
--- a/src/routes/_main/matching-test/matching-result/route.tsx
+++ b/src/routes/_main/matching/test/result/route.tsx
@@ -9,7 +9,7 @@ type Step4Search = {
recommendedBrand?: string;
};
-export const Route = createFileRoute("/_main/matching-test/matching-result")({
+export const Route = createFileRoute("/_main/matching/test/result")({
component: MatchingResultContent,
validateSearch: (search: Record
): Step4Search => ({
userName: typeof search.userName === "string" ? search.userName : undefined,
diff --git a/src/routes/_main/matching-test/matching-test/step1/route.tsx b/src/routes/_main/matching/test/step1/route.tsx
similarity index 92%
rename from src/routes/_main/matching-test/matching-test/step1/route.tsx
rename to src/routes/_main/matching/test/step1/route.tsx
index 6facb39..24095e4 100644
--- a/src/routes/_main/matching-test/matching-test/step1/route.tsx
+++ b/src/routes/_main/matching/test/step1/route.tsx
@@ -19,7 +19,7 @@ const SECTIONS: Array<{
{ key: "makeupStyle", title: "메이크업 스타일", items: ["내추럴", "화려한", "글로우", "매트"] },
] as const;
-export const Route = createFileRoute("/_main/matching-test/matching-test/step1")({
+export const Route = createFileRoute("/_main/matching/test/step1")({
component: MatchingTestStep1Page,
});
@@ -49,7 +49,7 @@ function MatchingTestStep1Page() {
onToggle={(section, label) => toggleStore(section, label, MAX_PER_SECTION)}
canGoNext={canGoNext}
onBack={() => navigate({ to: "/" })}
- onNext={() => navigate({ to: "/matching-test/matching-test/step2" })}
+ onNext={() => navigate({ to: "/matching/test/step2" })}
/>
);
}
diff --git a/src/routes/_main/matching-test/matching-test/step1/step1-content.tsx b/src/routes/_main/matching/test/step1/step1-content.tsx
similarity index 87%
rename from src/routes/_main/matching-test/matching-test/step1/step1-content.tsx
rename to src/routes/_main/matching/test/step1/step1-content.tsx
index 8d5394a..7adf670 100644
--- a/src/routes/_main/matching-test/matching-test/step1/step1-content.tsx
+++ b/src/routes/_main/matching/test/step1/step1-content.tsx
@@ -1,5 +1,6 @@
-import SelectChip from "../../components/SelectChip";
-import MatchingTestTopBar from "../../components/MatchingTestHeader";
+import SelectChip from "../components/SelectChip";
+import MatchingTestTopBar from "../components/MatchingTestHeader";
+import Button from "../../../../../components/common/Button";
type SectionKey = "style" | "function" | "skinType" | "skinTone" | "makeupStyle";
@@ -90,19 +91,15 @@ export default function MatchingTestContent({
{/* ✅ 하단 고정 */}
-
다음
-
+
);
diff --git a/src/routes/_main/matching-test/matching-test/step2/route.tsx b/src/routes/_main/matching/test/step2/route.tsx
similarity index 90%
rename from src/routes/_main/matching-test/matching-test/step2/route.tsx
rename to src/routes/_main/matching/test/step2/route.tsx
index 3e107ff..9e67f4a 100644
--- a/src/routes/_main/matching-test/matching-test/step2/route.tsx
+++ b/src/routes/_main/matching/test/step2/route.tsx
@@ -3,7 +3,7 @@ import { useMemo } from "react";
import MatchingTestStep2Content from "./step2-content";
import { useMatchingTestStore, type Step2SectionKey } from "../../../../../stores/matching-test";
-export const Route = createFileRoute("/_main/matching-test/matching-test/step2")({
+export const Route = createFileRoute("/_main/matching/test/step2")({
component: MatchingTestStep2Page,
});
@@ -55,8 +55,8 @@ function MatchingTestStep2Page() {
onTopSizeChange={setTopSize}
onBottomSizeChange={setBottomSizeIn}
canGoNext={canGoNext}
- onBack={() => navigate({ to: "/matching-test/matching-test/step1" })}
- onNext={() => navigate({ to: "/matching-test/matching-test/step3" })}
+ onBack={() => navigate({ to: "/matching/test/step1" })}
+ onNext={() => navigate({ to: "/matching/test/step3" })}
/>
);
}
diff --git a/src/routes/_main/matching-test/matching-test/step2/step2-content.tsx b/src/routes/_main/matching/test/step2/step2-content.tsx
similarity index 93%
rename from src/routes/_main/matching-test/matching-test/step2/step2-content.tsx
rename to src/routes/_main/matching/test/step2/step2-content.tsx
index 264354d..7aec17d 100644
--- a/src/routes/_main/matching-test/matching-test/step2/step2-content.tsx
+++ b/src/routes/_main/matching/test/step2/step2-content.tsx
@@ -1,12 +1,13 @@
import { useMemo, useState } from "react";
import type { Step2SectionKey, Step2SelectedState } from "../../../../../stores/matching-test";
-import SelectChip from "../../components/SelectChip";
-import FormField from "../../components/FormField";
-import BottomSheet from "../../components/BottomSheet";
-import InputSheet from "../../components/InputSheet";
-import SelectSheet from "../../components/SelectSheet";
-import MatchingTestTopBar from "../../components/MatchingTestHeader";
+import SelectChip from "../components/SelectChip";
+import FormField from "../components/FormField";
+import BottomSheet from "../components/BottomSheet";
+import InputSheet from "../components/InputSheet";
+import SelectSheet from "../components/SelectSheet";
+import MatchingTestTopBar from "../components/MatchingTestHeader";
+import Button from "../../../../../components/common/Button";
type Props = {
maxText: string;
@@ -191,17 +192,15 @@ export default function MatchingTestStep2Content({
{/* CTA */}
-
다음
-
+
diff --git a/src/routes/_main/matching-test/matching-test/step3/route.tsx b/src/routes/_main/matching/test/step3/route.tsx
similarity index 92%
rename from src/routes/_main/matching-test/matching-test/step3/route.tsx
rename to src/routes/_main/matching/test/step3/route.tsx
index 816157e..953f679 100644
--- a/src/routes/_main/matching-test/matching-test/step3/route.tsx
+++ b/src/routes/_main/matching/test/step3/route.tsx
@@ -3,7 +3,7 @@ import { useMemo } from "react";
import MatchingTestStep3Content from "./step3-content";
import { useMatchingTestStore, type Step3ChipKey, type Step3SelectKey } from "../../../../../stores/matching-test";
-export const Route = createFileRoute("/_main/matching-test/matching-test/step3")({
+export const Route = createFileRoute("/_main/matching/test/step3")({
component: MatchingTestStep3Page,
});
@@ -61,7 +61,8 @@ function MatchingTestStep3Page() {
step3Chips={step3Chips}
onToggleChip={onToggleChip}
canGoNext={canGoNext}
- onBack={() => navigate({ to: "/matching-test/matching-test/step2" })}
+ onBack={() => navigate({ to: "/matching/test/step2" })}
+ onNext={() => navigate({ to: "/matching/test/result" })}
/>
);
}
diff --git a/src/routes/_main/matching-test/matching-test/step3/step3-content.tsx b/src/routes/_main/matching/test/step3/step3-content.tsx
similarity index 89%
rename from src/routes/_main/matching-test/matching-test/step3/step3-content.tsx
rename to src/routes/_main/matching/test/step3/step3-content.tsx
index 81d0061..0f152f7 100644
--- a/src/routes/_main/matching-test/matching-test/step3/step3-content.tsx
+++ b/src/routes/_main/matching/test/step3/step3-content.tsx
@@ -1,20 +1,14 @@
import { useMemo, useState } from "react";
-import { useNavigate } from "@tanstack/react-router";
-
-import type {
- Step3ChipKey,
- Step3ChipsState,
- Step3SelectKey,
- Step3SelectedState,
-} from "../../../../../stores/matching-test";
-
-import MatchingTestTopBar from "../../components/MatchingTestHeader";
-import SelectChip from "../../components/SelectChip";
-import FormField from "../../components/FormField";
-import BottomSheet from "../../components/BottomSheet";
-import InputSheet from "../../components/InputSheet";
-import SelectSheet from "../../components/SelectSheet";
-import CheckDropdown from "../../components/CheckDropdown";
+import type { Step3ChipKey, Step3ChipsState, Step3SelectKey, Step3SelectedState } from "../../../../../stores/matching-test";
+
+import MatchingTestTopBar from "../components/MatchingTestHeader";
+import SelectChip from "../components/SelectChip";
+import FormField from "../components/FormField";
+import BottomSheet from "../components/BottomSheet";
+import InputSheet from "../components/InputSheet";
+import SelectSheet from "../components/SelectSheet";
+import CheckDropdown from "../components/CheckDropdown";
+import Button from "../../../../../components/common/Button";
type Props = {
snsUrl: string;
@@ -29,6 +23,7 @@ type Props = {
canGoNext: boolean;
onBack: () => void;
+ onNext: () => void;
};
type Sheet = null | "snsUrl" | "gender" | "ageGroup" | "videoLength" | "views";
@@ -54,9 +49,8 @@ export default function MatchingTestStep3Content({
onToggleChip,
canGoNext,
onBack,
+ onNext,
}: Props) {
- const navigate = useNavigate();
-
const [sheet, setSheet] = useState(null);
const open = (s: Sheet) => setSheet(s);
const close = () => setSheet(null);
@@ -72,22 +66,17 @@ export default function MatchingTestStep3Content({
}),
[step3Chips]
);
-
const genderValue = step3Selected.gender.join("\n");
const ageValue = step3Selected.ageGroup.join("\n");
const lenValue = step3Selected.videoLength[0] ?? "";
const viewsValue = step3Selected.views[0] ?? "";
- // ✅ step3 완료 → matching-result로 이동
- const goToResult = () => {
- if (!canGoNext) return;
- navigate({ to: "/matching-test/matching-result" });
- };
-
return (
+ {/* ✅ step1과 동일한 상단 컴포넌트만 사용 */}
+ {/* ✅ step1 기준: px-6 */}
콘텐츠 특성을 모두 선택해주세요
@@ -210,18 +199,17 @@ export default function MatchingTestStep3Content({
+ {/* ✅ step1 기준: px-6 */}
-
다음
-
+
{sheet === "snsUrl" ? (
@@ -238,6 +226,8 @@ export default function MatchingTestStep3Content({
) : null}
+
+
{sheet === "gender" ? (
) : null}
+
{sheet === "ageGroup" ? (
) : null}
+
{sheet === "videoLength" ? (
navigate({ to: "/matching-test/matching-test/step1" })}
+ onGoMatchingTest={() => navigate({ to: "/matching/test/step1" })}
onOpenProfileCard={() => navigate({ to: "/mypage/profileCard" })}
onOpenLikes={() => navigate({ to: "/mypage/likes" })}
onOpenEditProfile={() => navigate({ to: "/mypage/edit" })}