Skip to content

Commit

Permalink
TINU-77 feat: Step1 컴포넌트 구현 (#44)
Browse files Browse the repository at this point in the history
* [TINU-77] install: use-funnel 라이브러리 설치

* chore: svg 폴더 생성 및 이미지파일 추가

* chore: 상수 폴더 생성

* feat: 회원가입 Step1 페이지 퍼블리싱 1차 작업

* style: 버튼 컴포넌트 cursor-pointer 추가

* style: 글로벌스타일 수정 및 이미지파일 export

* fix: Step1 퍼블리싱 수정

* chore: <br/> 태그 추가

* 버튼 컴포넌트 cursor-pointer 삭제

* style: svg 파일 대체

* refactor: Step1 컴포넌트 리팩토링 및 체크박스 추가

* fix: 불필요한 코드 제거

* feat: 체크박스 컴포넌트 1차 작업

* chore: svg 아이콘 추가

* feat: step 4단계에 따른 아이콘 분리

* chore: export 수정 및 svg파일 추가

* fix: 체크박스 코드 수정 및 파일위치 변경

* feat: Step1 컴포넌트 구현 및 리팩토링

* chore: 체크박스 컴포넌트 위치 변경

* fix: 불필요한 패키지 삭제

* fix: 리뷰반영-StepIcon 로직 수정

* fix: 리뷰 반영- 이용약관 데이터 상수로 관리

* chore: 체크박스 아이콘명 변경

* chore: 리뷰 반영- 불필요한 주석 제거

* chore: IcCheckComplete 아이콘명 변경

* fix: yarn.lock 변경사항 반영

* fix: 리뷰 반영-네이밍 Screaming Snake Case로 통일

* fix: 리뷰 반영 1차 작업

* fix: 체크박스 리뷰 반영 1차 작업
  • Loading branch information
juheehasaeyo authored Feb 13, 2025
1 parent cad18ec commit 26da7b1
Show file tree
Hide file tree
Showing 21 changed files with 338 additions and 6 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions src/Router.tsx
Original file line number Diff line number Diff line change
@@ -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: <Home />,
},
{
path: "/register",
element: <Step1 />,
},
]);
29 changes: 28 additions & 1 deletion src/assets/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
/// <reference types="vite-plugin-svgr/client" />

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,
};
4 changes: 4 additions & 0 deletions src/assets/svg/check/ic_check-complete.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/svg/check/ic_check-grey.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/svg/check/ic_check-mint.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/svg/check/ic_check-transparent.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/svg/check/ic_check-whitegrey.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/svg/ic_back.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/svg/ic_next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/svg/register/ic_grey-circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/svg/register/ic_step1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/svg/register/ic_step2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/svg/register/ic_step3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/svg/register/ic_step4.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions src/components/Register/CheckBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import styled from "@emotion/styled";
import { InputHTMLAttributes } from "react";
import { IcCheckComplete, IcCheckWhiteGrey } from "../../assets";

interface CheckBoxProps extends InputHTMLAttributes<HTMLInputElement> {
onClick?: () => void;
}

function CheckBox({ onClick, checked }: CheckBoxProps): JSX.Element {
return (
<CheckBoxWrapper isChecked={checked} onClick={onClick}>
<CheckBoxContent>
{checked ? <IcCheckComplete /> : <IcCheckWhiteGrey />}
<CheckBoxText>네, 동의합니다.</CheckBoxText>
</CheckBoxContent>
</CheckBoxWrapper>
);
}

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;
129 changes: 129 additions & 0 deletions src/components/Register/Step1.tsx
Original file line number Diff line number Diff line change
@@ -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<string[]>([]);

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 (
<Step1Container>
<Step1Wrapper>
<IcBack />
<StepIcon step={1} />
<TitleWrapper dangerouslySetInnerHTML={{ __html: REGISTER_TEXT.STEP1 }} />

<AgreeContainer>
<CheckBox
onClick={() => handleAllChecked(checkedItems.length !== AGREE_DATA.length)}
checked={checkedItems.length === AGREE_DATA.length}
>
{checkedItems.length === AGREE_DATA.length ? <IcCheckComplete /> : <IcCheckWhiteGrey />}
</CheckBox>

<OptionalAgreeContainer>
{AGREE_DATA.map((data) => (
<OptionalAgreeWrapper
key={data.text}
onClick={() => handleSingleChecked(!checkedItems.includes(data.key), data.key)}
>
<AgreeWrapper>
{checkedItems.includes(data.key) ? <IcCheckMint /> : <IcCheckGrey />}
<AgreeText isRequired={data.key === "required"}>{data.text}</AgreeText>
</AgreeWrapper>
<IcNext />
</OptionalAgreeWrapper>
))}
</OptionalAgreeContainer>
</AgreeContainer>

<NextButton
disabled={AGREE_DATA.some(({ key }) => key.includes("required") && !checkedItems.includes(key))}
onClick={handleClickNextButton}
>
<span>다음</span>
</NextButton>
</Step1Wrapper>
</Step1Container>
);
}

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<AgreeTextProps>`
${({ theme }) => theme.fonts.body2};
color: ${({ theme, isRequired }) => (isRequired ? theme.colors.main_mint : theme.colors.gray_3)};
`;
15 changes: 15 additions & 0 deletions src/constant/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const REGISTER_TEXT = {
STEP1: "간편 회원가입을 위해<br /> 약관 동의가 필요해요",
STEP2: " 학교 계정 인증을 위한<br /> 이메일을 입력하세요",
DESCRIPTION_STEP2: " 최초 1회 이메일 인증이 필요해요.",
STEP3: "나만의 프로필을<br /> 설정하세요",
DESCRIPTION_STEP3: "마이페이지에서 언제든 수정 가능해요.",
STEP4: "추가적인 정보를<br /> 입력하세요",
DESCRIPTION_STEP4: "추가정보를 입력하고 신뢰도를 높여보세요!",
} as const;

export const AGREE_DATA = [
{ text: "[필수] TinU 서비스 이용약관", key: "required1" },
{ text: "[필수] 개인정보 수집 및 이용 동의", key: "required2" },
{ text: "[선택] 광고성 정보 수신 동의", key: "optional" },
];
5 changes: 0 additions & 5 deletions src/styles/GlobalStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,6 @@ const resetCss = css`
const gStyle = css`
${resetCss}
#root,
body,
html {
margin: 0 auto;
Expand Down Expand Up @@ -202,9 +200,6 @@ const gStyle = css`
}
body {
display: flex;
justify-content: center;
scroll-behavior: smooth;
}
input,
Expand Down
56 changes: 56 additions & 0 deletions src/utils/StepIcon.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<StepIconWrapper key={index}>
<StepComponent />
<StepText>{STEP_CONFIG[step].text}</StepText>
</StepIconWrapper>
);
}

return currentStep < step ? <IcCheckTransparent key={index} /> : <IcGreyCircle key={index} />;
});
};
return <StepIconContainer>{renderIcons()}</StepIconContainer>;
}

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);
Loading

0 comments on commit 26da7b1

Please sign in to comment.