diff --git a/README.md b/README.md
index 5b1aaaf..5c07751 100644
--- a/README.md
+++ b/README.md
@@ -1,76 +1,197 @@
-# 정석코딩 2기 1팀 프로젝트 포스토리
-## 팀원
-- 황인태 (팀장)
-- 김정호
-- 강세민
+# POSTORY-검색형 웹툰 웹소설 플랫폼
-# Getting Started with Create React App
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
+작가들 번아웃과 독자들 피로감을 해결하기 위한 구글식 컨텐츠 플랫폼입니다.
+내용을 검색하고, 마음에 드는 채널을 구독하고, 보고 싶은 내용을 스크랩합니다.
-## Available Scripts
+## 프로젝트 설명
+지속가능한 웹툰 웹소설 플랫폼을 만들고자 하였습니다.
+그러기 위해 크리에이터에게 비교보단 자기 자신 작품에 집중하는 환경을 만들어주고,
+독자에겐 주어진 작품을 보기보단 원하는 작품을 찾아 보관하는 경험을 제공했습니다.
+독자와 크리에이터, 크리에이터와 크리에이터, 작품과 작품 간 상호작용이 많아질 수록
+비슷한 작품만 만들어지고, 그런 작품 중 수작이 나오는 건 규모가 큰 기업만 가능하다는 판단을 했기 때문입니다.
-In the project directory, you can run:
+### 사용한 기술들
-### `yarn start`
+1. Back-end
-Runs the app in the development mode.\
-Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
+ 처음엔 Spring Legacy를 maven 방식으로 빌드했습니다. 그리고 spring security를 적용했습니다. 그리고 완성한 영역 외에 결제, 멤버십, 상품, 포인트 등 모두 68개 테이블을 설계했었습니다.
-The page will reload when you make changes.\
-You may also see any lint errors in the console.
+ spring security를 spring legacy 환경에서 사용할 수 있게 했으나,
+ 팀원 모두가 이해하기엔 어렵다 판단해 spring boot로 전환했습니다. 프로젝트 완성 기한을 맞추기 위해 앞서 언급한 상당수 기능 구현을 포기했습니다. 더 자세한 이유는 링크로 첨부합니다.
+ https://velog.io/@iamloved5959/Spring-Legacy%EC%99%80-React%EC%97%90%EC%84%9C-spring-security-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0
-### `yarn test`
+ 최종적으로 사용한 기술은 다음과 같습니다.
+ * Spring Boot
-Launches the test runner in the interactive watch mode.\
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
+ spring security 적용을 모두가 쉽게 할 수 있어 사용했습니다.
+ * Spring Security
-### `yarn build`
+ JWT 방식을 사용하기 쉬워 사용했습니다.
+ * Mybatis
-Builds the app for production to the `build` folder.\
-It correctly bundles React in production mode and optimizes the build for the best performance.
+ 쿼리를 연습할 시간이라 판단하여 사용했습니다.
+ * gradle
-The build is minified and the filenames include the hashes.\
-Your app is ready to be deployed!
+ 추후 서비스를 확장할 때 변경을 쉽게 할 수 있게 maven보다 gradle을 선택했습니다.
+ * MySql
-See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
+ AWS 서비스들과 연동이 쉬워 사용했습니다.
+ * AWS RDS
-### `yarn eject`
+ 공용 database를 위해 선택했습니다. 공식문서와 유저풀이 풍부해 자료를 찾기 쉬웠습니다.
+ * AWS S3
-**Note: this is a one-way operation. Once you `eject`, you can't go back!**
+ 이미지 업로드를 하기 위해 사용했습니다. 처음엔 AWS 공부할 여유가 없다 판단해 local react 서버에 저장하는 방법을 택했습니다. 그러나 client가 server 컴퓨터 경로를 알아낼 수 있다는 점과 리액트 서버가 무거워진다는 단점이 있어 AWS S3를 도입했습니다.
+ * AWS EC2
-If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
+ 배포를 하기 위해 이용한 서비스입니다. 다른 서비스들보다 현장에서 많이 사용하는 서비스였고, 공식문서와 그외 자료들에 접근하기 쉬웠습니다. 또한 웹툰 웹소설 플랫폼 특성상 많은 트래픽 처리를 하게 될 거라 판단했습니다. 접근성과 중요성, 확장성을 모두 만족하는 서비스이기에 선택했습니다.
-Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
-You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
+3. Front-end
-## Learn More
+ 팀원이 3명밖에 되지 않아 재사용성에 초점을 뒀습니다.
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
+ 팀 프로젝트를 위해 상품 게시판, 상품 뷰, 결제, 주문, 주문 목록, 주문 상세를 포기했습니다.
-To learn React, check out the [React documentation](https://reactjs.org/).
+ 프로젝트를 기한 내 맞추기 위해 팀원들이 사용할 수 있게 자체 제작 form library를 만들었습니다.
+ 참조하는 POSTYPE 사이트 폼 형식에 패턴을 발견했고, 변수만 바꿔서 사용하면 개발 시간이 단축된다 판단했기 때문입니다.
+ 백엔드 개발자 취업이 목표기에 렌더링 시간은 고려 대상이 아니었기 때문이기도 합니다.
+ 그러나 짧은 시간에 팀원들을 설득하지 못해 사용하지 못했습니다.
+ 폼 라이브러리 자료는 컴퓨터 백업 문제로 자료가 날라갔습니다. 자료를 복원시키는 즉시 링크를 첨부하겠습니다.
-### Code Splitting
+ * React, JSX
-This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
+ 재사용성을 위해 javascript보다 React를 이용한 JSX를 사용했습니다.
+ * styled-components
-### Analyzing the Bundle Size
+ 위와 같은 이유로 html, css보다 styled-componets를 사용했습니다.
+ * javascript, html, css
-This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
+ 위 기술들을 보완하는 차원에서 사용했습니다.
+ 동적으로 조건을 판단할 때 javascript를 사용했고, 포스트 내용을 통으로 저장할 때 html, css를 사용했습니다.
+ * react quill
-### Making a Progressive Web App
+ 핸들러 기능을 사용하고 에디터 객체에 접근하기 쉬워 사용했습니다. DB 비용을 줄여야 했습니다.
+ 그렇기 위해서 base64로 나타나는 이미지 태그 src 값을 짧은 String URL로 변환해야 했습니다.
+ react quill은 업로드하는 이미지에 이미지 핸들러로 접근할 수 있게 해줬고,
+ 그 이미지 base64 src 값만 서버에 보내 짧은 String 고유값(URL)으로 변환했습니다.
+ S3 서버에 업로드한 후, 고유값으로 바뀐 URL을 client에게 전달했습니다.
+ 그리고 에디터 객체에 접근해 현재 커서가 있는 곳 위치에 src를 이 값으로 바꿨습니다.
+ 포스트를 수정할 때도 위 편의 기능들을 유용하게 썼습니다.
+ * AWS EC2
-This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
+ 백엔드에서 서술했던 내용과 동일합니다.
+ * nginix
-### Advanced Configuration
+ 동시에 여러명이 이용하는 웹툰 웹소설 플랫폼 특성을 고려해 비동기 통신에 효율적인 nginix를 이용했습니다.
+ * yarn
-This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
+ 팀원 간 컴퓨터 환경이 달랐기에 yarn을 이용해 package를 관리했습니다.
+ * ZUSTAND
-### Deployment
+ 로그인한 유저 정보와 같이 전역변수를 관리하기 위해 사용했습니다.
-This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
+## 프로젝트 설치 및 실행 방법
+develop branch 기준으로 진행해주세요.(main, dev_kjh는 완성본이 아닙니다.)
-### `yarn build` fails to minify
+1. git clone으로 코드를 받아주세요.
+ ```
+ git clone git clone -b develop https://github.com/dancingKim/postory_back.git
+ ```
-This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
+2. 아직 도메인을 사진 않았습니다. 현재 AWS 계정 비밀번호를 아는 것 외에 프로그램을 로컬에서 실행시킬 수 있는 방법이 없습니다. 추후 도메인을 산 후에 업데이트 하겠습니다.
+3. 불편하시더라도 코드 위주로 봐주세요.
+## 프로젝트 사용 방법
+
+
+
+원하는 키워드로 검색을 합니다.
+
+
+만화 키워드로 웹툰을 검색했습니다.
+관심 포스트를 스크랩합니다.
+
+
+
+
+보관함에서 스크랩한 포스트를 모아볼 수 있습니다.
+
+
+
+
+채널을 검색해 들어갈 수도 있습니다.
+채널을 구독할 수도 있습니다.
+구독한 채널과 채널이 발행한 포스트들은 구독 탭에서 볼 수 있습니다.
+
+
+
+
+내 채널에선 포스트를 발행할 수 있습니다.
+구독버튼 자리에 포스트 발행하기 버튼이 뜹니다.
+
+
+
+
+웹툰, 웹소설 타입으로 포스트를 발행할 수 있습니다.
+
+
+
+포스트를 보고, 마음에 안 들면 수정할 수도 있습니다.
+이미지를 추가해 보았습니다.
+
+
+
+
+## 팀원 및 참고 자료
+### 팀 소개
+1. 팀장
+황인태
+
+2. 팀원
+김정호
+강세민
+
+### 참고 자료
+### 참조 사이트
+POSTYPE
+### 참조 컨텐츠
+NAVER WEBTOON
+
+https://comic.naver.com/webtoon/list?titleId=769209
+
+https://comic.naver.com/webtoon/list?titleId=687915
+
+https://comic.naver.com/webtoon/list?titleId=809054
+
+https://comic.naver.com/webtoon/list?titleId=807178
+
+https://comic.naver.com/webtoon/list?titleId=747269
+
+https://comic.naver.com/webtoon/list?titleId=758662
+
+NAVER WEBNOVEL
+
+https://novel.naver.com/webnovel/list?novelId=1062250
+
+https://novel.naver.com/webnovel/list?novelId=1073928
+
+https://novel.naver.com/webnovel/list?novelId=949763
+
+https://novel.naver.com/webnovel/list?novelId=1058954
+
+https://novel.naver.com/best/list?novelId=22398
+
+OTHERS
+
+https://bbs.ruliweb.com/family/212/board/300063/read/30643759
+
+https://cafe.naver.com/steamindiegame/5396703
+
+https://softwaremill.com/programmers-day-programming-memes-2022/
+
+https://velog.io/@heelieben/%EA%B0%9C%EB%B0%9C-%EA%B3%B5%EA%B0%90-%EC%A7%A4-%EA%B5%AC%EA%B2%BD%ED%95%98%EA%B3%A0-%EA%B0%80%EC%84%B8%EC%9A%94
+
+## 라이센스
+
+“This project is licensed under the terms of the MIT license.”
diff --git a/package.json b/package.json
index 8b82a09..e147d4c 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.0",
+ "@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@testing-library/jest-dom": "^5.14.1",
@@ -12,10 +13,12 @@
"axios": "^1.3.6",
"date-fns": "^2.30.0",
"json-server": "^0.17.3",
+ "quill-image-resize": "^3.0.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.8.0",
"react-modal": "^3.16.1",
+ "react-quill": "^2.0.0",
"react-router-dom": "^6.10.0",
"react-scripts": "5.0.1",
"react-tippy": "^1.4.0",
diff --git a/src/api.js b/src/api.js
index fb2ca6e..c6dc34f 100644
--- a/src/api.js
+++ b/src/api.js
@@ -2,6 +2,8 @@ import axios from 'axios';
export const api = axios.create({
baseURL: 'http://localhost:8080',
+ // baseURL: 'http://3.39.6.61:8080',
+
});
api.interceptors.request.use(
diff --git a/src/appRoute.js b/src/appRoute.js
index 9af8142..d9a2718 100644
--- a/src/appRoute.js
+++ b/src/appRoute.js
@@ -1,7 +1,7 @@
import { Navigate } from 'react-router-dom';
import Home from './pages/general/home/Home';
-import SearchPost from './pages/general/search/SearchPost';
-import SearchSeries from './pages/general/search/SearchSeries';
+import SearchWebtoon from './pages/general/search/SearchWebtoon';
+import SearchSeries from './pages/general/search/SearchWebnovel';
import SearchChannel from './pages/general/search/SearchChannel';
import Login from './pages/auth/Login';
import Signup from './pages/auth/Signup';
@@ -21,9 +21,12 @@ import ProfileSettings from './pages/general/accSettings/ProfileSettings';
import BlackedChannel from './pages/general/accSettings/BlackedChannel';
import ChannelCreate from './pages/general/ChannelCreate';
import ChannelHome from './pages/general/channel/ChannelHome';
-import ChannelPosts from './pages/general/channel/ChannelPosts';
-import ChannelSeries from './pages/general/channel/ChannelSeries';
+import ChannelWebtoon from './pages/general/channel/ChannelWebtoon';
+import ChannelWebnovel from './pages/general/channel/ChannelWebnovel';
import ChannelAbout from './pages/general/channel/ChannelAbout';
+import PostCreate from './pages/general/create/PostCreate';
+import PostView from './pages/general/view/PostView';
+import SearchWebnovel from './pages/general/search/SearchWebnovel';
const urls = {
login: '/login',
@@ -58,24 +61,25 @@ const urls = {
//channel
channelCreate: '/channel/create',
- // TODO: chnlUri 변수 수정
- // channel: '/channel/:chnlUri',
- // channelPosts: '/channel/:chnlUri/posts',
- // channelSeries: '/channel/:chnlUri/series',
- // channelAbout: '/channel/:chnlUri/about',
- channel: '/channel/buksan',
- channelPosts: '/channel/buksan/posts',
- channelSeries: '/channel/buksan/series',
- channelAbout: '/channel/buksan/about',
-
- search: '/search',
- searchSeries: '/search/series',
+ channel: '/channel/:chnlUri',
+ channelWebtoon: '/channel/:chnlUri/webtoon',
+ channelWebnovel: '/channel/:chnlUri/webnovel',
+ channelAbout: '/channel/:chnlUri/about',
+
+ searchWebtoon: '/search/webtoon',
+ searchWebnovel: '/search/webnovel',
searchChannel: '/search/channel',
//profile
profile: `/profile/:nic`,
profilePost: `/profile/:nic/post`,
profileSeries: `/profile/:nic/series`,
+
+ // create
+ postCreate: `/:chnlUri/post/create`,
+
+ // view
+ postView: '/post/:postId',
};
// Links
@@ -88,7 +92,7 @@ export const headerLinks = [
// navLinks
export const homeLinks = [
- { to: urls.home, children: '추천' },
+ { to: urls.home, children: '채널' },
{ to: urls.webtoon, children: '웹툰' },
{ to: urls.webnovel, children: '웹소설' },
];
@@ -120,8 +124,8 @@ export const accSetLinks = [
export const channelLinks = [
{ to: urls.channel, children: '홈' },
- { to: urls.channelPosts, children: '포스트' },
- { to: urls.channelSeries, children: '시리즈' },
+ { to: urls.channelWebtoon, children: '웹툰' },
+ { to: urls.channelWebnovel, children: '웹소설' },
{ to: urls.channelAbout, children: '소개' },
];
@@ -158,8 +162,8 @@ export const pageRoutes = [
{ id: 19, path: urls.setBlacklist, element: },
//search
- { id: 20, path: urls.search, element: },
- { id: 21, path: urls.searchSeries, element: },
+ { id: 20, path: urls.searchWebtoon, element: },
+ { id: 21, path: urls.searchWebnovel, element: },
{ id: 22, path: urls.searchChannel, element: },
//profile
@@ -170,7 +174,13 @@ export const pageRoutes = [
//channel
{ id: 26, path: urls.channelCreate, element: },
{ id: 27, path: urls.channel, element: },
- { id: 28, path: urls.channelPosts, element: },
- { id: 29, path: urls.channelSeries, element: },
+ { id: 28, path: urls.channelWebtoon, element: },
+ { id: 29, path: urls.channelWebnovel, element: },
{ id: 30, path: urls.channelAbout, element: },
+
+ // create
+ { id: 31, path: urls.postCreate, element: },
+
+ // view
+ { id: 32, path: urls.postView, element: },
];
diff --git a/src/components/atoms/Button/ConfirmBlackBtnSC.js b/src/components/atoms/Button/ConfirmBlackBtnSC.js
new file mode 100644
index 0000000..92ef410
--- /dev/null
+++ b/src/components/atoms/Button/ConfirmBlackBtnSC.js
@@ -0,0 +1,19 @@
+import styled from 'styled-components';
+import { btnStyle } from '../../styled/utils';
+
+const ConfirmBlackBtnSC = styled.button`
+ ${btnStyle};
+
+ color: white;
+ background-color: ${(p) => p.theme.color.blackA80};
+ border: 1px solid ${(p) => p.theme.color.blackA80};
+ opacity: ${(p) => (p.disabled ? 0.3 : 1)};
+
+ &:hover {
+ color: white;
+ background-color: ${(p) => p.theme.color.blackA30};
+ border-color: ${(p) => p.theme.color.blackA30};
+ }
+`;
+
+export default ConfirmBlackBtnSC;
diff --git a/src/components/atoms/Channel/SubsBlackBtn.js b/src/components/atoms/Channel/SubsBlackBtn.js
new file mode 100644
index 0000000..1d21d3f
--- /dev/null
+++ b/src/components/atoms/Channel/SubsBlackBtn.js
@@ -0,0 +1,11 @@
+import styled from 'styled-components';
+import ConfirmBtnSC from '../Button/ConfirmBtnSC';
+import ConfirmBlackBtnSC from '../Button/ConfirmBlackBtnSC';
+
+const SubsBtnSC = styled(ConfirmBlackBtnSC)`
+ padding: 4px 8px;
+`;
+
+export default function SubsBlackBtn(props) {
+ return + 구독 ;
+}
diff --git a/src/components/molecules/channel/SubscribeBlackBtn.js b/src/components/molecules/channel/SubscribeBlackBtn.js
new file mode 100644
index 0000000..5e20537
--- /dev/null
+++ b/src/components/molecules/channel/SubscribeBlackBtn.js
@@ -0,0 +1,89 @@
+import { useState } from 'react';
+import SubsBtn from '../../atoms/Channel/SubsBtn';
+import UnsubsBtn from '../../atoms/Channel/UnsubsBtn';
+import { useApiPost, useApiDelete } from '../../../hooks/useApi';
+import useModal from '../modal/useModal';
+import ConfirmBox from '../modal/ConfirmBox';
+import { useEffect } from 'react';
+import useSubChannelList from '../../../stores/useSubChannelList';
+import SubsBlackBtn from '../../atoms/Channel/SubsBlackBtn';
+
+export default function SubscribeBlackBtn({ isSubsed, chnlId }) {
+ const [isSubscribed, setIsSubscribed] = useState(isSubsed);
+ const { isOpen, openModal, closeModal } = useModal();
+ const {
+ res: subsRes,
+ error: subsErr,
+ setError: setSubsErr,
+ postData: subscribe,
+ } = useApiPost(`/subscriptions`, { chnlId: chnlId });
+ const {
+ res: unsubsRes,
+ error: unsubsErr,
+ setError: setUnsubsErr,
+ deleteData: unsubscribe,
+ } = useApiDelete(`/subscriptions/cancle?chnlId=${chnlId}`);
+ const { removeChannel } = useSubChannelList();
+ const currentPath = window.location.pathname;
+
+ useEffect(() => {
+ if (subsRes) {
+ setIsSubscribed(true);
+ }
+ }, [subsRes]);
+
+ useEffect(() => {
+ if (unsubsRes) {
+ if (!currentPath.includes('/profile')) {
+ removeChannel(chnlId);
+ }
+ setIsSubscribed(false);
+ }
+ }, [unsubsRes, currentPath]);
+
+ const handleSubscribe = () => {
+ console.log('구독 실행');
+
+ subscribe({
+ chnlId: chnlId,
+ });
+ };
+
+ const handleUnsubscribe = () => {
+ openModal();
+ };
+
+ // 'Confirm' 버튼이 클릭되었을 때 호출될 콜백 함수
+ const handleConfirm = () => {
+ console.log('구독 취소 실행');
+ closeModal();
+ unsubscribe();
+ };
+
+ // 'Cancel' 버튼이 클릭되었을 때 호출될 콜백 함수
+ const handleCancel = () => {
+ console.log('구독 취소 취소');
+ closeModal();
+ };
+
+ return (
+
+ {isSubscribed ? (
+ <>
+ 구독 중
+ {isOpen && (
+
+ )}
+ >
+ ) : (
+ + 구독
+ )}
+
+ );
+}
diff --git a/src/components/molecules/general/HomeSearchBox.js b/src/components/molecules/general/HomeSearchBox.js
new file mode 100644
index 0000000..709bca4
--- /dev/null
+++ b/src/components/molecules/general/HomeSearchBox.js
@@ -0,0 +1,58 @@
+import styled from 'styled-components';
+import { TfiSearch } from 'react-icons/tfi';
+import { BsFillXCircleFill } from 'react-icons/bs';
+import WrapBtnSC from '../../atoms/Button/WrapBtnSC';
+import SearchInput from '../../atoms/Input/SearchInputSC';
+import { useSearchBox } from './useSearchBox';
+
+const SearchBoxSC = styled.div`
+ flex: 1;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+const SearchInnerSC = styled.div`
+ position: relative;
+
+ .icon {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ left: 14px;
+ color: ${(p) => p.theme.color.link};
+ }
+
+ .btn {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ right: 16px;
+ color: ${(p) => p.theme.color.link};
+ }
+`;
+
+export default function HomeSearchBox({type}) {
+ console.log(type);
+ const { value, onChange, onKeyDown, onClick } = useSearchBox(`/search/${type}`);
+
+ return (
+
+
+
+
+ {value.length > 0 && (
+
+
+
+ )}
+
+
+ );
+}
diff --git a/src/components/molecules/general/SearchBox.js b/src/components/molecules/general/SearchBox.js
index f7be979..0188217 100644
--- a/src/components/molecules/general/SearchBox.js
+++ b/src/components/molecules/general/SearchBox.js
@@ -34,7 +34,7 @@ const SearchInnerSC = styled.div`
`;
export default function SearchBox() {
- const { value, onChange, onKeyDown, onClick } = useSearchBox('/search');
+ const { value, onChange, onKeyDown, onClick } = useSearchBox('/search/webtoon');
return (
diff --git a/src/components/molecules/general/useSearchBox.js b/src/components/molecules/general/useSearchBox.js
index 575b815..b769608 100644
--- a/src/components/molecules/general/useSearchBox.js
+++ b/src/components/molecules/general/useSearchBox.js
@@ -12,7 +12,7 @@ export const useSearchBox = (resultUrl) => {
const onKeyDown = (e) => {
if (e.key === 'Enter') {
// Enter 키를 눌렀을 때 실행할 동작
- navigate(`${resultUrl}?keyword=${encodeURIComponent(value)}`);
+ navigate(`${resultUrl}?keyword=${encodeURIComponent(value)}&option=all`);
}
};
diff --git a/src/components/molecules/post/HeartBtn.js b/src/components/molecules/post/HeartBtn.js
new file mode 100644
index 0000000..6698089
--- /dev/null
+++ b/src/components/molecules/post/HeartBtn.js
@@ -0,0 +1,16 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import styled from 'styled-components';
+
+const HeartBtnSC = styled.button`
+
+`
+
+export default function HeartBtn(props) {
+
+
+ return (
+
+ ;
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/molecules/post/LikeBtn.js b/src/components/molecules/post/LikeBtn.js
new file mode 100644
index 0000000..458225e
--- /dev/null
+++ b/src/components/molecules/post/LikeBtn.js
@@ -0,0 +1,92 @@
+import { useState } from 'react';
+import { useApiPost, useApiDelete } from '../../../hooks/useApi';
+import useModal from '../modal/useModal';
+import ConfirmBox from '../modal/ConfirmBox';
+import { useEffect } from 'react';
+import useSubChannelList from '../../../stores/useSubChannelList';
+import { AiOutlineHeart } from 'react-icons/ai';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import UnHeartBtn from './UnHeartBtn';
+import HeartBtn from './HeartBtn';
+
+export default function LikeBtn({ liked, postId }) {
+ const [isLiked, setIsLiked] = useState(liked);
+ const { isOpen, openModal, closeModal } = useModal();
+ const {
+ res: likeRes,
+ error: likeErr,
+ setError: setLikeErr,
+ postData: like,
+ } = useApiPost(`/library/like`, { postId: postId });
+ const {
+ res: unlikeRes,
+ error: unlikeErr,
+ setError: setUnlikeErr,
+ deleteData: unlike,
+ } = useApiDelete(`/library/like?postId=${postId}`);
+ const { removeChannel } = useSubChannelList();
+ const currentPath = window.location.pathname;
+
+ useEffect(() => {
+ if (likeRes) {
+ setIsLiked(true);
+ }
+ }, [likeRes]);
+
+ useEffect(() => {
+ if (unlikeRes) {
+ if (!currentPath.includes('/profile')) {
+ removeChannel(postId);
+ }
+ setIsLiked(false);
+ }
+ }, [unlikeRes, currentPath]);
+
+ const handleLike = () => {
+ console.log('구독 실행');
+
+ like({
+ postId: postId,
+ });
+ };
+
+ const handleUnlike = () => {
+ openModal();
+ };
+
+ // 'Confirm' 버튼이 클릭되었을 때 호출될 콜백 함수
+ const handleConfirm = () => {
+ console.log('좋아요 취소 실행');
+ closeModal();
+ unlike();
+ };
+
+ // 'Cancel' 버튼이 클릭되었을 때 호출될 콜백 함수
+ const handleCancel = () => {
+ console.log('좋아요 취소 취소');
+ closeModal();
+ };
+
+ return (
+
+ {isLiked ? (
+ <>
+
+
+ {isOpen && (
+
+ )}
+ >
+ ) : (
+
+
+ )}
+
+ );
+}
diff --git a/src/components/molecules/post/PostRadioButton.js b/src/components/molecules/post/PostRadioButton.js
new file mode 100644
index 0000000..0eeb5ee
--- /dev/null
+++ b/src/components/molecules/post/PostRadioButton.js
@@ -0,0 +1,55 @@
+import React, { useState } from 'react';
+import styled from 'styled-components';
+
+const RadioButtonContainer = styled.div`
+ display: flex;
+ gap: 2px;
+`;
+
+const RadioButtonLabel = styled.label`
+ display: inline-block;
+ background-color: ${({ checked }) => (checked ? '#3478FF' : '#fff')};
+ color: ${({ checked }) => (checked ? '#fff' : 'rgba(0, 0, 0, 0.3)')};
+ border: 2px solid #eee;
+ border-radius: 8px;
+ padding: 8px 30px;
+ cursor: pointer;
+ transition: background-color 0.3s, color 0.3s;
+
+ &:hover {
+ background-color: #3478FF;
+ color: #fff;
+ }
+`;
+
+const RadioButtonInput = styled.input`
+ display: none;
+`
+const PostRadioButton = ({ options, selectedOption, handleOptionChange }) => {
+ // const [selectedOption, setSelectedOption] = useState('');
+
+ // const handleOptionChange = (event) => {
+ // setSelectedOption(event.target.value);
+ // };
+
+ return (
+
+ {options.map((option) => (
+
+
+ {option.label}
+
+ ))}
+
+ );
+};
+
+export default PostRadioButton;
diff --git a/src/components/molecules/post/UnHeartBtn.js b/src/components/molecules/post/UnHeartBtn.js
new file mode 100644
index 0000000..4b2b0a4
--- /dev/null
+++ b/src/components/molecules/post/UnHeartBtn.js
@@ -0,0 +1,15 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import styled from 'styled-components';
+
+const UnHeartBtnSC = styled.button`
+
+`
+
+export default function UnHeartBtn(props) {
+
+ return (
+
+ ;
+
+ )
+}
diff --git a/src/components/templates/general/ChannelTemplate.js b/src/components/templates/general/ChannelTemplate.js
index a2a5442..1760278 100644
--- a/src/components/templates/general/ChannelTemplate.js
+++ b/src/components/templates/general/ChannelTemplate.js
@@ -2,8 +2,15 @@ import NavMenu from '../../molecules/general/NavMenu';
import Nav from '../../organisms/general/Nav';
import Header from '../../organisms/general/Header';
import Main from '../../organisms/general/Main';
-import channelData from '../../../tempData/channel/channel.json';
import styled from 'styled-components';
+import { useApiGet } from '../../../hooks/useApi';
+import { useParams } from 'react-router-dom';
+import useUserStore from '../../../stores/useUserStore';
+import { useEffect, useState } from 'react';
+import BtnLinkSC from '../../atoms/Link/BtnLinkSC';
+import SubscribeBtn from '../../molecules/channel/SubscribeBtn';
+import SubscribeBlackBtn from '../../molecules/channel/SubscribeBlackBtn';
+
const Thumbnail = styled.div`
width: 80px;
@@ -29,28 +36,46 @@ const ChannelTitle = styled.h2`
`;
export default function ChannelTemplate(p) {
- console.log(channelData);
- // TODO: channlUri 변수 처리
+ const { chnlUri } = useParams();
+
+ const [url, setUrl] = useState(`/channel/${encodeURIComponent(chnlUri)}`);
+ const { data, isLoading, error } = useApiGet(
+ url,
+ [url]
+ );
+ useEffect(() => {
+ setUrl(`/channel/${encodeURIComponent(chnlUri)}`);
+ }, [chnlUri]);
+
+ const { user } = useUserStore();
+ const {data: subsData, isLoading: isSubsLoading, error: subsError} = useApiGet(`/channel/${encodeURIComponent(chnlUri)}/subs/${encodeURIComponent(user.nic)}`,[chnlUri])
+
+ if (isLoading) return;
+ if (error) return {`[${error.code}] ${error.message}`} ;
+
+ if (!data) {
+ return null;
+ }
+
+ const isOwner = user.nic === data.data.channelUser.nic;
+
+
const mainNavLinks = [
{
- // to: `/channel/${encodeURIComponent(p.nic)}`,
- to: `/channel/buksan`,
+ to: `/channel/${encodeURIComponent(chnlUri)}`,
children: '홈',
end: true,
},
{
- // to: `/channel/${encodeURIComponent(p.nic)}/posts`,
- to: `/channel/buksan/posts`,
- children: '포스트',
+ to: `/channel/${encodeURIComponent(chnlUri)}/webtoon`,
+ children: '웹툰',
},
{
- // to: `/channel/${encodeURIComponent(p.nic)}/series`,
- to: `/channel/buksan/series`,
- children: '시리즈',
+ to: `/channel/${encodeURIComponent(chnlUri)}/webnovel`,
+ children: '웹소설',
},
{
- // to: `/channel/${encodeURIComponent(p.nic)}/about`,
- to: `/channel/buksan/about`,
+ to: `/channel/${encodeURIComponent(chnlUri)}/about`,
children: '소개',
},
];
@@ -58,19 +83,21 @@ export default function ChannelTemplate(p) {
return (
<>
- {p.nic}
-
+
- 구독자 {channelData.data.channel.suberCnt}명 · 포스트{' '}
- {channelData.data.channel.chnlPostCnt}개
+ 구독자 {data.data.channel.suberCnt}명 · 포스트{' '}
+ {data.data.channel.chnlPostCnt}개
- {channelData.data.channel.chnlTtl}
- {/*
- // TODO: 구독하기 버튼 처리
-
- 구독하기 버튼
-
*/}
+ {data.data.channel.chnlTtl}
+
+ {isOwner ?
+ 포스트 발행하기 :
+ // 구독하기
+
+ }
+
+
@@ -78,4 +105,4 @@ export default function ChannelTemplate(p) {
>
);
-}
+}
\ No newline at end of file
diff --git a/src/components/templates/general/CreateTemplate.js b/src/components/templates/general/CreateTemplate.js
new file mode 100644
index 0000000..4df08b8
--- /dev/null
+++ b/src/components/templates/general/CreateTemplate.js
@@ -0,0 +1,14 @@
+import Nav from '../../organisms/general/Nav';
+import Header from '../../organisms/general/Header';
+import Main from '../../organisms/general/Main';
+
+export default function CreateTemplate(p) {
+
+ return (
+ <>
+
+ {p.nic}
+ {p.children}
+ >
+ );
+}
diff --git a/src/components/templates/general/SearchTemplate.js b/src/components/templates/general/SearchTemplate.js
index 6674537..359fbf5 100644
--- a/src/components/templates/general/SearchTemplate.js
+++ b/src/components/templates/general/SearchTemplate.js
@@ -6,22 +6,22 @@ import Main from '../../organisms/general/Main';
export default function SearchTemplate(p) {
const navLinks = [
{
- to: `/search?keyword=${encodeURIComponent(p.keyword)}`,
+ to: `/search/channel?keyword=${encodeURIComponent(p.keyword)}`,
children: '검색 결과',
isTitle: true,
},
{
- to: `/search?keyword=${encodeURIComponent(p.keyword)}`,
- children: '포스트',
- end: true,
+ to: `/search/channel?keyword=${encodeURIComponent(p.keyword)}`,
+ children: '채널',
},
{
- to: `/search/series?keyword=${encodeURIComponent(p.keyword)}`,
- children: '시리즈',
+ to: `/search/webtoon?keyword=${encodeURIComponent(p.keyword)}`,
+ children: '웹툰',
+ end: true,
},
{
- to: `/search/channel?keyword=${encodeURIComponent(p.keyword)}`,
- children: '채널',
+ to: `/search/webnovel?keyword=${encodeURIComponent(p.keyword)}`,
+ children: '웹소설',
},
];
diff --git a/src/components/templates/general/ViewTemplate.js b/src/components/templates/general/ViewTemplate.js
new file mode 100644
index 0000000..e29ea96
--- /dev/null
+++ b/src/components/templates/general/ViewTemplate.js
@@ -0,0 +1,13 @@
+import Nav from '../../organisms/general/Nav';
+import Header from '../../organisms/general/Header';
+import Main from '../../organisms/general/Main';
+
+export default function ViewTemplate(p) {
+ return (
+ <>
+
+ {p.nic}
+ {p.children}
+ >
+ );
+}
diff --git a/src/hooks/useApi.js b/src/hooks/useApi.js
index 76fae39..298fe76 100644
--- a/src/hooks/useApi.js
+++ b/src/hooks/useApi.js
@@ -7,6 +7,7 @@ export function useApiGet(url) {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
+ console.dir(url);
useEffect(() => {
(async () => {
@@ -213,3 +214,28 @@ export function useApiFormPut(url) {
return { isUpdating, putData, error, res, setRes, setError };
}
+export function useConditionalApiGet(url, postId) {
+ const [data, setData] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (postId !== null) {
+ (async () => {
+ setIsLoading(true);
+ try {
+ const res = await api.get(url);
+ console.log('get 성공', res);
+ setData(res.data);
+ } catch (err) {
+ console.log('get 실패', err);
+ setError(err);
+ } finally {
+ setIsLoading(false);
+ }
+ })();
+ }
+ }, [url, postId]);
+
+ return { data, isLoading, error };
+}
\ No newline at end of file
diff --git a/src/pages/general/channel/ChannelAbout.js b/src/pages/general/channel/ChannelAbout.js
index 19828e3..ae31264 100644
--- a/src/pages/general/channel/ChannelAbout.js
+++ b/src/pages/general/channel/ChannelAbout.js
@@ -1,31 +1,139 @@
import ChannelTemplate from '../../../components/templates/general/ChannelTemplate';
-import channelAboutData from '../../../tempData/channel/channelAbout.json';
+import styled from 'styled-components';
+import { Link, useParams } from 'react-router-dom';
+import { useApiGet } from '../../../hooks/useApi';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faClone } from '@fortawesome/free-regular-svg-icons'
+import MetaDataSC from '../../../components/atoms/User/MetaDataSC';
+import { AiOutlineEye, AiOutlineHeart } from 'react-icons/ai';
+import { countDate } from '../../../components/molecules/user/dateConversion';
+import { faCalendarAlt, faUser } from '@fortawesome/free-solid-svg-icons';
+import { useNavigate } from 'react-router';
+
+const ContentContainer = styled.div`
+ padding: 20px 0;
+`;
+
+const AboutSectionContainer = styled.div`
+ padding: 32px 0;
+`;
+
+const ChannelSection = styled(AboutSectionContainer)`
+ display: flex;
+ border-bottom: 1px solid rgba(0,0,0,.05);
+`;
+
+const UserSection = styled(AboutSectionContainer)`
+ display: flex;
+
+`;
+
+const RecThumnail = styled.div`
+ width: 60px;
+ height: 60px;
+ background-color: #ffffff;
+ border-radius: 8px;
+ margin-right: 10px;
+ display: inline-block;
+ background-image: url(${props => props.imageUrl});
+ background-size: cover;
+ border: 1px solid rgba(0,0,0,.05);
+ margin-right: 28px;
+`;
+
+const SectionTitle = styled.h3`
+ font-size: 20px;
+`
+
+const ChannelBottomContainer = styled.div`
+ display: flex;
+ list-style: none;
+ color: rgba(0,0,0,.47);
+ margin: 20px 0 0;
+ li {
+ margin: 10px; // Adds 10px of space on all sides of the list items
+ }
+<<<<<<< HEAD
+=======
+
+>>>>>>> 4bb3fd1 (style: update style in ChannelAbout)
+`
+
+const RoundThumnail = styled.div`
+ width: 60px;
+ height: 60px;
+ background-color: #ffffff;
+ border-radius: 60px;
+ margin-right: 10px;
+ display: inline-block;
+ background-image: url(${props => props.imageUrl});
+ background-size: cover;
+ border: 1px solid rgba(0,0,0,.05);
+ margin-right: 28px;
+`;
+const UserBannerSC = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding-right: 20px;
+ padding-top: 20px;
+
+ .nextImg {
+ flex: 1;
+ }
+
+ a {
+ display: flex;
+ align-items: center;
+ }
+`;
export default function ChannelAbout() {
+ const { chnlUri } = useParams();
+ const { data, isLoading, error } = useApiGet(
+ `/channel/${encodeURIComponent(chnlUri)}/about`,
+ [chnlUri]
+ );
+
+ const navigate = useNavigate();
+
+ if (isLoading) return;
+ if (error) return {`[${error.code}] ${error.message}`} ;
+
+ if (!data) {
+ return null;
+ }
+
+ const goProfile = (nic) => {
+ navigate(`/profile/${nic}`);
+ }
return (
- 채널 소개
-
-
채널 영역
-
-
{channelAboutData.data.channel.chnlTtl}
-
{channelAboutData.data.channel.chnlIntro}
-
{channelAboutData.data.channel.crtNic}
-
-
{channelAboutData.data.channel.chnlOpenDtm}
-
-
-
-
-
-
유저 영역
-
- {channelAboutData.data.user.nic}
- {channelAboutData.data.user.eid}
- {channelAboutData.data.user.userIntro}
-
-
+
+
+
+
+
{data.data.channel.chnlTtl}
+
{data.data.channel.chnlIntro}
+
+
+ 구독자 {data.data.channel.suberCnt}명
+ 포스트 {data.data.channel.chnlPostCnt}개
+ 좋아요 {data.data.channel.chnlLikCnt}개
+ 개설일 {countDate(data.data.channel.chnlOpenDtm)}
+
+
+
+
+
+ goProfile(data.data.user.nic)}>
+
+
{data.data.user.nic}
+
{data.data.user.userIntro}
+
+
+
);
}
diff --git a/src/pages/general/channel/ChannelHome.js b/src/pages/general/channel/ChannelHome.js
index c05bb8e..d25b257 100644
--- a/src/pages/general/channel/ChannelHome.js
+++ b/src/pages/general/channel/ChannelHome.js
@@ -1,6 +1,20 @@
import ChannelTemplate from '../../../components/templates/general/ChannelTemplate';
-import channelData from '../../../tempData/channel/channel.json';
import styled from 'styled-components';
+import useUserStore from '../../../stores/useUserStore';
+import { NavLink, useParams } from 'react-router-dom';
+import { useApiGet } from '../../../hooks/useApi';
+import PostItem from '../../../components/organisms/general/PostItem';
+import NoContent from '../../../components/molecules/error/NoContent';
+import BtnLinkSC from '../../../components/atoms/Link/BtnLinkSC';
+import { useNavigate } from 'react-router';
+import { countDate } from '../../../components/molecules/user/dateConversion';
+import SubsBtn from '../../../components/atoms/Channel/SubsBtn';
+import ScrapPostBtn from '../../../components/molecules/user/ScrapPostBtn';
+import UserBanner from '../../../components/molecules/user/UserBanner';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faCalendarAlt, faUser } from '@fortawesome/free-solid-svg-icons';
+import { faClone } from '@fortawesome/free-regular-svg-icons';
+import { AiOutlineHeart } from 'react-icons/ai';
const SectionHeader = styled.div`
padding: 0 0 10px;
@@ -12,31 +26,6 @@ const SectionHeader = styled.div`
justify-content: space-between;
`;
-// const PostListContainer = styled.div`
-
-// `;
-
-// const PostListItem = styled.div`
-// display: flex;
-// margin: 10px;
-// background-color: #ffffff;
-// padding: 10px;
-// justify-content: space-between;
-// border-bottom: 1px solid rgba(0,0,0,.05);
-// font-weightL 500;
-// `;
-
-// const Thumbnail = styled.div`
-// width: 144px;
-// height: 108px;
-// background-color: #ffffff;
-// border-radius: 8px;
-// margin-right: 10px;
-// display: inline-block;
-// background-image: url(${props => props.imageUrl});
-// background-size: cover;
-// `;
-
const WebtoonListContainer = styled.div`
display: flex;
flex-wrap: wrap;
@@ -80,6 +69,7 @@ const WebtoonTitle = styled.h3`
const WebtoonSubInfo = styled.div`
display: flex;
+ justify-content: space-between;
`;
const WebtoonWriter = styled.span`
@@ -91,7 +81,7 @@ const WebtoonCount = styled.span`
margin-left: 8px;
color: rgba(0,0,0,.47);
font-size: 12px;
-
+
`;
const WebtoonViewCountIcon = styled.img`
@@ -100,67 +90,78 @@ const WebtoonViewCountIcon = styled.img`
`
export default function ChannelHome() {
+ const { user } = useUserStore();
+ const { chnlUri } = useParams();
+ const { data, isLoading, error } = useApiGet(
+ `/channel/${encodeURIComponent(chnlUri)}`,
+ [chnlUri]
+ );
+ const navigate = useNavigate();
+
+ if (isLoading) return;
+ if (error) return {`[${error.code}] ${error.message}`} ;
+
+ if (!data) {
+ return null;
+ }
+ const goPost = (postId) => {
+ console.log("postId",postId);
+ navigate(`/post/${postId}`);
+ //조회수 올라가는 함수 필요
+ };
+
+
return (
- 포스트
- {">"}
+ 웹툰
+ {">"}
- {/*
- {channelData.data.channelPosts.map((post, index) => (
-
-
-
{post.postTtl}
-
포스트 내용이 들어갈 예정입니다.포스트 내용이 들어갈 예정입니다.포스트 내용이 들어갈 예정입니다.
-
-
-
- ))}
- */}
-
- {channelData.data.channelPosts.map((post, index) => (
+
+ {data.data.webtoons.map((post, index) => (
-
+ goPost(post.postId)} />
{ post.serTtl != null ? {post.serTtl} : null}
{post.postTtl}
- {channelData.data.channelUser.nic}
-
-
- {post.postInqrCnt}
-
-
- {/* */}
- ♡
- {post.postLikCnt}
-
-
- 21시간 전
-
-
-
+
+
{data.data.channelUser.nic}
+
+
+ {post.postInqrCnt}
+
+
+
+ {post.postLikCnt}
+
+
+ {countDate(post.postPblcDtm)}
+
+
+
))}
-
- 시리즈
- {">"}
+ 웹소설
+ {">"}
- 시리즈 준비중..
- {/*
- {channelData.data.channelSerieses.map((series, index) => (
-
- {series.serTtl}
-
-
- ))}
- */}
+ {data.data.webnovels.length !== 0 ? (
+ data.data.webnovels.map((post) => )
+ ) : (
+ <>
+
+ 아직 발행한 포스트가 없습니다.
+ {data.data.channelUser.eid === user.eid && (
+ 포스트 발행하기
+ )}
+
+ >
+ )}
-
);
-}
+}
\ No newline at end of file
diff --git a/src/pages/general/channel/ChannelPosts.js b/src/pages/general/channel/ChannelPosts.js
deleted file mode 100644
index c93e2ca..0000000
--- a/src/pages/general/channel/ChannelPosts.js
+++ /dev/null
@@ -1,156 +0,0 @@
-import styled from 'styled-components';
-import ChannelTemplate from '../../../components/templates/general/ChannelTemplate';
-import channelPostsData from '../../../tempData/channel/channelPosts.json';
-
-const SectionHeader = styled.div`
- padding: 0 0 10px;
- color: #121212;
- font-size: 16px;
- font-weight: 500;
- border-bottom: 1px solid rgba(0,0,0,.05);
- display: flex;
- justify-content: space-between;
-`;
-
-const SectionHeaderFilter = styled.div`
- color: rgba(0,0,0,.47);
- font-size: 14px;
- font-weight: 500;
-`;
-
-
-// const PostListContainer = styled.div`
-// `;
-
-// const PostListItem = styled.div`
-// display: flex;
-// margin: 10px;
-// background-color: #ffffff;
-// padding: 10px;
-// justify-content: space-between;
-// border-bottom: 1px solid rgba(0,0,0,.05);
-// font-weightL 500;
-// `;
-
-// const Thumbnail = styled.div`
-// width: 144px;
-// height: 108px;
-// background-color: #ffffff;
-// border-radius: 8px;
-// margin-right: 10px;
-// display: inline-block;
-// background-image: url(${props => props.imageUrl});
-// background-size: cover;
-// `;
-
-const WebtoonListContainer = styled.div`
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between;
-`;
-
-const WebtoonListItem = styled.div`
- width: calc(33.33% - 10px); /* 33.33% width minus margin */
- margin-bottom: 20px;
- padding: 10px;
- background-color: #ffffff;
- border-radius: 8px;
- border: 1px solid rgba(0, 0, 0, 0.05);
- font-weight: 500;
-`;
-
-const WebtoonThumbnail = styled.div`
- width: 100%;
- height: 250px;
- background-color: #ffffff;
- border-radius: 8px;
- margin-right: 10px;
- display: inline-block;
- background-image: url(${props => props.imageUrl});
- background-size: cover;
- border: 1px solid rgba(0,0,0,.05);
-`;
-
-const WebtoonSeriesTitle = styled.p`
- font-weight: 400;
- font-size: 13px;
- margin-bottom: 4px;
- color: rgba(0,0,0,.54);
-`;
-
-const WebtoonTitle = styled.h3`
- font-weight: 600;
- font-size: 16px;
- margin-bottom: 6px;
-`;
-
-const WebtoonSubInfo = styled.div`
- display: flex;
-`;
-
-const WebtoonWriter = styled.span`
- color: #000;
- font-size: 12px;
-`;
-
-const WebtoonCount = styled.span`
- margin-left: 8px;
- color: rgba(0,0,0,.47);
- font-size: 12px;
-
-`;
-
-const WebtoonViewCountIcon = styled.img`
- vertical-align: top;
- margin-right: 2px;
-`
-
-export default function ChannelPosts() {
- return (
-
-
- 67개의 포스트
- 최신순 | 인기순
-
-
- {channelPostsData.data.channelPosts.map((post, index) => (
-
-
-
- { post.serTtl != null ? {post.serTtl} : null}
- {post.postTtl}
-
-
- {channelPostsData.data.channelUser.nic}
-
-
- {post.postInqrCnt}
-
-
- {/* */}
- ♡
- {post.postLikCnt}
-
-
- 21시간 전
-
-
-
-
-
- ))}
-
- {/*
- {channelPostsData.data.channelPosts.map((post, index) => (
-
-
-
{post.postTtl}
-
포스트 내용이 들어갈 예정입니다.포스트 내용이 들어갈 예정입니다.포스트 내용이 들어갈 예정입니다.
-
-
-
- ))}
- */}
-
- );
-}
diff --git a/src/pages/general/channel/ChannelSeries.js b/src/pages/general/channel/ChannelSeries.js
deleted file mode 100644
index 288ca77..0000000
--- a/src/pages/general/channel/ChannelSeries.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import ChannelTemplate from '../../../components/templates/general/ChannelTemplate';
-
-export default function ChannelSeries() {
- return (
-
- 시리즈 준비중 입니다...
-
- );
-}
diff --git a/src/pages/general/channel/ChannelWebnovel.js b/src/pages/general/channel/ChannelWebnovel.js
new file mode 100644
index 0000000..fc95133
--- /dev/null
+++ b/src/pages/general/channel/ChannelWebnovel.js
@@ -0,0 +1,157 @@
+import styled from 'styled-components';
+import ChannelTemplate from '../../../components/templates/general/ChannelTemplate';
+import React, { useState, useEffect } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
+import {faChevronRight} from '@fortawesome/free-solid-svg-icons';
+import useUserStore from '../../../stores/useUserStore';
+import { useLocation, useParams } from 'react-router-dom';
+import { useApiGet } from '../../../hooks/useApi';
+import PostItem from '../../../components/organisms/general/PostItem';
+import NoContent from '../../../components/molecules/error/NoContent';
+import BtnLinkSC from '../../../components/atoms/Link/BtnLinkSC';
+
+const SectionHeader = styled.div`
+ padding: 0 0 10px;
+ color: #121212;
+ font-size: 16px;
+ font-weight: 500;
+ border-bottom: 1px solid rgba(0,0,0,.05);
+ display: flex;
+ justify-content: space-between;
+`;
+
+const SectionHeaderFilter = styled.div`
+ color: rgba(0,0,0,.47);
+ font-size: 14px;
+ font-weight: 500;
+`;
+
+// Pagination style
+const PaginationContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 20px;
+`;
+
+const PaginationButton = styled.div`
+ min-width: 30px;
+ height: 30px;
+ padding: 4px;
+ margin: 0 2px;
+ font-weight: 500;
+ font-size: 14px;
+ //width: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: ${({ isSelected }) => (isSelected ? '1px solid #ccc' : '')};
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: white;
+`;
+
+export default function ChannelWebnovel() {
+ const { user } = useUserStore();
+ const { chnlUri } = useParams();
+ const location = useLocation();
+ const queryParams = new URLSearchParams(location.search);
+ const pageString = queryParams.get('page'); // This will be a string
+ const page = pageString !== null ? Number(pageString) - 1 : 0;
+ const [currentPage, setCurrentPage] = useState(page);
+ const postType = "webnovel";
+ const [url, setUrl] = useState(`/channel/${encodeURIComponent(chnlUri)}/posts/${postType}?page=${currentPage+1}`
+ );
+ const [pageCount, setPageCount] = useState(0);
+ const [totalCount, setTotalCount] = useState(null);
+ const { data, isLoading, error } = useApiGet(
+ url,
+ [url]
+ );
+
+ const size = 12;
+
+ useEffect(() => {
+ if (data) {
+ setTotalCount(data.data.channel.chnlWebnovelCnt || 0);
+ }
+ }, [data]);
+
+ useEffect(() => {
+ const calculatePageCount = () => {
+ setPageCount(Math.ceil(totalCount / size));
+ };
+
+ calculatePageCount();
+ }, [totalCount, size]);
+
+ useEffect(() => {
+ setUrl(`/channel/${encodeURIComponent(chnlUri)}/posts/${postType}?page=${currentPage+1}`);
+ }, [currentPage, chnlUri]);
+
+
+ const handlePrevPage = () => {
+ if (currentPage > 0) {
+ setCurrentPage(currentPage - 1);
+ }
+ };
+
+ const handleNextPage = () => {
+ if (currentPage < pageCount - 1) {
+ setCurrentPage(currentPage + 1);
+ }
+ };
+
+ if (isLoading) returnLoading...
;
+ if (error) return {`[${error.code}] ${error.message}`} ;
+
+ if (!data) {
+ return null;
+ }
+
+
+
+
+ return (
+
+
+ {data.data.channel.chnlWebnovelCnt}개의 포스트
+ 최신순 | 인기순
+
+ {data.data.webnovels.length !== 0 ? (
+ data.data.webnovels.map((post) => )
+ ) : (
+ <>
+
+ 아직 발행한 포스트가 없습니다.
+ {data.data.channelUser.eid === user.eid && (
+ 포스트 발행하기
+ )}
+
+ >
+ )}
+
+ {data.data.channel.chnlWebnovelCnt !== 0 ?
+ (
+
+
+
+
+ {Array.from({ length: pageCount }).map((_, index) => (
+ setCurrentPage(index)}
+ >
+ {index + 1}
+
+ ))}
+
+
+
+
+ ) : ''}
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/general/channel/ChannelWebtoon.js b/src/pages/general/channel/ChannelWebtoon.js
new file mode 100644
index 0000000..478b6f3
--- /dev/null
+++ b/src/pages/general/channel/ChannelWebtoon.js
@@ -0,0 +1,250 @@
+import styled from 'styled-components';
+import ChannelTemplate from '../../../components/templates/general/ChannelTemplate';
+import React, { useState, useEffect } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
+import {faChevronRight} from '@fortawesome/free-solid-svg-icons';
+import useUserStore from '../../../stores/useUserStore';
+import { useLocation, useParams } from 'react-router-dom';
+import { useApiGet } from '../../../hooks/useApi';
+import { post } from 'axios';
+import { useNavigate } from 'react-router';
+import { countDate } from '../../../components/molecules/user/dateConversion';
+import { AiOutlineHeart } from 'react-icons/ai';
+import ScrapPostBtn from '../../../components/molecules/user/ScrapPostBtn';
+
+const SectionHeader = styled.div`
+ padding: 0 0 10px;
+ color: #121212;
+ font-size: 16px;
+ font-weight: 500;
+ border-bottom: 1px solid rgba(0,0,0,.05);
+ display: flex;
+ justify-content: space-between;
+`;
+
+const SectionHeaderFilter = styled.div`
+ color: rgba(0,0,0,.47);
+ font-size: 14px;
+ font-weight: 500;
+`;
+
+
+const WebtoonListContainer = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+`;
+
+const WebtoonListItem = styled.div`
+ width: calc(33.33% - 10px); /* 33.33% width minus margin */
+ margin-bottom: 20px;
+ padding: 10px;
+ background-color: #ffffff;
+ border-radius: 8px;
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ font-weight: 500;
+`;
+
+const WebtoonThumbnail = styled.div`
+ width: 100%;
+ height: 250px;
+ background-color: #ffffff;
+ border-radius: 8px;
+ margin-right: 10px;
+ display: inline-block;
+ background-image: url(${props => props.imageUrl});
+ background-size: cover;
+ border: 1px solid rgba(0,0,0,.05);
+`;
+
+const WebtoonSeriesTitle = styled.p`
+ font-weight: 400;
+ font-size: 13px;
+ margin-bottom: 4px;
+ color: rgba(0,0,0,.54);
+`;
+
+const WebtoonTitle = styled.h3`
+ font-weight: 600;
+ font-size: 16px;
+ margin-bottom: 6px;
+`;
+
+const WebtoonSubInfo = styled.div`
+ display: flex;
+ justify-content: space-between;
+`;
+
+const WebtoonWriter = styled.span`
+ color: #000;
+ font-size: 12px;
+`;
+
+const WebtoonCount = styled.span`
+ margin-left: 8px;
+ color: rgba(0,0,0,.47);
+ font-size: 12px;
+`;
+
+const WebtoonViewCountIcon = styled.img`
+ vertical-align: top;
+ margin-right: 2px;
+`
+
+// Pagination style
+const PaginationContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 20px;
+`;
+
+const PaginationButton = styled.div`
+ min-width: 30px;
+ height: 30px;
+ padding: 4px;
+ margin: 0 2px;
+ font-weight: 500;
+ font-size: 14px;
+ //width: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: ${({ isSelected }) => (isSelected ? '1px solid #ccc' : '')};
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: white;
+`;
+
+export default function ChannelWebtoon() {
+ const { user } = useUserStore();
+ const navigate = useNavigate();
+ const { chnlUri } = useParams();
+ const location = useLocation();
+ const queryParams = new URLSearchParams(location.search);
+ const pageString = queryParams.get('page'); // This will be a string
+ const page = pageString !== null ? Number(pageString) - 1 : 0;
+ const [currentPage, setCurrentPage] = useState(page);
+ const postType = "webtoon";
+ const [url, setUrl] = useState(`/channel/${encodeURIComponent(chnlUri)}/posts/${postType}?page=${currentPage+1}`);
+ const [pageCount, setPageCount] = useState(0);
+ const [totalCount, setTotalCount] = useState(null);
+ const { data, isLoading, error } = useApiGet(
+ url,
+ [url]
+ );
+
+ const size = 12;
+
+ useEffect(() => {
+ if (data) {
+ setTotalCount(data.data.channel.chnlWebtoonCnt || 0);
+ }
+ }, [data]);
+
+ useEffect(() => {
+ if (data) {
+ setTotalCount(data.data.channel.chnlWebtoonCnt || 0);
+ }
+ }, [data]);
+
+ useEffect(() => {
+ const calculatePageCount = () => {
+ setPageCount(Math.ceil(totalCount / size));
+ };
+
+ calculatePageCount();
+ }, [totalCount, size]);
+
+ useEffect(() => {
+ setUrl(`/channel/${encodeURIComponent(chnlUri)}/posts/${postType}?page=${currentPage+1}`);
+ }, [currentPage, chnlUri]);
+
+ const goPost = (postId) => {
+ console.log("postId",postId);
+ navigate(`/post/${postId}`);
+ //조회수 올라가는 함수 필요
+ };
+
+
+ const handlePrevPage = () => {
+ if (currentPage > 0) {
+ setCurrentPage(currentPage - 1);
+ }
+ };
+
+ const handleNextPage = () => {
+ if (currentPage < pageCount - 1) {
+ setCurrentPage(currentPage + 1);
+ }
+ };
+
+ if (isLoading) returnLoading...
;
+ if (error) return {`[${error.code}] ${error.message}`} ;
+
+ if (!data) {
+ return null;
+ }
+
+
+
+
+ return (
+
+
+ {data.data.channel.chnlWebtoonCnt}개의 포스트
+ 최신순 | 인기순
+
+
+ {data.data.webtoons.map((post, index) => (
+
+ goPost(post.postId)} />
+
+ { post.serTtl != null ? {post.serTtl} : null}
+ {post.postTtl}
+
+
+
+
{data.data.channelUser.nic}
+
+
+ {post.postInqrCnt}
+
+
+
+ {post.postLikCnt}
+
+
+ {countDate(post.postPblcDtm)}
+
+
+
+
+
+ ))}
+
+
+ {data.data.channel.chnlWebtoonCnt != 0 ?
+
+
+
+
+ {Array.from({ length: pageCount }).map((_, index) => (
+ setCurrentPage(index)}
+ >
+ {index + 1}
+
+ ))}
+
+
+
+
+ : ''
+ }
+
+ );
+}
diff --git a/src/pages/general/create/PostCreate.js b/src/pages/general/create/PostCreate.js
new file mode 100644
index 0000000..30ef205
--- /dev/null
+++ b/src/pages/general/create/PostCreate.js
@@ -0,0 +1,337 @@
+import styled from 'styled-components';
+import { useLocation, useParams } from 'react-router-dom';
+import { useApiGet, useApiPost, useConditionalApiGet } from '../../../hooks/useApi';
+import CreateTemplate from '../../../components/templates/general/CreateTemplate';
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import ReactQuill, {Quill} from 'react-quill';
+import 'react-quill/dist/quill.snow.css';
+import TextInputSC from '../../../components/atoms/Input/TextInputSC';
+import ImageResize from 'quill-image-resize';
+import PostRadioButton from '../../../components/molecules/post/PostRadioButton';
+import PostImg from '../../../components/atoms/Post/PostImgSC';
+import { useNavigate } from 'react-router';
+import useUserStore from '../../../stores/useUserStore';
+
+export const SubmitButton = styled.button`
+ display: block;
+ width: fit-content;
+ padding: 8px 30px;
+ font-size: 15px;
+ border-radius: 4px;
+ font-weight: normal;
+ color: white;
+ background-color: #3478FF;
+ border: 1px solid #3478FF;
+`;
+
+export const PostImgInputSC = styled.input`
+ display: inline-block;
+ padding: 8px 30px;
+ font-size: 15px;
+ border-radius: 4px;
+ font-weight: normal;
+ color: white;
+ background-color: #3478FF;
+ border: 1px solid #3478FF;
+ cursor: pointer;
+ margin-left: 10px; // 이미지와 버튼 사이에 간격을 주기 위함
+`;
+
+export const ImageContainerSC = styled.div`
+ display: flex;
+ align-items: center;
+ margin-bottom: 20px;
+`;
+
+
+export default function PostCreate() {
+ Quill.register('modules/imageResize', ImageResize);
+ const { user } = useUserStore();
+ const { chnlUri } = useParams();
+ const navigate = useNavigate();
+ const location = useLocation();
+ const queryParams = new URLSearchParams(location.search);
+ const [title, setTitle] = useState('');
+ const [content, setContent] = useState('');
+ const [subTitle, setSubTitle] = useState('');
+ const [thumbnailImageUrl, setThumbnailImageUrl] = useState('');
+ const [postType, setPostType] = useState('');
+ const [postId, setPostId] = useState(null);
+ const [imageUrls, setImageUrls] = useState([]);
+ const [imgFile, setImgFile] = useState("");
+ const imgRef = useRef();
+ const quillRef = useRef();
+ const imageData = new FormData();
+ const thumnData = new FormData();
+ const {
+ res: postRes,
+ error: postErr,
+ setError: setPostErr,
+ postData: post,
+ } = useApiPost(`post/create`, {
+ postType: postType,
+ postTtl: title,
+ postSbTtl: subTitle,
+ postContent: content,
+ postThumnPath: thumbnailImageUrl,
+ imageUrls: imageUrls,
+
+ });
+ const {
+ res: postEditRes,
+ error: postEditErr,
+ setError: setPostEditErr,
+ postData: postEdit,
+ } = useApiPost(`post/${postId}/edit`, {
+ postType: postType,
+ postTtl: title,
+ postSbTtl: subTitle,
+ postContent: content,
+ postThumnPath: thumbnailImageUrl,
+ imageUrls: imageUrls,
+ });
+
+ const {
+ res: uploadRes,
+ error: uploadErr,
+ setError: setUploadErr,
+ postData: upload,
+ } = useApiPost(`file/uploadFiles`, {imageData: imageData});
+ const {
+ res: uploadThumRes,
+ error: uploadThumnErr,
+ setError: Err,
+ postData: uploadThumn,
+ } = useApiPost(`file/uploadThumn`, {thumnData: thumnData});
+ const { data, isLoading, error } = useConditionalApiGet(`/post/${postId}`, postId);
+ const imageHandler = () => {
+ const input = document.createElement('input');
+
+ input.setAttribute('type', 'file');
+ input.setAttribute('accept', 'image/*');
+ input.click(); // 에디터 이미지버튼을 클릭하면 이 input이 클릭된다.
+ input.addEventListener('change', async () => {
+ const files = input.files;
+ // multer에 맞는 형식으로 데이터 만들어준다.
+ Array.prototype.forEach.call(files, function(file) {
+ imageData.append('multipartFiles',file);
+ });
+
+ await upload(imageData);
+ });
+ };
+
+
+ // 이미지 업로드 input의 onChange
+ const saveImgFile = async () => {
+ if(imgRef.current.files.length > 0) {
+ const file = imgRef.current.files[0];
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onloadend = () => {
+ setImgFile(reader.result);
+ console.log(imgFile);
+
+ };
+ console.log("file",file);
+ thumnData.append("file", file);
+ console.log("thumnData", thumnData);
+ await uploadThumn(thumnData);
+ } else {
+ console.warn('No file selected');
+ }
+ };
+
+ const modules = useMemo(() => {
+
+ return {
+ toolbar: {
+ container: [
+ [{ 'header': [1, 2, false] }],
+ [{ 'size': ['small', false, 'large', 'huge'] }], // 새로운 글자 크기 조절 옵션
+ ['bold', 'italic', 'underline', 'strike', 'blockquote'],
+ [{ 'align': [] }], // 새로운 텍스트 정렬 옵션
+ [{ 'list': 'ordered' }, { 'list': 'bullet' }, { 'indent': '-1' }, { 'indent': '+1' }],
+ ['link', 'image'],
+ ['clean']
+ ],
+ handlers: {
+ image: imageHandler,
+ },
+ imageResize: {}
+ },
+ }
+ },[]);
+
+ const formats = [
+ 'header',
+ 'bold', 'italic', 'underline', 'strike', 'blockquote',
+ 'align', 'list', 'bullet', 'indent',
+ 'link', 'image',
+ 'size'
+ ]
+ useEffect(() =>{
+ setPostId(queryParams.get('postId'));
+ console.log(postId);
+ }, [])
+
+
+ useEffect(() =>{
+ if (uploadThumRes !== null){
+ // setThumbnailImageUrl(process.env.REACT_APP_PUBLIC_URL+uploadThumRes.data.url);
+ setThumbnailImageUrl(uploadThumRes.data.url);
+ }
+ }, [uploadThumRes])
+
+ useEffect(() => {
+ if (uploadRes !== null) {
+ const editor = quillRef.current.getEditor(); // 에디터 객체 가져오기
+
+ uploadRes.data.urls.forEach((imgUrl) => {
+
+ let range = editor.getSelection();
+ // editor.insertEmbed(range.index, 'image', process.env.REACT_APP_PUBLIC_URL+imgUrl);
+ editor.insertEmbed(range.index, 'image', imgUrl);
+
+ // setImageUrls((prevImageUrls) => [...prevImageUrls, process.env.REACT_APP_PUBLIC_URL+imgUrl]);
+ setImageUrls((prevImageUrls) => [...prevImageUrls, imgUrl]);
+ }
+ )
+
+ }
+ }, [uploadRes]);
+
+ useEffect(() =>{
+ if(postRes != null){
+ console.dir(postRes);
+ }
+ })
+
+
+ const handleTitleChange = (event) => {
+ setTitle(event.target.value);
+ };
+
+ const handleSubTitleChange = (event) =>{
+ setSubTitle(event.target.value);
+ }
+
+ const handleOptionChange = (event) => {
+ setPostType(event.target.value);
+ };
+
+ const handleEditorChange = (value) => {
+ setContent(value);
+ };
+
+ const handleFormSubmit = async (event) => {
+ event.preventDefault();
+
+
+ const editor = quillRef.current.getEditor(); // 에디터 객체 가져오기
+ const delta = editor.getContents(); // 현재 문서의 내용을 가져오기
+ setContent(JSON.stringify(delta)); // Quill 에디터의 내용을 Delta 형식의 JSON 문자열로 설정
+
+
+ const imagePathes = delta.ops
+ .filter(op => typeof op.insert === 'object' && op.insert.image)
+ .map(op => op.insert.image);
+
+ setImageUrls(imagePathes);
+
+ const postData = {
+ chnlUri: chnlUri,
+ postType: postType,
+ postTtl: title,
+ postSbTtl: subTitle,
+ postContent: content,
+ postThumnPath: thumbnailImageUrl,
+ imageUrls: imageUrls
+ };
+ // API 호출 등의 로직을 추가합니다.
+
+ if (postId !== null){
+ // 포스트 수정을 위한 postApi
+ await postEdit(postData);
+
+ } else {
+ // 포스트 생성을 위한 postApi
+ await post(postData);
+ }
+
+ navigate(`/channel/${chnlUri}`);
+ };
+
+ const options = [
+ { label: '웹툰', value: '웹툰' },
+ { label: '웹소설', value: '웹소설' },
+ ];
+
+ useEffect(() => {
+ if (data && data.post) {
+ setTitle(data.post.postTtl || '');
+ setContent(data.post.postContent || '');
+ setSubTitle(data.post.postSbTtl || '');
+ setThumbnailImageUrl(data.post.postThumnPath || '');
+ setPostType(data.post.postType || '');
+ setImageUrls([...data.post.imageUrls])
+ }
+ }, [data]);
+
+ if (isLoading) return;
+ if (error) return {`[${error.code}] ${error.message}`} ;
+
+
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/general/home/Home.js b/src/pages/general/home/Home.js
index 350b301..5123474 100644
--- a/src/pages/general/home/Home.js
+++ b/src/pages/general/home/Home.js
@@ -1,26 +1,48 @@
import { NavLink } from 'react-router-dom';
import HomeTemplate from '../../../components/templates/general/HomeTemplate';
+import HomeSearchBox from '../../../components/molecules/general/HomeSearchBox';
+import styled from 'styled-components';
+import postory from '../../../logo.svg';
+
+
+
+const HomeSearchSC = styled.div`
+ display: flex;
+ margin-top: 50px;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+`
+
+const StyledLogoSC = styled.div`
+ position: relative;
+
+ cursor: ${(p) => (p.disable ? 'not-allowed' : 'pointer')};
+ height: 50px;
+ width: 108px;
+ /* border: 1px solid black; */
+
+ a {
+ cursor: inherit;
+ }
+
+ img {
+ position: relative;
+ width: 100%;
+ user-select: none;
+ }
+`;
-const homeStyle = {
- minHeight: '100px',
-};
export default function Home() {
return (
- 이거 홈 맞지?
-
-
-
- 공부하는 여우원숭이 프로필 이동
-
-
-
-
- BUKSAN 채널 바로가기
-
-
-
+
+
+
+
+
+
);
}
diff --git a/src/pages/general/home/Webnovel.js b/src/pages/general/home/Webnovel.js
index 602ebb6..d725f36 100644
--- a/src/pages/general/home/Webnovel.js
+++ b/src/pages/general/home/Webnovel.js
@@ -1,9 +1,45 @@
import HomeTemplate from '../../../components/templates/general/HomeTemplate';
+import HomeSearchBox from '../../../components/molecules/general/HomeSearchBox';
+import styled from 'styled-components';
+import postory from '../../../logo.svg';
+
+const HomeSearchSC = styled.div`
+ display: flex;
+ margin-top: 50px;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+`
+
+const StyledLogoSC = styled.div`
+ position: relative;
+
+ cursor: ${(p) => (p.disable ? 'not-allowed' : 'pointer')};
+ height: 50px;
+ width: 108px;
+ /* border: 1px solid black; */
+
+ a {
+ cursor: inherit;
+ }
+
+ img {
+ position: relative;
+ width: 100%;
+ user-select: none;
+ }
+`;
+
export default function Webnovel() {
return (
- 웹소설
+
+
+
+
+
+
);
}
diff --git a/src/pages/general/home/Webtoon.js b/src/pages/general/home/Webtoon.js
index d4b01e4..bbedf65 100644
--- a/src/pages/general/home/Webtoon.js
+++ b/src/pages/general/home/Webtoon.js
@@ -1,9 +1,47 @@
import HomeTemplate from '../../../components/templates/general/HomeTemplate';
+import HomeSearchBox from '../../../components/molecules/general/HomeSearchBox';
+import styled from 'styled-components';
+import Home from './Home';
+import postory from '../../../logo.svg';
+
+
+const StyledLogoSC = styled.div`
+ position: relative;
+
+ cursor: ${(p) => (p.disable ? 'not-allowed' : 'pointer')};
+ height: 50px;
+ width: 108px;
+ /* border: 1px solid black; */
+
+ a {
+ cursor: inherit;
+ }
+
+ img {
+ position: relative;
+ width: 100%;
+ user-select: none;
+ }
+`;
+
+
+const HomeSearchSC = styled.div`
+ display: flex;
+ margin-top: 50px;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+`
export default function Webtoon() {
return (
- 웹툰
-
+
+
+
+
+
+
+
);
}
diff --git a/src/pages/general/search/SearchChannel.js b/src/pages/general/search/SearchChannel.js
index 8b21daf..5cf6d33 100644
--- a/src/pages/general/search/SearchChannel.js
+++ b/src/pages/general/search/SearchChannel.js
@@ -1,13 +1,160 @@
-import { useSearchParams } from 'react-router-dom';
+import { useLocation, useParams, useSearchParams } from 'react-router-dom';
import SearchTemplate from '../../../components/templates/general/SearchTemplate';
+import { useApiGet } from '../../../hooks/useApi';
+import { useNavigate } from 'react-router';
+import HomeSearchBox from '../../../components/molecules/general/HomeSearchBox';
+import ProfileChannelItem from '../../../components/organisms/general/ProfileChannelItem';
+import NoContent from '../../../components/molecules/error/NoContent';
+import BtnLinkSC from '../../../components/atoms/Link/BtnLinkSC';
+import useUserStore from '../../../stores/useUserStore';
+import React, { useEffect, useState } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
+import styled from 'styled-components';
+const SectionHeader = styled.div`
+ padding: 0 0 10px;
+ color: #121212;
+ font-size: 16px;
+ font-weight: 500;
+ border-bottom: 1px solid rgba(0,0,0,.05);
+ display: flex;
+ justify-content: space-between;
+`;
+
+const SectionHeaderFilter = styled.div`
+ color: rgba(0,0,0,.47);
+ font-size: 14px;
+ font-weight: 500;
+`;
+
+// Pagination style
+const PaginationContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 20px;
+`;
+
+const PaginationButton = styled.div`
+ min-width: 30px;
+ height: 30px;
+ padding: 4px;
+ margin: 0 2px;
+ font-weight: 500;
+ font-size: 14px;
+ //width: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: ${({ isSelected }) => (isSelected ? '1px solid #ccc' : '')};
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: white;
+`;
export default function SearchChannel() {
+ const navigate = useNavigate();
const [searchParams] = useSearchParams();
const keyword = searchParams.get('keyword');
+ const page = 0;
+ const option = 'all';
+ const [pageCount, setPageCount] = useState(0);
+ const [totalCount, setTotalCount] = useState(null);
+ const [currentPage, setCurrentPage] = useState(page);
+ const [url, setUrl] = useState(`/search/channel?keyword=${encodeURIComponent(keyword)}&option=${option}`)
+ const size = 12;
+ const { data, isLoading, error } = useApiGet(
+ url,
+ [keyword]
+ );
+
+
+
+
+
+ useEffect(() => {
+ if (data) {
+ setTotalCount(data.searchCnt || 0);
+ }
+ }, [data]);
+
+ useEffect(() => {
+ const calculatePageCount = () => {
+ setPageCount(Math.ceil(totalCount / size));
+ };
+ calculatePageCount();
+ }, [totalCount, size]);
+
+ useEffect(() => {
+ setUrl(`/search/channel?keyword=${encodeURIComponent(keyword)}&option=${option}&page=${currentPage+1}`);
+ }, [currentPage, keyword]);
+
+ const goPost = (postId) => {
+ console.log("postId",postId);
+ navigate(`/post/${postId}`);
+ //조회수 올라가는 함수 필요
+ };
+
+
+ const handlePrevPage = () => {
+ if (currentPage > 0) {
+ setCurrentPage(currentPage - 1);
+ }
+ };
+
+ const handleNextPage = () => {
+ if (currentPage < pageCount - 1) {
+ setCurrentPage(currentPage + 1);
+ }
+ };
+
+ if (isLoading) returnLoading...
;
+ if (error) return {`[${error.code}] ${error.message}`} ;
+
+ if (!data) {
+ return null;
+ }
return (
+
채널 {keyword} 검색 결과
+
+ {data.searchCnt}개의 채널
+ 최신순 | 인기순
+
+ {data.channels.length !== 0 ? (
+ <>
+ {data.channels.map((channel) => (
+
+ ))}
+
+
+
+
+ {Array.from({ length: pageCount }).map((_, index) => (
+ setCurrentPage(index)}
+ >
+ {index + 1}
+
+ ))}
+
+
+
+
+ >
+ ) : (
+ <>
+
+ 채널이 없습니다.
+
+ >
+ )}
+
+
);
}
diff --git a/src/pages/general/search/SearchPost.js b/src/pages/general/search/SearchPost.js
deleted file mode 100644
index d997283..0000000
--- a/src/pages/general/search/SearchPost.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useSearchParams } from 'react-router-dom';
-import SearchTemplate from '../../../components/templates/general/SearchTemplate';
-
-export default function SearchPost() {
- const [searchParams] = useSearchParams();
- const keyword = searchParams.get('keyword');
-
- return (
-
- 포스트 {keyword} 검색 결과
-
- );
-}
diff --git a/src/pages/general/search/SearchSeries.js b/src/pages/general/search/SearchSeries.js
deleted file mode 100644
index 671dd9d..0000000
--- a/src/pages/general/search/SearchSeries.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useSearchParams } from 'react-router-dom';
-import SearchTemplate from '../../../components/templates/general/SearchTemplate';
-
-export default function SearchPost() {
- const [searchParams] = useSearchParams();
- const keyword = searchParams.get('keyword');
-
- return (
-
- 시리즈 {keyword} 검색 결과
-
- );
-}
diff --git a/src/pages/general/search/SearchWebnovel.js b/src/pages/general/search/SearchWebnovel.js
new file mode 100644
index 0000000..1c55d64
--- /dev/null
+++ b/src/pages/general/search/SearchWebnovel.js
@@ -0,0 +1,156 @@
+import { useSearchParams } from 'react-router-dom';
+import SearchTemplate from '../../../components/templates/general/SearchTemplate';
+import { useApiGet } from '../../../hooks/useApi';
+import HomeSearchBox from '../../../components/molecules/general/HomeSearchBox';
+import PostItem from '../../../components/organisms/general/PostItem';
+import NoContent from '../../../components/molecules/error/NoContent';
+import BtnLinkSC from '../../../components/atoms/Link/BtnLinkSC';
+import React, { useEffect, useState } from 'react';
+import styled from 'styled-components';
+import { useNavigate } from 'react-router';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
+const SectionHeader = styled.div`
+ padding: 0 0 10px;
+ color: #121212;
+ font-size: 16px;
+ font-weight: 500;
+ border-bottom: 1px solid rgba(0,0,0,.05);
+ display: flex;
+ justify-content: space-between;
+`;
+
+const SectionHeaderFilter = styled.div`
+ color: rgba(0,0,0,.47);
+ font-size: 14px;
+ font-weight: 500;
+`;
+
+// Pagination style
+const PaginationContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 20px;
+`;
+
+const PaginationButton = styled.div`
+ min-width: 30px;
+ height: 30px;
+ padding: 4px;
+ margin: 0 2px;
+ font-weight: 500;
+ font-size: 14px;
+ //width: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: ${({ isSelected }) => (isSelected ? '1px solid #ccc' : '')};
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: white;
+`;
+
+export default function SearchWebnovel() {
+ const [searchParams] = useSearchParams();
+ const keyword = searchParams.get('keyword');
+ const navigate = useNavigate();
+ const page = 0;
+ const postType = 'webnovel';
+ const option = 'all';
+ const [totalCount, setTotalCount] = useState(null);
+ const [currentPage, setCurrentPage] = useState(page);
+ const [url, setUrl] = useState(`/search/${postType}/posts?option=${option}&keyword=${encodeURIComponent(keyword)}&page=${currentPage+1}`);
+ const [pageCount, setPageCount] = useState(0);
+ const { data, isLoading, error } = useApiGet(
+ url,
+ [keyword, url]
+ );
+
+ const size = 12;
+
+ useEffect(() => {
+ if (data) {
+ setTotalCount(data.searchCnt || 0);
+ }
+ }, [data]);
+
+ useEffect(() => {
+ const calculatePageCount = () => {
+ setPageCount(Math.ceil(totalCount / size));
+ };
+
+ calculatePageCount();
+ }, [totalCount, size]);
+
+ useEffect(() => {
+ setUrl(`/search/${postType}/posts?option=${option}&keyword=${encodeURIComponent(keyword)}&page=${currentPage+1}
+`);
+ }, [currentPage,keyword]);
+
+ const goPost = (postId) => {
+ console.log("postId",postId);
+ navigate(`/post/${postId}`);
+ //조회수 올라가는 함수 필요
+ };
+
+
+ const handlePrevPage = () => {
+ if (currentPage > 0) {
+ setCurrentPage(currentPage - 1);
+ }
+ };
+
+ const handleNextPage = () => {
+ if (currentPage < pageCount - 1) {
+ setCurrentPage(currentPage + 1);
+ }
+ };
+
+ if (isLoading) returnLoading...
;
+ if (error) return {`[${error.code}] ${error.message}`} ;
+
+ if (!data) {
+ return null;
+ }
+
+ return (
+
+
+ 웹소설 {keyword} 검색 결과
+
+
+ {data.searchCnt}개의 포스트
+ 최신순 | 인기순
+
+ {data.searchCnt !== 0 ? (
+ <>
+ {data.posts.map((post) => )}
+
+
+
+
+ {Array.from({ length: pageCount }).map((_, index) => (
+ setCurrentPage(index)}
+ >
+ {index + 1}
+
+ ))}
+
+
+
+
+ >
+ ) : (
+ <>
+
+ 검색 결과가 없습니다.
+
+ >
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/general/search/SearchWebtoon.js b/src/pages/general/search/SearchWebtoon.js
new file mode 100644
index 0000000..94a48b6
--- /dev/null
+++ b/src/pages/general/search/SearchWebtoon.js
@@ -0,0 +1,254 @@
+import { useSearchParams } from 'react-router-dom';
+import SearchTemplate from '../../../components/templates/general/SearchTemplate';
+import { useApiGet } from '../../../hooks/useApi';
+import HomeSearchBox from '../../../components/molecules/general/HomeSearchBox';
+import React, { useEffect, useState } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
+import styled from 'styled-components';
+import { useNavigate } from 'react-router';
+import NoContent from '../../../components/molecules/error/NoContent';
+import { countDate } from '../../../components/molecules/user/dateConversion';
+import { AiOutlineHeart } from 'react-icons/ai';
+import ScrapPostBtn from '../../../components/molecules/user/ScrapPostBtn';
+
+const SectionHeader = styled.div`
+ padding: 0 0 10px;
+ color: #121212;
+ font-size: 16px;
+ font-weight: 500;
+ border-bottom: 1px solid rgba(0,0,0,.05);
+ display: flex;
+ justify-content: space-between;
+`;
+
+const SectionHeaderFilter = styled.div`
+ color: rgba(0,0,0,.47);
+ font-size: 14px;
+ font-weight: 500;
+`;
+
+const WebtoonListContainer = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+`;
+
+const WebtoonListItem = styled.div`
+ width: calc(33.33% - 10px); /* 33.33% width minus margin */
+ margin-bottom: 20px;
+ padding: 10px;
+ background-color: #ffffff;
+ border-radius: 8px;
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ font-weight: 500;
+`;
+
+const WebtoonThumbnail = styled.div`
+ width: 100%;
+ height: 250px;
+ background-color: #ffffff;
+ border-radius: 8px;
+ margin-right: 10px;
+ display: inline-block;
+ background-image: url(${props => props.imageUrl});
+ background-size: cover;
+ border: 1px solid rgba(0,0,0,.05);
+`;
+
+const WebtoonSeriesTitle = styled.p`
+ font-weight: 400;
+ font-size: 13px;
+ margin-bottom: 4px;
+ color: rgba(0,0,0,.54);
+`;
+
+const WebtoonTitle = styled.h3`
+ font-weight: 600;
+ font-size: 16px;
+ margin-bottom: 6px;
+`;
+
+const WebtoonSubInfo = styled.div`
+ display: flex;
+ justify-content: space-between;
+`;
+
+const WebtoonWriter = styled.span`
+ color: #000;
+ font-size: 12px;
+`;
+
+const WebtoonCount = styled.span`
+ margin-left: 8px;
+ color: rgba(0,0,0,.47);
+ font-size: 12px;
+`;
+
+const WebtoonViewCountIcon = styled.img`
+ vertical-align: top;
+ margin-right: 2px;
+`
+
+// Pagination style
+const PaginationContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 20px;
+`;
+
+const PaginationButton = styled.div`
+ min-width: 30px;
+ height: 30px;
+ padding: 4px;
+ margin: 0 2px;
+ font-weight: 500;
+ font-size: 14px;
+ //width: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: ${({ isSelected }) => (isSelected ? '1px solid #ccc' : '')};
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: white;
+`;
+
+export default function SearchWebtoon() {
+ const [searchParams] = useSearchParams();
+ const keyword = searchParams.get('keyword');
+ const navigate = useNavigate();
+ const page = 0;
+ const postType = 'webtoon';
+ const option = 'all';
+ const [totalCount, setTotalCount] = useState(null);
+ const [currentPage, setCurrentPage] = useState(page);
+ const [url, setUrl] = useState(`/search/${postType}/posts?option=${option}&keyword=${encodeURIComponent(keyword)}&page=${currentPage+1}`);
+ const [pageCount, setPageCount] = useState(0);
+ const { data, isLoading, error } = useApiGet(
+ url,
+ [keyword, url]
+ );
+
+ const size = 12;
+
+ useEffect(() => {
+ if (data) {
+ setTotalCount(data.searchCnt || 0);
+ }
+ }, [data]);
+
+ useEffect(() => {
+ const calculatePageCount = () => {
+ setPageCount(Math.ceil(totalCount / size));
+ };
+
+ calculatePageCount();
+ }, [totalCount, size]);
+
+ useEffect(() => {
+ setUrl(`/search/${postType}/posts?option=${option}&keyword=${encodeURIComponent(keyword)}&page=${currentPage+1}
+`);
+ }, [currentPage,keyword]);
+
+ const goPost = (postId) => {
+ console.log("postId",postId);
+ navigate(`/post/${postId}`);
+ //조회수 올라가는 함수 필요
+ };
+
+
+ const handlePrevPage = () => {
+ if (currentPage > 0) {
+ setCurrentPage(currentPage - 1);
+ }
+ };
+
+ const handleNextPage = () => {
+ if (currentPage < pageCount - 1) {
+ setCurrentPage(currentPage + 1);
+ }
+ };
+
+ if (isLoading) returnLoading...
;
+ if (error) return {`[${error.code}] ${error.message}`} ;
+
+ if (!data) {
+ return null;
+ }
+
+
+ return (
+
+
+ 웹툰 {keyword} 검색 결과
+
+ {data.searchCnt}개의 포스트
+ 최신순 | 인기순
+
+ {data.searchCnt !== 0 ?
+ (
+ <>
+
+
+ {data.posts.map((post, index) => (
+
+ goPost(post.postId)} />
+
+ { post.serTtl != null ? {post.serTtl} : null}
+ {post.postTtl}
+
+
+
+
{post.userNic}
+
+
+ {post.postInqrCnt}
+
+
+
+ {post.postLikCnt}
+
+
+ {countDate(post.postPblcDtm)}
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ {Array.from({ length: pageCount }).map((_, index) => (
+ setCurrentPage(index)}
+ >
+ {index + 1}
+
+ ))}
+
+
+
+
+ >
+ ) :
+ (
+ <>
+
+ 검색 결과가 없습니다.
+
+ >
+ )
+ }
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/general/view/PostView.js b/src/pages/general/view/PostView.js
new file mode 100644
index 0000000..6a7b32c
--- /dev/null
+++ b/src/pages/general/view/PostView.js
@@ -0,0 +1,144 @@
+import styled from 'styled-components';
+import useUserStore from '../../../stores/useUserStore';
+import { useParams } from 'react-router-dom';
+import { useApiGet } from '../../../hooks/useApi';
+import React, { useState } from 'react';
+import 'react-quill/dist/quill.snow.css';
+import ViewTemplate from '../../../components/templates/general/ViewTemplate';
+import BtnLinkSC from '../../../components/atoms/Link/BtnLinkSC';
+import { useNavigate } from 'react-router';
+import LikeBtn from '../../../components/molecules/post/LikeBtn';
+
+export const SubmitButton = styled.button`
+ display: block;
+ width: fit-content;
+ padding: 8px 30px;
+ font-size: 15px;
+ border-radius: 4px;
+ font-weight: normal;
+ color: white;
+ background-color: #3478FF;
+ border: 1px solid #3478FF;
+`;
+
+const RoundThumnail = styled.div`
+ width: 40px;
+ height: 40px;
+ background-color: #ffffff;
+ border-radius: 60px;
+ margin-right: 10px;
+ display: inline-block;
+ background-image: url(${props => props.imageUrl});
+ background-size: cover;
+ border: 1px solid rgba(0,0,0,.05);
+ margin-right: 28px;
+`;
+
+const PostViewHeader = styled.div`
+ padding: 40px 0 24px 0;
+ border-bottom: 1px solid rgba(0,0,0,.05);
+
+`
+
+const PostViewHeaderBottom = styled.div`
+ display: flex;
+ margin-top: 32px;
+`
+
+const PostInfo = styled.div`
+ color: rgba(0,0,0,.47);
+ font-size: 14px;
+`
+
+const PostViewBody = styled.div`
+
+`
+
+const PostEditButtonContainer = styled.div`
+ display: flex;
+ justify-content: end;
+`
+
+const ChannelInfo = styled.div`
+ font-size: 40px;
+ font-weight: 600;
+`
+
+const PostSbTtl = styled.div`
+ color: dimgray;
+ font-size: 18px;
+`
+
+
+export default function PostView() {
+ const { user } = useUserStore();
+ const { chnlUri, postId } = useParams();
+ const { data, isLoading, error } = useApiGet(
+ `/post/${postId}`,
+ [chnlUri, postId]
+ );
+ console.log(user);
+
+ // const {data: likeData, isLoading: likeIsLoading, error: likeErr} = useApiGet(
+ // `/post/${postId}/like/${user.nic}`,[]
+ // );
+
+ const navigate = useNavigate();
+
+ if (isLoading) return;
+ if (error) return {`[${error.code}] ${error.message}`} ;
+
+ if (!data) {
+ return null;
+ }
+
+ const isOwner = user.nic === data.writer.nic;
+
+
+ const goProfile = (nic) =>{
+ navigate(`/profile/${nic}`);
+ }
+ const goChannel = (chnlUri) => {
+ navigate(`/channel/${data.channel.chnlUri}`);
+ }
+
+
+ return (
+
+ goChannel(data.channel.chnlUri)}>
+ {data.channel.chnlTtl} 채널
+
+
+
+ {data.post.postTtl}
+
+ {data.post.postSbTtl}
+
+
+
+ goProfile(data.writer.nic)}>
+
+
+
{data.writer.nic}
+
+ {data.post.postPblcDtm} / 조회 {data.post.postInqrCnt}
+
+
+
+
+
+ {/*{ isOwner ?*/}
+ {/* : ''*/}
+ {/*}*/}
+ {isOwner ?
+ (수정하기
+ ) : ''
+ }
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/tempData/channel/channel.json b/src/tempData/channel/channel.json
deleted file mode 100644
index 0927acf..0000000
--- a/src/tempData/channel/channel.json
+++ /dev/null
@@ -1,256 +0,0 @@
-{
- "data": {
- "channel": {
- "chnlId": 7,
- "chnlTtl": "산왕잡기",
- "chnlIntro": "포기 ㄴㄴ해.",
- "chnlUri": "buksan",
- "chnlSbTtl": null,
- "chnlImgPath": "https://blog.kakaocdn.net/dn/d8FDB8/btquA1JkNEk/3rimTHGPZXiuhMe93Uw751/img.png",
- "suberCnt": 0,
- "chnlPostCnt": 23,
- "chnlLikCnt": 0,
- "chnlOpenDtm": "2023-06-17 16:03:15",
- "crtId": null,
- "hmpgUrl": null,
- "instaUrl": null,
- "githUrl": null,
- "ytbUrl": null,
- "twchUrl": null,
- "chnlStusCd": null,
- "chnlStusChgrId": null,
- "chnlStusChgDtm": "2023-06-17 16:03:15",
- "crtNic": "genius"
- },
- "channelUser": {
- "eid": "iamloved5959@gmail.com",
- "nic": "genius",
- "userIntro": "genius한 아이디입니다.",
- "userImgPath": null,
- "msgAlowYn": true
- },
- "channelPosts": [
- {
- "postId": 20,
- "postTtl": "웹 디자인과 사용자 경험",
- "postSbTtl": "웹 디자인과 사용자 경험",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": null,
- "postPchrgYn": true,
- "postThumnPath": "https://d3mcojo3jv0dbr.cloudfront.net/2023/04/24/14/10/c9c37e9d5fa6df93437d957f29b4d2aa.jpeg?w=360&h=270&q=65",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-07-03 22:10:33",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-07-03 22:10:40",
- "befPostId": 19,
- "nextPostId": 21,
- "serTtl": "잡설",
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 22,
- "postTtl": "데이터 시각화 방법",
- "postSbTtl": "데이터 시각화 방법",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://d3mcojo3jv0dbr.cloudfront.net/2023/07/01/08/33/f7594512ebfde6c80d3aa44d12500181.jpeg?w=360&h=270&q=65",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-07-03 22:10:33",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-07-03 22:10:40",
- "befPostId": 21,
- "nextPostId": 23,
- "serTtl": "잡설",
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 21,
- "postTtl": "모바일 앱 개발 팁",
- "postSbTtl": "모바일 앱 개발 팁",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": null,
- "postPchrgYn": true,
- "postThumnPath": "https://d3mcojo3jv0dbr.cloudfront.net/2023/04/24/14/07/8b9125f3a5ba52114082d71597c66f2c.jpeg?w=360&h=270&q=65",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-07-03 22:10:33",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-07-03 22:10:40",
- "befPostId": 20,
- "nextPostId": 22,
- "serTtl": "잡설",
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 23,
- "postTtl": "블록체인 기술 개요",
- "postSbTtl": "블록체인 기술 개요",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://d3mcojo3jv0dbr.cloudfront.net/2023/06/22/17/48/61402e2724483712413946312.png?w=360&h=270&q=65",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-07-03 22:10:33",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-07-03 22:10:40",
- "befPostId": 22,
- "nextPostId": null,
- "serTtl": "잡설",
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 19,
- "postTtl": "소프트웨어 테스팅 방법론",
- "postSbTtl": "소프트웨어 테스팅 방법론",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": null,
- "postPchrgYn": true,
- "postThumnPath": "https://d3mcojo3jv0dbr.cloudfront.net/2023/04/24/14/05/10d1430a8b5cc9b04304814d33b9e851.jpeg?w=360&h=270&q=65",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-07-03 22:10:33",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-07-03 22:10:40",
- "befPostId": 17,
- "nextPostId": 20,
- "serTtl": "잡설",
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 17,
- "postTtl": "사이버 보안 최신 동향",
- "postSbTtl": "사이버 보안의 최신 동향",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": null,
- "postPchrgYn": true,
- "postThumnPath": "https://d3mcojo3jv0dbr.cloudfront.net/2023/06/17/13/25/6f191cc1c17147691b8e8aac6adae34f.jpeg?w=360&h=270&q=65",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-07-03 22:10:33",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-07-03 22:10:40",
- "befPostId": 16,
- "nextPostId": 19,
- "serTtl": "잡설",
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- }
- ],
- "channelSerieses": [
- {
- "serId": 1,
- "serThumnPath": "https://cdn.pixabay.com/photo/2014/08/08/20/55/worried-girl-413690_150.jpg",
- "serTtl": "잡설",
- "serDesc": "잡다한 이야기를 적습니다.",
- "serOpenDtm": "2023-06-08 14:29:22",
- "serInqrCnt": 0,
- "serLikCnt": 0,
- "serRepCnt": 0,
- "serPostCnt": 1,
- "chnlDsgntSerOdr": 1,
- "serStusChgDtm": "2023-06-08 14:29:22",
- "serStusCdNm": "단편",
- "userNic": "genius"
- },
- {
- "serId": 5,
- "serThumnPath": "https://cdn.pixabay.com/photo/2017/05/07/19/46/desperate-2293377_150.jpg",
- "serTtl": "정호 시리즈 테스트5 edit",
- "serDesc": "시리즈 edit",
- "serOpenDtm": "2023-06-29 21:48:01",
- "serInqrCnt": 0,
- "serLikCnt": 0,
- "serRepCnt": 0,
- "serPostCnt": 0,
- "chnlDsgntSerOdr": 1,
- "serStusChgDtm": "2023-06-29 21:48:01",
- "serStusCdNm": "단편",
- "userNic": "genius"
- },
- {
- "serId": 6,
- "serThumnPath": "https://cdn.pixabay.com/photo/2014/09/07/22/03/bubble-gum-438404_150.jpg",
- "serTtl": "정호 시리즈 테스트1",
- "serDesc": "정호 시리즈 테스트1",
- "serOpenDtm": "2023-06-30 17:07:02",
- "serInqrCnt": 0,
- "serLikCnt": 0,
- "serRepCnt": 0,
- "serPostCnt": 0,
- "chnlDsgntSerOdr": 1,
- "serStusChgDtm": "2023-06-30 17:07:02",
- "serStusCdNm": "단편",
- "userNic": "genius"
- }
- ]
- }
-}
\ No newline at end of file
diff --git a/src/tempData/channel/channelAbout.json b/src/tempData/channel/channelAbout.json
deleted file mode 100644
index 57eea67..0000000
--- a/src/tempData/channel/channelAbout.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "data": {
- "channel": {
- "chnlId": 7,
- "chnlTtl": "산왕잡기",
- "chnlIntro": "포기 ㄴㄴ해.",
- "chnlUri": "buksan",
- "chnlSbTtl": null,
- "chnlImgPath": "https://blog.kakaocdn.net/dn/d8FDB8/btquA1JkNEk/3rimTHGPZXiuhMe93Uw751/img.png",
- "suberCnt": null,
- "chnlPostCnt": null,
- "chnlLikCnt": null,
- "chnlOpenDtm": "2023-06-17 16:03:15",
- "crtId": null,
- "hmpgUrl": null,
- "instaUrl": null,
- "githUrl": null,
- "ytbUrl": null,
- "twchUrl": null,
- "chnlStusCd": null,
- "chnlStusChgrId": null,
- "chnlStusChgDtm": "2023-06-17 16:03:15",
- "crtNic": "genius"
- },
- "user": {
- "eid": "iamloved5959@gmail.com",
- "nic": "genius",
- "userIntro": "genius한 아이디입니다.",
- "userImgPath": null,
- "msgAlowYn": true
- }
- }
-}
\ No newline at end of file
diff --git a/src/tempData/channel/channelPosts.json b/src/tempData/channel/channelPosts.json
deleted file mode 100644
index aaad171..0000000
--- a/src/tempData/channel/channelPosts.json
+++ /dev/null
@@ -1,383 +0,0 @@
-{
- "data": {
- "channelPosts": [
- {
- "postId": 28,
- "postTtl": "자바의 정석 정리3",
- "postSbTtl": "정호 포스트 테스트25",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2016/03/03/10/17/social-media-1233873_150.jpg",
- "serId": null,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-06-30 18:51:40",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-06-30 18:51:40",
- "befPostId": 26,
- "nextPostId": null,
- "serTtl": null,
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 27,
- "postTtl": "자바의 정석 정리2",
- "postSbTtl": "정호 포스트 테스트24",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2016/03/03/10/17/social-media-1233873_150.jpg",
- "serId": 5,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-06-30 18:47:28",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-06-30 18:47:28",
- "befPostId": null,
- "nextPostId": null,
- "serTtl": "정호 시리즈 테스트5 edit",
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 26,
- "postTtl": "자바의 정석 정리1",
- "postSbTtl": "정호 포스트 테스트21",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2016/06/09/20/38/woman-1446557_150.jpg",
- "serId": null,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-06-30 17:15:41",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-06-30 17:15:41",
- "befPostId": 25,
- "nextPostId": 28,
- "serTtl": null,
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 25,
- "postTtl": "컴퓨터 그래픽스 개발 기법",
- "postSbTtl": "정호 포스트 테스트19",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2014/03/22/22/17/phone-292994_150.jpg",
- "serId": null,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-06-29 19:51:55",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-06-29 19:51:55",
- "befPostId": 24,
- "nextPostId": 26,
- "serTtl": null,
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 24,
- "postTtl": "빅데이터 처리 기술",
- "postSbTtl": "정호 포스트 edit 테스트",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2016/12/19/08/39/mobile-phone-1917737_150.jpg",
- "serId": null,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-06-29 18:32:09",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-06-29 18:32:09",
- "befPostId": 12,
- "nextPostId": 25,
- "serTtl": null,
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 23,
- "postTtl": "블록체인 기술 개요",
- "postSbTtl": "정호 포스트 테스트14",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2018/11/29/21/51/social-media-3846597_150.png",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-06-29 17:59:05",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": "2023-06-29 17:59:05",
- "befPostId": 22,
- "nextPostId": null,
- "serTtl": "잡설",
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 22,
- "postTtl": "데이터 시각화 방법",
- "postSbTtl": "정호 포스트 테스트11",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2016/11/07/00/00/maze-1804499_150.jpg",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": "2023-06-29 14:34:25",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": null,
- "befPostId": 21,
- "nextPostId": 23,
- "serTtl": "잡설",
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 3,
- "postTtl": "데이터 분석 결과",
- "postSbTtl": "멘탈 잡기 비법 대공개",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2014/03/22/22/17/phone-292994_150.jpg",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": false,
- "postPblcDtm": "2023-06-22 13:29:07",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00410",
- "nowPostStudChgDtm": "2023-06-22 13:29:07",
- "befPostId": null,
- "nextPostId": 13,
- "serTtl": "잡설",
- "nowPostStusCdNm": "임시저장",
- "styleText": null
- },
- {
- "postId": 2,
- "postTtl": "사용자 인터페이스 개선",
- "postSbTtl": "아무거나 씁니다.",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2016/12/19/08/39/mobile-phone-1917737_150.jpg",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": false,
- "postPblcDtm": "2023-06-22 13:27:52",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00410",
- "nowPostStudChgDtm": "2023-06-22 13:27:52",
- "befPostId": null,
- "nextPostId": null,
- "serTtl": "잡설",
- "nowPostStusCdNm": "임시저장",
- "styleText": null
- },
- {
- "postId": 1,
- "postTtl": "첫 번째 포스트",
- "postSbTtl": "사랑은 ?다.",
- "postInqrCnt": 0,
- "postRepCnt": 0,
- "postLikCnt": 0,
- "spacInclCharCnt": 0,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2018/11/29/21/51/social-media-3846597_150.png",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": false,
- "postPblcDtm": "2023-06-16 14:00:30",
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00410",
- "nowPostStudChgDtm": "2023-06-16 14:00:30",
- "befPostId": null,
- "nextPostId": null,
- "serTtl": "잡설",
- "nowPostStusCdNm": "임시저장",
- "styleText": null
- },
- {
- "postId": 14,
- "postTtl": "데이터베이스 최적화 방법",
- "postSbTtl": "정호 포스트 테스트 3",
- "postInqrCnt": null,
- "postRepCnt": null,
- "postLikCnt": null,
- "spacInclCharCnt": null,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2016/12/09/11/33/smartphone-1894723_150.jpg",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": null,
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": null,
- "befPostId": 13,
- "nextPostId": 15,
- "serTtl": "잡설",
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- },
- {
- "postId": 15,
- "postTtl": "컴퓨터 네트워킹 기초",
- "postSbTtl": "정호 포스트 테스트4",
- "postInqrCnt": null,
- "postRepCnt": null,
- "postLikCnt": null,
- "spacInclCharCnt": null,
- "postPchrgYn": true,
- "postThumnPath": "https://cdn.pixabay.com/photo/2017/01/18/08/25/social-media-1989152_150.jpg",
- "serId": 1,
- "opubPlanId": null,
- "pchrgBlkPurcPnt": 1000,
- "ntceSettYn": true,
- "adoYn": true,
- "postPblcDtm": null,
- "chnlId": 7,
- "basicFontCd": "FT00110",
- "basicParagAlgnCd": "AL00110",
- "itdYn": true,
- "paragGapMargYn": true,
- "nowPostStusCd": "ST00420",
- "nowPostStudChgDtm": null,
- "befPostId": 14,
- "nextPostId": 16,
- "serTtl": "잡설",
- "nowPostStusCdNm": "최초발행",
- "styleText": null
- }
- ],
- "channelUser": {
- "eid": "iamloved5959@gmail.com",
- "nic": "genius",
- "userIntro": "genius한 아이디입니다.",
- "userImgPath": null,
- "msgAlowYn": true
- },
- "channel": {
- "chnlId": 7,
- "chnlTtl": "산왕잡기",
- "chnlIntro": "포기 ㄴㄴ해.",
- "chnlUri": "buksan",
- "chnlSbTtl": null,
- "chnlImgPath": "https://blog.kakaocdn.net/dn/d8FDB8/btquA1JkNEk/3rimTHGPZXiuhMe93Uw751/img.png",
- "suberCnt": 0,
- "chnlPostCnt": 23,
- "chnlLikCnt": null,
- "chnlOpenDtm": "2023-06-17 16:03:15",
- "crtId": null,
- "hmpgUrl": null,
- "instaUrl": null,
- "githUrl": null,
- "ytbUrl": null,
- "twchUrl": null,
- "chnlStusCd": null,
- "chnlStusChgrId": null,
- "chnlStusChgDtm": "2023-06-17 16:03:15",
- "crtNic": "genius"
- }
- }
-}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 180bd82..8d89af6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1288,6 +1288,13 @@
dependencies:
"@fortawesome/fontawesome-common-types" "6.4.0"
+"@fortawesome/free-regular-svg-icons@^6.4.0":
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz#cacc53bd8d832d46feead412d9ea9ce80a55e13a"
+ integrity sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "6.4.0"
+
"@fortawesome/free-solid-svg-icons@^6.4.0":
version "6.4.0"
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz#48c0e790847fa56299e2f26b82b39663b8ad7119"
@@ -2118,6 +2125,13 @@
resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz"
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
+"@types/quill@^1.3.10":
+ version "1.3.10"
+ resolved "https://registry.yarnpkg.com/@types/quill/-/quill-1.3.10.tgz#dc1f7b6587f7ee94bdf5291bc92289f6f0497613"
+ integrity sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==
+ dependencies:
+ parchment "^1.1.2"
+
"@types/range-parser@*":
version "1.2.4"
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz"
@@ -3242,6 +3256,11 @@ cliui@^8.0.1:
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"
+clone@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
+ integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
+
co@^4.6.0:
version "4.6.0"
resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz"
@@ -3738,6 +3757,18 @@ dedent@^0.7.0:
resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz"
integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==
+deep-equal@^1.0.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
+ integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
+ dependencies:
+ is-arguments "^1.0.4"
+ is-date-object "^1.0.1"
+ is-regex "^1.0.4"
+ object-is "^1.0.1"
+ object-keys "^1.1.1"
+ regexp.prototype.flags "^1.2.0"
+
deep-equal@^2.0.5:
version "2.2.0"
resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz"
@@ -4562,6 +4593,11 @@ etag@~1.8.1:
resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
+eventemitter3@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
+ integrity sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==
+
eventemitter3@^4.0.0:
version "4.0.7"
resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz"
@@ -4663,11 +4699,21 @@ express@^4.17.1, express@^4.17.3:
utils-merge "1.0.1"
vary "~1.1.2"
+extend@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+fast-diff@1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
+ integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==
+
fast-glob@^3.2.12, fast-glob@^3.2.9:
version "3.2.12"
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz"
@@ -5402,7 +5448,7 @@ ipaddr.js@^2.0.1:
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz"
integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
-is-arguments@^1.1.1:
+is-arguments@^1.0.4, is-arguments@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz"
integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
@@ -5544,7 +5590,7 @@ is-promise@^2.1.0:
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
-is-regex@^1.1.4:
+is-regex@^1.0.4, is-regex@^1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz"
integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
@@ -6565,7 +6611,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz"
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
-lodash@4, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
+lodash@4, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -6907,7 +6953,7 @@ object-inspect@^1.12.3, object-inspect@^1.9.0:
resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz"
integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
-object-is@^1.1.5:
+object-is@^1.0.1, object-is@^1.1.5:
version "1.1.5"
resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz"
integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
@@ -7103,6 +7149,11 @@ param-case@^3.0.4:
dot-case "^3.0.4"
tslib "^2.0.3"
+parchment@^1.1.2, parchment@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5"
+ integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==
+
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
@@ -7949,6 +8000,36 @@ quick-lru@^5.1.1:
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+quill-delta@^3.6.2:
+ version "3.6.3"
+ resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032"
+ integrity sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==
+ dependencies:
+ deep-equal "^1.0.1"
+ extend "^3.0.2"
+ fast-diff "1.1.2"
+
+quill-image-resize@^3.0.9:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/quill-image-resize/-/quill-image-resize-3.0.9.tgz#5677fb6257560bff951153ddbdb836758e49f804"
+ integrity sha512-5Dk0nixhbFsCwSWtPU9qqqtfM2gURfaP+pbBhQvAoMJoF4p99xbAibfAI3gsZJkbWUodoK2iAPf1V5oTSpv9ww==
+ dependencies:
+ lodash "^4.17.4"
+ quill "^1.2.2"
+ raw-loader "^0.5.1"
+
+quill@^1.2.2, quill@^1.3.7:
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8"
+ integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==
+ dependencies:
+ clone "^2.1.1"
+ deep-equal "^1.0.1"
+ eventemitter3 "^2.0.3"
+ extend "^3.0.2"
+ parchment "^1.1.4"
+ quill-delta "^3.6.2"
+
raf@^3.4.1:
version "3.4.1"
resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz"
@@ -7988,6 +8069,11 @@ raw-body@2.5.2:
iconv-lite "0.4.24"
unpipe "1.0.0"
+raw-loader@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa"
+ integrity sha512-sf7oGoLuaYAScB4VGr0tzetsYlS8EJH6qnTCfQ/WVEa89hALQ4RQfCKt5xCyPQKPDUbVUAIP1QsxAwfAjlDp7Q==
+
react-app-polyfill@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz"
@@ -8078,6 +8164,15 @@ react-modal@^3.16.1:
react-lifecycles-compat "^3.0.0"
warning "^4.0.3"
+react-quill@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/react-quill/-/react-quill-2.0.0.tgz#67a0100f58f96a246af240c9fa6841b363b3e017"
+ integrity sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==
+ dependencies:
+ "@types/quill" "^1.3.10"
+ lodash "^4.17.4"
+ quill "^1.3.7"
+
react-refresh@^0.11.0:
version "0.11.0"
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz"
@@ -8247,7 +8342,7 @@ regex-parser@^2.2.11:
resolved "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz"
integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==
-regexp.prototype.flags@^1.4.3:
+regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.3:
version "1.5.0"
resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz"
integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==