From 10090072f66a7d1034c9496fef12515ac8ede8ce Mon Sep 17 00:00:00 2001 From: rachel4w2 Date: Wed, 4 Oct 2023 18:38:27 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat=20:=20getData=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API 호출 방식 변경 : axiox -> fetch --- frontend/package.json | 1 + .../src/app/(read)/post/[postId]/page.tsx | 255 ++++++++++++++++++ .../(search)/search-post/[postName]/page.tsx | 7 +- frontend/src/components/main/ProjectCard.tsx | 4 +- .../page.tsx => components/read/GptModal.tsx} | 0 frontend/src/components/read/PutReply.tsx | 0 frontend/src/components/read/QuillWritten.tsx | 0 frontend/src/components/read/ReadReReply.tsx | 0 frontend/src/components/read/ReadReply.tsx | 0 frontend/src/components/read/Reply.tsx | 0 frontend/src/components/read/WriteReReply.tsx | 0 frontend/src/utils/atoms.ts | 102 ++++++- frontend/yarn.lock | 217 ++++++++++++++- 13 files changed, 574 insertions(+), 12 deletions(-) create mode 100644 frontend/src/app/(read)/post/[postId]/page.tsx rename frontend/src/{app/(read)/post/[title]/page.tsx => components/read/GptModal.tsx} (100%) create mode 100644 frontend/src/components/read/PutReply.tsx create mode 100644 frontend/src/components/read/QuillWritten.tsx create mode 100644 frontend/src/components/read/ReadReReply.tsx create mode 100644 frontend/src/components/read/ReadReply.tsx create mode 100644 frontend/src/components/read/Reply.tsx create mode 100644 frontend/src/components/read/WriteReReply.tsx diff --git a/frontend/package.json b/frontend/package.json index 3f9df1d2..96f9e64b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "dependencies": { "@hookform/resolvers": "^3.3.1", "@tanstack/react-query": "^4.35.3", + "@testing-library/react": "^14.0.0", "@types/node": "20.6.3", "@types/react": "18.2.22", "@types/react-dom": "18.2.7", diff --git a/frontend/src/app/(read)/post/[postId]/page.tsx b/frontend/src/app/(read)/post/[postId]/page.tsx new file mode 100644 index 00000000..bbf6ab88 --- /dev/null +++ b/frontend/src/app/(read)/post/[postId]/page.tsx @@ -0,0 +1,255 @@ +'use client' + +import { act } from '@testing-library/react' +import { useEffect, useState, useCallback } from 'react' +import { useRouter } from 'next/navigation' +import { useRecoilState, useRecoilValue } from 'recoil' +import gptIcon from '../../../../../public/images/svg/gptIcon.svg' +import ProfileIcon from '../../../../../public/images/svg/profileIcon.svg' +// TODO: import NavBar from '../../components/general/NavBar' +// TODO: import GptModal from '../../components/read/ReadingPage/GptModal' +// TODO: import QuillWrtten from '../../components/read/ReadingPage/QuillWritten' +// TODO: import Reply from '../../../../components/read/Reply' +import { + contentsState, + selectedStackState, + titleState, + tldrState, + refreshState, +} from '../../../../utils/atoms' +import AllStacks from '../../../../utils/stacks' + +type ReadReplyObject = { + commentId: number + childCount: number + content: string + createdAt: string +} + +type ParamsType = { + params: { + postId: number + } +} + +function ReadingPage({ params }: ParamsType) { + const [title, setTitle] = useRecoilState(titleState) + const [tldr, setTldr] = useRecoilState(tldrState) + const [selectedStack, setSelectedStack] = useRecoilState(selectedStackState) + const [, setContents] = useRecoilState(contentsState) + const [readReply, setReadReply] = useState([]) + const refresh = useRecoilValue(refreshState) + const accessToken = + typeof window !== 'undefined' ? sessionStorage.getItem('accessToken') : null + const persistToken = + typeof window !== 'undefined' ? localStorage.getItem('persistToken') : null + const router = useRouter() + + const [isOpenModal, setOpenModal] = useState(false) + const [nickname, setNickname] = useState('') + const [createdAt, setCreatedAt] = useState('') + + const onClickToggleModal = useCallback(() => { + setOpenModal(!isOpenModal) + }, [isOpenModal]) + + function toWrite() { + router.push('/write') + } + + function toModify() { + router.push('/modify') + } + // GET요청 보내서 데이터 가져오고 받은 데이터 변수에 넣어주는 함수 + async function getData() { + const res = await fetch( + `${process.env.NEXT_PUBLIC_BASE_URL}/projects/${Number(params.postId)}`, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ) + + if (!res.ok) { + const error = await res.json() + if (error.data.message === '이미 삭제되거나 존재하지 않는 프로젝트') { + alert('이미 삭제되거나 존재하지 않는 프로젝트입니다.') + } else { + alert('프로젝트 상세 조회 실패') + throw new Error('프로젝트 상세 조회 실패') + } + router.push('/') + } + + const resData = await res.json() + + setTitle(resData.data.projectName) + setTldr(resData.data.description) + setSelectedStack(resData.data.techTags) + setContents(resData.data.content) + setReadReply(resData.data.commentsList) + setNickname(resData.data.member.nickname) + setCreatedAt(resData.data.createdAt) + } + // DELETE 요청 보내는 함수 + // async function deleteData() { + // if (!navigator.onLine) { + // alert('오프라인 상태입니다. 네트워크 연결을 확인해주세요.') + // } else { + // try { + // await tokenApi.delete(`/projects/${params.id}`) + // act(() => { + // navigate('/') + // }) + // } catch (error) { + // if (isAxiosError(error)) { + // if ( + // error.response?.data.message === + // '이미 삭제되거나 존재하지 않는 프로젝트' + // ) { + // alert('이미 삭제되거나 존재하지 않는 프로젝트입니다.') + // } else { + // alert('네트워크 오류') + // } + // } + // } + // } + // } + // 렌더링할때 데이터 가져옴 + useEffect(() => { + getData() + }, [refresh]) + // 제목 변경시 재 렌더링 + useEffect(() => { + if (title) { + setTitle(title) + } + }, [title]) + // 소개 변경시 재 렌더링 + useEffect(() => { + if (tldr) { + setTldr(tldr) + } + }, [tldr]) + // 선택 스택 변경시 재 렌더링 + useEffect(() => { + if (selectedStack.length !== 0) { + setSelectedStack(selectedStack) + } + }, [selectedStack]) + + // 이미지 찾는 함수 + function findImage(tag: string) { + return AllStacks.map((x) => x.image)[ + AllStacks.map((x) => x.name).findIndex((x) => x === tag) + ] + } + + return ( +
+ {/* TODO: */} + {/** 전체 컨텐츠 영역* */} +
+ {/* AI 고도화 버튼 */} + {accessToken || persistToken ? ( + + ) : null} + + {/* TODO: {isOpenModal ? ( + + ) : null} */} + {/** 텍스트 영역* */} +
+ {/** 제목* */} +
+ {title} +
+
+ ProfileIcon + {nickname} + +   |  {createdAt.slice(0, 10)} + +
+
+
+ 한줄 소개 +
+ {/** 한줄소개* */} +
{tldr}
+
+ {/** 사용기술* */} + {selectedStack.length !== 0 ? ( +
+
+ 기술 스택 +
+ {selectedStack.map((x: string) => ( +
+ Stack +

{x}

+
+ ))} +
+ ) : ( +
+
+ 기술 스택 +
+
없음
+
+ )} +
+ {/** 글 영역* */} + {/* TODO: */} + {/** 버튼 영역* */} + {accessToken || persistToken ? ( +
+ + + +
+ ) : null} +
+ {/* TODO: */} +
+
+
+ ) +} + +export default ReadingPage diff --git a/frontend/src/app/(search)/search-post/[postName]/page.tsx b/frontend/src/app/(search)/search-post/[postName]/page.tsx index d1774ce3..55b59d40 100644 --- a/frontend/src/app/(search)/search-post/[postName]/page.tsx +++ b/frontend/src/app/(search)/search-post/[postName]/page.tsx @@ -17,7 +17,7 @@ type DataObject = { thumbNail: string } -type ParamsType = { +export type ParamsType = { params: { postName: string } @@ -41,13 +41,14 @@ export default function SearchProjectPage({ params }: ParamsType) { }, ) - const resData = await res.json() - if (!res.ok) { alert('검색 결과가 없습니다.') router.push('/') throw new Error('프로젝트 검색에 실패했습니다.') } + + const resData = await res.json() + setData(resData.data) } diff --git a/frontend/src/components/main/ProjectCard.tsx b/frontend/src/components/main/ProjectCard.tsx index 30e3b5f1..c8936e86 100644 --- a/frontend/src/components/main/ProjectCard.tsx +++ b/frontend/src/components/main/ProjectCard.tsx @@ -42,7 +42,7 @@ export default function ProjectCard({ items, index }: ProjectCardProps) { messageChannel.port1.onmessage = (event) => { if (event.data.hasMatch) { - router.push(`/read/${items.id}`) + router.push(`/post/${items.id}`) setProjectId(items.id) } else { alert('오프라인 상태입니다. 네트워크 연결을 확인해주세요.') @@ -58,7 +58,7 @@ export default function ProjectCard({ items, index }: ProjectCardProps) { } }) } else { - router.push(`/read/${items.id}`) + router.push(`/post/${items.id}`) setProjectId(items.id) } } diff --git a/frontend/src/app/(read)/post/[title]/page.tsx b/frontend/src/components/read/GptModal.tsx similarity index 100% rename from frontend/src/app/(read)/post/[title]/page.tsx rename to frontend/src/components/read/GptModal.tsx diff --git a/frontend/src/components/read/PutReply.tsx b/frontend/src/components/read/PutReply.tsx new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/components/read/QuillWritten.tsx b/frontend/src/components/read/QuillWritten.tsx new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/components/read/ReadReReply.tsx b/frontend/src/components/read/ReadReReply.tsx new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/components/read/ReadReply.tsx b/frontend/src/components/read/ReadReply.tsx new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/components/read/Reply.tsx b/frontend/src/components/read/Reply.tsx new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/components/read/WriteReReply.tsx b/frontend/src/components/read/WriteReReply.tsx new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/atoms.ts b/frontend/src/utils/atoms.ts index f6fbacd5..2300bb0d 100644 --- a/frontend/src/utils/atoms.ts +++ b/frontend/src/utils/atoms.ts @@ -3,21 +3,119 @@ import { recoilPersist } from 'recoil-persist' const { persistAtom } = recoilPersist() +const titleState = atom({ + key: 'titleState', + default: '', + effects_UNSTABLE: [persistAtom], +}) + +const tldrState = atom({ + key: 'tldrState', + default: '', + effects_UNSTABLE: [persistAtom], +}) + +const selectedStackState = atom({ + key: 'selectedStackState', + default: [], + effects_UNSTABLE: [persistAtom], +}) + +const contentsState = atom({ + key: 'contentsState', + default: '', + effects_UNSTABLE: [persistAtom], +}) + +const thumbnailUrlState = atom({ + key: 'thumbnailUrlState', + default: null, +}) + const projectIdState = atom({ key: 'projectIdState', default: 0, effects_UNSTABLE: [persistAtom], }) +const refreshState = atom({ + key: 'refreshState', + default: false, +}) + const searchTextState = atom({ key: 'searchTextState', default: '', }) -const autoLoginState = atom({ +const autoLoginState = atom({ key: 'autoLoginState', default: false, effects_UNSTABLE: [persistAtom], }) -export { projectIdState, searchTextState, autoLoginState } +const techStacksState = atom({ + key: 'techStacksState', + default: [], +}) + +const topicState = atom({ + key: 'topicState', + default: '', +}) + +const featuresState = atom({ + key: 'featuresState', + default: [], +}) + +const plansState = atom({ + key: 'plansState', + default: [], +}) + +const gptLoadingState = atom({ + key: 'gptLoadingState', + default: false, +}) + +const statusOpenState = atom({ + key: 'statusOpenState', + default: false, +}) + +const modalContentState = atom({ + key: 'modalContentState', + default: '', +}) + +const nicknameState = atom({ + key: 'nicknameState', + default: '', +}) + +const projectDataState = atom({ + key: 'projectDataState', + default: '', +}) + +export { + titleState, + tldrState, + selectedStackState, + contentsState, + thumbnailUrlState, + projectIdState, + refreshState, + searchTextState, + autoLoginState, + techStacksState, + topicState, + featuresState, + plansState, + gptLoadingState, + statusOpenState, + modalContentState, + nicknameState, + projectDataState, +} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e4d706fc..ce745b58 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -12,6 +12,35 @@ resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== +"@babel/code-frame@^7.10.4": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/runtime@^7.12.5": + version "7.23.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.1.tgz#72741dc4d413338a91dcb044a86f3c0bc402646d" + integrity sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.20.7": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" @@ -215,6 +244,34 @@ "@tanstack/query-core" "4.35.3" use-sync-external-store "^1.2.0" +"@testing-library/dom@^9.0.0": + version "9.3.3" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.3.tgz#108c23a5b0ef51121c26ae92eb3179416b0434f5" + integrity sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.1.3" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + pretty-format "^27.0.2" + +"@testing-library/react@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.0.0.tgz#59030392a6792450b9ab8e67aea5f3cc18d6347c" + integrity sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^9.0.0" + "@types/react-dom" "^18.0.0" + +"@types/aria-query@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.2.tgz#6f1225829d89794fd9f891989c9ce667422d7f64" + integrity sha512-PHKZuMN+K5qgKIWhBodXzQslTo5P+K/6LqeKXS6O/4liIDdZqaX5RXrCK++LAw+y/nptN48YmUMFiQHRSWYwtQ== + "@types/json-schema@^7.0.12": version "7.0.13" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" @@ -242,6 +299,13 @@ dependencies: "@types/react" "*" +"@types/react-dom@^18.0.0": + version "18.2.8" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.8.tgz#338f1b0a646c9f10e0a97208c1d26b9f473dffd6" + integrity sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@18.2.22": version "18.2.22" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.22.tgz#abe778a1c95a07fa70df40a52d7300a40b949ccb" @@ -421,6 +485,13 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -428,6 +499,11 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -451,6 +527,13 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +aria-query@5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" + integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== + dependencies: + deep-equal "^2.0.5" + aria-query@^5.1.3: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" @@ -643,7 +726,16 @@ caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.300015 resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz#9dbc6b9af1ff06b5eb12350c2012b3af56744f3f" integrity sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw== -chalk@^4.0.0: +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -671,6 +763,13 @@ client-only@0.0.1: resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -678,6 +777,11 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" @@ -736,6 +840,30 @@ debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: dependencies: ms "2.1.2" +deep-equal@^2.0.5: + version "2.2.2" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.2.tgz#9b2635da569a13ba8e1cc159c2f744071b115daa" + integrity sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.1" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.0" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -795,6 +923,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + electron-to-chromium@^1.4.477: version "1.4.525" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.525.tgz#614284f33901fbecd3e90176c0d60590cd939700" @@ -858,6 +991,21 @@ es-abstract@^1.22.1: unbox-primitive "^1.0.2" which-typed-array "^1.1.11" +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + es-iterator-helpers@^1.0.12: version "1.0.15" resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" @@ -908,6 +1056,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -1393,6 +1546,11 @@ has-bigints@^1.0.1, has-bigints@^1.0.2: resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -1465,7 +1623,7 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -internal-slot@^1.0.5: +internal-slot@^1.0.4, internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== @@ -1474,6 +1632,14 @@ internal-slot@^1.0.5: has "^1.0.3" side-channel "^1.0.4" +is-arguments@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" @@ -1557,7 +1723,7 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-map@^2.0.1: +is-map@^2.0.1, is-map@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== @@ -1592,7 +1758,7 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-set@^2.0.1: +is-set@^2.0.1, is-set@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== @@ -1671,7 +1837,7 @@ jiti@^1.18.2: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.20.0.tgz#2d823b5852ee8963585c8dd8b7992ffc1ae83b42" integrity sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA== -"js-tokens@^3.0.0 || ^4.0.0": +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== @@ -1790,6 +1956,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -1898,6 +2069,14 @@ object-inspect@^1.12.3, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== +object-is@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -2115,6 +2294,15 @@ prettier@^3.0.3: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -2157,6 +2345,11 @@ react-is@^16.13.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" @@ -2350,6 +2543,13 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + streamsearch@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" @@ -2434,6 +2634,13 @@ sucrase@^3.32.0: pirates "^4.0.1" ts-interface-checker "^0.1.9" +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" From f622a9b41f832fe6d633de044084b4a938500f25 Mon Sep 17 00:00:00 2001 From: rachel4w2 Date: Thu, 5 Oct 2023 19:45:39 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat=20:=20QuillWritten=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 1 + .../src/app/(read)/post/[postId]/page.tsx | 266 ++---------------- frontend/src/components/read/QuillWritten.tsx | 34 +++ frontend/src/components/read/ReadReply.tsx | 1 + frontend/src/components/read/Reply.tsx | 228 +++++++++++++++ frontend/yarn.lock | 87 +++++- 6 files changed, 364 insertions(+), 253 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 96f9e64b..18a94042 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,6 +32,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.46.2", + "react-quill": "^2.0.0", "recoil": "^0.7.7", "recoil-persist": "^5.1.0", "sass": "^1.68.0", diff --git a/frontend/src/app/(read)/post/[postId]/page.tsx b/frontend/src/app/(read)/post/[postId]/page.tsx index bbf6ab88..82c91ae5 100644 --- a/frontend/src/app/(read)/post/[postId]/page.tsx +++ b/frontend/src/app/(read)/post/[postId]/page.tsx @@ -1,255 +1,23 @@ 'use client' -import { act } from '@testing-library/react' -import { useEffect, useState, useCallback } from 'react' -import { useRouter } from 'next/navigation' -import { useRecoilState, useRecoilValue } from 'recoil' -import gptIcon from '../../../../../public/images/svg/gptIcon.svg' -import ProfileIcon from '../../../../../public/images/svg/profileIcon.svg' -// TODO: import NavBar from '../../components/general/NavBar' -// TODO: import GptModal from '../../components/read/ReadingPage/GptModal' -// TODO: import QuillWrtten from '../../components/read/ReadingPage/QuillWritten' -// TODO: import Reply from '../../../../components/read/Reply' -import { - contentsState, - selectedStackState, - titleState, - tldrState, - refreshState, -} from '../../../../utils/atoms' -import AllStacks from '../../../../utils/stacks' - -type ReadReplyObject = { - commentId: number - childCount: number - content: string - createdAt: string -} - -type ParamsType = { - params: { - postId: number - } -} - -function ReadingPage({ params }: ParamsType) { - const [title, setTitle] = useRecoilState(titleState) - const [tldr, setTldr] = useRecoilState(tldrState) - const [selectedStack, setSelectedStack] = useRecoilState(selectedStackState) - const [, setContents] = useRecoilState(contentsState) - const [readReply, setReadReply] = useState([]) - const refresh = useRecoilValue(refreshState) - const accessToken = - typeof window !== 'undefined' ? sessionStorage.getItem('accessToken') : null - const persistToken = - typeof window !== 'undefined' ? localStorage.getItem('persistToken') : null - const router = useRouter() - - const [isOpenModal, setOpenModal] = useState(false) - const [nickname, setNickname] = useState('') - const [createdAt, setCreatedAt] = useState('') - - const onClickToggleModal = useCallback(() => { - setOpenModal(!isOpenModal) - }, [isOpenModal]) - - function toWrite() { - router.push('/write') - } - - function toModify() { - router.push('/modify') - } - // GET요청 보내서 데이터 가져오고 받은 데이터 변수에 넣어주는 함수 - async function getData() { - const res = await fetch( - `${process.env.NEXT_PUBLIC_BASE_URL}/projects/${Number(params.postId)}`, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ) - - if (!res.ok) { - const error = await res.json() - if (error.data.message === '이미 삭제되거나 존재하지 않는 프로젝트') { - alert('이미 삭제되거나 존재하지 않는 프로젝트입니다.') - } else { - alert('프로젝트 상세 조회 실패') - throw new Error('프로젝트 상세 조회 실패') - } - router.push('/') - } - - const resData = await res.json() - - setTitle(resData.data.projectName) - setTldr(resData.data.description) - setSelectedStack(resData.data.techTags) - setContents(resData.data.content) - setReadReply(resData.data.commentsList) - setNickname(resData.data.member.nickname) - setCreatedAt(resData.data.createdAt) - } - // DELETE 요청 보내는 함수 - // async function deleteData() { - // if (!navigator.onLine) { - // alert('오프라인 상태입니다. 네트워크 연결을 확인해주세요.') - // } else { - // try { - // await tokenApi.delete(`/projects/${params.id}`) - // act(() => { - // navigate('/') - // }) - // } catch (error) { - // if (isAxiosError(error)) { - // if ( - // error.response?.data.message === - // '이미 삭제되거나 존재하지 않는 프로젝트' - // ) { - // alert('이미 삭제되거나 존재하지 않는 프로젝트입니다.') - // } else { - // alert('네트워크 오류') - // } - // } - // } - // } - // } - // 렌더링할때 데이터 가져옴 - useEffect(() => { - getData() - }, [refresh]) - // 제목 변경시 재 렌더링 - useEffect(() => { - if (title) { - setTitle(title) - } - }, [title]) - // 소개 변경시 재 렌더링 - useEffect(() => { - if (tldr) { - setTldr(tldr) - } - }, [tldr]) - // 선택 스택 변경시 재 렌더링 - useEffect(() => { - if (selectedStack.length !== 0) { - setSelectedStack(selectedStack) - } - }, [selectedStack]) - - // 이미지 찾는 함수 - function findImage(tag: string) { - return AllStacks.map((x) => x.image)[ - AllStacks.map((x) => x.name).findIndex((x) => x === tag) - ] - } - +import QuillWrtten from '@/components/read/QuillWritten' +import { useState } from 'react' +// import Reply from '../../../../components/read/Reply' +import { set } from 'react-hook-form' +import { Quill } from 'react-quill' + +// type ReadReplyObject = { +// commentId: number +// childCount: number +// content: string +// createdAt: string +// } + +export default function ReadingPage() { + // const [readReply, setReadReply] = useState([]) return ( -
- {/* TODO: */} - {/** 전체 컨텐츠 영역* */} -
- {/* AI 고도화 버튼 */} - {accessToken || persistToken ? ( - - ) : null} - - {/* TODO: {isOpenModal ? ( - - ) : null} */} - {/** 텍스트 영역* */} -
- {/** 제목* */} -
- {title} -
-
- ProfileIcon - {nickname} - -   |  {createdAt.slice(0, 10)} - -
-
-
- 한줄 소개 -
- {/** 한줄소개* */} -
{tldr}
-
- {/** 사용기술* */} - {selectedStack.length !== 0 ? ( -
-
- 기술 스택 -
- {selectedStack.map((x: string) => ( -
- Stack -

{x}

-
- ))} -
- ) : ( -
-
- 기술 스택 -
-
없음
-
- )} -
- {/** 글 영역* */} - {/* TODO: */} - {/** 버튼 영역* */} - {accessToken || persistToken ? ( -
- - - -
- ) : null} -
- {/* TODO: */} -
-
+
+ {/* */}
) } - -export default ReadingPage diff --git a/frontend/src/components/read/QuillWritten.tsx b/frontend/src/components/read/QuillWritten.tsx index e69de29b..859b8d25 100644 --- a/frontend/src/components/read/QuillWritten.tsx +++ b/frontend/src/components/read/QuillWritten.tsx @@ -0,0 +1,34 @@ +'use client' + +import { useEffect } from 'react' +import ReactQuill from 'react-quill' +import 'react-quill/dist/quill.bubble.css' +import { useRecoilState } from 'recoil' + +import { contentsState } from '../../utils/atoms' + +function QuillWrtten() { + const [contents, setContents] = useRecoilState(contentsState) + + useEffect(() => { + if (contents) { + setContents(contents) + } + }, [contents]) + + return ( +
+ {contents ? ( + + ) : ( + + )} +
+ ) +} + +export default QuillWrtten diff --git a/frontend/src/components/read/ReadReply.tsx b/frontend/src/components/read/ReadReply.tsx index e69de29b..8b137891 100644 --- a/frontend/src/components/read/ReadReply.tsx +++ b/frontend/src/components/read/ReadReply.tsx @@ -0,0 +1 @@ + diff --git a/frontend/src/components/read/Reply.tsx b/frontend/src/components/read/Reply.tsx index e69de29b..43ff1590 100644 --- a/frontend/src/components/read/Reply.tsx +++ b/frontend/src/components/read/Reply.tsx @@ -0,0 +1,228 @@ +'use client' + +import { useEffect, useRef, useState } from 'react' +import { useRecoilState, useRecoilValue } from 'recoil' + +// TODO: import ReadReply from './ReadReply' +import { projectIdState, refreshState } from '../../utils/atoms' + +type ReadReplyObject = { + commentId: number + childCount: number + content: string + createdAt: string +} + +type ReplyProps = { + contents: ReadReplyObject[] + setReadReply: React.Dispatch> +} + +function Reply({ contents, setReadReply }: ReplyProps) { + const [count, SetCount] = useState(0) + const [selectedValue, setSelectedValue] = useState('regist_order') + const textAreaRef = useRef(null) + const [value, setValue] = useState('') + const accessToken = + typeof window !== 'undefined' ? sessionStorage.getItem('accessToken') : null + const persistToken = + typeof window !== 'undefined' ? localStorage.getItem('persistToken') : null + const projectId = useRecoilValue(projectIdState) + const [refresh, setRefresh] = useRecoilState(refreshState) + const [visible, setVisible] = useState(true) + + const handleChange = (e: React.ChangeEvent) => { + const inputValue = e.target.value + if (textAreaRef.current) { + textAreaRef.current.style.height = 'auto' + textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px` + } + if (inputValue.length > 255) { + setValue(inputValue.substring(0, 255)) + alert('대댓글은 255자까지 입력하실 수 있습니다.') + return + } + setValue(inputValue) + } + + function MoveToTop() { + // top: 0 >> 맨 위로 behavior: 'smooth' >> 부드럽게 이동할 수 있게 설정하는 속성 + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + async function postData() { + const data = { + content: value, + projectId, + } + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/comments`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken || persistToken}`, + }, + body: JSON.stringify(data), + }) + + if (!res.ok) { + if (!navigator.onLine) { + alert('오프라인 상태입니다. 네트워크 연결을 확인해주세요.') + } else if (value.trim().length === 0) { + alert('댓글을 입력해주세요.') + } else { + alert('네트워크 오류') + throw new Error('네트워크 오류') + } + } else { + setRefresh(!refresh) + setValue('') + setSelectedValue('regist_order') + } + } + + function myFunction(selected: string) { + switch (selected) { + case 'newest_order': { + const sortedContents = contents + .slice() + .sort((a: ReadReplyObject, b: ReadReplyObject) => { + const aTime = new Date(a.createdAt).getTime() + const bTime = new Date(b.createdAt).getTime() + return bTime - aTime + }) + setReadReply(sortedContents) + break + } + case 'reply_order': { + const replyedContents = contents + .slice() + .sort( + (a: ReadReplyObject, b: ReadReplyObject) => + b.childCount - a.childCount, + ) + setReadReply(replyedContents) + break + } + default: { + const regisedContents = contents + .slice() + .sort((a: ReadReplyObject, b: ReadReplyObject) => { + const aTime = new Date(a.createdAt).getTime() + const bTime = new Date(b.createdAt).getTime() + return aTime - bTime + }) + setReadReply(regisedContents) + break + } + } + } + + function handleselectChange(event: { target: { value: string } }) { + const selectValue = event.target.value // 선택된 값 가져오기 + setSelectedValue(selectValue) // 선택된 값 상태 업데이트 + myFunction(selectValue) // 선택된 값 전달하여 실행할 함수 호출 + } + + function handleRefresh() { + setRefresh(!refresh) + setSelectedValue('regist_order') + } + + useEffect(() => { + SetCount( + contents.reduce( + (acc: number, cur: ReadReplyObject) => + acc + (cur.content !== '삭제된 댓글입니다.' ? 1 : 0), + 0, + ), + ) + }, [contents, setReadReply]) + + return ( +
+ {/* 댓글 개수, 댓글 나열 카테고리 */} +
+ + 전체 댓글

{count}

개 +
+ +
+ + + +
+
+ {/* 댓글 표시 */} +
+ {/* TODO: {visible ? ( + <> + {contents.map((x: ReadReplyObject, y: number) => + contents[y].content === '삭제된 댓글입니다.' && + contents[y].childCount === 0 ? null : ( + + ), + )} + + ) : null} */} + {/* 댓글 입력창 */} + {accessToken || persistToken ? ( +
+
+