diff --git a/package.json b/package.json
index efebac6..c646e42 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"@emotion/styled": "^11.13.0",
"@tanstack/react-query": "^5.56.2",
"@tanstack/react-query-devtools": "^5.58.0",
+ "@use-funnel/react-router-dom": "^0.0.7",
"husky": "^9.1.6",
"ky": "^1.7.2",
"react": "^18.3.1",
diff --git a/src/Router.tsx b/src/Router.tsx
index d436fcc..14631fb 100644
--- a/src/Router.tsx
+++ b/src/Router.tsx
@@ -1,9 +1,14 @@
import { createBrowserRouter } from "react-router-dom";
import Home from "./pages/Home";
+import Step1 from "./components/Register/Step1";
export const router = createBrowserRouter([
{
path: "/",
element: ,
},
+ {
+ path: "/register",
+ element: ,
+ },
]);
diff --git a/src/assets/index.ts b/src/assets/index.ts
index 053f512..6ec2587 100644
--- a/src/assets/index.ts
+++ b/src/assets/index.ts
@@ -1,5 +1,32 @@
///
import IcRabbit from "./icons/ic_rabbit.svg?react";
+import IcCheckGrey from "./svg/check/ic_check-grey.svg?react";
+import IcCheckMint from "./svg/check/ic_check-mint.svg?react";
+import IcCheckComplete from "./svg/check/ic_check-complete.svg?react";
+import IcCheckWhiteGrey from "./svg/check/ic_check-whitegrey.svg?react";
+import IcCheckTransparent from "./svg/check/ic_check-transparent.svg?react";
-export { IcRabbit };
+import IcStep1 from "./svg/register/ic_step1.svg?react";
+import IcStep2 from "./svg/register/ic_step2.svg?react";
+import IcStep3 from "./svg/register/ic_step3.svg?react";
+import IcStep4 from "./svg/register/ic_step4.svg?react";
+import IcGreyCircle from "./svg/register/ic_grey-circle.svg?react";
+import IcBack from "./svg/ic_back.svg?react";
+import IcNext from "./svg/ic_next.svg?react";
+
+export {
+ IcRabbit,
+ IcCheckGrey,
+ IcCheckMint,
+ IcCheckComplete,
+ IcCheckWhiteGrey,
+ IcCheckTransparent,
+ IcStep1,
+ IcStep2,
+ IcStep3,
+ IcStep4,
+ IcBack,
+ IcNext,
+ IcGreyCircle,
+};
diff --git a/src/assets/svg/check/ic_check-complete.svg b/src/assets/svg/check/ic_check-complete.svg
new file mode 100644
index 0000000..c14b9d6
--- /dev/null
+++ b/src/assets/svg/check/ic_check-complete.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/svg/check/ic_check-grey.svg b/src/assets/svg/check/ic_check-grey.svg
new file mode 100644
index 0000000..7b7577b
--- /dev/null
+++ b/src/assets/svg/check/ic_check-grey.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/svg/check/ic_check-mint.svg b/src/assets/svg/check/ic_check-mint.svg
new file mode 100644
index 0000000..20ecc9d
--- /dev/null
+++ b/src/assets/svg/check/ic_check-mint.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/svg/check/ic_check-transparent.svg b/src/assets/svg/check/ic_check-transparent.svg
new file mode 100644
index 0000000..08c760e
--- /dev/null
+++ b/src/assets/svg/check/ic_check-transparent.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/svg/check/ic_check-whitegrey.svg b/src/assets/svg/check/ic_check-whitegrey.svg
new file mode 100644
index 0000000..0dfae4e
--- /dev/null
+++ b/src/assets/svg/check/ic_check-whitegrey.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/svg/ic_back.svg b/src/assets/svg/ic_back.svg
new file mode 100644
index 0000000..5af108f
--- /dev/null
+++ b/src/assets/svg/ic_back.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/svg/ic_next.svg b/src/assets/svg/ic_next.svg
new file mode 100644
index 0000000..947b45c
--- /dev/null
+++ b/src/assets/svg/ic_next.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/svg/register/ic_grey-circle.svg b/src/assets/svg/register/ic_grey-circle.svg
new file mode 100644
index 0000000..78c0c95
--- /dev/null
+++ b/src/assets/svg/register/ic_grey-circle.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/svg/register/ic_step1.svg b/src/assets/svg/register/ic_step1.svg
new file mode 100644
index 0000000..7bfe074
--- /dev/null
+++ b/src/assets/svg/register/ic_step1.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/svg/register/ic_step2.svg b/src/assets/svg/register/ic_step2.svg
new file mode 100644
index 0000000..92eb14a
--- /dev/null
+++ b/src/assets/svg/register/ic_step2.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/svg/register/ic_step3.svg b/src/assets/svg/register/ic_step3.svg
new file mode 100644
index 0000000..1dbaf07
--- /dev/null
+++ b/src/assets/svg/register/ic_step3.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/svg/register/ic_step4.svg b/src/assets/svg/register/ic_step4.svg
new file mode 100644
index 0000000..aff526f
--- /dev/null
+++ b/src/assets/svg/register/ic_step4.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/components/Register/CheckBox.tsx b/src/components/Register/CheckBox.tsx
new file mode 100644
index 0000000..3cf233e
--- /dev/null
+++ b/src/components/Register/CheckBox.tsx
@@ -0,0 +1,38 @@
+import styled from "@emotion/styled";
+import { InputHTMLAttributes } from "react";
+import { IcCheckComplete, IcCheckWhiteGrey } from "../../assets";
+
+interface CheckBoxProps extends InputHTMLAttributes {
+ onClick?: () => void;
+}
+
+function CheckBox({ onClick, checked }: CheckBoxProps): JSX.Element {
+ return (
+
+
+ {checked ? : }
+ 네, 동의합니다.
+
+
+ );
+}
+
+const CheckBoxWrapper = styled.div<{ disabled?: boolean; isChecked?: boolean }>`
+ padding: 1.5rem;
+
+ background-color: ${({ theme }) => theme.colors.light_6};
+ border-radius: 1.2rem;
+`;
+
+const CheckBoxContent = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+`;
+
+const CheckBoxText = styled.span`
+ ${({ theme }) => theme.fonts.subtitle2};
+ color: ${({ theme }) => theme.colors.black_2};
+`;
+
+export default CheckBox;
diff --git a/src/components/Register/Step1.tsx b/src/components/Register/Step1.tsx
new file mode 100644
index 0000000..285b095
--- /dev/null
+++ b/src/components/Register/Step1.tsx
@@ -0,0 +1,129 @@
+import styled from "@emotion/styled";
+import { IcBack, IcCheckGrey, IcCheckWhiteGrey, IcNext, IcCheckComplete, IcCheckMint } from "../../assets";
+import Button from "../Common/Button/Button";
+import { AGREE_DATA, REGISTER_TEXT } from "../../constant";
+import { useState } from "react";
+import CheckBox from "./CheckBox";
+import StepIcon from "../../utils/StepIcon";
+import { useNavigate } from "react-router-dom";
+
+interface AgreeTextProps {
+ isRequired: boolean;
+}
+
+function Step1() {
+ const navigate = useNavigate();
+ const [checkedItems, setCheckedItems] = useState([]);
+
+ const handleClickNextButton = () => {
+ navigate("/register/step2");
+ };
+
+ // 전체 선택
+ const handleAllChecked = (checked: boolean) => {
+ setCheckedItems(checked ? AGREE_DATA.map((item) => item.key) : []);
+ };
+
+ // 개별 선택
+ const handleSingleChecked = (checked: boolean, key: string) => {
+ setCheckedItems((prev) => (checked ? [...prev, key] : prev.filter((item) => item !== key)));
+ };
+
+ return (
+
+
+
+
+
+
+
+ handleAllChecked(checkedItems.length !== AGREE_DATA.length)}
+ checked={checkedItems.length === AGREE_DATA.length}
+ >
+ {checkedItems.length === AGREE_DATA.length ? : }
+
+
+
+ {AGREE_DATA.map((data) => (
+ handleSingleChecked(!checkedItems.includes(data.key), data.key)}
+ >
+
+ {checkedItems.includes(data.key) ? : }
+ {data.text}
+
+
+
+ ))}
+
+
+
+ key.includes("required") && !checkedItems.includes(key))}
+ onClick={handleClickNextButton}
+ >
+ 다음
+
+
+
+ );
+}
+
+export default Step1;
+
+const Step1Container = styled.div`
+ height: 100dvh;
+
+ padding: 2rem;
+ position: relative;
+`;
+
+const Step1Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 3rem;
+`;
+
+const TitleWrapper = styled.div`
+ margin-left: 1rem;
+ ${({ theme }) => theme.fonts.title1};
+`;
+
+const AgreeContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+`;
+
+const NextButton = styled(Button)<{ disabled?: boolean }>`
+ width: calc(100% - 4rem);
+ position: absolute;
+ bottom: 3rem;
+`;
+
+const OptionalAgreeContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1.8rem;
+ padding: 0 2rem;
+`;
+
+const OptionalAgreeWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+`;
+
+const AgreeWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 1.5rem;
+`;
+
+const AgreeText = styled.div`
+ ${({ theme }) => theme.fonts.body2};
+
+ color: ${({ theme, isRequired }) => (isRequired ? theme.colors.main_mint : theme.colors.gray_3)};
+`;
diff --git a/src/constant/index.ts b/src/constant/index.ts
new file mode 100644
index 0000000..26e61c0
--- /dev/null
+++ b/src/constant/index.ts
@@ -0,0 +1,15 @@
+export const REGISTER_TEXT = {
+ STEP1: "간편 회원가입을 위해
약관 동의가 필요해요",
+ STEP2: " 학교 계정 인증을 위한
이메일을 입력하세요",
+ DESCRIPTION_STEP2: " 최초 1회 이메일 인증이 필요해요.",
+ STEP3: "나만의 프로필을
설정하세요",
+ DESCRIPTION_STEP3: "마이페이지에서 언제든 수정 가능해요.",
+ STEP4: "추가적인 정보를
입력하세요",
+ DESCRIPTION_STEP4: "추가정보를 입력하고 신뢰도를 높여보세요!",
+} as const;
+
+export const AGREE_DATA = [
+ { text: "[필수] TinU 서비스 이용약관", key: "required1" },
+ { text: "[필수] 개인정보 수집 및 이용 동의", key: "required2" },
+ { text: "[선택] 광고성 정보 수신 동의", key: "optional" },
+];
diff --git a/src/styles/GlobalStyles.ts b/src/styles/GlobalStyles.ts
index ff46a55..694e46e 100644
--- a/src/styles/GlobalStyles.ts
+++ b/src/styles/GlobalStyles.ts
@@ -173,8 +173,6 @@ const resetCss = css`
const gStyle = css`
${resetCss}
- #root,
- body,
html {
margin: 0 auto;
@@ -202,9 +200,6 @@ const gStyle = css`
}
body {
- display: flex;
- justify-content: center;
-
scroll-behavior: smooth;
}
input,
diff --git a/src/utils/StepIcon.tsx b/src/utils/StepIcon.tsx
new file mode 100644
index 0000000..fbae58a
--- /dev/null
+++ b/src/utils/StepIcon.tsx
@@ -0,0 +1,56 @@
+import styled from "@emotion/styled";
+import { memo } from "react";
+import { IcStep1, IcStep2, IcStep3, IcStep4, IcCheckTransparent, IcGreyCircle } from "../assets";
+
+interface StepIconProps {
+ step: 1 | 2 | 3 | 4;
+}
+
+const STEP_CONFIG = {
+ 1: { text: "약관동의", Icon: IcStep1 },
+ 2: { text: "메일인증", Icon: IcStep2 },
+ 3: { text: "정보입력", Icon: IcStep3 },
+ 4: { text: "상세정보", Icon: IcStep4 },
+} as const;
+
+function StepIcon({ step }: StepIconProps) {
+ const renderIcons = () => {
+ return Array(4)
+ .fill(null)
+ .map((_, index) => {
+ const currentStep = index + 1;
+
+ if (currentStep === step) {
+ const StepComponent = STEP_CONFIG[step].Icon;
+ return (
+
+
+ {STEP_CONFIG[step].text}
+
+ );
+ }
+
+ return currentStep < step ? : ;
+ });
+ };
+ return {renderIcons()};
+}
+
+const StepIconContainer = styled.div`
+ display: flex;
+ gap: 0.5rem;
+`;
+
+const StepIconWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const StepText = styled.span`
+ margin-top: 0.3rem;
+ ${({ theme }) => theme.fonts.description3};
+ color: ${({ theme }) => theme.colors.gray_3};
+`;
+
+export default memo(StepIcon);
diff --git a/yarn.lock b/yarn.lock
index e5bf068..c28268b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2404,6 +2404,27 @@ __metadata:
languageName: node
linkType: hard
+"@use-funnel/core@npm:^0.0.7":
+ version: 0.0.7
+ resolution: "@use-funnel/core@npm:0.0.7"
+ peerDependencies:
+ react: ">=16.8"
+ checksum: 10c0/edf90214a37d9952f5e76ff4075c74ef1663c849d9cd524ecb5476a55ac451cd7f8cbcd6fba012b241cfa4af1690d553c23bce0d7585492cd57049ce3476b174
+ languageName: node
+ linkType: hard
+
+"@use-funnel/react-router-dom@npm:^0.0.7":
+ version: 0.0.7
+ resolution: "@use-funnel/react-router-dom@npm:0.0.7"
+ dependencies:
+ "@use-funnel/core": "npm:^0.0.7"
+ peerDependencies:
+ react: ">=16.8"
+ react-router-dom: ">=6"
+ checksum: 10c0/a529384e32655c0936464ec0926b0fd9ae4560b4cae7d706d2bd2924a030579f62c3b440573555ebb709a9bf48429f9b93ca25f727d6c0e9157c76397103b651
+ languageName: node
+ linkType: hard
+
"@vitejs/plugin-react-swc@npm:^3.5.0":
version: 3.7.0
resolution: "@vitejs/plugin-react-swc@npm:3.7.0"
@@ -5821,6 +5842,7 @@ __metadata:
"@tanstack/react-query-devtools": "npm:^5.58.0"
"@types/react": "npm:^18.3.3"
"@types/react-dom": "npm:^18.3.0"
+ "@use-funnel/react-router-dom": "npm:^0.0.7"
"@vitejs/plugin-react-swc": "npm:^3.5.0"
eslint: "npm:^9.9.0"
eslint-plugin-react-hooks: "npm:^5.1.0-rc.0"