From be61520cdd7dbb2ce153ac666d5dfa1f44c76f6f Mon Sep 17 00:00:00 2001 From: yeyounging <133792082+yeyounging@users.noreply.github.com> Date: Sun, 10 Nov 2024 18:56:51 +0900 Subject: [PATCH] =?UTF-8?q?[feat]=20=ED=99=88=20=ED=99=94=EB=A9=B4=20/=20?= =?UTF-8?q?=EB=B9=A0=EB=A5=B8=EC=8B=9C=EC=9E=91=20ui=20=EB=B0=8F=20api=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 홈화면 ui 및 데이터 페칭 * feat: 홈화면 분기별 ui * feat: 빠른시작 등록, 리스트 * refactor * fix: 라우팅 경로 오류 수정 --- src/api/core.ts | 6 ++- src/app/home/api/api.ts | 8 ++- src/app/home/api/queries.ts | 9 +++- src/app/home/api/type.ts | 20 +++++++- src/app/home/components/NoQuickBox.tsx | 25 ++++++++++ src/app/home/components/NoTimePiece.tsx | 23 +++++++++ src/app/home/components/QuickBox.tsx | 44 +++++++++++++++++ src/app/home/components/TimeCard.tsx | 26 ++++++++++ src/app/home/components/TimePiece.tsx | 19 +++++++ src/app/home/fast/add/page.tsx | 44 ++++++++++------- src/app/home/fast/components/FastCard.tsx | 22 +++++---- src/app/home/fast/components/Fetcher.tsx | 17 +++++-- src/app/home/fast/layout.tsx | 23 +++++++++ src/app/home/fast/page.tsx | 37 +++++++++----- src/app/home/layout.tsx | 6 +-- src/app/home/page.tsx | 60 +++++++---------------- src/app/home/type.ts | 2 +- src/components/Icons/Clock.tsx | 21 ++++++++ src/components/Icons/Cup.tsx | 36 ++++++++++++++ src/components/Icons/Pencil.tsx | 21 ++++++++ src/components/Icons/Plus.tsx | 29 +++++++++++ src/components/Icons/index.tsx | 4 ++ src/components/common/Badge/index.tsx | 2 +- src/components/common/CheckBox/index.tsx | 3 +- src/components/common/index.tsx | 2 + src/types/home.ts | 9 ++++ src/types/index.ts | 1 + tailwind.config.ts | 11 +++++ 28 files changed, 432 insertions(+), 98 deletions(-) create mode 100644 src/app/home/components/NoQuickBox.tsx create mode 100644 src/app/home/components/NoTimePiece.tsx create mode 100644 src/app/home/components/QuickBox.tsx create mode 100644 src/app/home/components/TimeCard.tsx create mode 100644 src/app/home/components/TimePiece.tsx create mode 100644 src/app/home/fast/layout.tsx create mode 100644 src/components/Icons/Clock.tsx create mode 100644 src/components/Icons/Cup.tsx create mode 100644 src/components/Icons/Pencil.tsx create mode 100644 src/components/Icons/Plus.tsx create mode 100644 src/types/home.ts diff --git a/src/api/core.ts b/src/api/core.ts index ec87931..05ad44e 100644 --- a/src/api/core.ts +++ b/src/api/core.ts @@ -22,11 +22,13 @@ axiosInstance.interceptors.request.use( const accessToken = Cookies.get(ACCESS_TOKEN) as string const accessTokenTest = process.env.NEXT_PUBLIC_MASTER_TOKEN + // TODO: 마스터토큰제거 + config.headers.set('Authorization', `Bearer ${accessTokenTest}`) + return config + if (!accessToken) { return config } - config.headers.set('Authorization', `Bearer ${accessTokenTest}`) - return config }, (error: AxiosError) => { throw error diff --git a/src/app/home/api/api.ts b/src/app/home/api/api.ts index 04aec31..8166461 100644 --- a/src/app/home/api/api.ts +++ b/src/app/home/api/api.ts @@ -1,5 +1,11 @@ import { http } from '@/api' -import { QuickStartRequest, QuickStartResponse } from './type' +import { HomeResponse, QuickStartRequest, QuickStartResponse } from './type' + +export const getHomeData = () => { + return http.get({ + url: '/home', + }) +} export const getQuickList = () => { return http.get({ diff --git a/src/app/home/api/queries.ts b/src/app/home/api/queries.ts index a9a0208..909a285 100644 --- a/src/app/home/api/queries.ts +++ b/src/app/home/api/queries.ts @@ -1,7 +1,14 @@ import { useMutation, useSuspenseQuery } from '@tanstack/react-query' -import { getQuickList, postQuickStart } from './api' +import { getHomeData, getQuickList, postQuickStart } from './api' import { QuickStartRequest } from './type' +export const useGetHomeData = () => + useSuspenseQuery({ + queryKey: ['home'], + queryFn: () => getHomeData(), + select: (data) => data.data, + }) + export const useGetQuickList = () => useSuspenseQuery({ queryKey: ['quick-list'], diff --git a/src/app/home/api/type.ts b/src/app/home/api/type.ts index b793cc2..c743ea6 100644 --- a/src/app/home/api/type.ts +++ b/src/app/home/api/type.ts @@ -9,7 +9,7 @@ export interface QuickStart { } export interface QuickStartResponse { - quickStartResponse: QuickStart[] + quickStartResponses: QuickStart[] } export interface QuickStartRequest { @@ -20,3 +20,21 @@ export interface QuickStartRequest { spareTime: number type: 'ONLINE' | 'OFFLINE' | 'ONLINE_AND_OFFLINE' } +export interface HomeResponse { + member: { + id: string + nickname: string + profileImage: string + } + quickStart: QuickStart + totalSavedTime: number + activities: { + id: number + keyword: { + category: string + image: string + } + title: string + savedTime: number + }[] +} diff --git a/src/app/home/components/NoQuickBox.tsx b/src/app/home/components/NoQuickBox.tsx new file mode 100644 index 0000000..907bf23 --- /dev/null +++ b/src/app/home/components/NoQuickBox.tsx @@ -0,0 +1,25 @@ +import { Button } from '@/components' +import { useRouter } from 'next/navigation' + +export default function NoQuickBox() { + const { push } = useRouter() + + return ( + <> +

+ 아직 빠른 시작이 등록되지 않았어요. +

+

+ 나만의 반복되는 자투리 시간을 저장하고 +
+ 모을 시간 조각을 빠르게 추천받아보세요. +

+ + + ) +} diff --git a/src/app/home/components/NoTimePiece.tsx b/src/app/home/components/NoTimePiece.tsx new file mode 100644 index 0000000..8515062 --- /dev/null +++ b/src/app/home/components/NoTimePiece.tsx @@ -0,0 +1,23 @@ +import Div from '@/components/common/Div' +import Image from 'next/image' + +export default function NoTimePiece() { + return ( +
+ + 아직 자투리 시간에 +
모은 시간 조각이 없어요! +
+ + 시간 조각이 뭔가요? + + home-img +
+ ) +} diff --git a/src/app/home/components/QuickBox.tsx b/src/app/home/components/QuickBox.tsx new file mode 100644 index 0000000..e749089 --- /dev/null +++ b/src/app/home/components/QuickBox.tsx @@ -0,0 +1,44 @@ +import { Clock, Right, Div, Badge } from '@/components' +import { useRouter } from 'next/navigation' +import { ActiveTypeMap } from '@/types' +import { useHomeContext } from '../fast/components/Fetcher' + +export function QuickBox() { + const { push } = useRouter() + const { name, hour, meridiem, minute, type, spareTime } = + useHomeContext().quickStart + + return ( + <> +
+
+ +

가장 가까운 자투리 시간

+
+ +
+ +
+
+

{name}

+
+ {spareTime}분 + + {meridiem} + {hour}시{minute}분 + + {ActiveTypeMap[type]} +
+
+ {/* TODO: 추천경로 */} + push('/home/fast')} /> +
+ + ) +} diff --git a/src/app/home/components/TimeCard.tsx b/src/app/home/components/TimeCard.tsx new file mode 100644 index 0000000..adf01e5 --- /dev/null +++ b/src/app/home/components/TimeCard.tsx @@ -0,0 +1,26 @@ +import Badge from '@/components/common/Badge' +import Div from '@/components/common/Div' +import Cup from '@/components/Icons/Cup' + +interface TimeCardProps { + time: number + category: string + title: string +} + +export default function TimeCard({ time, category, title }: TimeCardProps) { + return ( +
+
+ +
+ +{time}분 +
+
+
+ {category}의 조각 +

{title}

+
+
+ ) +} diff --git a/src/app/home/components/TimePiece.tsx b/src/app/home/components/TimePiece.tsx new file mode 100644 index 0000000..3666bad --- /dev/null +++ b/src/app/home/components/TimePiece.tsx @@ -0,0 +1,19 @@ +import TimeCard from './TimeCard' +import { useHomeContext } from '../fast/components/Fetcher' + +export default function TimePiece() { + const { activities } = useHomeContext() + + return ( +
+ {activities.map(({ id, savedTime, title, keyword }) => ( + + ))} +
+ ) +} diff --git a/src/app/home/fast/add/page.tsx b/src/app/home/fast/add/page.tsx index 5d366b7..abc9c8c 100644 --- a/src/app/home/fast/add/page.tsx +++ b/src/app/home/fast/add/page.tsx @@ -1,9 +1,8 @@ 'use client' -import { Button, HeaderWithBack, Input, Toggle } from '@/components' +import { Button, HeaderWithBack, Input, Toggle, Div } from '@/components' import { useState, useEffect } from 'react' import '@/app/start/start.css' -import Div from '@/components/common/Div' import CheckboxWithLabel from '@/components/common/CheckBox' import { useRouter } from 'next/navigation' import { usePostQuickStart } from '../../api/queries' @@ -85,22 +84,31 @@ export default function FastPage() { ]) const handleSubmit = (): void => { - mutate({ - name, - hour: parseInt(hour, 10), - minute: parseInt(minute, 10), - spareTime: parseInt(extraTime, 10), - meridiem: time, - type: (() => { - if (isOnline && isOffline) { - return 'ONLINE_AND_OFFLINE' - } - if (isOnline) { - return 'ONLINE' - } - return 'OFFLINE' - })(), - }) + mutate( + { + name, + hour: parseInt(hour, 10), + minute: parseInt(minute, 10), + spareTime: parseInt(extraTime, 10), + meridiem: time, + type: (() => { + if (isOnline && isOffline) { + return 'ONLINE_AND_OFFLINE' + } + if (isOnline) { + return 'ONLINE' + } + return 'OFFLINE' + })(), + }, + { + onSuccess: () => { + // TODO: toast 구현 + alert('빠른 시작이 등록되었습니다.') + router.push('/home/fast') + }, + }, + ) } return ( diff --git a/src/app/home/fast/components/FastCard.tsx b/src/app/home/fast/components/FastCard.tsx index 978de99..be2842f 100644 --- a/src/app/home/fast/components/FastCard.tsx +++ b/src/app/home/fast/components/FastCard.tsx @@ -1,8 +1,10 @@ -import Badge from '@/components/common/Badge' -import { Right } from '@/components' +import { Badge, Pencil, Right } from '@/components' +import { useRouter } from 'next/navigation' +import { ActiveTypeMap } from '@/types' import { QuickStart } from '../../api/type' export default function FastCard({ + id, name, hour, minute, @@ -10,21 +12,23 @@ export default function FastCard({ meridiem, type, }: QuickStart) { + const { push } = useRouter() return ( - + ) } diff --git a/src/app/home/fast/components/Fetcher.tsx b/src/app/home/fast/components/Fetcher.tsx index 4a9f90e..25b1748 100644 --- a/src/app/home/fast/components/Fetcher.tsx +++ b/src/app/home/fast/components/Fetcher.tsx @@ -2,17 +2,24 @@ import { generateContext } from '@/react-utils' import { StrictPropsWithChildren } from '@/types' -import { QuickStartResponse } from '../../api/type' -import { useGetQuickList } from '../../api/queries' +import { HomeResponse, QuickStartResponse } from '../../api/type' +import { useGetHomeData, useGetQuickList } from '../../api/queries' + +export const [HomeProvider, useHomeContext] = generateContext({ + name: 'homeData', +}) export const [QuickStartProvider, useQuickStartContext] = generateContext({ name: 'quickStartList', }) -export default function QuickStartFetcher({ - children, -}: StrictPropsWithChildren) { +export function HomeFetcher({ children }: StrictPropsWithChildren) { + const { data } = useGetHomeData() + return {children} +} + +export function QuickStartFetcher({ children }: StrictPropsWithChildren) { const { data } = useGetQuickList() return {children} diff --git a/src/app/home/fast/layout.tsx b/src/app/home/fast/layout.tsx new file mode 100644 index 0000000..9b1edcd --- /dev/null +++ b/src/app/home/fast/layout.tsx @@ -0,0 +1,23 @@ +import { AsyncBoundaryWithQuery } from '@/react-utils' +import { StrictPropsWithChildren } from '@/types' +import type { Metadata } from 'next' +import { QuickStartFetcher } from './components/Fetcher' + +export const metadata: Metadata = { + title: '나의 시간조각을 모아, 조각조각', + description: '자투리 시간 앱', +} + +export default function QuickStartLayout({ + children, +}: StrictPropsWithChildren) { + return ( + // TODO: fallback 구현 + Loading...} + errorFallback={<>error..} + > + {children} + + ) +} diff --git a/src/app/home/fast/page.tsx b/src/app/home/fast/page.tsx index 2f98e55..9f92271 100644 --- a/src/app/home/fast/page.tsx +++ b/src/app/home/fast/page.tsx @@ -1,27 +1,38 @@ 'use client' -import { HeaderWithBack } from '@/components' -import Div from '@/components/common/Div' +import { Button, HeaderWithBack, Div, Plus } from '@/components' import { useRouter } from 'next/navigation' import FastCard from './components/FastCard' +import { useQuickStartContext } from './components/Fetcher' export default function FastPage() { const router = useRouter() + const { quickStartResponses } = useQuickStartContext() return ( router.back()}> -
-
- -
+
+ {quickStartResponses.map( + ({ id, meridiem, type, name, hour, minute, spareTime }) => ( + + ), + )}
+ ) } diff --git a/src/app/home/layout.tsx b/src/app/home/layout.tsx index 3bfead9..f224ae7 100644 --- a/src/app/home/layout.tsx +++ b/src/app/home/layout.tsx @@ -1,7 +1,7 @@ import { AsyncBoundaryWithQuery } from '@/react-utils' import { StrictPropsWithChildren } from '@/types' import type { Metadata } from 'next' -import QuickStartFetcher from './fast/components/Fetcher' +import { HomeFetcher } from './fast/components/Fetcher' export const metadata: Metadata = { title: '나의 시간조각을 모아, 조각조각', @@ -12,10 +12,10 @@ export default function HomeLayout({ children }: StrictPropsWithChildren) { return ( // TODO: fallback 구현 Loading...} + pendingFallback={<>Loading...} errorFallback={<>error..} > - {children} + {children} ) } diff --git a/src/app/home/page.tsx b/src/app/home/page.tsx index 8877007..d436097 100644 --- a/src/app/home/page.tsx +++ b/src/app/home/page.tsx @@ -1,12 +1,14 @@ 'use client' -import { Button, Category, HomeHeader, House, Right } from '@/components' -import Div from '@/components/common/Div' -import Image from 'next/image' -import { useRouter } from 'next/navigation' +import { Button, Category, HomeHeader, House, Div, Right } from '@/components' +import { useHomeContext } from './fast/components/Fetcher' +import { QuickBox } from './components/QuickBox' +import NoQuickBox from './components/NoQuickBox' +import NoTimePiece from './components/NoTimePiece' +import TimePiece from './components/TimePiece' export default function Home() { - const { push } = useRouter() + const { quickStart, totalSavedTime, activities } = useHomeContext() return ( @@ -15,28 +17,15 @@ export default function Home() {

고먕님,
지금 시간 조각을 모아볼까요?

- +
-

- 아직 빠른 시작이 등록되지 않았어요. -

-

- 나만의 반복되는 자투리 시간을 저장하고 -
- 모을 시간 조각을 빠르게 추천받아보세요. -

- + {quickStart ? : }
@@ -44,26 +33,15 @@ export default function Home() {

오늘 모은 시간 조각

- 오늘은 0분의 - 시간조각을 모았어요! + 오늘은{' '} + + {totalSavedTime}분 + + 의 시간조각을 모았어요!

-
- - 아직 자투리 시간에 -
모은 시간 조각이 없어요! -
- - 시간 조각이 뭔가요? - - home-img -
+ + {activities.length ? : }
diff --git a/src/app/home/type.ts b/src/app/home/type.ts index 0ca564f..b045f0e 100644 --- a/src/app/home/type.ts +++ b/src/app/home/type.ts @@ -1,4 +1,4 @@ -export interface IFast { +export interface QuickItem { title: string hour: number minute: number diff --git a/src/components/Icons/Clock.tsx b/src/components/Icons/Clock.tsx new file mode 100644 index 0000000..e5a6dfe --- /dev/null +++ b/src/components/Icons/Clock.tsx @@ -0,0 +1,21 @@ +import { SVGProps } from 'react' + +export default function Category({ ...props }: SVGProps) { + return ( + + + + ) +} diff --git a/src/components/Icons/Cup.tsx b/src/components/Icons/Cup.tsx new file mode 100644 index 0000000..020e882 --- /dev/null +++ b/src/components/Icons/Cup.tsx @@ -0,0 +1,36 @@ +import { SVGProps } from 'react' + +export default function Cup({ ...props }: SVGProps) { + return ( + + + + + + + + + + ) +} diff --git a/src/components/Icons/Pencil.tsx b/src/components/Icons/Pencil.tsx new file mode 100644 index 0000000..5693e55 --- /dev/null +++ b/src/components/Icons/Pencil.tsx @@ -0,0 +1,21 @@ +import { SVGProps } from 'react' + +export default function Caution({ ...props }: SVGProps) { + return ( + + + + ) +} diff --git a/src/components/Icons/Plus.tsx b/src/components/Icons/Plus.tsx new file mode 100644 index 0000000..89a27c6 --- /dev/null +++ b/src/components/Icons/Plus.tsx @@ -0,0 +1,29 @@ +import { SVGProps } from 'react' + +export default function Plus({ ...props }: SVGProps) { + return ( + + + + + ) +} diff --git a/src/components/Icons/index.tsx b/src/components/Icons/index.tsx index 025da5b..8f54123 100644 --- a/src/components/Icons/index.tsx +++ b/src/components/Icons/index.tsx @@ -13,3 +13,7 @@ export { default as Logo } from './Logo' export { default as Right } from './Right' export { default as House } from './House' export { default as Category } from './Category' +export { default as Clock } from './Clock' +export { default as Cup } from './Cup' +export { default as Plus } from './Plus' +export { default as Pencil } from './Pencil' diff --git a/src/components/common/Badge/index.tsx b/src/components/common/Badge/index.tsx index a9943b8..0f4e128 100644 --- a/src/components/common/Badge/index.tsx +++ b/src/components/common/Badge/index.tsx @@ -9,7 +9,7 @@ export default function Badge({ className, children }: IBadge) { return (
diff --git a/src/components/common/CheckBox/index.tsx b/src/components/common/CheckBox/index.tsx index 9667a52..93784a3 100644 --- a/src/components/common/CheckBox/index.tsx +++ b/src/components/common/CheckBox/index.tsx @@ -32,8 +32,7 @@ export default function CheckboxWithLabel({ isChecked && 'bg-black', )} > - {!isChecked && } - {isChecked && } + {label} diff --git a/src/components/common/index.tsx b/src/components/common/index.tsx index 7a1ce29..7547947 100644 --- a/src/components/common/index.tsx +++ b/src/components/common/index.tsx @@ -2,3 +2,5 @@ export { default as Input } from './Input' export { default as Button } from './Button' export { default as If } from './If' export { default as Toggle } from './Toggle' +export { default as Badge } from './Badge' +export { default as Div } from './Div' diff --git a/src/types/home.ts b/src/types/home.ts new file mode 100644 index 0000000..b48b2f0 --- /dev/null +++ b/src/types/home.ts @@ -0,0 +1,9 @@ +export const ActiveTypeMap = { + ONLINE: '온라인', + OFFLINE: '오프라인', + ONLINE_AND_OFFLINE: '온라인,오프라인', +} + +export function convertActiveType(type: keyof typeof ActiveTypeMap) { + return ActiveTypeMap[type] +} diff --git a/src/types/index.ts b/src/types/index.ts index 4b1062c..5946776 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,2 +1,3 @@ export * from './react' export * from './className' +export * from './home' diff --git a/tailwind.config.ts b/tailwind.config.ts index e8abaae..895bc60 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -33,9 +33,20 @@ const config: Config = { system_blue: '#528DFF', accent_100: '#FF4F38', accent: { + 5: ' #FFF6F5', 10: '#FFEEEC', + 20: '#FFDCD7', 100: '#FF4F38', }, + primay_foundation: { + 100: '#1A1A25', + 40: '#A3A3A8', + 10: '#E9E9EA', + 50: '#8D8D92', + 60: '#76767C', + 30: '#BBBBBE', + 5: '#F3F3F4', + }, primary_foundation_100: '#1A1A25', primary_foundation_40: '#A3A3A8', primary_foundation_10: '#E9E9EA',