Skip to content

Commit

Permalink
Merge pull request #625 from cindycho0423/Next.js-조현지-sprint10
Browse files Browse the repository at this point in the history
[조현지] sprint10
  • Loading branch information
ding-co authored Jun 11, 2024
2 parents ee33e71 + 67cd0b8 commit 1a64151
Show file tree
Hide file tree
Showing 59 changed files with 1,438 additions and 372 deletions.
39 changes: 39 additions & 0 deletions components/article-detail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Image from 'next/image';
import icHeart from '@/public/images/icons/ic_heart.png';
import icProfile from '@/public/images/icons/ic_profile.png';
import icKebab from '@/public/images/icons/ic_kebab.svg';
import getFormatDate from '@/lib/utils/formatDate';
import { ArticleProps } from '@/types';

export default function ArticleDetail({ content, createdAt, image, likeCount, title, writer }: ArticleProps) {
const createdDate = getFormatDate(createdAt);

return (
<>
<div className='flex justify-between mb-4'>
<h1 className=''>{title}</h1>
<Image src={icKebab} width={24} alt='케밥 메뉴' />
</div>
<div className='mb-10'>
<div className='flex items-center gap-4 mb-4'>
<div className='flex items-center gap-2'>
<Image src={icProfile} width={24} alt='프로필 기본 사진' />
<span className='text-[14px] text-cool-gray600'>{writer.nickname}</span>
<span className='text-[12px] text-cool-gray400'>{createdDate}</span>
</div>

<div className='w-[1px] h-[24px] bg-cool-gray200'></div>

<div className='flex items-center gap-1 text-[14px] text-cool-gray600'>
<Image src={icHeart} width={24} height={24} alt='좋아요 하트' />

{likeCount}
</div>
</div>

<div className='w-[343px] md:w-[696px] lg:w-[1200px] h-[1px] bg-cool-gray200 mb-4'></div>
<div>{content}</div>
</div>
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import getFormatDate from '@/lib/utils/formatDate';
import Image from 'next/image';
import ic_heart from '@/public/images/ic_heart.png';
import ic_profile from '@/public/images/ic_profile.png';
import { ListProps } from '@/lib/getArticles';

export default function ArticleComponent({ createdAt, likeCount, image, title, writer }: ListProps) {
const date = new Date(createdAt).getDate();
const month = new Date(createdAt).getMonth() + 1;
const year = new Date(createdAt).getFullYear();
const createdDate = `${year}. ${month}. ${date}`;
import icHeart from '@/public/images/icons/ic_heart.png';
import icProfile from '@/public/images/icons/ic_profile.png';
import { ArticleProps } from '@/types';

export default function ArticlePreview({ createdAt, likeCount, image, title, writer }: ArticleProps) {
const createdDate = getFormatDate(createdAt);
return (
<div className='flex flex-col gap-4'>
<div className='flex justify-between gap-2 font-semibold text-cool-gray800'>
Expand All @@ -21,12 +18,12 @@ export default function ArticleComponent({ createdAt, likeCount, image, title, w
</div>
<div className='flex justify-between pb-6 border-b border-solid text-cool-gray400 border-cool-gray200'>
<div className='flex items-center gap-2'>
<Image src={ic_profile} alt='프로필 이미지' width={24} />
<Image src={icProfile} alt='프로필 이미지' width={24} />
<span className='text-cool-gray600'>{writer.nickname}</span>
<div>{createdDate}</div>
<time>{createdDate}</time>
</div>
<div className='flex items-center gap-1'>
<Image src={ic_heart} alt='좋아요 하트' width={16}></Image>
<Image src={icHeart} alt='좋아요 하트' width={16}></Image>
<span>{likeCount}+</span>
</div>
</div>
Expand Down
53 changes: 35 additions & 18 deletions components/articles.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,72 @@
import Image from 'next/image';
import Link from 'next/link';
import ArticleComponent from './article-component';
import ic_search from '../public/images/ic_search.png';
import icSearch from '@/public/images/icons/ic_search.png';
import SelectBox from './select-box';
import { getArticles, ListProps } from '@/lib/getArticles';
import { getArticles } from '@/lib/api/getArticles';
import { ArticleProps } from '@/types';
import { ChangeEvent, useState, useEffect } from 'react';
import useDebounce from '@/hooks/useDebounce';

const PAGE_NUM = 1;
const PAGE_SIZE = 10;
import { constants, SEARCH_TIME } from '../lib/constants';
import ArticlePreview from './article-preview';
import Pagination from './pagination';

interface Props {
articlesServer: ListProps[];
articlesServer: ArticleProps[];
totalCount: number;
}

export default function Articles({ articlesServer }: Props) {
export default function Articles({ articlesServer, totalCount: initialTotalCount }: Props) {
const [orderBy, setOrderby] = useState('recent');
const [keyword, setKeyword] = useState('');
const [articles, setArticles] = useState<ListProps[]>(articlesServer);
const debouncedValue = useDebounce(keyword, 300);
const [articles, setArticles] = useState<ArticleProps[]>(articlesServer);
const [pageNum, setPageNum] = useState(constants.PAGE_NUM);
const [totalCount, setTotalCount] = useState(initialTotalCount);
const debouncedValue = useDebounce(keyword, SEARCH_TIME);

const handleOrderClick = async (sortType: string): Promise<void> => {
setOrderby(sortType);
setPageNum(1);
try {
const sortData = await getArticles(PAGE_NUM, PAGE_SIZE, sortType, keyword);
const { list: sortData, totalCount: updatedTotalCount } = await getArticles(
1,
constants.PAGE_SIZE,
sortType,
keyword
);
setArticles(sortData);
setTotalCount(updatedTotalCount);
} catch (error) {
console.error(error);
}
};

const handleChange = async (e: ChangeEvent<HTMLInputElement>): Promise<void> => {
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
setKeyword(e.target.value);
setPageNum(1);
};

useEffect(() => {
const fetchArticles = async () => {
try {
const sortData = await getArticles(PAGE_NUM, PAGE_SIZE, orderBy, debouncedValue);
const { list: sortData, totalCount: updatedTotalCount } = await getArticles(
pageNum,
constants.PAGE_SIZE,
orderBy,
debouncedValue
);
setArticles(sortData);
setTotalCount(updatedTotalCount);
} catch (error) {
console.error(error);
}
};

fetchArticles();
}, [debouncedValue, orderBy]);
}, [orderBy, pageNum, debouncedValue]);

return (
<div>
<div className='relative flex justify-between gap-2 mb-6'>
<Image src={ic_search} width={24} alt='돋보기' className='absolute z-10 top-2 left-3' />
<Image src={icSearch} width={24} alt='돋보기' className='absolute z-10 top-2 left-3' />
<input
value={keyword}
onChange={handleChange}
Expand All @@ -60,13 +76,14 @@ export default function Articles({ articlesServer }: Props) {
/>
<SelectBox handleOrder={handleOrderClick} />
</div>
<div className='flex flex-col gap-6'>
<div className='flex flex-col gap-6 min-h-[350px]'>
{articles.map(article => (
<Link key={article.id} href={`/boards/${article.id}`}>
<ArticleComponent {...article} />
<ArticlePreview {...article} />
</Link>
))}
</div>
<Pagination pageNum={pageNum} setPageNum={setPageNum} totalCount={totalCount} />
</div>
);
}
37 changes: 0 additions & 37 deletions components/best-article-component.tsx

This file was deleted.

50 changes: 41 additions & 9 deletions components/best-articles.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import BestArticleComponent from './best-article-component';
import { useEffect, useState } from 'react';
import { getBestArticles, ListProps } from '@/lib/getArticles';
import { getBestArticles } from '@/lib/api/getArticles';
import { ArticleProps } from '@/types';
import getFormatDate from '@/lib/utils/formatDate';
import Link from 'next/link';

const PAGE_NUM = 1;
const ORDERBY = 'like';
import Image from 'next/image';
import imageBadge from '@/public/images/img_badge.svg';
import icHeart from '@/public/images/icons/ic_heart.png';
import { constants, IS_SERVER } from '../lib/constants';

function getPageSize() {
const width = window.innerWidth;
if (typeof window === 'undefined') {
if (IS_SERVER) {
return 3;
}
if (width < 768) return 1;
Expand All @@ -17,13 +19,13 @@ function getPageSize() {
}

export default function BestArticles() {
const [bestArticles, setBestArticles] = useState<ListProps[]>([]);
const [bestArticles, setBestArticles] = useState<ArticleProps[]>([]);
const [pageSize, setPageSize] = useState(3);

useEffect(() => {
const fetchBestArticles = async () => {
try {
const data = await getBestArticles(PAGE_NUM, pageSize, ORDERBY);
const data = await getBestArticles(constants.PAGE_NUM, pageSize, constants.ORDERBY);
setBestArticles(data);
} catch (error) {
console.error('Failed to fetch items:', error);
Expand All @@ -46,9 +48,39 @@ export default function BestArticles() {
<div className='flex gap-6 md:gap-4'>
{bestArticles.map(article => (
<Link key={article.id} href={`/boards/${article.id}`}>
<BestArticleComponent {...article} />
<BestArticlePreview {...article} />
</Link>
))}
</div>
);
}

function BestArticlePreview({ createdAt, likeCount, image, title, writer }: ArticleProps) {
const createdDate = getFormatDate(createdAt);

return (
<div className='h-[169px] px-6 pb-6 bg-cool-gray50 rounded-lg sm:min-w-[343px] md:min-w-[340px] lg:min-w-[384px]'>
<Image src={imageBadge} alt='베스트 뱃지' />
<div className='flex gap-2 mt-4 mb-4 h-[72px]'>
<div className=' text-cool-gray800 text-[18px] leading-5 sm:min-w[212px] w-[212px] lg:w-[256px] lg:text-xl'>
{title}
</div>
{image && (
<div className='flex justify-center items-center bg-white border border-solid rounded-md border-cool-gray200 w-[72px] h-[72px]'>
<Image src={image} alt='게시글 이미지' width={48} height={45} />
</div>
)}
</div>
<div className='flex justify-between'>
<div className='flex items-center gap-2'>
<span className='text-cool-gray600'>{writer.nickname}</span>
<div className='flex items-center gap-1'>
<Image src={icHeart} alt='좋아요 하트' width={16} height={16}></Image>
<span className='text-cool-gray500'>{likeCount}</span>
</div>
</div>
<time className='text-cool-gray400'>{createdDate}</time>
</div>
</div>
);
}
58 changes: 58 additions & 0 deletions components/comment-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { postComment } from '@/lib/api/postComment';
import SubmitButton from '@/components/submit-btn';
import { useState, useEffect, ChangeEvent, FormEvent } from 'react';

type Props = {
id: string;
onNewComment: () => void;
};

export default function CommentForm({ id, onNewComment }: Props) {
const [comment, setComment] = useState('');
const [token, setToken] = useState('');

useEffect(() => {
const localToken = typeof window !== 'undefined' ? localStorage.getItem('accessToken') : null;
if (localToken) {
setToken(localToken);
}
}, []);

const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
setComment(e.target.value);
};

const handleSubmit = async (e: FormEvent) => {
e.preventDefault();

try {
const response = await postComment(comment, id, token);
if (response) {
setComment('');
onNewComment();
} else {
console.error('댓글 작성 실패');
}
} catch (error) {
console.error('댓글 작성 중 오류 발생:', error);
}
};

return (
<form onSubmit={handleSubmit}>
<label className='font-semibold' htmlFor='comment'>
댓글달기
</label>
<textarea
id='comment'
value={comment}
onChange={handleChange}
className='w-[343px] md:w-[696px] lg:w-[1200px] min-w-[344px] h-[104px] rounded-2xl py-4 px-6 my-4'
placeholder='댓글을 입력해주세요'
/>
<div className='flex justify-end'>
<SubmitButton checkValue={comment.length > 0 ? true : false}>등록</SubmitButton>
</div>
</form>
);
}
Loading

0 comments on commit 1a64151

Please sign in to comment.