From f62d9efc574b8f72e79bd7809529ddbb5cae0e51 Mon Sep 17 00:00:00 2001 From: Jisung Jeong Date: Sat, 25 May 2024 22:12:26 +0900 Subject: [PATCH] feat: create dynamic route --- components/FolderCards/index.tsx | 20 ++----- components/Foldermenu/index.tsx | 98 ++++++++++++++++++-------------- lib/axios.js | 7 +++ package-lock.json | 91 +++++++++++++++++++++++++++++ package.json | 1 + pages/folder/[folderid].tsx | 80 ++++++++++++++++++++++++++ pages/folder/index.tsx | 16 ++++-- types/folder-type.ts | 9 +++ 8 files changed, 260 insertions(+), 62 deletions(-) create mode 100644 lib/axios.js create mode 100644 pages/folder/[folderid].tsx create mode 100644 types/folder-type.ts diff --git a/components/FolderCards/index.tsx b/components/FolderCards/index.tsx index 2f22f4621..de3aa5933 100644 --- a/components/FolderCards/index.tsx +++ b/components/FolderCards/index.tsx @@ -7,22 +7,14 @@ import moreoptionicon from '@/public/moreoptionicon.svg'; import { useState } from 'react'; import ModalFolder from '@/components/modal/ModalFolder'; import ModalDelete from '@/components/modal/ModalDelete'; +import { CardData } from '@/types/folder-type'; import Image from 'next/image'; import Link from 'next/link'; -interface Link { - id: string; - url: string; - image_source?: string; - thumbnail?: string; - title: string; - created_at: Date; - description: string; -} - -function FolderCards({ url }: { url: string }) { - const card = useFetch<{ data: Link[] }>(url); - const cardData = card?.data; +// function FolderCards({ url }: { url: string }) { +function FolderCards({ cards: cardData }: { cards: CardData[] }) { + // const card = useFetch<{ data: Link[] }>(url); + // const cardData = card?.data; const [isModalOpen, setIsModalOpen] = useState(false); const [isModalDeleteOpen, setIsModalDeleteOpen] = useState(false); const [popoverStates, setPopoverStates] = useState<{ @@ -59,7 +51,7 @@ function FolderCards({ url }: { url: string }) { return (
- {!cardData ? ( + {!cardData.length ? (

저장된 링크가 없습니다

diff --git a/components/Foldermenu/index.tsx b/components/Foldermenu/index.tsx index 7fb467edb..2a326fa8e 100644 --- a/components/Foldermenu/index.tsx +++ b/components/Foldermenu/index.tsx @@ -4,7 +4,7 @@ import addfolderIcon from '@/public/addfolder.svg'; import deleteicon from '@/public/deleteicon.svg'; import changenameicon from '@/public/changenameicon.svg'; import shareicon from '@/public/shareicon.svg'; -import { useState } from 'react'; +import { useMemo, useState, useEffect } from 'react'; import FolderCards from '@/components/FolderCards'; import ModalFolder from '@/components/modal/ModalFolder'; import ModalShare from '@/components/modal/ModalShare'; @@ -15,8 +15,9 @@ import S from './foldermenu.module.css'; import { BASE_URL_FOLDER, BASE_URL_ALL_FOLDER, - BASE_FOLDER_ID, } from '@/constant/folder-constant'; +import { CardData } from '@/types/folder-type'; +import { useRouter } from 'next/router'; interface Card { id: string; @@ -25,7 +26,7 @@ interface Card { description: string; } interface Folder { - id: string; + id: string | number; name: string; } @@ -33,14 +34,25 @@ interface FolderResponse { data: Folder[]; } -function Foldermenu() { - const folder = useFetch(BASE_URL_FOLDER); - const folderNames = folder?.data; - const [activeButton, setActiveButton] = useState('전체'); - const [url, setUrl] = useState(BASE_URL_ALL_FOLDER); - const [currentFolderId, setCurrentFolderId] = useState( - BASE_FOLDER_ID - ); +function Foldermenu({ selectedFolderId }: { selectedFolderId?: string }) { + const router = useRouter(); + + const foldersResponse = useFetch(BASE_URL_FOLDER); + const folders = useMemo(() => foldersResponse?.data ?? [], [foldersResponse]); + const selectedFolder = useMemo(() => { + if (!folders || !selectedFolderId) return undefined; + return folders.find( + (folder) => folder.id.toString() === selectedFolderId.toString() + ); + }, [folders, selectedFolderId]); + + const requestCardUrl = selectedFolderId + ? `${BASE_URL_ALL_FOLDER}?folderId=${selectedFolderId}` + : BASE_URL_ALL_FOLDER; + + const cardsResponse = useFetch<{ data: CardData[] }>(requestCardUrl); + const cards = cardsResponse?.data ?? []; + const [filteredLinks, setFilteredLinks] = useState([]); const [modalState, setModalState] = useState({ addFolder: false, @@ -62,21 +74,20 @@ function Foldermenu() { [modalType]: false, })); }; + const handleSearch = (filteredLinks: Card[]) => { setFilteredLinks(filteredLinks); }; - const handleOnClick = (e: React.MouseEvent) => { - const folderId = e.currentTarget.id; - const folderName = e.currentTarget.textContent || ''; - setActiveButton(folderName); - setCurrentFolderId(folderId); - - if (folderId !== BASE_FOLDER_ID) { - setUrl(`${BASE_URL_ALL_FOLDER}?folderId=${folderId}`); - } else { - setUrl(BASE_URL_ALL_FOLDER); + const handleOnClick = (selectedFolderId: string) => { + if (selectedFolderId) { + router.push(`/folder/${selectedFolderId}`, undefined, { + shallow: true, + }); + return; } + + router.push('/', undefined, { shallow: true }); }; return ( @@ -86,26 +97,23 @@ function Foldermenu() {
- {folderNames && - folderNames.map((foldername) => ( - - ))} + {folders.map((folder) => ( + + ))}
-

{activeButton}

- {activeButton === '전체' && ( +

+ {selectedFolder ? selectedFolder.name : '전체'} +

+ {!selectedFolderId && (
)}
- + {modalState.addFolder && ( handleModalClose('shareFolder')} isModalOpen={modalState.shareFolder} - currentFolderId={currentFolderId} + currentFolderId={modalState.shareFolder} /> )} {modalState.renameFolder && ( handleModalClose('renameFolder')} buttonName={'변경하기'} isModalOpen={modalState.renameFolder} @@ -163,7 +173,7 @@ function Foldermenu() { {modalState.deleteFolder && ( handleModalClose('deleteFolder')} isModalOpen={modalState.deleteFolder} /> diff --git a/lib/axios.js b/lib/axios.js new file mode 100644 index 000000000..8ac158dc7 --- /dev/null +++ b/lib/axios.js @@ -0,0 +1,7 @@ +import axios from 'axios'; + +const instance = axios.create({ + baseURL: 'https://bootcamp-api.codeit.kr/api', +}); + +export default instance; diff --git a/package-lock.json b/package-lock.json index 332492bc0..9c3f966c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "fe-weekly-mission", "version": "0.1.0", "dependencies": { + "axios": "^1.7.2", "babel-plugin-styled-components": "^2.1.4", "moment": "^2.30.1", "next": "13.5.6", @@ -1235,6 +1236,11 @@ "has-symbols": "^1.0.3" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -1256,6 +1262,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -1440,6 +1456,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1548,6 +1575,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2267,6 +2302,25 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -2276,6 +2330,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3149,6 +3216,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3553,6 +3639,11 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 72e24a66c..b50bc9d37 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "axios": "^1.7.2", "babel-plugin-styled-components": "^2.1.4", "moment": "^2.30.1", "next": "13.5.6", diff --git a/pages/folder/[folderid].tsx b/pages/folder/[folderid].tsx new file mode 100644 index 000000000..668ea1d0f --- /dev/null +++ b/pages/folder/[folderid].tsx @@ -0,0 +1,80 @@ +import Navigation from '@/components/Navigation'; +import styled from 'styled-components'; +import Footer from '@/components/Footer'; +import Addlink from '@/components/Addlink'; +import Foldermenu from '@/components/Foldermenu'; +import { useRouter } from 'next/router'; +import { useEffect, useMemo } from 'react'; + +/*테블릿 1124 이상 모바일 최소여백 32 테블릿 768~1199 모바일 375 ~767 */ + +const PageWrapper = styled.div` + display: flex; + flex-direction: column; + min-height: 100vh; /* 페이지의 최소 높이를 화면의 높이로 설정 */ +`; + +const PageDisplay = styled.div` + display: flex; + row-gap: 4rem; + padding: 4rem 0 10rem; + flex-direction: column; + margin: 0 auto; + width: 106rem; + + @media (max-width: 1199px) { + margin-left: min(3.2rem) max(auto); + margin-right: min(3.2rem) max(auto); + } + + @media (max-width: 1123px) { + width: 70.4rem; + + @media (max-width: 767px) { + width: 32.5rem; + } + } +`; +const FooterWrapper = styled.footer` + margin-top: auto; /* 페이지 컨텐츠 아래에 위치하도록 설정 */ +`; + +function FolderIdPage() { + const router = useRouter(); + const folderId = useMemo(() => { + const param = router.query.folderid; + if (typeof param !== 'string') { + return; + } + return param; + }, [router.query]); + + const handleLogout = () => { + localStorage.removeItem('accessToken'); + router.push('/signin'); + }; + + useEffect(() => { + const accessToken = localStorage.getItem('accessToken'); + if (!accessToken) { + router.push('/signin'); + return; + } + }, []); + + return ( + + + + + + + + +