-
Notifications
You must be signed in to change notification settings - Fork 4
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
[온보딩] funnel 이용하여 사용자 정보 수집 로직 #68
Changes from all commits
c884202
8c9fe84
4eb911d
3543afd
cfe9839
30e34b5
796b866
4a78ae3
c8b2eba
d5c7441
375ef8b
7900ec3
40311e6
9aa2183
3610661
cd9d2fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 와 정말 어렵네요.. 구조 파악이 정말 어려웠어요..! 근데 라이브러리 사용보니 저희 온보딩에 진짜 적합한 라이브러리군요..! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Children, isValidElement, PropsWithChildren, ReactElement, useEffect } from 'react'; | ||
import { NonEmptyArray } from '../../../types/utility'; | ||
import { assert } from '../../../../utils/errors'; | ||
|
||
export interface FunnelProps<Steps extends NonEmptyArray<string>> { | ||
steps: Steps; | ||
step: Steps[number]; | ||
children: Array<ReactElement<StepProps<Steps>>> | ReactElement<StepProps<Steps>>; | ||
Comment on lines
+6
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 정말 그냥 의견제시인데 지금 그럼 steps가 스텝을 구별하는 카테고리고 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 맞아요 그래서 레퍼런스 보면 steps를 그냥 일반적인 typescript에서 사용하는 T를 사용해서 구현했더라구요! 가영님이 알려준 방식이 훨씬 가독성이 좋을 것 같습니다!! 저도 긁어오면서 굉장히 헷갈렸거든요 ㅠ |
||
} | ||
|
||
export interface StepProps<Steps extends NonEmptyArray<string>> extends PropsWithChildren { | ||
name: Steps[number]; | ||
onNext?: VoidFunction; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 저는 |
||
} | ||
|
||
export const Funnel = <Steps extends NonEmptyArray<string>>(props: FunnelProps<Steps>) => { | ||
const { steps, step, children } = props; | ||
const validChildren = Children.toArray(children) | ||
.filter(isValidElement<StepProps<Steps>>) | ||
.filter(({ props }) => steps.includes(props.name)); | ||
|
||
const targetStep = validChildren.find((child) => child.props.name === step); | ||
|
||
assert(targetStep != null, `${step} 스텝 컴포넌트를 찾지 못했습니다.`); | ||
|
||
return targetStep; | ||
}; | ||
|
||
export const Step = <T extends NonEmptyArray<string>>({ onNext, children }: StepProps<T>) => { | ||
useEffect(() => { | ||
onNext?.(); | ||
}, [onNext]); | ||
|
||
return children; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,14 @@ import { useState } from 'react'; | |
import Title from '../../common/title/Title'; | ||
import * as S from './Step01.style'; | ||
import { IcCancelCircleFinal } from '../../../assets/svg'; | ||
import BtnNext from '../../common/Button/Next/BtnNext'; | ||
|
||
const NameInput = () => { | ||
interface NameInputProps { | ||
onNext: VoidFunction; | ||
} | ||
|
||
const NameInput = (props: NameInputProps) => { | ||
const { onNext } = props; | ||
const [text, setText] = useState<string>(''); | ||
|
||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
|
@@ -13,7 +19,6 @@ const NameInput = () => { | |
inputValue.length + unicodeChars <= 10 ? setText(inputValue) : e.preventDefault(); | ||
}; | ||
|
||
|
||
const handleBtnClick = () => { | ||
setText(''); | ||
}; | ||
|
@@ -42,6 +47,18 @@ const NameInput = () => { | |
</S.IconField> | ||
</S.Wrapper> | ||
<S.LetterLength>({text.length}/10)</S.LetterLength> | ||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 스타일 컴포넌트를 적용하면 더 가독성이 좋을 것 같아요~! |
||
<BtnNext | ||
type='button' | ||
onClick={onNext} | ||
customStyle={{ | ||
position: 'absolute', | ||
bottom: '0', | ||
}} | ||
> | ||
Comment on lines
+50
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아 인라인 보다 확실히 이런 방법이 좋겠네요. 아무래도 좀 가독성이 떨어져서 지금... |
||
다음 | ||
</BtnNext> | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,5 @@ export const ThumbnailWrapper = styled.div` | |
width: 24rem; | ||
height: 24rem; | ||
margin: 0 auto; | ||
margin-top: 6.1rem; | ||
Comment on lines
8
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. margin 설정이 두 번 들어가서 한 번 확인 부탁드려요! |
||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { useMemo, useState } from 'react'; | ||
import { NonEmptyArray } from '../types/utility'; | ||
import { Funnel, FunnelProps, Step } from '../components/common/Funnel/Funnel'; | ||
|
||
export const useFunnel = <Steps extends NonEmptyArray<string>>( | ||
steps: Steps, | ||
defaultStep: Steps[number], | ||
) => { | ||
const [step, setStep] = useState<Steps[number]>(defaultStep); | ||
|
||
const FunnelComponent = useMemo( | ||
() => | ||
Object.assign( | ||
(props: Omit<FunnelProps<Steps>, 'step' | 'steps'>) => ( | ||
<Funnel<Steps> step={step} steps={steps} {...props} /> | ||
), | ||
{ Step }, | ||
), | ||
[step], | ||
); | ||
Comment on lines
+11
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. useMemo 사용 너무 좋습니다.. Omit 대박이네요..시간이 되면 더 뜯어보고 싶은 코드네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Omit이 일단 간지가 좀 나보여서... ㅋㅋㅋㅋㅋㅋ 저도 라이브러리 뜯어보면서 배운거라 제가 구현했다고 하기에는 좀 그러네요. |
||
|
||
return { Funnel: FunnelComponent, step, setStep }; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,36 @@ | ||
//온보딩 모든 컴포넌트를 funnel로 관리하는 최상위 페이지 | ||
|
||
import styled from 'styled-components'; | ||
import { ONBOARDING_FORM_STEP } from '../core/onboarding'; | ||
import { useFunnel } from '../hooks/useFunnel'; | ||
import NameInput from '../components/onboarding/step01/Step01'; | ||
import ThumbnailInput from '../components/onboarding/step02/Step02'; | ||
import GiftDelivery from '../components/onboarding/step03/Step03'; | ||
import SetTournamentSchedule from '../components/onboarding/step04/Step04'; | ||
import SetTournamentDuration from '../components/onboarding/step05/Step05'; | ||
|
||
const OnBoardingPage = () => { | ||
const { Funnel, setStep } = useFunnel(ONBOARDING_FORM_STEP, ONBOARDING_FORM_STEP[0]); | ||
|
||
return ( | ||
<OnBoardingPageWrapper> | ||
<NameInput /> | ||
<Funnel> | ||
<Funnel.Step name='NAME'> | ||
<NameInput onNext={() => setStep(() => 'THUMBNAIL')} /> | ||
</Funnel.Step> | ||
Comment on lines
+18
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오오 이렇게 쓰는 거군요! 대박 |
||
<Funnel.Step name='THUMBNAIL'> | ||
<ThumbnailInput onNext={() => setStep(() => 'PRESENT')} /> | ||
</Funnel.Step> | ||
<Funnel.Step name='PRESENT'> | ||
<GiftDelivery onNext={() => setStep(() => 'TOURNAMENT_SCHEDULE_REGISTRATION')} /> | ||
</Funnel.Step> | ||
<Funnel.Step name='TOURNAMENT_SCHEDULE_REGISTRATION'> | ||
<SetTournamentSchedule onNext={() => setStep(() => 'TOURNAMENT_PROCEEDING')} /> | ||
</Funnel.Step> | ||
<Funnel.Step name='TOURNAMENT_PROCEEDING'> | ||
<SetTournamentDuration onNext={() => setStep(() => 'GIFT_ROOM_FIX')} /> | ||
</Funnel.Step> | ||
</Funnel> | ||
</OnBoardingPageWrapper> | ||
); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export type NonEmptyArray<T> = readonly [T, ...T[]]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export function assert(condition: unknown, error: Error | string = new Error()): asserts condition { | ||
if (!condition) { | ||
if (typeof error === 'string') { | ||
throw new Error(error); | ||
} else { | ||
throw error; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지금 고민해보니까 Layout에서 전체 모바일뷰 설정해주는 게 좋긴 한 것 같아서 Layout에 min-height를 지정하거나 Layout과 겹치는 속성을 Wrapper에서 제거하는 식으로 main으로 머지 전에 수정하면 좋을 것 같다는 개인적인 생각이 들었어요..!