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: 사용자 정보기입 페이지 구현 #10

Merged
merged 18 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from 14 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
1 change: 1 addition & 0 deletions spark-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
"prettier": "^3.4.2",
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",
"vite": "^6.0.5",
Expand Down
207 changes: 207 additions & 0 deletions spark-web/src/domains/UserInformation/UserInformation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { useState, useEffect, useRef, RefObject } from 'react';

import UserInformationProfiles from './components/UserInformationProfiles';
import Questionbox from './components/Questionbox';
import UserDialogbox from './components/UserDialogbox';
import BottomSheetModal from '@/shared/components/BottomSheetModal/BottomSheetModal';
import PrimaryChips from '@/shared/components/Chips/PrimaryChips';
import Buttons from '@/shared/components/Buttons/Buttons';
import FulltimeParttime from './components/Footers/FulltimeParttime';
import SNSGoal from './components/Footers/SNSGoal';

import { QUESTIONS } from './questions';

interface userAnswer {
CONTENTS: string;
FULLTIME: string;
GOAL: string;
}

const buttons: string[] = [
Inoansta marked this conversation as resolved.
Show resolved Hide resolved
'푸드/먹방',
'패션',
'뷰티',
'여행',
'반려동물',
'IT/과학기술',
'엔터테인먼트',
'게임',
'영화',
'음악',
'육아/패밀리',
'경제',
'교육',
'vlog',
'운동',
];

function UserInformation() {
const [steps, setSteps] = useState<number>(0);
const [onlyClicked, setOnlyClicked] = useState<string>('');
const [open, setOpen] = useState<boolean>(false);
const [userAnswer, setUserAnswer] = useState<userAnswer>({
CONTENTS: '',
FULLTIME: '',
GOAL: '',
});
const [isTyped, setIsTyped] = useState<boolean>(false);
const bottomRef: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RefObject라고 정의하신 이유가 궁금합니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOM에 접근해야했고 readonly로 충분하기 떄문에 RefObject를 썼습니다


useEffect(() => {
bottomRef.current?.scrollIntoView();

switch (steps) {
case 0:
setTimeout(() => setSteps(1), 1000);
break;
case 1:
setTimeout(() => setSteps(2), 1000);
break;
case 3:
setTimeout(() => setSteps(4), 1000);
break;
case 4:
setTimeout(() => setSteps(5), 1000);
break;
case 5:
setTimeout(() => setSteps(6), 1000);
break;
case 7:
setTimeout(() => setSteps(8), 1000);
break;
case 8:
setTimeout(() => setSteps(9), 1000);
break;
case 10:
setTimeout(() => setSteps(11), 1000);
break;
case 11:
setTimeout(() => setSteps(12), 1000);
break;
}
}, [steps]);

function SheetContents() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컴포넌트 안에 또 컴포넌트로 정의한 이유가 있으신가요?
이렇게 정의하면 리렌더링에 대해서 대응할수 없다는 이슈가 있어서 안티패턴이라고 알고 있어요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유용한 정보 감사합니다! userInformation에서 쓰이는 상태가 많아서 컴포넌트 안에서 정의했습니다
분리하겠습니다!

return (
<div>
<div className={'flex flex-wrap gap-[10px] py-[20px]'}>
{buttons.map((item) => {
return (
<PrimaryChips
text={item}
key={item}
setOnlyClicked={() => {
setOnlyClicked(item);
setUserAnswer({ ...userAnswer, CONTENTS: item });
}}
onlyClicked={onlyClicked}
/>
);
})}
</div>
<div className={'flex justify-end pb-[30px]'}>
<Buttons
text={'확인'}
onClick={() => {
setOpen(false);
setSteps(3);
}}
buttonType={'small-outlined-button'}
/>
</div>
</div>
);
}

return (
<div className={'px-[20px] flex flex-col'}>
{steps === 0 ? (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 로직은 분기처리가 많이 되어있는거 같아요.
만약에 steps가 더 늘어나면 또 분기처리를 해줘야할것 같은데 이 부분에 대해서 한번 더 고민해봤으면 좋겠어요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 동의합니다! 두고두고 생각해봐야 할 것 같습니다

<>
<UserInformationProfiles />
<Questionbox questions={QUESTIONS.contents1} questionType={'last'} />
</>
) : null}
{steps >= 1 ? (
<>
<UserInformationProfiles />
<Questionbox questions={QUESTIONS.contents1} questionType={'first'} />
<Questionbox questions={QUESTIONS.contents2} questionType={'last'} />
</>
) : null}
{steps === 2 ? (
<>
<div className={'fixed left-0 right-0 px-[20px] py-[12px] bottom-0'}>
<Buttons
text={'답변하기'}
buttonType={'large-filled-button'}
onClick={() => setOpen(true)}
/>
</div>
<BottomSheetModal
open={open}
setClose={() => setOpen(false)}
title={'활동 분야를 선택해주세요.'}
contents={<SheetContents />}
/>
</>
) : null}
{steps >= 4 ? (
<UserDialogbox answers={userAnswer.CONTENTS} isTyped={isTyped} />
) : null}
{steps >= 5 ? (
<>
<UserInformationProfiles />
<Questionbox questions={QUESTIONS.fulltime} questionType={'last'} />
</>
) : null}
{steps === 6 ? (
<FulltimeParttime
onClick={(fulltime) =>
setUserAnswer({ ...userAnswer, FULLTIME: fulltime })
}
setSteps={() => setSteps(7)}
/>
) : null}
{steps >= 7 ? (
<UserDialogbox answers={userAnswer.FULLTIME} isTyped={isTyped} />
) : null}
{steps >= 8 ? (
<>
<UserInformationProfiles />
<Questionbox questions={QUESTIONS.goal} questionType={'last'} />
</>
) : null}
{steps === 9 ? (
<SNSGoal
onClick={(goal) => {
setUserAnswer({ ...userAnswer, GOAL: goal });
setSteps(10);
}}
setIsTyped={() => setIsTyped(true)}
/>
) : null}
{steps >= 10 ? (
<UserDialogbox answers={userAnswer.GOAL} isTyped={isTyped} />
) : null}
{steps >= 11 ? (
<>
<UserInformationProfiles />
<Questionbox questions={QUESTIONS.analyze} questionType={'last'} />
</>
) : null}
{steps === 12 ? (
<div className={'bottom-0 sticky py-[12px] bg-white'}>
{/* onClick에 api연결하기 */}
<Buttons
text={'나만의 성장비법 받기'}
buttonType={'large-filled-button'}
onClick={() => {}}
/>
</div>
) : null}
<div ref={bottomRef} />
</div>
);
}

export default UserInformation;

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState } from 'react';
import Buttons from '@/shared/components/Buttons/Buttons';

interface FulltimeParttime {
onClick: (fulltime: string) => void;
setSteps: () => void;
}

const TIME: string[] = ['전업', '부업'];
Inoansta marked this conversation as resolved.
Show resolved Hide resolved

function FulltimeParttime({ onClick, setSteps }: FulltimeParttime) {
const [clicked, setClicked] = useState<boolean>(true);

return (
<div
className={`${clicked ? 'block' : 'hidden'} bg-white pt-[12px] pb-[30px] fixed left-0 right-0 bottom-0 px-[20px] z-10 flex flex-col gap-[10px]`}
onClick={() => {
setClicked(false);
setSteps();
}}
>
{TIME.map((item) => {
return (
<Buttons
text={item}
onClick={() => onClick(item)}
buttonType={'large-outlined-button'}
key={item}
/>
);
})}
</div>
);
}

export default FulltimeParttime;
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useState } from 'react';
import Buttons from '@/shared/components/Buttons/Buttons';

interface SNSGoal {
onClick: (goal: string) => void;
setIsTyped: () => void;
}

const list: string[] = [
'개인 브랜딩 및 전문성 강화',
'수익화 및 경제적 이익',
'개인적인 취미 및 즐거움',
'직접입력',
];

function SNSGoal({ onClick, setIsTyped }: SNSGoal) {
const [touched, setTouched] = useState<boolean>(true);
const [inputText, setInputText] = useState<string>('');

const submitOnEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
onClick(inputText);
setIsTyped();
}
};

return (
<>
<div
className={`${touched ? 'block' : 'hidden'} sticky bottom-0 bg-white pt-[12px] pb-[30px] flex flex-col gap-[10px]`}
>
{list.map((item) => {
return (
<Buttons
text={item}
onClick={() => {
if (item !== '직접입력') {
onClick(item);
}
setTouched(item !== '직접입력');
}}
Comment on lines +36 to +41
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이벤트 정의 함수가 길어지면 가독성 측면에 안좋아요!
따로 빼서 정의하면 좋을것 같아요

buttonType={'large-outlined-button'}
key={item}
/>
);
})}
</div>
<div
className={`${touched ? 'hidden' : 'block'} fixed left-0 right-0 pt-[12px] pb-[20px] px-[20px] flex justify-center bottom-0`}
>
<input
className={
'block text-body-m w-[335px] h-[48px] border px-[20px] border-primary5 rounded-medium '
}
onChange={(e) => setInputText(e.target.value)}
onKeyDown={submitOnEnter}
/>
</div>
</>
);
}

export default SNSGoal;

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
interface Questionbox {
questions: string;
questionType: 'first' | 'last';
}

const style = {
first: 'rounded-medium',
last: 'rounded-b-medium rounded-tr-medium',
};

function Questionbox({ questions, questionType }: Questionbox) {
return (
<div
className={`text-left mt-[5px] ${questionType === 'last' ? 'mb-[5px]' : null}`}
>
<div
className={`bg-primary1 text-black inline-block px-[20px] py-[10px] ${style[questionType]}`}
>
{questions}
</div>
</div>
);
}

export default Questionbox;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import SparkIcon from '@/assets/svg/sparkIcon.svg?react';

function UserInformationProfiles() {
return (
<div className={'flex flex-row mt-[20px] items-center'}>
<SparkIcon style={{ width: '48px', height: '48px' }} />
<div className={'text-body-m text-black ml-[10px]'}>스파크</div>
</div>
);
}

export default UserInformationProfiles;
Loading
Loading