Skip to content

Commit

Permalink
feat: 온보딩 페이지
Browse files Browse the repository at this point in the history
  • Loading branch information
yeyounging committed Oct 19, 2024
1 parent ca5b9f2 commit f68c34f
Show file tree
Hide file tree
Showing 38 changed files with 3,189 additions and 9 deletions.
Binary file added public/images/bg-start.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/images/bg-start2.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/images/blue-check.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/images/image.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/images/profile-1-red.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/images/profile-1.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/images/profile-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 16 additions & 4 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,26 @@
@tailwind components;
@tailwind utilities;

:root {
--background: #ffffff;
--foreground: #171717;
html,
body {
height: 100vh;
}

body {
color: var(--foreground);
background: var(--background);
min-height: 100vh;
max-width: 600px;
min-height: 600px;
color: #1a1a25;
overflow: hidden;
}

input[type='number'] {
-moz-appearance: textfield;
appearance: textfield;
}

input[type='number']::-webkit-outer-spin-button,
input[type='number']::-webkit-inner-spin-button {
-webkit-appearance: none;
}
18 changes: 18 additions & 0 deletions src/app/start/components/StartHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Left } from '@/components/Icons'
import { StrictPropsWithChildren } from '@/types'

interface StartHeaderProps extends StrictPropsWithChildren {
onBack: () => void
}

export default function StartHeader({ children, onBack }: StartHeaderProps) {
return (
<div className="flex flex-col h-screen mt-10">
<header className="relative font-semibold flex justify-center items-center py-4 min-h-52">
<Left className="absolute left-20" onClick={onBack} />
<span>조각조각 시작하기</span>
</header>
<main className="flex-grow">{children}</main>
</div>
)
}
60 changes: 60 additions & 0 deletions src/app/start/components/Step1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client'

import { Input } from '@/components/common'
import useUserInfo from '@/hooks/store/useUserInfo'
import Image from 'next/image'
import { useState } from 'react'

interface Step1Props {
setError: (error: boolean) => void
}

export default function Step1({ setError }: Step1Props) {
const { userInfo, setUserInfo } = useUserInfo()
const [username, setUsername] = useState(userInfo.name)
const [errorMessage, setErrorMessage] = useState<string>()

const validateName = (name: string) => {
const regex = /^[가-힣a-zA-Z0-9]{0,6}$/
return regex.test(name)
}

const handleChangeName = (e: React.ChangeEvent<HTMLInputElement>) => {
const newName = e.target.value
setUsername(newName)

if (!validateName(newName)) {
setErrorMessage('닉네임은 한글/영문/숫자를 포함한 6자 이내만 가능해요.')
} else {
setError(false)
setErrorMessage('')
setUserInfo({ ...userInfo, name: newName })
}
}
return (
<div className="px-20">
<h1 className="title">
안녕하세요, 조각조각이에요 :)
<br />
어떻게 불러드릴까요?
</h1>

<Input
success="멋진 이름이에요!"
label="6자 이내의 한글/영문/숫자를 입력해주세요."
value={username}
placeholder="닉네임을 적어주세요."
error={errorMessage}
onChange={handleChangeName}
/>

<Image
className="absolute right-0 bottom-0"
src="/images/bg-start.png"
alt="bg"
width={292}
height={291}
/>
</div>
)
}
94 changes: 94 additions & 0 deletions src/app/start/components/Step2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use client'

import { Input } from '@/components/common'
import CheckboxWithLabel from '@/components/common/CheckBox'
import useUserInfo from '@/hooks/store/useUserInfo'
import Image from 'next/image'
import { ChangeEvent, useState } from 'react'

interface Step2Props {
setError: (error: boolean) => void
}

export default function Step2({ setError }: Step2Props) {
const [errorMessage, setErrorMessage] = useState<string>()
const { userInfo, setUserInfo } = useUserInfo()
const { name, age, gender } = userInfo

const [userAge, setUserAge] = useState<string>(age ? String(age) : '')

const handleGenderChange = (data: string) => {
setUserInfo({ ...userInfo, gender: data })

if (!errorMessage) {
setError(false)
}
}
const handleAgeChange = (e: ChangeEvent<HTMLInputElement>) => {
const { value } = e.target
if (/^\d+$/.test(value) || value === '') {
setUserAge(value)

const regex = /^[0-9]{4}$/

if (regex.test(value)) {
setUserInfo({ ...userInfo, age: value ? Number(value) : undefined })
setErrorMessage('')
if (!gender) {
setError(false)
}
} else {
setError(true)
setErrorMessage('4자리 숫자로 입력해주세요.')
}
}
}

return (
<div className="px-20">
<h1 className="title">
{name} 님의 나이와 성별을 <br />
알려주세요.
</h1>

<h2 className="subtitle">출생년도</h2>
<Input
value={userAge}
placeholder="출생년도를 적어주세요. (예시: 2001)."
onChange={handleAgeChange}
error={errorMessage}
/>

<h2 className="subtitle">성별</h2>
<div className="flex justify-between mt-10">
<CheckboxWithLabel
id="1"
isChecked={gender === 'female'}
label="여성"
onChange={() => handleGenderChange('female')}
/>

<CheckboxWithLabel
id="2"
isChecked={gender === 'male'}
label="남성"
onChange={() => handleGenderChange('male')}
/>
<CheckboxWithLabel
id="3"
isChecked={gender === 'no'}
label="미선택"
onChange={() => handleGenderChange('no')}
/>
</div>

<Image
className="absolute right-0 bottom-0"
src="/images/bg-start2.png"
alt="bg"
width={292}
height={291}
/>
</div>
)
}
77 changes: 77 additions & 0 deletions src/app/start/components/Step3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use client'

import {
Profile1,
Profile2,
Profile3,
Profile4,
Profile5,
Profile6,
Profile7,
SelectProfile,
} from '@/components'
import useUserInfo from '@/hooks/store/useUserInfo'
import { cn } from '@/util'
import { useState } from 'react'

const profiles = [
{ id: '1', Component: Profile1 },
{ id: '2', Component: Profile2 },
{ id: '3', Component: Profile3 },
{ id: '4', Component: Profile4 },
{ id: '5', Component: Profile5 },
{ id: '6', Component: Profile6 },
{ id: '7', Component: Profile7 },
]

export default function Step3() {
const { userInfo, setUserInfo } = useUserInfo()
const [selectedProfile, setSelectedProfile] = useState(userInfo.profileIcon)

const handleProfileSelect = (profile: string) => {
setSelectedProfile(profile)
setUserInfo({ ...userInfo, profileIcon: profile })
}

return (
<div className="h-full px-20 w-full flex flex-col relative">
<h1 className="title !mb-6">어떤 프로필로 함께 하시겠어요?</h1>
<h2 className="text-primary_foundation_50 text-sm font-medium leading-snug">
프로필은 나중에 바꿀 수 있어요
</h2>

<div className="relative w-full flex justify-center items-center mt-150">
{profiles.map(({ id, Component }) => (
<div
key={id}
className={cn(
'absolute transition-opacity duration-300 opacity-0 ',
selectedProfile === id && 'opacity-100',
)}
>
<Component active width={180} height={180} />
</div>
))}
</div>

<div className="grid grid-cols-4 gap-y-22 gap-x-4 w-full justify-items-stretch mt-170">
{profiles.map(({ id, Component }) => (
<button
type="button"
key={id}
onClick={() => handleProfileSelect(id)}
className="relative flex justify-center"
tabIndex={0}
>
{selectedProfile === id && (
<div className="absolute -top-18">
<SelectProfile />
</div>
)}
<Component active={selectedProfile === id} />
</button>
))}
</div>
</div>
)
}
Loading

0 comments on commit f68c34f

Please sign in to comment.