diff --git a/components/Heart/Heart.tsx b/components/Heart/Heart.tsx new file mode 100644 index 0000000..14cd890 --- /dev/null +++ b/components/Heart/Heart.tsx @@ -0,0 +1,46 @@ +import { useState } from 'react'; + +import HeartIcon from './HeartIcon'; + +interface HeartProps { + initialCount: number; + onClick?: () => void; +} + +/** + * Heart count component + * @param {number} initialCount - 초기 카운트 수 + * @param {function} onClick - 클릭 시 동작 할 이벤트(옵션) + * @example console.log('클릭')} /> + */ +export default function Heart({ initialCount, onClick }: HeartProps) { + const [isClicked, setIsClicked] = useState(false); + const [count, setCount] = useState(initialCount); + + const handleClick = () => { + if (onClick) { + onClick(); + setIsClicked((prev) => !prev); + setCount((prevCount) => (isClicked ? prevCount - 1 : prevCount + 1)); + } + }; + + const clickStyles = { + icon: isClicked ? 'var(--red-100)' : 'var(--gray-400)', + text: isClicked && 'text-red-100', + }; + + const Wrapper = onClick ? 'button' : 'div'; + + return ( + + + + {count} + + + ); +} diff --git a/components/Heart/HeartIcon.tsx b/components/Heart/HeartIcon.tsx new file mode 100644 index 0000000..a1d461f --- /dev/null +++ b/components/Heart/HeartIcon.tsx @@ -0,0 +1,19 @@ +/** + * Heart icon + * @param {string} fill - 색상 + */ +export default function HeartIcon({ fill = '#8F95B2' }: { fill?: string }) { + return ( + + + + ); +} diff --git a/next.config.ts b/next.config.ts index 0748d34..fbfc619 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,6 +3,9 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { /* config options here */ reactStrictMode: true, + images: { + domains: ['via.placeholder.com'], // 허용할 이미지 도메인 추가 + }, }; export default nextConfig; diff --git a/pages/boards/components/BoardCard.tsx b/pages/boards/components/BoardCard.tsx new file mode 100644 index 0000000..5e320da --- /dev/null +++ b/pages/boards/components/BoardCard.tsx @@ -0,0 +1,57 @@ +import Image from 'next/image'; +import Link from 'next/link'; + +import Heart from '@/components/Heart/Heart'; +import dateConversion from '@/utils/dateConversion'; + +interface BoardCardProps { + id: number; + image: string; + title: string; + name: string; + updatedAt: string; + likeCount: number; +} + +/** + * 게시글 카드 컴포넌트 + * @param {number} id - 게시글 id + * @param {string} image - 게시글 이미지 + * @param {string} title - 게시글 제목 + * @param {string} name - 유저 이름 + * @param {string} updatedAt - 게시글 수정일 + * @param {number} likeCount - 게시글 좋아요 수 + */ +export default function BoardCard({ + id, + image, + title, + name, + updatedAt, + likeCount, +}: BoardCardProps) { + return ( + +
+ {`${title} +
+
+

+ {title} +

+
+

{name}

+ {dateConversion(updatedAt)} + +
+
+ + ); +} diff --git a/pages/boards/index.tsx b/pages/boards/index.tsx new file mode 100644 index 0000000..5756386 --- /dev/null +++ b/pages/boards/index.tsx @@ -0,0 +1,3 @@ +export default function Boards() { + return

Boards

; +} diff --git a/pages/test/boardCard.tsx b/pages/test/boardCard.tsx new file mode 100644 index 0000000..fa20a6e --- /dev/null +++ b/pages/test/boardCard.tsx @@ -0,0 +1,50 @@ +import BoardCard from '../boards/components/BoardCard'; + +const data_noImage = { + updatedAt: '2024-12-17T08:25:07.098Z', + createdAt: '2024-12-17T08:25:07.098Z', + likeCount: 0, + writer: { + name: '이름', + id: 1, + }, + image: '', + title: '게시글 제목입니다.', + id: 1, +}; + +const data_Image = { + updatedAt: '2024-12-17T08:25:07.098Z', + createdAt: '2024-12-17T08:25:07.098Z', + likeCount: 0, + writer: { + name: '이름', + id: 1, + }, + image: 'https://via.placeholder.com/1000', + title: '게시글 제목입니다.', + id: 1, +}; + +export default function BoardCardTest() { + return ( +
+ + +
+ ); +} diff --git a/pages/test/heart.tsx b/pages/test/heart.tsx new file mode 100644 index 0000000..0ea4e38 --- /dev/null +++ b/pages/test/heart.tsx @@ -0,0 +1,10 @@ +import Heart from '@/components/Heart/Heart'; + +export default function HeartTest() { + return ( + <> + + console.log('클릭')} /> + + ); +} diff --git a/public/icon/icon-no-image.svg b/public/icon/icon-no-image.svg new file mode 100644 index 0000000..fa7c7ee --- /dev/null +++ b/public/icon/icon-no-image.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/tsconfig.json b/tsconfig.json index c32ac62..bc5a593 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,14 +18,18 @@ "paths": { "@/components/*": ["components/*"], "@/styles/*": ["styles/*"], - "@/pages/*": ["pages/*"] + "@/pages/*": ["pages/*"], + "@/utils/*": ["utils/*"], + "@/hooks/*": ["hooks/*"] } }, "include": [ "pages/**/*.ts", "pages/**/*.tsx", "components/**/*.ts", - "components/**/*.tsx" + "components/**/*.tsx", + "utils/**/*.ts", + "utils/**/*.tsx" ], "exclude": ["node_modules", "dist", ".next"] } diff --git a/utils/dateConversion.ts b/utils/dateConversion.ts new file mode 100644 index 0000000..643999d --- /dev/null +++ b/utils/dateConversion.ts @@ -0,0 +1,25 @@ +/** + * isoString으로 받아온 날짜를 {year}.{month}.{day} 타입으로 변환해주는 함수 입니다. + * 현재 시간 기준 24시간 이내 일 경우 '{time}분 전', '{time}시간 전' 으로 표기 됩니다. + * @param {string} isoString + * @returns {string} + */ +export default function dateConversion(isoString: string): string { + const createdDate = new Date(isoString); // 생성일 + const currentDate = new Date(); // 현재 날짜 + const timeDifference = currentDate.getTime() - createdDate.getTime(); // 오차 계산 + const hoursDifference = Math.floor(timeDifference / (1000 * 60 * 60)); // 시간 환산 + const minutesDifference = Math.floor(timeDifference / (1000 * 60)); // 분 환산 + + if (hoursDifference < 1) { + return `${minutesDifference}분 전`; // 생성한지 1시간이 안되었을 때 + } else if (hoursDifference < 24) { + return `${hoursDifference}시간 전`; // 생성한지 24시간이 안되었을 때 + } else { + // 24시간 이후로는 날짜로 표기 + const year = createdDate.getFullYear(); + const month = (createdDate.getMonth() + 1).toString().padStart(2, '0'); + const day = createdDate.getDate().toString().padStart(2, '0'); + return `${year}-${month}-${day}`; + } +}