From f717a0b4d3892bd1c37af1f53b39c73539b0b8b3 Mon Sep 17 00:00:00 2001 From: jsh1147 Date: Sat, 2 Nov 2024 20:07:00 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20useQuery=20=ED=8C=A8=EC=B9=AD?= =?UTF-8?q?=20=EA=B6=8C=ED=95=9C=20=EC=9D=B4=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 훅이 갖고 있던 패칭 로직을 컴포넌트에게 넘김 --- src/components/pages/boards/BestItems.tsx | 5 +++-- src/hooks/useQuery.ts | 6 +----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/pages/boards/BestItems.tsx b/src/components/pages/boards/BestItems.tsx index 38f4c7f4..c2338c69 100644 --- a/src/components/pages/boards/BestItems.tsx +++ b/src/components/pages/boards/BestItems.tsx @@ -15,7 +15,7 @@ export default function BestItems() { pageSize: pageSizeTable[media], orderBy: "like", }); - const { isLoading, error, data } = useQuery< + const { isLoading, error, data, update } = useQuery< GetArticlesParams, GetArticlesRes >(getArticles, paramObj); @@ -25,7 +25,8 @@ export default function BestItems() { ...prevObj, pageSize: pageSizeTable[media], })); - }, [media]); + update(); + }, [media, update]); return (
diff --git a/src/hooks/useQuery.ts b/src/hooks/useQuery.ts index d506cdd1..c2188bd4 100644 --- a/src/hooks/useQuery.ts +++ b/src/hooks/useQuery.ts @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect } from "react"; +import { useState, useCallback } from "react"; export function useQuery( fetchFunc: (paramObj: Params) => Promise, @@ -29,9 +29,5 @@ export function useQuery( const update = () => wrappedFunc(paramObj); - useEffect(() => { - wrappedFunc(paramObj); - }, [wrappedFunc, paramObj]); - return { isLoading, error, data, update }; } From f652495b04a7df3565a68da72f093bf2abdf9714 Mon Sep 17 00:00:00 2001 From: jsh1147 Date: Sat, 2 Nov 2024 21:28:28 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=EB=AF=B8=EB=94=94=EC=96=B4=20?= =?UTF-8?q?=EC=A0=84=EC=97=AD=EC=83=81=ED=83=9C=ED=99=94=20=EB=B0=8F=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=ED=8C=A8=EC=B9=AD=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useMediaQuery 훅을 useMedia & MediaContext로 전역화 - 미디어 결정 이후 데이터 패칭이 발생하도록 수정 --- src/components/pages/boards/BestItems.tsx | 32 ++++++++++---------- src/hooks/useMedia.ts | 7 +++++ src/hooks/useMediaQuery.ts | 28 ------------------ src/hooks/useQuery.ts | 9 ++---- src/pages/_app.tsx | 15 ++++++---- src/store/MediaContext.tsx | 36 +++++++++++++++++++++++ 6 files changed, 72 insertions(+), 55 deletions(-) create mode 100644 src/hooks/useMedia.ts delete mode 100644 src/hooks/useMediaQuery.ts create mode 100644 src/store/MediaContext.tsx diff --git a/src/components/pages/boards/BestItems.tsx b/src/components/pages/boards/BestItems.tsx index c2338c69..3de703c0 100644 --- a/src/components/pages/boards/BestItems.tsx +++ b/src/components/pages/boards/BestItems.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { useMediaQuery } from "@/hooks/useMediaQuery"; +import { useMedia } from "@/hooks/useMedia"; import { getArticles } from "@/apis/apis"; import { GetArticlesParams, GetArticlesRes } from "@/apis/apis.type"; import { useQuery } from "@/hooks/useQuery"; @@ -9,24 +9,26 @@ import styles from "./BestItems.module.css"; const pageSizeTable = { PC: 3, TABLET: 2, MOBILE: 1 }; export default function BestItems() { - const media = useMediaQuery(); - const [paramObj, setParamObj] = useState({ - page: 1, - pageSize: pageSizeTable[media], - orderBy: "like", - }); - const { isLoading, error, data, update } = useQuery< + const media = useMedia(); + const [paramObj, setParamObj] = useState(); + const { isLoading, error, data, query } = useQuery< GetArticlesParams, GetArticlesRes - >(getArticles, paramObj); + >(getArticles); useEffect(() => { - setParamObj((prevObj) => ({ - ...prevObj, - pageSize: pageSizeTable[media], - })); - update(); - }, [media, update]); + if (!media) return; + setParamObj((prevObj) => + !prevObj + ? { page: 1, pageSize: pageSizeTable[media], orderBy: "like" } + : { ...prevObj, pageSize: pageSizeTable[media] } + ); + }, [media]); + + useEffect(() => { + if (!paramObj) return; + query(paramObj); + }, [query, paramObj]); return (
diff --git a/src/hooks/useMedia.ts b/src/hooks/useMedia.ts new file mode 100644 index 00000000..2d7b3459 --- /dev/null +++ b/src/hooks/useMedia.ts @@ -0,0 +1,7 @@ +import { useContext } from "react"; +import { MediaContext } from "@/store/MediaContext"; + +export function useMedia() { + const media = useContext(MediaContext); + return media; +} diff --git a/src/hooks/useMediaQuery.ts b/src/hooks/useMediaQuery.ts deleted file mode 100644 index 38c9205c..00000000 --- a/src/hooks/useMediaQuery.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useState, useEffect } from "react"; - -export type MediaType = "PC" | "TABLET" | "MOBILE"; - -function checkMedia(width: number): MediaType { - if (width >= 1200) return "PC"; - if (width >= 768) return "TABLET"; - return "MOBILE"; -} - -export function useMediaQuery() { - const [media, setMedia] = useState("PC"); - - useEffect(() => { - setMedia(checkMedia(window.innerWidth)); - - const handleWindowResize = () => { - setMedia(checkMedia(window.innerWidth)); - }; - - window.addEventListener("resize", handleWindowResize); - return () => { - window.removeEventListener("resize", handleWindowResize); - }; - }, []); - - return media; -} diff --git a/src/hooks/useQuery.ts b/src/hooks/useQuery.ts index c2188bd4..85ec8730 100644 --- a/src/hooks/useQuery.ts +++ b/src/hooks/useQuery.ts @@ -1,14 +1,13 @@ import { useState, useCallback } from "react"; export function useQuery( - fetchFunc: (paramObj: Params) => Promise, - paramObj: Params + fetchFunc: (paramObj: Params) => Promise ) { const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [data, setData] = useState(null); - const wrappedFunc = useCallback( + const query = useCallback( async (paramObj: Params) => { try { setIsLoading(true); @@ -27,7 +26,5 @@ export function useQuery( [fetchFunc] ); - const update = () => wrappedFunc(paramObj); - - return { isLoading, error, data, update }; + return { isLoading, error, data, query }; } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index f0671c05..6b3d2cd6 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,6 +1,7 @@ import type { AppProps } from "next/app"; import localFont from "next/font/local"; import Head from "next/head"; +import { MediaProvider } from "@/store/MediaContext"; import Layout from "@/components/layout/Layout"; import "@/styles/reset.css"; import "@/styles/variable.css"; @@ -23,13 +24,15 @@ export default function App({ Component, pageProps }: MyAppProps) { - {Component.isNotLayout ? ( - - ) : ( - + + {Component.isNotLayout ? ( - - )} + ) : ( + + + + )} + ); } diff --git a/src/store/MediaContext.tsx b/src/store/MediaContext.tsx new file mode 100644 index 00000000..f27717d2 --- /dev/null +++ b/src/store/MediaContext.tsx @@ -0,0 +1,36 @@ +import { createContext, useState, useEffect, ReactNode } from "react"; + +export type MediaType = "PC" | "TABLET" | "MOBILE"; + +export const MediaContext = createContext(undefined); + +interface MediaProviderProps { + children: ReactNode; +} + +export function MediaProvider({ children }: MediaProviderProps) { + const [media, setMedia] = useState(); + + useEffect(() => { + const checkMedia = (width: number): MediaType => { + if (width >= 1200) return "PC"; + if (width >= 768) return "TABLET"; + return "MOBILE"; + }; + + const handleWindowResize = () => { + setMedia(checkMedia(window.innerWidth)); + }; + + handleWindowResize(); + + window.addEventListener("resize", handleWindowResize); + return () => { + window.removeEventListener("resize", handleWindowResize); + }; + }, []); + + return ( + {children} + ); +} From 2e846655a8315b143b61e563777162c84ba0a00b Mon Sep 17 00:00:00 2001 From: jsh1147 Date: Sat, 2 Nov 2024 23:30:18 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=EB=B2=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20CSS=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자유게시판 베스트 게시글 CSS 구현 --- public/icons/medal.svg | 4 ++ src/components/pages/boards/BestItems.tsx | 4 +- .../boards/componnets/BestItem.module.css | 54 +++++++++++++++---- .../pages/boards/componnets/BestItem.tsx | 50 +++++++++-------- src/styles/variable.css | 3 ++ 5 files changed, 80 insertions(+), 35 deletions(-) create mode 100644 public/icons/medal.svg diff --git a/public/icons/medal.svg b/public/icons/medal.svg new file mode 100644 index 00000000..d650c401 --- /dev/null +++ b/public/icons/medal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/pages/boards/BestItems.tsx b/src/components/pages/boards/BestItems.tsx index 3de703c0..f44e3c17 100644 --- a/src/components/pages/boards/BestItems.tsx +++ b/src/components/pages/boards/BestItems.tsx @@ -37,8 +37,8 @@ export default function BestItems() { {!isLoading && !error && data && (
- {data.list.map((item) => ( - + {data.list.map((article) => ( + ))}
)} diff --git a/src/components/pages/boards/componnets/BestItem.module.css b/src/components/pages/boards/componnets/BestItem.module.css index 85e3a17f..aea1fb14 100644 --- a/src/components/pages/boards/componnets/BestItem.module.css +++ b/src/components/pages/boards/componnets/BestItem.module.css @@ -2,6 +2,39 @@ display: flex; flex-direction: column; gap: 16px; + padding: 0 24px 16px; + border-radius: 8px; + background-color: var(--gray-50); +} + +.tag { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + width: 100px; + height: 30px; + border-radius: 0 0 15px 15px; + background-color: var(--blue-100); + font-size: var(--size-lg); + font-weight: var(--semibold); + color: white; +} + +.content { + display: flex; + align-items: center; + justify-content: space-between; +} + +.title { + font-size: var(--size-xl); + font-weight: var(--semibold); +} +@media (max-width: 1199px) { + .title { + font-size: var(--size-2gl); + } } .imageWrapper { @@ -16,29 +49,30 @@ object-fit: contain; } -.content { +.info { display: flex; - flex-direction: column; - gap: 6px; -} - -.title { + align-items: center; + gap: 8px; font-size: var(--size-md); } -.price { - font-size: var(--size-lg); - font-weight: bold; +.nickname { + color: var(--gray-600); } .like { + flex-grow: 1; display: flex; align-items: center; gap: 4px; - font-size: var(--size-xs); + color: var(--gray-500); } .likeIcon { width: 16px; height: 16px; } + +.date { + color: var(--gray-400); +} diff --git a/src/components/pages/boards/componnets/BestItem.tsx b/src/components/pages/boards/componnets/BestItem.tsx index 1a82c1a8..98e286a9 100644 --- a/src/components/pages/boards/componnets/BestItem.tsx +++ b/src/components/pages/boards/componnets/BestItem.tsx @@ -1,39 +1,43 @@ import Link from "next/link"; import Image from "next/image"; +import { ArticleProps } from "@/apis/apis.type"; +import medalIcon from "#/icons/medal.svg"; import heartIcon from "#/icons/heart_inactive.svg"; import styles from "./BestItem.module.css"; -import { ArticleProps } from "@/apis/apis.type"; interface ItemProps { - data: ArticleProps; + article: ArticleProps; } -export default function BestItem({ data }: ItemProps) { - const { - id, - title, - writer: { nickname }, - updatedAt, - image, - likeCount, - } = data; +export default function BestItem({ article }: ItemProps) { + const date = new Date(article.updatedAt).toLocaleDateString(); + return ( - -
- {"이미지"} + +
+ + Best
- {title} - {updatedAt} +

{article.title}

+
+ {article.image ? ( + {"이미지"} + ) : undefined} +
+
+
+ {article.writer.nickname}
- 좋아요 수 - {likeCount} + + {article.likeCount}
+ {date}
); diff --git a/src/styles/variable.css b/src/styles/variable.css index 5da0cdb9..78c29188 100644 --- a/src/styles/variable.css +++ b/src/styles/variable.css @@ -44,4 +44,7 @@ --line-md: 24px; --line-sm: 22px; --line-xs: 18px; + + /* Font - Weight */ + --semibold: 600; }