Skip to content

Commit

Permalink
added comment function
Browse files Browse the repository at this point in the history
taedonn committed Sep 23, 2023
1 parent 3e887e5 commit 4de8fee
Showing 4 changed files with 175 additions and 101 deletions.
2 changes: 1 addition & 1 deletion src/pages/DetailPage/[fontId].tsx
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ function DetailPage({params}: any) {
await fetch("/api/detailpage/updateview", { method: "POST", body: JSON.stringify(font) });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
// useEffect(() => { viewUpdate(); }, [font]);
useEffect(() => { viewUpdate(); }, [font]);

// 좋아요 state
const [alertDisplay, setAlertDisplay] = useState<boolean>(false);
77 changes: 66 additions & 11 deletions src/pages/api/user/fetchcomments.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,87 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import prisma from '@/libs/client-prisma';

// SSR 전체 댓글수 불러오기
export async function FetchComments(user_id: number) {
// SSR 댓글 페이지 수
export async function FetchCommentsLength(user_id: number) {
const comments = await prisma.fontsComment.findMany({
select: { user_id: true },
where: { user_id: user_id }
});

return comments.length;
}

// SSR 첫 댓글 목록 가져오기
export async function FetchComments(user_id: number, lastId: number | undefined) {
const comments = await prisma.fontsComment.findMany({
where: { user_id: user_id },
orderBy: [{created_at: 'desc'}, {comment_id: 'desc'}], // 정렬순
take: 10, // 가져오는 데이터 수
skip: lastId ? 1 : 0,
...(lastId && { cursor: {comment_id: lastId} })
});

// 폰트 아이디 중복 체크
const commentsNoRepeat = comments.reduce((acc: any, current: any) => {
const x = acc.find((item: any) => item.font_id === current.font_id);
if (!x) { return acc.concat([current]); }
else { return acc; }
}, []);

// 중복 체크 후 배열에 저장
let commentsArr: any = [];
for (let i = 0; i < commentsNoRepeat.length; i++) {
commentsArr.push({code: commentsNoRepeat[i].font_id});
}

// 배열에 저장된 폰트 ID로 폰트 이름 가져오기
const fonts = await prisma.fonts.findMany({
select: {
code: true,
name: true
},
where: { OR: commentsArr }
});

// 폰트 이름을 댓글 배열에 저장
for (let i = 0; i < comments.length; i++) {
for (let o = 0; o < fonts.length; o++) {
if (comments[i].font_id === fonts[o].code) { Object.assign(comments[i], fonts[o]) }
}
}

return comments;
}

// API
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'GET') {
const allComments = await prisma.fontsComment.findMany({
select: { user_id: true },
where: { user_id: Number(req.query.user_id) }
});
const filter = req.query.filter === 'all'
? [{depth: 0}, {depth: 1}]
: req.query.filter === 'comment'
? [{depth: 0}]
: [{depth: 1}];

// 최대 댓글 로딩 개수
const limit = 10;
// 댓글 페이지 수 가져오기
const length = await prisma.fontsComment.findMany({
where: {
user_id: Number(req.query.user_id),
comment: {contains: req.query.text as string},
OR: filter
}
});
const count = Number(length.length) % 10 > 0 ? Math.floor(Number(length.length)/10) + 1 : Math.floor(Number(length.length)/10);

// 댓글 가져오기
const comments = await prisma.fontsComment.findMany({
where: { user_id: Number(req.query.user_id) },
where: {
user_id: Number(req.query.user_id),
comment: {contains: req.query.text as string},
OR: filter
},
orderBy: [{created_at: 'desc'}, {comment_id: 'desc'}], // 정렬순
take: limit // 가져오는 데이터 수
take: 10, // 가져오는 데이터 수
skip: Number(req.query.page) === 1 ? 0 : (Number(req.query.page) - 1) * 10
});

// 폰트 아이디 중복 체크
@@ -61,7 +116,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.json({
message: "댓글 가져오기 성공",
comments: comments,
length: allComments.length
count: count
});
}
}
179 changes: 95 additions & 84 deletions src/pages/user/comments.tsx
Original file line number Diff line number Diff line change
@@ -2,18 +2,18 @@
import { NextSeo } from 'next-seo';

// react hooks
import React, { useEffect, useState } from 'react';
import { useQuery, useQueryClient, QueryClient, QueryClientProvider } from "react-query";
import React, { useEffect, useState, useRef } from 'react';

// api
import axios from 'axios';
import { CheckIfSessionExists } from "../api/user/checkifsessionexists";
import { FetchUserInfo } from "../api/user/fetchuserinfo";
import { FetchComments } from '../api/user/fetchcomments'
import { FetchCommentsLength } from '../api/user/fetchcomments'
import { FetchComments } from '../api/user/fetchcomments';

// components
import Header from "@/components/header";
import { Pagination } from '@mui/material';;
import { Pagination } from '@mui/material';

const SendEmail = ({params}: any) => {
// 디바이스 체크
@@ -22,44 +22,63 @@ const SendEmail = ({params}: any) => {
// 빈 함수
const emptyFn = () => { return; }

// Pagination
async function fetchProjects(page: number) {
const { data } = await axios.get("/api/user/fetchcomments", {
params: {
page: page,
user_id: params.user.user_no
}
});
return data;
}
// 댓글 목록 state
const [comments, setComments] = useState(JSON.parse(params.comments));
const [count, setCount] = useState<number>(params.count);
const [filter, setFilter] = useState<string>('all');
const [text, setText] = useState<string>('');

const queryClient = useQueryClient();
const [page, setPage] = useState<number>(0);
// const [isLoading] = useState<boolean>(true);
const {
data,
isLoading,
isPreviousData
} = useQuery(
["comments", page],
() => fetchProjects(page),
{
staleTime: 5000,
keepPreviousData: true,
select: (data) => {
return {
comments: data.comments,
hasMore: data.totalPages > page
};
}
}
)
// 댓글 목록 ref
const selectRef = useRef<HTMLSelectElement>(null);
const textRef = useRef<HTMLInputElement>(null);

// 댓글 목록 페이지 변경
const [page, setPage] = useState<number>(1);
const handleChange = (e: React.ChangeEvent<unknown>, value: number) => {
setPage(value);
};

// 페이지 변경 시 데이터 다시 불러오기
useEffect(() => {
if (data?.hasMore) {
queryClient.prefetchQuery(["projects", page + 1], () => fetchProjects(page + 1) );
const fetchNewComments = async () => {
await axios.get('/api/user/fetchcomments', {
params: {
user_id: params.user.user_no,
page: page,
filter: filter,
text: text
}
})
.then((res) => { setComments(res.data.comments); })
.catch(err => console.log(err));
}
}, [data, page, queryClient]);
fetchNewComments();
}, [page]);

// 댓글 필터 버튼 클릭 시 값 state에 저장 후, API 호출
const handleClick = async () => {
if (selectRef &&selectRef.current && textRef && textRef.current) {
// state 저장
setPage(1);
setFilter(selectRef.current.value);
setText(textRef.current.value);

// API 호출
await axios.get('/api/user/fetchcomments', {
params: {
user_id: params.user.user_no,
page: 1,
filter: selectRef.current.value,
text: textRef.current.value
}
})
.then((res) => {
setComments(res.data.comments);
setCount(res.data.count);
})
.catch(err => console.log(err));
}
}

/** 댓글 시간 포맷 */
const commentsTimeFormat = (time: string) => {
@@ -101,62 +120,50 @@ const SendEmail = ({params}: any) => {
{/* 메인 */}
<form onSubmit={e => e.preventDefault()} className='w-[100%] flex flex-col justify-center items-center'>
<div className='w-[720px] tmd:w-[100%] flex flex-col justify-center items-start my-[100px] tlg:my-[40px]'>
<h2 className='text-[20px] tlg:text-[18px] text-theme-4 dark:text-theme-9 font-medium mb-[12px]'>내 댓글 목록</h2>
<div className='w-content flex items-center p-[6px] mb-[8px] rounded-[6px] text-theme-10 dark:text-theme-9 bg-theme-5 dark:bg-theme-3'>
<select className='w-[80px] h-[32px] text-[12px] pt-px px-[14px] bg-transparent rounded-[6px] outline-none border border-theme-6 dark:border-theme-5 cursor-pointer'>
<option defaultChecked>댓글</option>
<option>답글</option>
<h2 className='text-[20px] tlg:text-[18px] text-theme-4 dark:text-theme-9 font-medium mb-[16px] tlg:mb-[12px]'>내 댓글 목록</h2>
<div className='w-content flex items-center p-[6px] mb-[12px] tlg:mb-[8px] rounded-[6px] text-theme-10 dark:text-theme-9 bg-theme-5 dark:bg-theme-3'>
<select ref={selectRef} className='w-[80px] h-[32px] tlg:h-[28px] text-[12px] pt-px px-[14px] bg-transparent rounded-[6px] outline-none border border-theme-6 dark:border-theme-5 cursor-pointer'>
<option value='all' defaultChecked>전체</option>
<option value='comment'>댓글</option>
<option value='reply'>답글</option>
</select>
<input type='textbox' className='w-[200px] h-[32px] ml-[8px] px-[12px] text-[12px] bg-transparent border rounded-[6px] border-theme-6 dark:border-theme-5'/>
<button className='w-[68px] h-[32px] ml-[8px] text-[12px] border rounded-[6px] bg-theme-6/40 hover:bg-theme-6/60 tlg:hover:bg-theme-6/40 dark:bg-theme-4 hover:dark:bg-theme-5 tlg:hover:dark:bg-theme-4'>검색</button>
<input ref={textRef} type='textbox' className='w-[200px] tlg:w-[160px] h-[32px] tlg:h-[28px] ml-[8px] px-[12px] text-[12px] bg-transparent border rounded-[6px] border-theme-6 dark:border-theme-5'/>
<button onClick={handleClick} className='w-[68px] h-[32px] tlg:h-[28px] ml-[8px] text-[12px] border rounded-[6px] bg-theme-6/40 hover:bg-theme-6/60 tlg:hover:bg-theme-6/40 dark:bg-theme-4 hover:dark:bg-theme-5 tlg:hover:dark:bg-theme-4'>검색</button>
</div>
<div className='w-[100%] rounded-[8px] border border-theme-5 dark:border-theme-3 overflow-hidden'>
<table className='w-[100%] text-[12px] text-theme-10 dark:text-theme-9'>
<thead className='h-[40px] text-left bg-theme-5 dark:bg-theme-3'>
<div className='w-[100%] rounded-[8px] overflow-hidden'>
<table className='w-[100%] text-[12px] text-theme-10 dark:text-theme-9 bg-theme-5/80 dark:bg-theme-4/80'>
<thead className='h-[40px] tlg:h-[34px] text-left bg-theme-5 dark:bg-theme-3'>
<tr>
<th className='w-[128px] pl-[20px]'>폰트</th>
<th className='pl-[20px]'>댓글</th>
<th className='w-[128px] pl-[20px]'>작성 날짜</th>
<th className='w-[120px] pl-[20px] tlg:pl-[16px]'>폰트</th>
<th className='pl-[20px] tlg:pl-[16px]'>댓글</th>
<th className='w-[120px] pl-[20px] tlg:pl-[16px]'>작성 날짜</th>
</tr>
</thead>
<tbody>
{
isLoading
comments && comments.length > 0
? <>
<tr className='h-[40px]'><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td></tr>
<tr className='h-[40px]'><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td></tr>
<tr className='h-[40px]'><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td></tr>
<tr className='h-[40px]'><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td></tr>
<tr className='h-[40px]'><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td></tr>
<tr className='h-[40px]'><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td></tr>
<tr className='h-[40px]'><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td></tr>
<tr className='h-[40px]'><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td></tr>
<tr className='h-[40px]'><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td></tr>
<tr className='h-[40px]'><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td><td className='pl-[20px] py-[10px]'><div className='w-[80%] h-[16px] rounded-[4px] skeleton-gradient'></div></td></tr>
{
comments.map((comment: any) => {
return (
<tr key={comment.comment_id} className='h-[40px] tlg:h-[34px] border-t border-theme-5 dark:border-theme-3'>
<td className='pl-[20px] tlg:pl-[16px] py-[10px] break-keep'><a href={`/detailpage/${comment.code}`} className='hover:underline tlg:hover:no-underline'>{comment.name}</a></td>
<td className='pl-[20px] tlg:pl-[16px] py-[10px] break-keep'><a href={`/detailpage/${comment.code}#c${comment.comment_id}`} className='hover:underline tlg:hover:no-underline'>{comment.comment}</a></td>
<td className='pl-[20px] tlg:pl-[16px] py-[10px] break-keep'>{commentsDateFormat(comment.created_at)}</td>
</tr>
)
})
}
</>
: data && data.comments.length > 0
? <>
{
data.comments.map((comment: any) => {
return (
<tr key={comment.comment_id} className='h-[40px] border-t border-theme-5 dark:border-theme-3'>
<td className='pl-[20px] py-[10px]'>{comment.name}</td>
<td className='pl-[20px] py-[10px]'><a href={`/detailpage/${comment.code}#c${comment.comment_id}`} target="_blank" className='hover:underline tlg:hover:no-underline'>{comment.comment}</a></td>
<td className='pl-[20px] py-[10px]'>{commentsDateFormat(comment.created_at)}</td>
</tr>
)
})
}
</>
: <tr className='h-[60px]'>
<td colSpan={3} className='text-center'>아직 댓글이 없습니다.</td>
</tr>
: <tr className='h-[60px]'>
<td colSpan={3} className='text-center'>댓글이 없습니다.</td>
</tr>
}
</tbody>
</table>
</div>
<div className='w-[100%] flex justify-center mt-[12px]'>
<Pagination count={params.count} shape='rounded'/>
<Pagination count={count} page={page} onChange={handleChange} shape='rounded' showFirstButton showLastButton/>
</div>
</div>
</form>
@@ -179,10 +186,6 @@ export async function getServerSideProps(ctx: any) {
: null
)

// 댓글 length 가져오기
const length = await FetchComments(user.user_no);
const count = Number(length) % 10 > 0 ? Math.floor(Number(length)/10) + 1 : Math.floor(Number(length)/10);

// 쿠키에 저장된 세션ID가 유효하지 않다면, 메인페이지로 이동, 유효하면 클리이언트로 유저 정보 return
if (user === null) {
return {
@@ -192,13 +195,21 @@ export async function getServerSideProps(ctx: any) {
}
}
} else {
// 댓글 페이지 수
const length = await FetchCommentsLength(user.user_no);
const count = Number(length) % 10 > 0 ? Math.floor(Number(length)/10) + 1 : Math.floor(Number(length)/10);

// 첫 댓글 목록 가져오기
const comments: any = await FetchComments(user.user_no, undefined);

return {
props: {
params: {
theme: cookieTheme,
userAgent: userAgent,
user: user,
count: count,
comments: JSON.stringify(comments)
}
}
}
18 changes: 13 additions & 5 deletions src/styles/globals.css
Original file line number Diff line number Diff line change
@@ -325,14 +325,14 @@ html.dark .search-list::-webkit-scrollbar-track { background-color: #35363A; }

/* 검색 결과 active 상태일 때 스타일 정의 */
@media (hover: hover) and (pointer: fine) {
#active { background-color: #B1B2B6; }
#active { background-color: #B1B2B6CC; }
#active .when-active-1 { background-color: #97989C; border-color: #97989C; }
#active .when-active-2, { fill: #646568; }
#active .when-active-2, { fill: #E9EAEE; }
#active .when-active-3 { color: #4C4D50; }
#active .when-active-4 { color: #646568; }
#active .when-active-5 { color: #646568; }
#active .when-active-6 { fill: #646568; }
html.dark #active { background-color: #4C4D50; }
html.dark #active { background-color: #4C4D50CC; }
html.dark #active .when-active-1 { background-color: #646568; border-color: #646568; }
html.dark #active .when-active-2, { fill: #B1B2B6; }
html.dark #active .when-active-3 { color: #E9EAEE; }
@@ -536,8 +536,16 @@ html.dark .edit-btn-disabled {

/* 페이지네이션 */
#__next .MuiPagination-ul button {
color: #CDCED2;
color: #646568;
}
#__next .MuiPagination-ul .Mui-selected {
background-color: #35363A;
color: #E9EAEE;
background-color: #646568CC;
}
html.dark #__next .MuiPagination-ul button {
color: #CDCED2;
}
html.dark #__next .MuiPagination-ul .Mui-selected {
color: #E9EAEE;
background-color: #4C4D50CC;
}

1 comment on commit 4de8fee

@vercel
Copy link

@vercel vercel bot commented on 4de8fee Sep 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

fonts-archive – ./

fonts-archive-git-main-taedonn.vercel.app
fonts-archive-taedonn.vercel.app
fonts.taedonn.com

Please sign in to comment.