Skip to content

Commit 94896a2

Browse files
authored
Merge pull request #162 from TEAM-COMFIT/feat/#159/onboarding-agree-modal
[Feat] 이용약관 및 개인정보처리방침 모달, 페이지 구현
2 parents 7d08abb + e50c4b5 commit 94896a2

39 files changed

Lines changed: 1481 additions & 138 deletions

src/app/providers/modal-provider.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface ModalItem {
99
id: string;
1010
content: ReactNode;
1111
autoPlay?: number;
12+
size?: "default" | "auto";
1213
}
1314

1415
export const ModalProvider = () => {
@@ -33,6 +34,7 @@ export const ModalProvider = () => {
3334
isOpen={true}
3435
autoPlay={modal.autoPlay}
3536
onClose={() => modalStore.close(modal.id)}
37+
size={modal.size}
3638
>
3739
{modal.content}
3840
</Modal>

src/app/routes/paths.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@ export const ROUTES = {
1515
EXPERIENCE_DETAIL: (id = ":id") => `/experience/${id}`, // 경험 상세
1616
EXPERIENCE_EDIT: (id = ":id") => `/experience/${id}/edit`, // 경험 수정
1717

18+
POLICY_USE: "/policy/terms", // 이용약관
19+
POLICY_PRIVACY: "/policy/privacy", // 개인정보처리방침
20+
1821
MYPAGE: "/mypage",
1922
};

src/app/routes/public-routes.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,19 @@ const CompanyDetailPage = lazy(() =>
3232
}))
3333
);
3434

35+
const PolicyPage = lazy(() =>
36+
import("@/pages/policy/policy-page").then((module) => ({
37+
default: module.PolicyPage,
38+
}))
39+
);
40+
3541
export const guestRoutes = [{ path: ROUTES.LOGIN, element: <LoginPage /> }];
3642

3743
export const publicRoutes = [
3844
{ path: ROUTES.LOGIN_AUTH, element: <KakaoLoginPage /> },
3945
{ path: ROUTES.LANDING, element: <LandingPage /> },
4046
{ path: ROUTES.HOME, element: <HomePage /> },
4147
{ path: ROUTES.COMPANY(), element: <CompanyDetailPage /> },
48+
{ path: ROUTES.POLICY_USE, element: <PolicyPage mode="USE" /> },
49+
{ path: ROUTES.POLICY_PRIVACY, element: <PolicyPage mode="PRIVACY" /> },
4250
];

src/features/experience-detail/model/use-leave-confirm.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useCallback } from "react";
22
import { useBlocker } from "react-router-dom";
33

4+
import { IconWarn } from "@/shared/assets/icons";
45
import { modalStore } from "@/shared/model/store";
56
import { ModalBasic } from "@/shared/ui";
67

@@ -65,12 +66,13 @@ export const useLeaveConfirm = () => {
6566
if (blocker.state === "blocked") {
6667
modalStore.open(
6768
<ModalBasic
68-
title={`작성중인 내용이 있습니다.\n정말 나가시겠습니까?`}
69-
subTitle="저장하지 않으면 내용이 사라져요."
70-
closeText="이어서 작성"
71-
confirmText="나가기"
72-
onClose={cancelLeave}
73-
onConfirm={confirmLeave}
69+
icon={<IconWarn width={48} height={48} />}
70+
title={`작성 중인 내용이 있어요`}
71+
subTitle="저장하지 않으면 내용이 모두 사라져요."
72+
closeText="나가기"
73+
confirmText="계속 작성하기"
74+
onClose={confirmLeave}
75+
onConfirm={cancelLeave}
7476
/>,
7577
0,
7678
undefined,

src/features/experience-detail/ui/experience-viewer/experience-viewer.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { IconTrash } from "@/shared/assets/icons";
12
import {
23
EXPERIENCE_TYPE,
34
type ExperienceTypeCode,
@@ -48,15 +49,18 @@ const ExperienceViewer = () => {
4849
const handleOpenDeleteModal = () => {
4950
modalStore.open(
5051
<ModalBasic
51-
title="이 경험을 삭제하시겠습니까?"
52-
subTitle="작성한 내용은 즉시 제거되며, 복구할 수 없습니다."
53-
closeText="취소"
54-
confirmText="삭제"
55-
onClose={() => modalStore.reset()} // 취소 시 닫기
56-
onConfirm={() => {
52+
icon={<IconTrash width={48} height={48} />}
53+
title="이 경험을 삭제할까요?"
54+
subTitle="삭제하면 다시 복구할 수 없어요"
55+
closeText="삭제하기"
56+
confirmText="취소하기"
57+
onClose={() => {
5758
onClickDelete(); // 실제 삭제 동작
5859
modalStore.reset(); // 모달 닫기
5960
}}
61+
onConfirm={() => {
62+
modalStore.reset(); // 취소 시 닫기
63+
}}
6064
/>
6165
);
6266
};

src/features/experience-matching/ui/analyzing/analyzing.tsx

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,44 @@ import * as styles from "./analyzing.css";
1010

1111
import type { CustomErrorResponse } from "@/shared/api/generate/http-client";
1212

13+
let isRequesting = false;
14+
1315
export const Analyzing = ({ nextStep }: { nextStep: () => void }) => {
1416
const { company, experience, jobDescription, setReportId } = useReportStore();
15-
const { mutate } = useCreateReport();
17+
const { mutateAsync } = useCreateReport();
1618

1719
// 에러 핸들링 (임시)
1820
const [open, setOpen] = useState(false);
1921
const [errorMsg, setErrorMsg] = useState("");
2022

2123
useEffect(() => {
22-
mutate(
23-
{
24-
companyId: company?.id ?? 0,
25-
experienceId: experience?.id ?? 0,
26-
jobDescription: jobDescription,
27-
},
28-
{
29-
onSuccess: (response) => {
30-
setReportId(response?.id ?? 0);
31-
nextStep();
32-
},
33-
onError: (error: CustomErrorResponse) => {
34-
const serverMessage =
35-
error.message || "리포트 생성 중 에러가 발생했습니다";
36-
setErrorMsg(serverMessage);
37-
setOpen(true);
24+
if (isRequesting) return;
25+
isRequesting = true;
26+
27+
const handleRequest = async () => {
28+
try {
29+
const response = await mutateAsync({
30+
companyId: company?.id ?? 0,
31+
experienceId: experience?.id ?? 0,
32+
jobDescription: jobDescription,
33+
});
3834

39-
setTimeout(() => setOpen(false), 3000);
40-
},
35+
setReportId(response?.id ?? 0);
36+
nextStep();
37+
} catch (err) {
38+
const error = err as CustomErrorResponse;
39+
const serverMessage =
40+
error.message || "리포트 생성 중 에러가 발생했습니다";
41+
setErrorMsg(serverMessage);
42+
setOpen(true);
43+
setTimeout(() => setOpen(false), 3000);
44+
} finally {
45+
isRequesting = false;
4146
}
42-
);
43-
}, [nextStep, setReportId, mutate]);
47+
};
4448

49+
handleRequest();
50+
}, []);
4551
return (
4652
<>
4753
<div className={styles.layout}>

src/features/experience-matching/ui/select-company/select-company.tsx

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { useEffect, useState } from "react";
33
import { useNavigate } from "react-router-dom";
44

55
import { ROUTES } from "@/app/routes/paths";
6+
import { IconPen } from "@/shared/assets/icons";
67
import { modalStore } from "@/shared/model/store";
7-
import { Button, Modal } from "@/shared/ui";
8+
import { Modal, ModalBasic } from "@/shared/ui";
89
import {
910
useGetExperience,
1011
useGetCompanyList,
@@ -36,23 +37,21 @@ export const SelectCompany = ({ onClick }: { onClick: () => void }) => {
3637
useEffect(() => {
3738
if (data?.totalElements === 0) {
3839
modalStore.open(
39-
<>
40-
<Modal.Content>
41-
<Modal.Title>아직 등록된 경험이 없습니다</Modal.Title>
42-
<Modal.SubTitle>지금 바로 경험을 등록하러 가볼까요?</Modal.SubTitle>
43-
</Modal.Content>
44-
<Modal.Buttons>
45-
<Button variant="secondary" onClick={() => navigate(ROUTES.HOME)}>
46-
나가기
47-
</Button>
48-
<Button
49-
variant="primary"
50-
onClick={() => navigate(ROUTES.EXPERIENCE_CREATE)}
51-
>
52-
이동하기
53-
</Button>
54-
</Modal.Buttons>
55-
</>,
40+
<ModalBasic
41+
onClose={() => {
42+
modalStore.close("NO-EXPERIENCE");
43+
navigate(ROUTES.HOME);
44+
}}
45+
onConfirm={() => {
46+
modalStore.close("NO-EXPERIENCE");
47+
navigate(ROUTES.EXPERIENCE_CREATE);
48+
}}
49+
icon={<IconPen width={48} height={48} />}
50+
title="아직 등록된 경험이 없어요"
51+
subTitle="경험을 등록하고 AI매칭을 시작해보세요"
52+
closeText="나중에 할게요"
53+
confirmText="경험 등록하기"
54+
/>,
5655
undefined,
5756
undefined,
5857
"NO-EXPERIENCE"
@@ -65,13 +64,15 @@ export const SelectCompany = ({ onClick }: { onClick: () => void }) => {
6564
// 기업 선택 후, 대기하는 모달
6665
modalStore.open(
6766
<>
68-
<Modal.Content type="auto">
69-
<Modal.Title>
70-
{josa(selectedCompany.name, "을/를")} 선택하셨습니다
71-
</Modal.Title>
72-
<Modal.SubTitle>기업분석 내용을 불러오는 중입니다.</Modal.SubTitle>
67+
<Modal.Content>
68+
<Modal.Image />
69+
<Modal.TitleGroup>
70+
<Modal.Title>
71+
{josa(selectedCompany.name, "을/를")} 선택했어요
72+
</Modal.Title>
73+
<Modal.SubTitle>기업분석 내용을 불러오고 있어요</Modal.SubTitle>
74+
</Modal.TitleGroup>
7375
</Modal.Content>
74-
<Modal.Image />
7576
</>,
7677
3000,
7778
() => {

src/features/onboarding/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ export * from "./store/interest-select/selectors";
77

88
export { useGetUniversity } from "./api/use-get-university.query";
99
export { usePostOnboarding } from "./api/use-post-onboarding.mutation";
10+
11+
export { PolicyModal } from "./ui/policy-modal/policy-modal";

src/features/onboarding/lib/onboarding-form.validator.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ export const isOnboardingFormComplete = (params: {
66
selectedUniversity: SearchItem | null;
77
industry: Record<number, unknown>;
88
job: Record<number, unknown>;
9+
isAgreed: boolean;
910
}) => {
10-
const { selectedEducation, selectedUniversity, industry, job } = params;
11+
const { selectedEducation, selectedUniversity, industry, job, isAgreed } =
12+
params;
1113

1214
const hasEducation = Boolean(selectedEducation);
1315
const hasUniversity = Boolean(selectedUniversity);
1416

1517
const hasIndustry1 = Boolean(industry[1]);
1618
const hasJob1 = Boolean(job[1]);
1719

18-
return hasEducation && hasUniversity && hasIndustry1 && hasJob1;
20+
return hasEducation && hasUniversity && hasIndustry1 && hasJob1 && isAgreed;
1921
};

0 commit comments

Comments
 (0)