Skip to content

Commit 17e3d55

Browse files
committed
feat(#38): 온보딩 약관 동의 퍼널 컴포넌트 생성
1 parent ec1edb6 commit 17e3d55

File tree

4 files changed

+169
-0
lines changed

4 files changed

+169
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { style } from "@vanilla-extract/css";
2+
3+
import { radius, semantic } from "@/styles";
4+
5+
export const wrapper = style({
6+
height: "100%",
7+
});
8+
9+
export const allAgreeBox = style({
10+
backgroundColor: semantic.background.grayLight,
11+
padding: "1.4rem 2rem",
12+
borderRadius: radius[120],
13+
});
14+
15+
export const allAgreeCheckIcon = style({
16+
width: "2.4rem",
17+
height: "2.4rem",
18+
});
19+
20+
export const individualAgreeBox = style({
21+
display: "flex",
22+
flexDirection: "column",
23+
gap: "0.8rem",
24+
paddingLeft: "2rem",
25+
});
26+
27+
export const agreeList = style({
28+
display: "flex",
29+
padding: "0.5rem 0",
30+
});
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { useQueryClient } from "@tanstack/react-query";
2+
import { useRouter } from "next/navigation";
3+
import { useForm } from "react-hook-form";
4+
5+
import { useUpdateMemberMutation } from "@/app/member/_api";
6+
import { Button } from "@/components/ui/Button";
7+
import { CheckBox } from "@/components/ui/CheckBox";
8+
import { HStack, VStack } from "@/components/ui/Stack";
9+
import { Text } from "@/components/ui/Text";
10+
import { QUERY_KEYS } from "@/constants";
11+
12+
import { AGREEMENTS } from "../../_constants/agreement.constants";
13+
import { OnboardingTitle } from "../OnboardingTitle";
14+
import * as styles from "./AgreeStep.css";
15+
16+
export const AgreeStep = ({
17+
nickname,
18+
phoneNumber,
19+
}: {
20+
nickname: string;
21+
phoneNumber: string;
22+
}) => {
23+
const router = useRouter();
24+
const queryClient = useQueryClient();
25+
const { mutate: updateMember } = useUpdateMemberMutation();
26+
27+
const { register, handleSubmit, watch, setValue } = useForm<{
28+
agreements: string[];
29+
}>({
30+
defaultValues: { agreements: [] },
31+
});
32+
33+
const checkedIds = watch("agreements");
34+
35+
const isAllAgreed = checkedIds.length === AGREEMENTS.length;
36+
const isAllRequiredAgreed = AGREEMENTS.filter(a => a.required).every(a =>
37+
checkedIds.includes(a.id)
38+
);
39+
40+
const handleAllAgree = (checked: boolean) => {
41+
setValue("agreements", checked ? AGREEMENTS.map(a => a.id) : []);
42+
};
43+
44+
const handleIndividualAgree = (id: string, checked: boolean) => {
45+
setValue(
46+
"agreements",
47+
checked ? [...checkedIds, id] : checkedIds.filter(item => item !== id)
48+
);
49+
};
50+
51+
const onSubmit = () => {
52+
updateMember(
53+
{
54+
nickname: nickname,
55+
phoneNumber: phoneNumber,
56+
optInMarketing: checkedIds.includes("marketing"),
57+
},
58+
{
59+
onSuccess: () => {
60+
queryClient.invalidateQueries({
61+
queryKey: QUERY_KEYS.member,
62+
});
63+
router.push("/");
64+
},
65+
}
66+
);
67+
};
68+
69+
return (
70+
<VStack
71+
justify='between'
72+
className={styles.wrapper}
73+
as='form'
74+
onSubmit={handleSubmit(onSubmit)}
75+
>
76+
<VStack gap={44}>
77+
<OnboardingTitle>이용약관에 동의해주세요</OnboardingTitle>
78+
<VStack gap={20}>
79+
<HStack gap={8} align='center' className={styles.allAgreeBox}>
80+
<CheckBox
81+
width={24}
82+
height={24}
83+
checked={isAllAgreed}
84+
onCheckedChange={handleAllAgree}
85+
className={styles.allAgreeCheckIcon}
86+
/>
87+
<Text typo='body1Sb'>모두 동의합니다</Text>
88+
</HStack>
89+
<ul className={styles.individualAgreeBox}>
90+
{AGREEMENTS.map(agreement => (
91+
<HStack
92+
key={agreement.id}
93+
as='li'
94+
gap={8}
95+
className={styles.agreeList}
96+
>
97+
<CheckBox
98+
value={agreement.id}
99+
checked={checkedIds.includes(agreement.id)}
100+
onCheckedChange={value =>
101+
handleIndividualAgree(agreement.id, value)
102+
}
103+
hasBackground={false}
104+
{...register("agreements")}
105+
/>
106+
<Text typo='body2Md' color='neutral.50'>
107+
{agreement.label}
108+
</Text>
109+
</HStack>
110+
))}
111+
</ul>
112+
</VStack>
113+
</VStack>
114+
<Button size='fullWidth' type='submit' disabled={!isAllRequiredAgreed}>
115+
동의하고 시작하기
116+
</Button>
117+
</VStack>
118+
);
119+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { AgreeStep } from "./AgreeStep";
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { type AgreementConstants } from "../_types";
2+
3+
export const AGREEMENTS: AgreementConstants[] = [
4+
{
5+
id: "termsOfService",
6+
label: "[필수] 개인정보 수집 및 이용",
7+
required: true,
8+
},
9+
{
10+
id: "privacyPolicy",
11+
label: "[필수] 서비스 이용약관",
12+
required: true,
13+
},
14+
{
15+
id: "marketing",
16+
label: "[선택] 마케팅 정보 수신 동의",
17+
required: false,
18+
},
19+
];

0 commit comments

Comments
 (0)