Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] 온보딩 퍼블리싱 및 동작 구현 #31

Merged
merged 14 commits into from
Aug 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/correct.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/learningFinish.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/onboardingFinish.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/wrong.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/components/common/FinishScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const FinishScreen = () => {
return <div>FinishScreen</div>;
};

export default FinishScreen;
12 changes: 8 additions & 4 deletions src/components/common/NextBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ import { CommonBtn } from "./common.styled";
interface NextBtnProps {
isActive: boolean;
text: string;
width: string;
width?: string;
handleBtn: () => void;
}

const NextBtn = ({ width, isActive, text, handleBtn }: NextBtnProps) => {
const NextBtn = (props: NextBtnProps) => {
return (
<CommonBtn width={width} onClick={handleBtn} isActive={isActive}>
{text}
<CommonBtn
width={props.width}
onClick={props.handleBtn}
isActive={props.isActive}
>
{props.text}
</CommonBtn>
);
};
Expand Down
25 changes: 25 additions & 0 deletions src/components/common/progressBar/ProgressBar.styeld.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import styled from "styled-components";

export const ProgressBarWrapper = styled.div`
display: flex;
align-items: center;
width: 100%;
height: 14px;
border-radius: 20px;
background: #ededed;
margin-top: 2rem;
position: relative;
`;

export const Progress = styled.div<{ $percentage: number }>`
background-color: #ffd600;
border-top-left-radius: 20px;
border-bottom-left-radius: 20px;
border-top-right-radius: ${(props) => (props.$percentage >= 99 ? 20 : 0)}px;
border-bottom-right-radius: ${(props) =>
props.$percentage >= 99 ? 20 : 0}px;
height: 100%;
width: ${(props) => props.$percentage}%;
transition: width 0.5s ease-in-out;
position: relative;
`;
15 changes: 15 additions & 0 deletions src/components/common/progressBar/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as S from "./ProgressBar.styeld";

interface ProgressBarProps {
percentage: number;
}

const ProgressBar = (props: ProgressBarProps) => {
return (
<S.ProgressBarWrapper>
<S.Progress $percentage={props.percentage} />
</S.ProgressBarWrapper>
);
};

export default ProgressBar;
16 changes: 16 additions & 0 deletions src/components/common/selectOption/SelectBtn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { SelectBtnProps } from "@type/selectList";
import * as S from "./SelectOptionList.styled";
import { colorSets } from "@utils/defaultData";

const SelectBtn = (props: SelectBtnProps) => {
const colorSet = colorSets[props.colorName];

return (
<S.SelectBtnWrapper $colorSet={colorSet} onClick={props.onClick}>
{props.text}
{props.imgURL && <S.SelectIcon src={props.imgURL} alt="선택지" />}
</S.SelectBtnWrapper>
);
};

export default SelectBtn;
35 changes: 35 additions & 0 deletions src/components/common/selectOption/SelectOptionList.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ColorSet } from "@type/selectList";
import styled from "styled-components";

export const SelectOptionContainer = styled.div<{ width?: string }>`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
gap: 1rem;
width: ${({ width }) => (width ? width : "100%")};
`;

export const SelectBtnWrapper = styled.div<{ $colorSet: ColorSet }>`
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 1.5rem;
box-sizing: border-box;
width: 100%;
height: 5rem;
font-size: 2rem;
font-weight: 700;
background-color: ${({ $colorSet }) => $colorSet.background};
color: ${({ $colorSet }) => $colorSet.color};
border: 1px solid ${({ $colorSet }) => $colorSet.border};
border-radius: 6px;
`;

export const SelectIcon = styled.img`
width: 2.3rem;
height: 2.3rem;
margin-right: 10px;
vertical-align: middle;
`;
52 changes: 52 additions & 0 deletions src/components/common/selectOption/SelectOptionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { SelectListProps } from "@type/selectList";
import SelectBtn from "./SelectBtn";
import * as S from "./SelectOptionList.styled";

const SelectOptionList = (props: SelectListProps) => {
const getColorName = (state: string) => {
switch (state) {
case "correct":
return "green";
case "wrong":
return "red";
case "selected":
return "yellow";
case "default":
default:
return "gray";
}
};

const getImgURL = (state: string) => {
switch (state) {
case "correct":
return "/correct.png";
case "wrong":
return "/wrong.png";
case "selected":
return "";
default:
return "";
}
};

const handleSelect = (value: string | number | null) => {
props.setter(value);
};

return (
<S.SelectOptionContainer width={props.width}>
{props.selectList.map((element) => (
<SelectBtn
key={element.text}
text={element.text}
colorName={getColorName(element.state)}
imgURL={getImgURL(element.state)}
onClick={() => handleSelect(element.value)}
/>
))}
</S.SelectOptionContainer>
);
};

export default SelectOptionList;
10 changes: 5 additions & 5 deletions src/components/login/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommonWrapper } from "@common/common.styled";
import { LoginBtn, LogoBox, Title } from "./Login.styled";
import * as S from "./Login.styled";
import { kakaoURL } from "./kakaoLoginConfig";

const Login = () => {
Expand All @@ -9,17 +9,17 @@ const Login = () => {

return (
<CommonWrapper>
<Title>{`{메인소개 멘트}`}</Title>
<LogoBox>{`{로고이미지}`}</LogoBox>
<LoginBtn>
<S.Title>{`{메인소개 멘트}`}</S.Title>
<S.LogoBox>{`{로고이미지}`}</S.LogoBox>
<S.LoginBtn>
<img id="login-bubble" src="./loginBubble.png" alt="3초 로그인" />
<img
id="login-btn"
src="./loginbtn.png"
alt="카카오 로그인 버튼"
onClick={toLogin}
/>
</LoginBtn>
</S.LoginBtn>
</CommonWrapper>
);
};
Expand Down
88 changes: 88 additions & 0 deletions src/components/onboarding/AgeInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { colorSets } from "@utils/defaultData";
import React, { ChangeEvent, KeyboardEvent } from "react";
import styled from "styled-components";

interface AgeInputProps {
age: string | number | null;
setter: (value: number | null) => void;
}

const AgeInput = ({ age, setter }: AgeInputProps) => {
const [isEditing, setIsEditing] = React.useState<boolean>(false);

const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
const newValue = value === "" ? null : Number(value);
setter(newValue);
};

const handleBlur = () => {
setIsEditing(false);
};

const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter") {
setIsEditing(false);
}
};

return (
<Container $age={age}>
{isEditing ? (
<Input
type="number"
value={age ?? ""}
onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
autoFocus
/>
) : (
<Text onClick={() => setIsEditing(true)}>
{age !== null ? `만 ${age}세` : "만"}
</Text>
)}
</Container>
);
};

export default AgeInput;

const Container = styled.div<{ $age?: any }>`
display: flex;
align-items: center;
padding: 0 1rem;
box-sizing: border-box;
width: 50%;
height: 4.5rem;
font-size: 2rem;
font-weight: 700;
border: 1px solid
${({ $age }) =>
$age ? colorSets["yellow"].border : colorSets["gray"].border};
background-color: ${({ $age }) =>
$age ? colorSets["yellow"].background : "none"};
border-radius: 6px;
position: relative;
`;

const Text = styled.div`
cursor: pointer;
width: 100%;
line-height: 4.5rem;
padding: 0 1rem;
`;

const Input = styled.input`
font-size: 2rem;
width: 100%;
box-sizing: border-box;
padding: 0 1.5rem;
border: none;
border-radius: 6px;
position: absolute;
top: 0;
left: 0;
height: 100%;
background-color: transparent;
`;
21 changes: 21 additions & 0 deletions src/components/onboarding/SelectAge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import AgeInput from "./AgeInput";
import * as S from "./onboarding.styled";

interface SelectAgeProps {
age: string | number | null;
setter: (value: string | number | null) => void;
}

const SelectAge = ({ age, setter }: SelectAgeProps) => {
return (
<S.Container>
<S.Title>학습자의 나이는 몇살인가요?</S.Title>
<S.SubContainer>
<S.SubTitle>만 나이</S.SubTitle>
<AgeInput age={age} setter={setter} />
</S.SubContainer>
</S.Container>
);
};

export default SelectAge;
21 changes: 21 additions & 0 deletions src/components/onboarding/SelectLanguage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Dropdown from "@components/common/dropDown/Dropdown";
import * as S from "./onboarding.styled";
import { nationElements } from "@utils/defaultData";

interface SelectLanguageProps {
setter: (value: string | number | null) => void;
}

const SelectLanguage = ({ setter }: SelectLanguageProps) => {
return (
<S.Container>
<S.Title>어떤 언어를 배우시나요?</S.Title>
<S.SubContainer>
<S.SubTitle>언어</S.SubTitle>
<Dropdown selectList={nationElements} setter={setter} />
</S.SubContainer>
</S.Container>
);
};

export default SelectLanguage;
39 changes: 39 additions & 0 deletions src/components/onboarding/SelectLevel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as S from "./onboarding.styled";
import useSelectLevel from "@hooks/useSelectLevel";
import { nationElements } from "@utils/defaultData";
import SelectOptionList from "@common/selectOption/SelectOptionList";

interface SelectLevelProps {
languageId: number | string;
setter: (value: string | number | null) => void;
}

const SelectLevel = ({ languageId, setter }: SelectLevelProps) => {
const { selectedLevel, setSelectedLevel, levelOptions } = useSelectLevel();
const language = nationElements.find(
(element) => element.value === languageId
);

const handleOptionChange = (value: string | number | null) => {
setSelectedLevel(value);
setter(value);
};

return (
<S.Container>
<S.Title>{language?.text}를 얼마나 알고 계신가요?</S.Title>
<S.SubContainer>
<SelectOptionList
selectList={levelOptions.map((option) => ({
text: option.text,
value: option.value,
state: selectedLevel === option.value ? "selected" : "default",
}))}
setter={handleOptionChange}
/>
</S.SubContainer>
</S.Container>
);
};

export default SelectLevel;
Loading