diff --git a/package-lock.json b/package-lock.json index b95ba22..34eb2c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "prettier": "^3.3.3", "query-string": "^8.1.0", "react": "^18.3.1", + "react-bootstrap": "^2.10.5", "react-chartjs-2": "^5.0.0", "react-dom": "^18.2.0", "react-icons": "^4.10.1", @@ -3323,12 +3324,25 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-aria/ssr": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.6.tgz", + "integrity": "sha512-iLo82l82ilMiVGy342SELjshuWottlb5+VefO3jOQqQRNYnJBFpUSadswDPbRimSgJUZuFwIEYs6AabkP038fA==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@remix-run/router": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.18.0.tgz", @@ -3337,6 +3351,45 @@ "node": ">=14.0.0" } }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.8.0.tgz", + "integrity": "sha512-xJEOXUOTmT4FngTmhdjKFRrVVF0hwCLNPdatLCHkyS4dkiSK12cEu1Y0fjxktjJrdst9jJIc5J6ihMJCoWEN/g==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@popperjs/core": "^2.11.6", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.4.9", + "@types/warning": "^3.0.0", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3655,6 +3708,14 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@swc/helpers": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", + "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -4244,6 +4305,14 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -4312,6 +4381,11 @@ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, "node_modules/@types/ws": { "version": "8.5.12", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", @@ -5968,6 +6042,11 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -7023,6 +7102,15 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -9521,6 +9609,14 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -14680,6 +14776,23 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, + "node_modules/prop-types-extra/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -14876,6 +14989,35 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/react-bootstrap": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.5.tgz", + "integrity": "sha512-XueAOEn64RRkZ0s6yzUTdpFtdUXs5L5491QU//8ZcODKJNDLt/r01tNyriZccjgRImH1REynUc9pqjiRMpDLWQ==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.6.9", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-chartjs-2": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", @@ -15101,6 +15243,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "node_modules/react-redux": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", @@ -15265,6 +15412,21 @@ "node": ">=10" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -17435,6 +17597,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, "node_modules/underscore": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", @@ -17676,6 +17852,14 @@ "makeerror": "1.0.12" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", diff --git a/package.json b/package.json index 4750af0..41eb570 100644 --- a/package.json +++ b/package.json @@ -13,22 +13,23 @@ "babel-preset-react-app": "^10.0.1", "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", + "chart.js": "^4.0.0", "craco-alias": "^3.0.1", + "date-fns": "^2.28.0", "http-proxy-middleware": "^3.0.0", "js-cookie": "^3.0.5", "prettier": "^3.3.3", "query-string": "^8.1.0", "react": "^18.3.1", + "react-bootstrap": "^2.10.5", + "react-chartjs-2": "^5.0.0", "react-dom": "^18.2.0", "react-icons": "^4.10.1", "react-infinite-scroll-component": "^6.1.0", "react-redux": "^8.1.2", "react-router-dom": "^6.16.0", "react-scripts": "^5.0.1", - "web-vitals": "^3.3.2", - "date-fns": "^2.28.0", - "chart.js": "^4.0.0", - "react-chartjs-2": "^5.0.0" + "web-vitals": "^3.3.2" }, "scripts": { "start": "craco start", diff --git a/src/App.js b/src/App.js index 77c2526..f3a286a 100644 --- a/src/App.js +++ b/src/App.js @@ -4,20 +4,23 @@ import Home from "./pages/Home"; import NewsDetail from "./pages/NewsDetail"; import Login from "./pages/Login"; import News from "./layouts/News"; -import Signup from "./pages/SignUp" +import Signup from "./pages/SignUp"; +import CheckLocalStorage from "./routes/CheckLocalStorage"; const App = () => { return ( - - }> - } /> - } /> - } /> - } /> - } /> - - + + + }> + } /> + } /> + } /> + } /> + } /> + + + ); }; diff --git a/src/components/Header.js b/src/components/Header.js index 9c375dd..2c4a5ba 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -12,11 +12,11 @@ const Header = ({ categories = [], currentCategory, onCategorySelect }) => { // Check if JWT token exists in localStorage const token = localStorage.getItem("accessToken"); setIsLoggedIn(!!token); - }, []); + }, [categories, currentCategory]); // sub category 세팅 useEffect(() => { - if(currentCategory == null || currentCategory == undefined) return + if(currentCategory === null || currentCategory === undefined) return // 선택한 item이 top category일 경우에만 sub categories 세팅 if(currentCategory.parentCode == null) { @@ -30,7 +30,7 @@ const Header = ({ categories = [], currentCategory, onCategorySelect }) => { ) ]); } - }, [onCategorySelect]); + }, [onCategorySelect, categories, currentCategory]); // **************** component event handler **************** // const handleCategoryClick = (categoryParam) => { diff --git a/src/components/HotShorts.js b/src/components/HotShorts.js new file mode 100644 index 0000000..e806cfe --- /dev/null +++ b/src/components/HotShorts.js @@ -0,0 +1,34 @@ +import React from "react"; +import { Carousel } from "react-bootstrap"; +import VideoPlayer from "../components/VideoPlayer"; +import "bootstrap/dist/css/bootstrap.min.css"; + +const HotShorts = () => { + const titles = Array(5).fill("*"); + + return ( +
+

인기 쇼츠

+
+ + +
+ {titles.map((title, index) => ( +
+ +
+ ))} +
+
+
+
+
+ ); +}; + +export default HotShorts; diff --git a/src/components/HotTopicChart.js b/src/components/HotTopicChart.js index 66f554d..fb28dce 100644 --- a/src/components/HotTopicChart.js +++ b/src/components/HotTopicChart.js @@ -13,7 +13,6 @@ const chartLabel = { chart.data.datasets.forEach((dataset, i) => { const meta = chart.getDatasetMeta(i); meta.data.forEach((element, index) => { - const { x, y } = element.tooltipPosition(); const label = data.labels[index]; const radius = element.outerRadius * 0.7; const midAngle = (element.startAngle + element.endAngle) / 2; @@ -36,7 +35,6 @@ ChartJS.register(chartLabel); const HotTopicChart = ({onTopicSelect}) => { // **************** init values **************** // const [loading, setLoading] = useState(true); - const [topics, setTopics] = useState([]) const [chartData, setChartData] = useState({ labels: [], datasets: [ diff --git a/src/components/HotTopics.js b/src/components/HotTopics.js index c8709bd..96eecb2 100644 --- a/src/components/HotTopics.js +++ b/src/components/HotTopics.js @@ -15,7 +15,7 @@ const HotTopics = () => { if (page > 0) { fetchArticles(page, selectedTopic); } - }, [page]); + }, [page, selectedTopic]); const fetchArticles = (pageToFetch, topic) => { setLoading(true); diff --git a/src/components/NewsSection.js b/src/components/NewsSection.js index 546d037..f0bdb01 100644 --- a/src/components/NewsSection.js +++ b/src/components/NewsSection.js @@ -24,7 +24,7 @@ const NewsSection = ({ title, selectedCategory }) => { if (page > 0) { fetchArticles(page, selectedCategory); } - }, [page]); + }, [page, selectedCategory]); const fetchArticles = (pageToFetch, category) => { setLoading(true); diff --git a/src/components/VideoPlayer.js b/src/components/VideoPlayer.js index cd22f4a..a8becba 100644 --- a/src/components/VideoPlayer.js +++ b/src/components/VideoPlayer.js @@ -1,16 +1,18 @@ import React, { useState, useEffect } from "react"; import axiosClient from "@src/utils/axiosHelper"; -const VideoPlayer = ({ title }) => { +const VideoPlayer = ({ title, rank }) => { const [videoSrc, setVideoSrc] = useState(null); useEffect(() => { const fetchVideo = async () => { try { - title = "김예지"; - const response = await axiosClient.get(`/api/videos/search/${title}`, { - responseType: "blob" - }); + const response = await axiosClient.get( + `/api/videos/search?title=${title}&rank=${rank}`, + { + responseType: "blob" + } + ); const videoUrl = URL.createObjectURL(response.data); setVideoSrc(videoUrl); @@ -20,7 +22,7 @@ const VideoPlayer = ({ title }) => { }; fetchVideo(); - }, [title]); + }, [title, rank]); return (
diff --git a/src/layouts/News.js b/src/layouts/News.js index e4323a4..bbbe35d 100644 --- a/src/layouts/News.js +++ b/src/layouts/News.js @@ -1,38 +1,44 @@ -import React, { useState, useEffect } from 'react'; -import { useOutletContext } from 'react-router-dom'; -import HotTopics from '../components/HotTopics'; -import NewsSection from '../components/NewsSection'; -import RecommendedNews from '../components/RecommendedNews'; -import PopularNews from '../components/PopularNews'; -import Events from '../components/Events'; +import { useOutletContext } from "react-router-dom"; +import HotTopics from "../components/HotTopics"; +import NewsSection from "../components/NewsSection"; +import RecommendedNews from "../components/RecommendedNews"; +import PopularNews from "../components/PopularNews"; +import HotShorts from "../components/HotShorts"; +import Events from "../components/Events"; const News = () => { - // **************** init values **************** // - const { currentCategory } = useOutletContext(); + // **************** init values **************** // + const { currentCategory } = useOutletContext(); - // **************** UI **************** // - return ( -
-
- -
-
- -
-
- {currentCategory && ( - - )} -
-
- -
-
- -
-
- ); + // **************** UI **************** // + return ( +
+
+ +
+
+ +
+
+ +
+
+ {currentCategory && ( + + )} +
+
+ +
+
+ +
+
+ ); }; export default News; diff --git a/src/pages/Comments.js b/src/pages/Comments.js index 871aa1b..3921630 100644 --- a/src/pages/Comments.js +++ b/src/pages/Comments.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, {useState, useEffect, useCallback} from 'react'; import axiosClient from '@src/utils/axiosHelper'; import Cookies from "js-cookie"; import { format } from 'date-fns'; @@ -10,16 +10,12 @@ const Comments = ({ articleId }) => { const [hasMore, setHasMore] = useState(true); const [loading, setLoading] = useState(true); const [newComment, setNewComment] = useState(''); - const [userId, setUserID] = useState(Cookies.get('userId')); + const [userId] = useState(Cookies.get('userId')); const [commentCount, setCommentCount] = useState(0) const [editCommentId, setEditCommentId] = useState(null); const [editCommentText, setEditCommentText] = useState(''); - useEffect(() => { - fetchComments(page); - }, [page]); - - const fetchComments = (page) => { + const fetchComments = useCallback(() => { setLoading(true) axiosClient.get(`/api/articles/${articleId}/comments`, { @@ -43,7 +39,11 @@ const Comments = ({ articleId }) => { console.log("Error fetching comments: " + error); setLoading(false) }); - }; + }, [page, articleId]); + + useEffect(() => { + fetchComments(); + }, [page, fetchComments]); const handleLoadMoreComments = () => { setPage(prevPage => prevPage + 1) diff --git a/src/pages/Login.js b/src/pages/Login.js index 499c507..8442a73 100644 --- a/src/pages/Login.js +++ b/src/pages/Login.js @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Link } from "react-router-dom"; import Cookies from "js-cookie"; - +import { setTokenWithExpiry } from "../utils/auth"; import "../styles/login.css"; import axiosClient from "@src/utils/axiosHelper"; @@ -31,7 +31,7 @@ function Login(props) { } const { accessToken } = response.data.data; - localStorage.setItem("accessToken", accessToken); + setTokenWithExpiry("accessToken", accessToken, 5 * 60 * 60 * 1000); Cookies.set("userId", response.data.data.userId, { expires: 1 }); alert("로그인 성공"); window.location.href = "/"; diff --git a/src/pages/NewsDetail.js b/src/pages/NewsDetail.js index ba2b3a3..70a3901 100644 --- a/src/pages/NewsDetail.js +++ b/src/pages/NewsDetail.js @@ -1,10 +1,9 @@ -import React, { useEffect, useState } from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; import { useParams } from 'react-router-dom'; import axiosClient from "@src/utils/axiosHelper"; import Cookies from 'js-cookie'; import numberFormatter from '../utils/numberFormatter'; import Comments from './Comments'; -import VideoPlayer from '../components/VideoPlayer'; const NewsDetail = () => { const { id } = useParams(); @@ -63,30 +62,8 @@ const NewsDetail = () => { }); } - useEffect(() => { - axiosClient.get(`/api/articles/${id}`) - .then(response => { - setArticle(response.data); - setLoading(false); - - checkLikeStatus(); - - // 10초 이상 머물렀을 때 조회수 증가 요청 - const timer = setTimeout(() => { - updateViewCount(); - }, 10000); // 10,000 milliseconds = 10 seconds - - return () => clearTimeout(timer); // Cleanup timeout on component unmount - - }) - .catch(error => { - console.log("Error fetching article: " + error); - setLoading(false); - }); - }, [id]); - // 조회수 증가 요청 - const updateViewCount = () => { + const updateViewCount = useCallback(() => { const userId = Cookies.get('userId'); const viewedArticles = JSON.parse(Cookies.get('viewedArticleTime') || '{}'); @@ -110,9 +87,9 @@ const NewsDetail = () => { console.log("Error updating view count: " + error); }); } - }; + }, [id]); - const checkLikeStatus = () => { + const checkLikeStatus = useCallback(() => { const userId = Cookies.get('userId'); if (!userId || userId === "undefined") { @@ -126,7 +103,29 @@ const NewsDetail = () => { .catch(error => { console.log("Error checkLikeStatus: " + error); }); - }; + }, [id]); + + useEffect(() => { + axiosClient.get(`/api/articles/${id}`) + .then(response => { + setArticle(response.data); + setLoading(false); + + checkLikeStatus(); + + // 10초 이상 머물렀을 때 조회수 증가 요청 + const timer = setTimeout(() => { + updateViewCount(); + }, 10000); // 10,000 milliseconds = 10 seconds + + return () => clearTimeout(timer); // Cleanup timeout on component unmount + + }) + .catch(error => { + console.log("Error fetching article: " + error); + setLoading(false); + }); + }, [id, checkLikeStatus, updateViewCount]); const handleLike = () => { const userId = Cookies.get('userId'); @@ -205,10 +204,6 @@ const NewsDetail = () => { <>

{article.headline}

-
-

Video Player Example

- -
{article.author} | {article.articleCreatedAt}
diff --git a/src/pages/Replies.js b/src/pages/Replies.js index c581ec6..8036358 100644 --- a/src/pages/Replies.js +++ b/src/pages/Replies.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, {useState, useEffect, useCallback} from 'react'; import axiosClient from '@src/utils/axiosHelper'; import Cookies from "js-cookie"; import { format } from 'date-fns'; @@ -9,17 +9,13 @@ const Replies = ({ commentId }) => { const [hasMore, setHasMore] = useState(true); const [loading, setLoading] = useState(true); const [replyText, setReplyText] = useState(''); - const [userId, setUserID] = useState(Cookies.get('userId')); + const [userId] = useState(Cookies.get('userId')); const [isOpen, setIsOpen] = useState(false); const [replyCount, setReplyCount] = useState(0) const [editReplyId, setEditReplyId] = useState(null); const [editReplyText, setEditReplyText] = useState(''); - useEffect(() => { - fetchReplies(page); - }, [page]); - - const fetchReplies = (page) => { + const fetchReplies = useCallback(() => { setLoading(true) axiosClient.get(`/api/articles/${commentId}/replies`, { params: { @@ -42,7 +38,11 @@ const Replies = ({ commentId }) => { console.log("Error fetching replies: " + error); setLoading(false) }); - }; + }, [commentId, page]); + + useEffect(() => { + fetchReplies(); + }, [page, fetchReplies]); const handleAddReply = () => { if (!userId || userId === "undefined") { diff --git a/src/pages/SignUp.js b/src/pages/SignUp.js index 84771e9..dc5a327 100644 --- a/src/pages/SignUp.js +++ b/src/pages/SignUp.js @@ -182,19 +182,27 @@ function Signup(props) { + > + Like + + > + Comment + + > + Share + + > + Save +
diff --git a/src/routes/CheckLocalStorage.js b/src/routes/CheckLocalStorage.js new file mode 100644 index 0000000..094076e --- /dev/null +++ b/src/routes/CheckLocalStorage.js @@ -0,0 +1,15 @@ +// import { Navigate } from "react-router-dom"; +import { getTokenWithExpiry } from "../utils/auth"; + +const CheckLocalStorage = ({ children }) => { + const token = getTokenWithExpiry("accessToken"); + + if (!token) { + console.log("expired"); + // return ; + } + + return children; +}; + +export default CheckLocalStorage; diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100644 index 0000000..b99ee22 --- /dev/null +++ b/src/utils/auth.js @@ -0,0 +1,29 @@ +// ttl은 밀리초 단위로 지정 ex) 24시간 = 24 * 60 * 60 * 1000 +export const setTokenWithExpiry = (key, value, ttl) => { + const now = new Date(); + + const item = { + token: value, + expiry: now.getTime() + ttl + }; + + localStorage.setItem(key, JSON.stringify(item)); +}; + +export const getTokenWithExpiry = (key) => { + const itemStr = localStorage.getItem(key); + + if (!itemStr) { + return null; + } + + const item = JSON.parse(itemStr); + const now = new Date(); + + if (now.getTime() > item.expiry) { + localStorage.removeItem(key); + return null; + } + + return item.value; +}; diff --git a/src/utils/axiosHelper.js b/src/utils/axiosHelper.js index 0b241c2..9a2f4e9 100644 --- a/src/utils/axiosHelper.js +++ b/src/utils/axiosHelper.js @@ -1,25 +1,28 @@ -import axios from 'axios'; -import property from '@configs/propertyConfig'; +import axios from "axios"; +import property from "@configs/propertyConfig"; const server = property.catchweekServerHost; const axiosClient = axios.create({ baseURL: `${server}`, responseType: "json", headers: { - 'Content-Type': 'application/json', - 'Authorization': ' ' + "Content-Type": "application/json", + Authorization: " " } }); -axiosClient.interceptors.request.use((config) => { - const token = localStorage.getItem('accessToken'); - if (token) { - config.headers['Authorization'] = `Bearer ${token}`; +axiosClient.interceptors.request.use( + (config) => { + const tokenString = localStorage.getItem("accessToken"); + if (tokenString) { + const tokenJson = JSON.parse(tokenString); + config.headers["Authorization"] = `Bearer ${tokenJson.token}`; + } + return config; + }, + (error) => { + Promise.reject(error); } - return config; -}, (error) => { - Promise.reject(error); -}) - +); export default axiosClient;