Skip to content

Commit

Permalink
Merge pull request #54 from depromeet/feat/select-page
Browse files Browse the repository at this point in the history
[Task] 미션등록페이지 구현 (#31)
  • Loading branch information
wade3420 authored Dec 10, 2023
2 parents 8900e9a + 9cbc78b commit 96fdd91
Show file tree
Hide file tree
Showing 22 changed files with 397 additions and 6 deletions.
Binary file added public/images/emoji-book.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/emoji-exercise.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/emoji-laptop.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/emoji-open-book.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/emoji-pen.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/emoji-speech-bubble.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 7 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import Link from 'next/link';
import LogoIcon from '@/app/LogoIcon';
import { css } from '@styled-system/css';
import { flex } from '@styled-system/patterns';

export default function Home() {
return (
<main className={MainWrapperCss}>
<div>
<div className={logoCss}>
<LogoIcon />
<h1 className={MainTitleCss}>하루 10분의 변화를 경험하세요.</h1>
</div>
Expand All @@ -18,6 +19,11 @@ export default function Home() {
);
}

const logoCss = flex({
flexDirection: 'column',
alignItems: 'center',
});

const MainWrapperCss = css({
display: 'flex',
flexDirection: 'column',
Expand Down
24 changes: 24 additions & 0 deletions src/app/select/CheckCircleIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { type SVGProps } from 'react';

/**
* TODO : Icon 컴포넌트로 변경
*/
export default function CheckCircleIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_44_466)" {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0ZM17.4366 10.2365C17.7881 9.88502 17.7881 9.31517 17.4366 8.9637C17.0851 8.61223 16.5153 8.61223 16.1638 8.9637L10.8108 14.3167L7.83659 11.3425C7.48512 10.991 6.91527 10.991 6.5638 11.3425C6.21233 11.6939 6.21233 12.2638 6.5638 12.6153L10.1744 16.2259C10.5259 16.5773 11.0957 16.5773 11.4472 16.2259L17.4366 10.2365Z"
fill="#929DFF"
/>
</g>
<defs>
<clipPath id="clip0_44_466">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>
);
}
21 changes: 21 additions & 0 deletions src/app/select/RadioInputWithImg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Image from 'next/image';
import RadioInput, { type RadioInputProps } from '@/components/RadioInput';
import { css } from '@styled-system/css';

interface RadioInputWithEmojiProps extends RadioInputProps {
imgSrc: string;
label: string;
}

export default function RadioInputWithImg({ imgSrc, label, ...props }: RadioInputWithEmojiProps) {
return (
<RadioInput {...props}>
<Image src={imgSrc} width={36} height={36} alt={label} className={emojiBoxCss} />
{label}
</RadioInput>
);
}

const emojiBoxCss = css({
marginRight: '8px',
});
79 changes: 79 additions & 0 deletions src/app/select/SelectMissionForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import RadioInputWithImg from '@/app/select/RadioInputWithImg';
import { MISSION_CATEGORIES } from '@/app/select/select.constants';
import Button from '@/components/Button';
import { ROUTER } from '@/constants/router';
import { withQueryString } from '@/utils';
import { getObjectValues } from '@/utils/object';
import { css } from '@styled-system/css';
import { flex } from '@styled-system/patterns';

export default function SelectMissionForm() {
const { push } = useRouter();
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);

const handleClick = () => {
if (selectedCategory === null) {
alert('카테고리를 선택해주세요');
return;
}

push(withQueryString(ROUTER.TIMER, { category: selectedCategory }));
};

const handleRadioChange = (value: string) => {
setSelectedCategory(value);
};

return (
<section className={sectionCss}>
<div className={listCss}>
{getObjectValues(MISSION_CATEGORIES).map((category) => (
<RadioInputWithImg
key={category.id}
onChange={handleRadioChange}
imgSrc={category.imgSrc}
label={category.label}
name={'category'}
value={category.id}
/>
))}
</div>
<Button disabled={!selectedCategory} onClick={handleClick} className={nextButtonCss}>
다음
</Button>
</section>
);
}

const nextButtonCss = css({
padding: '16px 24px',
textStyle: 'subtitle2',
color: '#FFFFFF',
width: 'fit',
borderRadius: '30px',
background: '#929DFF',
transition: '0.3s ease',
cursor: 'pointer',
_disabled: {
background: '#E5E5E5',
},
});

const sectionCss = flex({
marginTop: '30px',
width: '100%',
flex: 1,
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'end',
});

const listCss = flex({
width: '100%',
flexDirection: 'column',
gap: '6px',
});
16 changes: 16 additions & 0 deletions src/app/select/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type PropsWithChildren } from 'react';
import { css } from '@styled-system/css';

export default function Layout({ children }: PropsWithChildren) {
return <div className={containerCss}>{children}</div>;
}

const containerCss = css({
maxWidth: '475px',
margin: '0 auto',
minHeight: '100vh',

display: 'flex',
flexDirection: 'column',
flex: 1,
});
49 changes: 49 additions & 0 deletions src/app/select/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import SelectMissionForm from '@/app/select/SelectMissionForm';
import Header from '@/components/Layout/Header';
import { css } from '@styled-system/css';

export default function SelectPage() {
return (
<main className={mainWrapperCss}>
<Header title={'미션 등록'} />
<div className={containerCss}>
<h1 className={mainTitleCss}>
하루 <strong>10분</strong><br />
어떤 일에 투자하고 싶은가요?
</h1>
<SelectMissionForm />
</div>
</main>
);
}
// TODO : 컬러 theme으로 적용
const grey800 = '#333D4B';
const purple = '#8D96F0';

const mainWrapperCss = css({
flex: 1,
display: 'flex',
flexDirection: 'column',

width: '100%',
});

const containerCss = css({
display: 'flex',
flexDirection: 'column',
flex: 1,

padding: '24px 16px',
});

const mainTitleCss = css({
marginTop: '2px',

textStyle: 'title2',

color: grey800,
'& strong': {
color: purple,
textStyle: 'title2',
},
});
32 changes: 32 additions & 0 deletions src/app/select/select.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const MISSION_CATEGORIES = {
exercise: {
id: 'exercise',
label: '운동',
imgSrc: '/images/emoji-exercise.png',
},
study: {
id: 'study',
label: '공부',
imgSrc: '/images/emoji-book.png',
},
reading: {
id: 'reading',
label: '글 읽기',
imgSrc: '/images/emoji-open-book.png',
},
writing: {
id: 'writing',
label: '글 쓰기',
imgSrc: '/images/emoji-pen.png',
},
video: {
id: 'video',
label: '영상 보기 / 팟캐스트 듣기',
imgSrc: '/images/emoji-laptop.png',
},
etc: {
id: 'etc',
label: '기타',
imgSrc: '/images/emoji-speech-bubble.png',
},
} as const;
9 changes: 8 additions & 1 deletion src/app/timer/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
'use client';
import { MISSION_CATEGORIES } from '@/app/select/select.constants';
import TimerView from '@/app/timer/TimerView';
import useTimer from '@/app/timer/useTimer';
import useTimerStatus from '@/app/timer/useTimerStatus';
import Header from '@/components/Layout/Header';
import useSearchParamsTypedValue from '@/hooks/useSearchParamsTypedValue';
import { type ObjectKeys } from '@/utils';
import { css } from '@styled-system/css';

export default function TimerPage() {
const { step, stepLabel, onNextStep } = useTimerStatus();
const { formattedTime } = useTimer(step);

const { searchParams } = useSearchParamsTypedValue<ObjectKeys<typeof MISSION_CATEGORIES>>('category');

const category = MISSION_CATEGORIES[searchParams ?? 'exercise'].label;

const onFinish = () => {
onNextStep('stop');
alert('정말 끝내시겠습니까?');
Expand All @@ -25,7 +32,7 @@ export default function TimerPage() {
<h1 className={titleCss}>{stepLabel.title}</h1>
<p className={descCss}>{stepLabel.desc}</p>

<TimerView category="카테고리" time={formattedTime} isActive={step !== 'stop'} />
<TimerView category={category} time={formattedTime} isActive={step !== 'stop'} />
<div className={css(buttonContainerCss)}>
{step === 'ready' && (
<button type="button" onClick={() => onNextStep('progress')}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {}

const Button: React.FC<ButtonProps> = ({ className, children, ...props }) => {
return (
<button className={`${className}`} {...props} type="button">
<button className={`${className}`} type="button" {...props}>
{children}
</button>
);
Expand Down
18 changes: 16 additions & 2 deletions src/components/Layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
'use client';

import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { flex } from '@/styled-system/patterns';
import { css } from '@styled-system/css';

Expand All @@ -7,10 +10,16 @@ interface Props {
}

function Header({ title }: Props) {
const router = useRouter();

const handleButtonClick = () => {
router.back();
};

return (
<>
<header className={wrapperCss}>
<button type="button">
<button className={buttonCss} type="button" onClick={handleButtonClick}>
<Image src="/assets/icons/left-arrow-icon.svg" alt="Left Arrow Icon" width={20} height={20} />
</button>
<h2 className={headingCss}>{title}</h2>
Expand All @@ -21,7 +30,7 @@ function Header({ title }: Props) {
}

const headerBlankCss = {
height: '42px;',
height: '40px;',
width: '100%',
};

Expand All @@ -34,9 +43,14 @@ const wrapperCss = flex({
zIndex: 100,
});

const buttonCss = css({
cursor: 'pointer',
});

const headingCss = css({
color: '#6B7684',
fontSize: '16px',
lineHeight: '20px',
fontFamily: 'Pretendard',
fontWeight: '600',
});
Expand Down
Loading

0 comments on commit 96fdd91

Please sign in to comment.