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

온보딩 구현 #206

Merged
merged 9 commits into from
Oct 20, 2023
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 apps/jurumarble/public/images/VoteOnboardD01.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 apps/jurumarble/public/images/VoteOnboardD02.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 apps/jurumarble/public/images/VoteOnboardD03.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 apps/jurumarble/public/images/VoteOnboardD04.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 apps/jurumarble/public/images/VoteOnboardM01.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 apps/jurumarble/public/images/VoteOnboardM02.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 apps/jurumarble/public/images/VoteOnboardM03.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 apps/jurumarble/public/images/VoteOnboardM04.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/jurumarble/public/images/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ export { default as DrinkCapacityMedium } from './DrinkCapacityMedium.png';
export { default as DrinkCapacityHigh } from './DrinkCapacityHigh.png';
export { default as ImgScroll } from './ImgScroll.png';
export { default as restaurantImg } from './restaurantImg.png';
export { default as Onboarding } from './onboarding.png';
export { default as DesktopOnboarding1 } from './VoteOnboardD01.png';
export { default as DesktopOnboarding2 } from './VoteOnboardD02.png';
export { default as DesktopOnboarding3 } from './VoteOnboardD03.png';
export { default as DesktopOnboarding4 } from './VoteOnboardD04.png';
export { default as MobileOnboarding1 } from './VoteOnboardM01.png';
export { default as MobileOnboarding2 } from './VoteOnboardM02.png';
export { default as MobileOnboarding3 } from './VoteOnboardM03.png';
export { default as MobileOnboarding4 } from './VoteOnboardM04.png';
Binary file added apps/jurumarble/public/images/onboarding.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions apps/jurumarble/src/app/main/components/Banner.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
'use client';

import { useEffect } from 'react';

import Path from 'lib/Path';
import userStorage from 'lib/utils/userStorage';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { mainBanner } from 'public/images';
import styled from 'styled-components';

function Banner() {
const router = useRouter();
useEffect(() => {
if (!userStorage.get() || !!localStorage.getItem('visited_home')) {
return;
}
router.push(Path.ONBOARDING_PAGE);
localStorage.setItem('visited_home', 'false');
}, []);
return (
<Container>
<Image
Expand Down
26 changes: 26 additions & 0 deletions apps/jurumarble/src/app/onboarding/components/BottomButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client';

import { Button } from 'components/button';
import { getClassNames } from 'lib/styles/getClassNames';
import { useRouter } from 'next/navigation';

import styles from '../page.module.css';

const BottomButton = () => {
const router = useRouter();
const cx = getClassNames(styles);
return (
<div className={cx('bottom-wrapper')}>
<Button
width="100%"
height="56px"
variant="primary"
onClick={() => router.push('/')}
>
시작하기
</Button>
</div>
);
};

export default BottomButton;
28 changes: 28 additions & 0 deletions apps/jurumarble/src/app/onboarding/page.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.container {
width: 100%;
display: flex;
justify-content: center;
/* background-color: var(--bg_01); */
padding-bottom: 96px;
}

.container img {
width: 100%;
height: 100%;
object-fit: cover;
}

.img-wrapper {
max-width: 720px;
margin: 0 auto;
background-color: var(--white);
}

.bottom-wrapper {
position: fixed;
bottom: 0;
padding: 20px;
width: 100%;
max-width: 480px;
background-color: var(--white);
}
21 changes: 21 additions & 0 deletions apps/jurumarble/src/app/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getClassNames } from 'lib/styles/getClassNames';
import Image from 'next/image';
import { Onboarding } from 'public/images';

import BottomButton from './components/BottomButton';
import styles from './page.module.css';

const OnboardingPage = () => {
const cx = getClassNames(styles);

return (
<div className={cx('container')}>
<div className={cx('img-wrapper')}>
<Image src={Onboarding} width={375} height={2112} alt="온보딩" />
</div>
<BottomButton />
</div>
);
};

export default OnboardingPage;
276 changes: 276 additions & 0 deletions apps/jurumarble/src/app/vote/components/OnboardingBottomsheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
import { forwardRef, useRef, useState } from 'react';

import { Portal } from 'components/index';
import { transitions } from 'lib/styles';
import Image, { StaticImageData } from 'next/image';
import {
DesktopOnboarding1,
DesktopOnboarding2,
DesktopOnboarding3,
DesktopOnboarding4,
MobileOnboarding1,
MobileOnboarding2,
MobileOnboarding3,
} from 'public/images';
import { SvgIcX } from 'src/assets/icons/components';
import styled, { css } from 'styled-components';

interface CardProps {
title: string;
description: string;
imgSrc: string | StaticImageData;
}

const Card = forwardRef<HTMLDivElement, CardProps>(
({ title, description, imgSrc }, ref) => {
return (
<CardWrapper ref={ref}>
<div className="title">{title}</div>
<div className="description">{description}</div>
<Image className="img" src={imgSrc} alt="img" />
</CardWrapper>
);
},
);

interface Props {
onToggleOnboarding: () => void;
}

const TAB_LIST = [
{ tabName: '후보 확대', id: 'enlarge' },
{ tabName: '후보 선택', id: 'select' },
{ tabName: '투표 이동', id: 'move' },
{ tabName: '자세히 보기', id: 'detail' },
];

const CARD_LIST = [
{
title: '후보를 확대해서 보기',
description: '마우스를 후보에 올리거나 좌우 방향키를 이용하세요.',
imgSrc: DesktopOnboarding1,
mobileImgSrc: MobileOnboarding1,
},
{
title: '투표 후보를 선택하기',
description: '원하는 후보를 클릭하세요.',
imgSrc: DesktopOnboarding2,
mobileImgSrc: MobileOnboarding2,
},
{
title: '자세한 내용을 확인하기',
description: '스크롤을 하거나 상하 방향키를 이용하세요.',
imgSrc: DesktopOnboarding3,
mobileImgSrc: MobileOnboarding3,
},
{
title: '자세히 보기',
description: '더보기 버튼을 클릭해주세요.',
imgSrc: DesktopOnboarding4,
mobileImgSrc: MobileOnboarding1,
},
];

const OnboardingBottomsheet = ({ onToggleOnboarding }: Props) => {
const [chip, setChip] = useState('mobile');

const card1Ref = useRef<HTMLDivElement>(null);
const card2Ref = useRef<HTMLDivElement>(null);
const card3Ref = useRef<HTMLDivElement>(null);
const card4Ref = useRef<HTMLDivElement>(null);

const cardRefs = [card1Ref, card2Ref, card3Ref, card4Ref];

// 탭 클릭시 ref로 이동하는 함수

const handleTabClick = (index: number) => {
cardRefs[index]?.current?.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center',
});
};

return (
<Portal selector="#portal">
<BottomSheet>
<Inner>
<Exit onClick={onToggleOnboarding}>
<SvgIcX />
</Exit>
<Title>투표를 좀 더 재밌게 참여해 볼까요?</Title>
<Description>
투표는 여러가지 조작 방법을 통해 참여할 수 있어요.
</Description>
<TabWrapper>
{TAB_LIST.map(({ id, tabName }, index) => (
<Tab
key={id}
active={id === 'enlarge'}
onClick={() => handleTabClick(index)}
>
{tabName}
</Tab>
))}
</TabWrapper>
<ChipWrapper>
<Chip active={chip === 'mobile'} onClick={() => setChip('mobile')}>
모바일
</Chip>
<Chip
active={chip === 'desktop'}
onClick={() => setChip('desktop')}
>
PC
</Chip>
</ChipWrapper>
{CARD_LIST.map(
({ title, description, imgSrc, mobileImgSrc }, index) => (
<Card
key={title}
title={title}
description={description}
imgSrc={chip === 'mobile' ? imgSrc : mobileImgSrc}
ref={cardRefs[index]}
/>
),
)}
</Inner>
<Background onClick={onToggleOnboarding} />
</BottomSheet>
</Portal>
);
};

const BottomSheet = styled.div`
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 9999;
`;

const Inner = styled.div`
position: absolute;
z-index: 9999;
background-color: white;
bottom: 0;
right: 0;
left: 0;
margin: auto;
width: 100%;
max-width: 720px;
height: 90%;
animation: ${transitions.popInFromBottom} 0.4s ease-in-out;
border-radius: 16px 16px 0px 0px;
overflow-y: scroll;
&::-webkit-scrollbar {
display: none;
}
`;

const Background = styled.div`
display: block;
width: 100%;
height: 100%;
background-color: black;
position: absolute;
left: 0;
top: 0;
opacity: 0.4;
`;

const Title = styled.div`
display: flex;
${({ theme }) => css`
${theme.typography.headline02}
`}
justify-content: flex-start;
padding: 64px 20px 0 20px;
`;

const Description = styled.div`
display: flex;
${({ theme }) => css`
${theme.typography.body02}
color: ${theme.colors.black_02};
`}
padding:12px 20px 16px 20px;
`;
// padding: 26px 20px 20px 20px;
const Exit = styled.div`
position: absolute;
top: 26px;
right: 20px;
width: 24px;
height: 24px;
cursor: pointer;
`;

const TabWrapper = styled.div`
display: flex;
padding: 16px 20px 0 20px;
border-bottom: 1px solid ${({ theme }) => theme.colors.line_01};
`;

const Tab = styled.div<{ active: boolean }>`
padding: 16px 10px;
width: 25%;
text-align: center;
cursor: pointer;
${({ active, theme }) =>
active
? css`
${theme.typography.body01}
color: ${({ theme }) => theme.colors.black_01};
border-bottom: 3px solid ${({ theme }) => theme.colors.black_01};
`
: css`
${theme.typography.body02}
color: ${({ theme }) => theme.colors.black_03};
`}
`;

const ChipWrapper = styled.div`
padding: 28px 20px;
display: flex;
gap: 4px;
`;

const Chip = styled.div<{ active: boolean }>`
padding: 10px;
border-radius: 4px;
cursor: pointer;
${({ theme, active }) => css`
${theme.typography.caption_chip}
color: ${active ? theme.colors.white : theme.colors.black_02};
background-color: ${active ? theme.colors.black_02 : theme.colors.bg_01};
`}
`;

const CardWrapper = styled.div`
padding: 0 20px;
margin-bottom: 42px;
.title {
padding-bottom: 4px;
${({ theme }) => css`
${theme.typography.body01}
`}
}
.description {
${({ theme }) => css`
${theme.typography.body_long03}
padding-bottom: 16px;
`}
}
.img {
width: 100%;
height: 100%;
border-radius: 16px;
}
`;

export default OnboardingBottomsheet;
Loading
Loading