diff --git a/axios/axiosInstance.ts b/axios/axiosInstance.ts new file mode 100644 index 000000000..5e42a35d5 --- /dev/null +++ b/axios/axiosInstance.ts @@ -0,0 +1,20 @@ +import axios from "axios"; + +const axiosInstance = axios.create({ + baseURL: "https://bootcamp-api.codeit.kr/api", +}); + +axiosInstance.interceptors.request.use( + (config) => { + const token = localStorage.getItem("accessToken"); + if (token) { + config.headers.Authorization = token; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +export default axiosInstance; diff --git a/component/FolderMain.tsx b/component/FolderMain.tsx index d9e4dcc3d..5c3b4aa7d 100644 --- a/component/FolderMain.tsx +++ b/component/FolderMain.tsx @@ -1,11 +1,12 @@ -import useUserFolders from "@/hooks/useUserFolders"; import { useEffect, useState } from "react"; import LinkCardListByFolderId from "./LinkCardListByFolderId"; import useSWR from "swr"; -import { fetcher } from "@/lib/fetcher"; import axios from "axios"; -import FolderButtons from "./FolderButtons"; +import FolderTabs from "./FolderTabs"; import FolderTitlebar from "./FolderTitlebar"; +import { useRouter } from "next/router"; +import axiosInstance from "@/axios/axiosInstance"; +import { fetchFolders } from "@/lib/folderFetcher"; interface UserData { id: number; @@ -41,8 +42,12 @@ export default function FolderMain({ if (!user) { return
Loading...
; } + + const router = useRouter(); + const [title, setTitle] = useState("전체"); const [clickedButton, setClickedButton] = useState(0); + const [folders, setFolders] = useState(); const [folderId, setFolderId] = useState(0); const [filteredLinks, setFilteredLinks] = useState([]); const [modalStates, setModalStates] = useState<{ @@ -60,14 +65,13 @@ export default function FolderMain({ addLinkModal: false, deleteLinkModal: false, }); - - const { data: folders } = useUserFolders(user.id); const { data: links } = useSWR( () => folderId === 0 - ? `https://bootcamp-api.codeit.kr/api/users/${user.id}/links` - : `https://bootcamp-api.codeit.kr/api/users/${user.id}/links?folderId=${folderId}`, - fetcher + ? `https://bootcamp-api.codeit.kr/api/links` + : `https://bootcamp-api.codeit.kr/api/links?folderId=${folderId}`, + // fetcher + (url) => axiosInstance.get(url).then((res) => res.data) ); const openModal = (modal: keyof typeof modalStates) => { @@ -78,14 +82,15 @@ export default function FolderMain({ setModalStates({ ...modalStates, [modal]: false }); }; - const handleButtonClick = (folderId: number) => { + const handleButtonClick = (folderId: number): void => { setClickedButton(folderId); - const clickedFolder = folders.data.find( + const clickedFolder = folders?.find( (folder: Folder) => folder.id === folderId ); if (clickedFolder) { setFolderId(clickedFolder.id); setTitle(clickedFolder.name); + router.push(`/folder/${folderId}`); } }; @@ -93,6 +98,7 @@ export default function FolderMain({ setClickedButton(0); setFolderId(0); setTitle("전체"); + router.push("/folder"); }; const getInputValue = async () => { @@ -104,7 +110,7 @@ export default function FolderMain({ const handleFilter = async () => { if (links) { const i = await getInputValue(); - const nextLinks = links.data.filter( + const nextLinks = links?.data?.folder?.filter( (link: any) => (link.url && link.url.includes(i)) || (link.title && link.title.includes(i)) || @@ -119,9 +125,37 @@ export default function FolderMain({ handleFilter(); }, [inputValue]); + useEffect(() => { + const folderIdFromURL = parseInt(router.query.folderId as string, 10) || 0; + setClickedButton(folderIdFromURL); + setFolderId(folderIdFromURL); + + if (folderIdFromURL === 0) { + setTitle("전체"); + } else { + const clickedFolder = folders?.find( + (folder: Folder) => folder.id === folderIdFromURL + ); + setTitle(clickedFolder ? clickedFolder.name : "전체"); + } + }, [router.query.folderId, folders]); + + useEffect(() => { + const fetchFoldersData = async () => { + try { + const { data } = await fetchFolders(); + setFolders(data.folder); + } catch (error) { + console.error(error); + } + }; + + fetchFoldersData(); + }, []); + return ( <> - void; + handleAllButtonClick: () => void; + folders: Folder[] | undefined | null; + openModal: (modal: keyof ModalStates) => void; + closeModal: (modal: keyof ModalStates) => void; + modalStates: ModalStates; }) { return (
@@ -39,7 +48,7 @@ export default function FolderButtons({ 전체 {folders && - folders.data.map((folder: Folder) => { + folders?.map((folder: Folder) => { return (
); diff --git a/component/LinkCard.tsx b/component/LinkCard.tsx index 5404b2b27..24acdb592 100644 --- a/component/LinkCard.tsx +++ b/component/LinkCard.tsx @@ -4,11 +4,11 @@ import { useState } from "react"; interface Link { id: string; - createdAt: Date; + created_at: Date; url: string; title: string; description: string; - imageSource: string; + image_source: string; } export default function LinkCard({ link }: { link: Link }) { @@ -22,7 +22,7 @@ export default function LinkCard({ link }: { link: Link }) {
link @@ -30,7 +30,7 @@ export default function LinkCard({ link }: { link: Link }) {

- {calculateTimeDiff(link.createdAt)} + {calculateTimeDiff(link.created_at)}

- {formatDate(link.createdAt)} + {formatDate(link.created_at)}

diff --git a/component/LinkCardByFolderId.tsx b/component/LinkCardByFolderId.tsx index 34b34ba23..549c7a4cc 100644 --- a/component/LinkCardByFolderId.tsx +++ b/component/LinkCardByFolderId.tsx @@ -2,6 +2,7 @@ import { calculateTimeDiff } from "@/utils/calculateTimeDiff"; import { useState } from "react"; import Modal from "./Modal"; import { formatDate } from "@/utils/formatDate"; +import Link from "next/link"; interface Link { id: string; @@ -29,15 +30,17 @@ export default function LinkCardByFolderId({ setShowPopover(!showPopover); }; + console.log(link); + return (
- + link - +

diff --git a/component/LinkCardList.tsx b/component/LinkCardList.tsx index 8e479f6c2..f87f7a5cc 100644 --- a/component/LinkCardList.tsx +++ b/component/LinkCardList.tsx @@ -1,20 +1,22 @@ -import useSampleFolder from "@/hooks/useSampleFolder"; import LinkCard from "./LinkCard"; interface Link { id: string; - createdAt: Date; + created_at: Date; url: string; title: string; description: string; - imageSource: string; + image_source: string; } -export default function LinkCardList() { - const { data } = useSampleFolder(); - - const links: Link[] = data?.folder?.links; - +export default function LinkCardList({ links }: { links: Link[] }) { + if (links?.length === 0) { + return ( +

+ 저장된 링크가 없습니다. +
+ ); + } return (
{links?.map((link) => { diff --git a/component/Navbar.tsx b/component/Navbar.tsx index dd19bcc81..30e64423e 100644 --- a/component/Navbar.tsx +++ b/component/Navbar.tsx @@ -1,31 +1,87 @@ +import { useState, useRef, useEffect } from "react"; import Link from "next/link"; +import { useRouter } from "next/router"; interface UserData { id: number; name: string; email: string; - profileImageSource: string; + profileImageSource?: string; + image_source?: string; } -export default function Navbar({ user }: { user: UserData }) { +export default function Navbar({ + user, +}: { + user: UserData | null | undefined; +}) { + const router = useRouter(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const popoverRef = useRef(null); + + const handleLogout = () => { + localStorage.removeItem("accessToken"); + setIsPopoverOpen(false); + alert("로그아웃 되었습니다."); + router.reload(); + }; + + const togglePopover = () => { + setIsPopoverOpen((prev) => !prev); + }; + + const handleClickOutside = (event: MouseEvent) => { + if ( + popoverRef.current && + !popoverRef.current.contains(event.target as Node) + ) { + setIsPopoverOpen(false); + } + }; + + useEffect(() => { + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + return (
logo {user ? ( -
+
profile -
{user.email}
+
+ {user.email} +
) : ( - + + + + )} + {isPopoverOpen && ( +
+ +
)}
); diff --git a/component/SignHeader.tsx b/component/SignHeader.tsx index 51ad2a276..d1649aa56 100644 --- a/component/SignHeader.tsx +++ b/component/SignHeader.tsx @@ -10,10 +10,10 @@ export default function SignHeader() { return ( <> logo router.push("/")} />
diff --git a/component/SigninForm.tsx b/component/SigninForm.tsx index e68bc6578..098a4ec0f 100644 --- a/component/SigninForm.tsx +++ b/component/SigninForm.tsx @@ -89,11 +89,11 @@ export default function SigninForm() { className="w-full mt-3 px-[15px] py-[18px] rounded-md" /> eye-off

{errors.password?.message}

diff --git a/component/SigninPasswordInput.tsx b/component/SigninPasswordInput.tsx deleted file mode 100644 index 2826f5302..000000000 --- a/component/SigninPasswordInput.tsx +++ /dev/null @@ -1,39 +0,0 @@ -export default function SigninPasswordInput({ - register, - inputType, - isClosed, - errors, - handleToggleImage, -}: { - register: any; - inputType: any; - isClosed: any; - errors: any; - handleToggleImage: any; -}) { - return ( -
- - - eye-off -

{errors.password?.message}

-
- ); -} diff --git a/component/SignupForm.tsx b/component/SignupForm.tsx index 594dfe8fe..34faaeffc 100644 --- a/component/SignupForm.tsx +++ b/component/SignupForm.tsx @@ -109,11 +109,11 @@ export default function SignupForm() { className="w-full mt-3 px-[15px] py-[18px] rounded-md" /> eye-off

{errors.password?.message}

@@ -139,11 +139,11 @@ export default function SignupForm() { className="w-full mt-3 px-[15px] py-[18px] rounded-md" /> eye-off

diff --git a/component/SocialLogin.tsx b/component/SocialLogin.tsx index 0729a82db..7177c7c0b 100644 --- a/component/SocialLogin.tsx +++ b/component/SocialLogin.tsx @@ -15,18 +15,18 @@ export default function SocialLogin() {

google
kakao
diff --git a/lib/folderFetcher.ts b/lib/folderFetcher.ts new file mode 100644 index 000000000..5d11c36cc --- /dev/null +++ b/lib/folderFetcher.ts @@ -0,0 +1,11 @@ +import axiosInstance from "@/axios/axiosInstance"; + +export const fetchFolders = async () => { + try { + const response = await axiosInstance.get("/folders"); + + return response.data; + } catch (error) { + throw error; + } +}; diff --git a/lib/linkFetcher.ts b/lib/linkFetcher.ts new file mode 100644 index 000000000..500ba9bd2 --- /dev/null +++ b/lib/linkFetcher.ts @@ -0,0 +1,11 @@ +import axiosInstance from "@/axios/axiosInstance"; + +export const fetchLinks = async () => { + try { + const response = await axiosInstance.get("/links"); + + return response.data; + } catch (error) { + throw error; + } +}; diff --git a/lib/userFetcher.ts b/lib/userFetcher.ts new file mode 100644 index 000000000..17f0d5cf7 --- /dev/null +++ b/lib/userFetcher.ts @@ -0,0 +1,11 @@ +import axiosInstance from "@/axios/axiosInstance"; + +export const fetchUser = async () => { + try { + const response = await axiosInstance.get("/users"); + + return response.data; + } catch (error) { + throw error; + } +}; diff --git a/next.config.mjs b/next.config.mjs index d5456a15d..61cd5eddd 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - reactStrictMode: true, + reactStrictMode: false, }; export default nextConfig; diff --git a/pages/folder.tsx b/pages/folder/[folderId].tsx similarity index 60% rename from pages/folder.tsx rename to pages/folder/[folderId].tsx index d852d9842..aceb7636d 100644 --- a/pages/folder.tsx +++ b/pages/folder/[folderId].tsx @@ -2,14 +2,23 @@ import AddLinkInput from "@/component/AddLinkInput"; import FolderMain from "@/component/FolderMain"; import Navbar from "@/component/Navbar"; import Searchbar from "@/component/Searchbar"; -import useCurrentUser from "@/hooks/useCurrentUser"; +import { fetchUser } from "@/lib/userFetcher"; import axios from "axios"; -import { useState } from "react"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; + +interface UserData { + id: number; + name: string; + email: string; + profileImageSource: string; +} export default function Folder() { + const [user, setUser] = useState(); const [inputValue, setInputValue] = useState(""); - const { data: user } = useCurrentUser(); + const router = useRouter(); const handleChange = (e: any) => { const value = e.target.value; @@ -31,10 +40,29 @@ export default function Folder() { setInputValue(""); }; + useEffect(() => { + const getUsers = async () => { + try { + const { data } = await fetchUser(); + setUser(data[0]); + console.log(data[0]); + } catch (error) { + console.error(error); + } + }; + + getUsers(); + }, []); + if (!user) { return
Loading...
; } + if (!localStorage.getItem("accessToken")) { + alert("로그인이 필요합니다."); + router.push("/signin"); + } + return ( <> diff --git a/pages/folder/index.tsx b/pages/folder/index.tsx new file mode 100644 index 000000000..fcd54e3e8 --- /dev/null +++ b/pages/folder/index.tsx @@ -0,0 +1,78 @@ +import AddLinkInput from "@/component/AddLinkInput"; +import FolderMain from "@/component/FolderMain"; +import Navbar from "@/component/Navbar"; +import Searchbar from "@/component/Searchbar"; +import { fetchUser } from "@/lib/userFetcher"; +import axios from "axios"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; + +interface UserData { + id: number; + name: string; + email: string; + profileImageSource: string; +} + +export default function Folder() { + const [user, setUser] = useState(); + const [inputValue, setInputValue] = useState(""); + + const router = useRouter(); + + const handleChange = (e: any) => { + const value = e.target.value; + setInputValue(value); + }; + + const handleSubmit = async (e: any) => { + e.preventDefault(); + + try { + const response = await axios.post("/api/input", { inputValue }); + setInputValue(response.data); + } catch (error) { + console.error("Error:", error); + } + }; + + const handleClose = () => { + setInputValue(""); + }; + + useEffect(() => { + const getUsers = async () => { + try { + const { data } = await fetchUser(); + setUser(data[0]); + } catch (error) { + console.error(error); + } + }; + + getUsers(); + }, []); + + if (typeof window !== "undefined" && !localStorage.getItem("accessToken")) { + alert("로그인이 필요합니다."); + router.push("/signin"); + } + + if (!user) { + return
Loading...
; + } + + return ( + <> + + + + + + ); +} diff --git a/pages/index.tsx b/pages/index.tsx index 309006262..7a34a3ae5 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -3,10 +3,36 @@ import Hero from "@/component/Hero"; import LandingDescription from "@/component/LandingDescription"; import LandingDescriptionMobile from "@/component/LandingDescriptionMobile"; import Navbar from "@/component/Navbar"; -import useCurrentUser from "@/hooks/useCurrentUser"; +import { fetchUser } from "@/lib/userFetcher"; +import { useEffect, useState } from "react"; + +interface UserData { + id: number; + name: string; + email: string; + profileImageSource: string; +} export default function Home() { - const { data: user } = useCurrentUser(); + const [user, setUser] = useState(); + + useEffect(() => { + const getUsers = async () => { + try { + const { data } = await fetchUser(); + setUser(data[0]); + console.log(data[0]); + } catch (error) { + console.error(error); + } + }; + + getUsers(); + }, []); + + if (!user) { +
Loading...
; + } return ( <> diff --git a/pages/shared/[folderId].tsx b/pages/shared/[folderId].tsx new file mode 100644 index 000000000..753b54ac3 --- /dev/null +++ b/pages/shared/[folderId].tsx @@ -0,0 +1,33 @@ +import Footer from "@/component/Footer"; +import LinkCardList from "@/component/LinkCardList"; +import Navbar from "@/component/Navbar"; +import SharedSearchBar from "@/component/SharedSearchBar"; +import User from "@/component/User"; +import useCurrentUser from "@/hooks/useCurrentUser"; +import { fetcher } from "@/lib/fetcher"; +import { useParams } from "next/navigation"; +import useSWR from "swr"; + +export default function Page() { + const { data: user } = useCurrentUser(); + + const params = useParams(); + + const { data: links } = useSWR( + () => + !params.folderId + ? `https://bootcamp-api.codeit.kr/api/users/${user?.id}/links` + : `https://bootcamp-api.codeit.kr/api/users/${user?.id}/links?folderId=${params.folderId}`, + fetcher + ); + + return ( + <> + + + + +