diff --git a/api/folder.ts b/api/folder.ts index 8434842b3..92fd1cb92 100644 --- a/api/folder.ts +++ b/api/folder.ts @@ -2,12 +2,12 @@ import { BASE_URL } from "@/constants/url"; export const getFolderNavInfo = async (userId: number) => { try { - const response = await fetch(`${BASE_URL}/api/users/${userId}/folders`); + const response = await fetch(`${BASE_URL}/users/${userId}/folders`); if (!response.ok) { throw new Error(`${response.status}`); } - const result = await response.json(); + return result; } catch (error) { if (error instanceof Error) alert(`${error.message}에러 발생!`); @@ -19,9 +19,9 @@ export const getFolderListInfo = async ( navId: string | string[] | undefined, userToken: string | undefined ) => { - let query = "/api/links"; + let query = "/links"; if (navId) { - query += `?folderId=${navId}`; + query = `/folders/${navId}${query}`; } try { @@ -44,3 +44,95 @@ export const getFolderListInfo = async ( return false; } }; + +export const addFolder = async (folderName: string, userToken: string) => { + const response = await fetch(`${BASE_URL}/folders`, { + method: "POST", + headers: { + Authorization: `Bearer ${userToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: folderName, + }), + }); + + if (!response.ok) { + throw new Error("Failed to add Folder!!"); + } +}; + +export const changeFolderName = async ( + pageNavId: string | string[], + folderName: string, + userToken: string +) => { + const response = await fetch(`${BASE_URL}/folders/${pageNavId}`, { + method: "PUT", + headers: { + Authorization: `Bearer ${userToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: folderName, + }), + }); + + if (!response.ok) { + throw new Error("Failed to add Folder!!"); + } +}; + +export const deleteFolder = async ( + pageNavId: string | string[], + userToken: string +) => { + const response = await fetch(`${BASE_URL}/folders/${pageNavId}`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${userToken}`, + }, + }); + + if (!response.ok) { + throw new Error("Failed to add Folder!!"); + } +}; + +export const addLink = async ( + linkUrl: string, + folderId: number, + userToken: string +) => { + const response = await fetch(`${BASE_URL}/links`, { + method: "POST", + headers: { + Authorization: `Bearer ${userToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + url: linkUrl, + folderId: folderId, + }), + }); + + if (!response.ok) { + if (response.status === 400) { + throw new Error("이미 존재하는 URL입니다."); + } + throw new Error("Failed to add Folder!!"); + } +}; + +export const deleteLink = async (linkId: number, userToken: string) => { + const response = await fetch(`${BASE_URL}/links/${linkId}`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${userToken}`, + }, + }); + + if (!response.ok) { + throw new Error("Failed to add Folder!!"); + } +}; diff --git a/api/shared.ts b/api/shared.ts index 73ee75730..5d769cc67 100644 --- a/api/shared.ts +++ b/api/shared.ts @@ -4,12 +4,13 @@ export const getSharedFolderInfo = async ( folderId: string | string[] | undefined ) => { try { - const response = await fetch(`${BASE_URL}/api/folders/${folderId}`); + const response = await fetch(`${BASE_URL}/folders/${folderId}`); if (!response.ok) { throw new Error(`${response.status}`); } const result = await response.json(); + return result; } catch (error) { if (error instanceof Error) alert(`${error.message}에러 발생!!`); @@ -23,13 +24,14 @@ export const getSharedLinkList = async ( ) => { try { const response = await fetch( - `${BASE_URL}/api/users/${userId}/links?folderId=${folderId}` + `${BASE_URL}/users/${userId}/links?folderId=${folderId}` ); if (!response.ok) { throw new Error(`${response.status}`); } const result = await response.json(); + return result; } catch (error) { if (error instanceof Error) alert(`${error.message}에러 발생2!`); diff --git a/api/user.ts b/api/user.ts index c57d5d0c1..2a7ad3be7 100644 --- a/api/user.ts +++ b/api/user.ts @@ -11,9 +11,8 @@ interface SignUserData { export const getSignInProfile = async (userToken: string) => { let result = null; - try { - const response = await fetch(`${BASE_URL}/api/users`, { + const response = await fetch(`${BASE_URL}/users`, { headers: { Authorization: `Bearer ${userToken}`, }, @@ -34,7 +33,7 @@ export const getUserInfo = async (userId: string) => { let result = null; try { - const response = await fetch(`${BASE_URL}/api/users/${userId}`); + const response = await fetch(`${BASE_URL}/users/${userId}`); if (!response.ok) { throw new Error(`${response.status}`); } @@ -51,7 +50,7 @@ export const postSign = async (apiUrl: string, userData: SignUserData) => { let result = null; try { - const response = await fetch(`${BASE_URL}/api/${apiUrl}`, { + const response = await fetch(`${BASE_URL}/${apiUrl}`, { method: "POST", headers: POST_HEADER, body: JSON.stringify(userData), @@ -72,7 +71,7 @@ export const postCheckEmail = async (email: string) => { let result = null; try { - const response = await fetch(`${BASE_URL}/api/check-email`, { + const response = await fetch(`${BASE_URL}/users/check-email`, { method: "POST", headers: POST_HEADER, body: JSON.stringify({ email: email }), diff --git a/components/common/KebabList.tsx b/components/common/KebabList.tsx index 82b0c3efb..de6a70768 100644 --- a/components/common/KebabList.tsx +++ b/components/common/KebabList.tsx @@ -1,22 +1,25 @@ import * as S from "./KebabList.styled"; import useModal from "@/hooks/useModal"; import { ModalParam } from "@/types/Modal"; +import LinkDeleteContent from "./modal/modalContent/LinkDeleteContent"; +import LinkAddContent from "./modal/modalContent/LinkAddContent"; interface IKebabList { + linkId: number; linkUrl: string; setKebabOpen: (isOpen: boolean) => void; } -const KebabList = ({ linkUrl, setKebabOpen }: IKebabList) => { +const KebabList = ({ linkId, linkUrl, setKebabOpen }: IKebabList) => { const { openModal } = useModal(); const handleOpenModal = ( e: React.MouseEvent, - { type, props }: ModalParam + { props, component }: ModalParam ) => { e.preventDefault(); setKebabOpen(false); - openModal({ type, props }); + openModal({ props, component }); }; return ( @@ -24,8 +27,8 @@ const KebabList = ({ linkUrl, setKebabOpen }: IKebabList) => { { handleOpenModal(e, { - type: "linkDelete", props: { title: "링크 삭제", subTitle: linkUrl }, + component: , }); }} > @@ -34,8 +37,8 @@ const KebabList = ({ linkUrl, setKebabOpen }: IKebabList) => { { handleOpenModal(e, { - type: "linkAdd", - props: { title: "폴더에 추가", subTitle: "링크 주소" }, + props: { title: "폴더에 추가", subTitle: linkUrl }, + component: , }); }} > diff --git a/components/common/Link.tsx b/components/common/Link.tsx index 2c954ca0f..b890eaa82 100644 --- a/components/common/Link.tsx +++ b/components/common/Link.tsx @@ -49,7 +49,11 @@ const Link = ({ linkInfo, isSetting }: LinkItemParam) => { )} {isKebabOpen && ( - + )} {linkInfo.description} diff --git a/components/common/modal/ModalPortal.tsx b/components/common/modal/ModalPortal.tsx index e1322e934..6bd7de6b8 100644 --- a/components/common/modal/ModalPortal.tsx +++ b/components/common/modal/ModalPortal.tsx @@ -2,22 +2,7 @@ import { useContext } from "react"; import { createPortal } from "react-dom"; import { ModalStateContext } from "@/context/Modal"; import ModalLayOut from "./ModalLayout"; -import ShareModalContent from "./modalContent/ShareModalContent"; -import FolderAddModalContent from "./modalContent/FolderAddModalContent"; -import FolderNameChangeContent from "./modalContent/FolderNameChangeContent"; -import FolderDeleteContent from "./modalContent/FolderDeleteContent"; -import LinkDeleteContent from "./modalContent/LinkDeleteContent"; -import LinkAddContent from "./modalContent/LinkAddContent"; -import { TModalParam, ModalType } from "@/types/Modal"; - -const MODAL_COMPONENTS: Record JSX.Element> = { - share: ShareModalContent, - folderAdd: FolderAddModalContent, - folderNameChange: FolderNameChangeContent, - folderDelete: FolderDeleteContent, - linkDelete: LinkDeleteContent, - linkAdd: LinkAddContent, -}; +import { TModalParam } from "@/types/Modal"; function MoadlPortal() { const modalParam: TModalParam = useContext(ModalStateContext); @@ -25,13 +10,12 @@ function MoadlPortal() { return null; } - const { type, props } = modalParam; + const { props, component } = modalParam; - const Modal = MODAL_COMPONENTS[type]; return createPortal( <> - + {component} , document.getElementById("modal") as HTMLElement diff --git a/components/common/modal/modalContent/FolderAddModalContent.tsx b/components/common/modal/modalContent/FolderAddModalContent.tsx index 8c980f667..e34371f90 100644 --- a/components/common/modal/modalContent/FolderAddModalContent.tsx +++ b/components/common/modal/modalContent/FolderAddModalContent.tsx @@ -1,11 +1,52 @@ +import { useContext, useState } from "react"; import * as S from "./FolderAddModalContent.styled"; import Button from "@/components/common/Button"; +import { useMutation } from "@tanstack/react-query"; +import { addFolder } from "@/api/folder"; +import { UserInfoContext } from "@/context/User"; const FolderAddModalContent = () => { + const [folderName, setFolderName] = useState(""); + const userInfo = useContext(UserInfoContext); + + const changeInputFolderName = (e: React.ChangeEvent) => { + setFolderName(e.target.value); + }; + + const handleFolderAdd = useMutation({ + mutationFn: () => { + if (!userInfo || !userInfo.token) { + return Promise.reject(new Error("UserToken Error!")); + } + return addFolder(folderName, userInfo.token); + }, + onSuccess: () => { + alert("폴더 추가 성공"); + window.location.reload(); + }, + }); + + const handleFolderAddBtnClick = () => { + if (folderName.trim() === "") { + alert("폴더 이름 입력"); + return; + } + handleFolderAdd.mutate(); + }; + return ( - - diff --git a/components/common/modal/modalContent/FolderDeleteContent.tsx b/components/common/modal/modalContent/FolderDeleteContent.tsx index 4b04bf97d..4f3b4d8ec 100644 --- a/components/common/modal/modalContent/FolderDeleteContent.tsx +++ b/components/common/modal/modalContent/FolderDeleteContent.tsx @@ -1,8 +1,44 @@ +import { deleteFolder } from "@/api/folder"; import Button from "@/components/common/Button"; +import { UserInfoContext } from "@/context/User"; +import useModal from "@/hooks/useModal"; +import { useMutation } from "@tanstack/react-query"; +import { useRouter } from "next/router"; +import { useContext } from "react"; + +interface IFolderDeleteContentProps { + pageNavId: string | string[]; +} + +const FolderDeleteContent = ({ pageNavId }: IFolderDeleteContentProps) => { + const userInfo = useContext(UserInfoContext); + const router = useRouter(); + const { closeModal } = useModal(); + + const handleFolderDeleteBtnClick = () => { + handleFolderDelete.mutate(); + }; + + const handleFolderDelete = useMutation({ + mutationFn: () => { + if (!userInfo || !userInfo.token) { + return Promise.reject(new Error("UserToken Error!")); + } + return deleteFolder(pageNavId, userInfo.token); + }, + onSuccess: () => { + alert("삭제 성공"); + closeModal(); + router.replace("/folder"); + }, + }); -const FolderDeleteContent = () => { return ( - ); diff --git a/components/common/modal/modalContent/FolderNameChangeContent.tsx b/components/common/modal/modalContent/FolderNameChangeContent.tsx index 4364c598c..f32e8cb79 100644 --- a/components/common/modal/modalContent/FolderNameChangeContent.tsx +++ b/components/common/modal/modalContent/FolderNameChangeContent.tsx @@ -1,11 +1,61 @@ +import { UserInfoContext } from "@/context/User"; +import { useState, useContext } from "react"; import * as S from "./FolderNameChangeContent.styled"; import Button from "@/components/common/Button"; +import { useMutation } from "@tanstack/react-query"; +import { changeFolderName } from "@/api/folder"; + +interface FolderNameChangeContentProps { + navName: string; + pageNavId: string | string[]; +} + +const FolderNameChangeContent = ({ + navName, + pageNavId, +}: FolderNameChangeContentProps) => { + const [folderName, setFolderName] = useState(navName); + const userInfo = useContext(UserInfoContext); + + const changeInputFolderName = (e: React.ChangeEvent) => { + setFolderName(e.target.value); + }; + + const handleFolderChange = useMutation({ + mutationFn: () => { + if (!userInfo || !userInfo.token) { + return Promise.reject(new Error("UserToken Error!")); + } + return changeFolderName(pageNavId, folderName, userInfo.token); + }, + onSuccess: () => { + alert("이름 변경 성공"); + window.location.reload(); + }, + }); + + const handleFolderChangeBtnClick = () => { + if (folderName.trim() === "") { + alert("폴더 이름 입력"); + return; + } + handleFolderChange.mutate(); + }; -const FolderNameChangeContent = () => { return ( - - + + ); }; diff --git a/components/common/modal/modalContent/LinkAddContent.tsx b/components/common/modal/modalContent/LinkAddContent.tsx index f9d5d333a..fe264d93e 100644 --- a/components/common/modal/modalContent/LinkAddContent.tsx +++ b/components/common/modal/modalContent/LinkAddContent.tsx @@ -1,18 +1,27 @@ import * as S from "./LinkAddContent.styled"; import { useState, useEffect, useRef, useContext } from "react"; import Button from "@/components/common/Button"; -import { getFolderNavInfo } from "@/api/folder"; +import { addLink, getFolderNavInfo } from "@/api/folder"; import checkIcon from "@/public/image/icon/check.svg"; import { INavItem } from "@/types/FolderNav"; import Image from "next/image"; import { UserInfoContext } from "@/context/User"; +import { useMutation } from "@tanstack/react-query"; +import { useRouter } from "next/router"; +import useModal from "@/hooks/useModal"; -const LinkAddContent = () => { +interface ILinkAddContentProps { + linkUrl: string; +} + +const LinkAddContent = ({ linkUrl }: ILinkAddContentProps) => { const [navList, setNavList] = useState([]); const [isLoading, setIsLoading] = useState(false); - const [currentSelectedFolder, setFolder] = useState(null); + const [currentSelectedFolder, setFolder] = useState(); const selectedFoler: React.MutableRefObject = useRef({}); const userInfo = useContext(UserInfoContext); + const router = useRouter(); + const { closeModal } = useModal(); useEffect(() => { const folderNavInfo = async () => { @@ -23,7 +32,7 @@ const LinkAddContent = () => { const response = await getFolderNavInfo(userInfo?.id); if (response !== null) { - setNavList(response.data); + setNavList(response); } } catch (error) { console.log(error); @@ -39,11 +48,40 @@ const LinkAddContent = () => { setFolder(selectedFoler.current[i]); }; + const handleLinkAdd = useMutation({ + mutationFn: () => { + if (!userInfo || !userInfo.token) { + return Promise.reject(new Error("UserToken Error!")); + } else if (!currentSelectedFolder) { + return Promise.reject(new Error("No Seleted Folder!")); + } + return addLink(linkUrl, currentSelectedFolder, userInfo.token); + }, + onSuccess: () => { + alert("링크 생성 성공"); + closeModal(); + router.push(`/folder/${currentSelectedFolder}`); + }, + onError: (error) => { + alert(error.message); + }, + }); + + const handleLinkAddBtnClick = () => { + if (!currentSelectedFolder) { + alert("폴더를 선택해주세요."); + return; + } else if (linkUrl === "") { + alert("url을 입력해주세요."); + return; + } + handleLinkAdd.mutate(); + }; + return ( {navList.map((navItem: INavItem, i) => { - console.log(navItem); return (
  • { >

    {navItem.name} - {navItem.link?.count}개 링크 + {navItem.link_count}개 링크

    {selectedFoler.current[i] === currentSelectedFolder && ( 폴더 선택 아이콘 @@ -69,7 +107,11 @@ const LinkAddContent = () => { })} {isLoading &&
    폴더 목록 불러오는중...
    } - {isLoading || } + {isLoading || ( + + )} ); }; diff --git a/components/common/modal/modalContent/LinkDeleteContent.tsx b/components/common/modal/modalContent/LinkDeleteContent.tsx index 4a624c3e8..5a0c51f7a 100644 --- a/components/common/modal/modalContent/LinkDeleteContent.tsx +++ b/components/common/modal/modalContent/LinkDeleteContent.tsx @@ -1,7 +1,43 @@ +import { deleteLink } from "@/api/folder"; import Button from "@/components/common/Button"; +import { UserInfoContext } from "@/context/User"; +import { useMutation } from "@tanstack/react-query"; +import { useRouter } from "next/router"; +import { useContext } from "react"; -const LinkDeleteContent = () => { - return ; +interface ILinkDeleteContentProps { + linkId: number; +} + +const LinkDeleteContent = ({ linkId }: ILinkDeleteContentProps) => { + const userInfo = useContext(UserInfoContext); + const router = useRouter(); + + const handleLinkDeleteBtnClick = () => { + handleLinkDelete.mutate(); + }; + + const handleLinkDelete = useMutation({ + mutationFn: () => { + if (!userInfo || !userInfo.token) { + return Promise.reject(new Error("UserToken Error!")); + } + return deleteLink(linkId, userInfo.token); + }, + onSuccess: () => { + alert("삭제 성공"); + router.reload(); + }, + }); + + return ( + + ); }; export default LinkDeleteContent; diff --git a/components/folder/LinkAdd.tsx b/components/folder/LinkAdd.tsx index be80fa81c..bb7b2b9f2 100644 --- a/components/folder/LinkAdd.tsx +++ b/components/folder/LinkAdd.tsx @@ -3,24 +3,36 @@ import useModal from "@/hooks/useModal"; import linkAdd from "@/public/image/icon/link-add.svg"; import Button from "@/components/common/Button"; import Image from "next/image"; +import LinkAddContent from "../common/modal/modalContent/LinkAddContent"; +import { useState } from "react"; const LinkAdd = () => { const { openModal } = useModal(); + const [linkText, setLinkText] = useState(""); const handleButtonClick = () => { openModal({ - type: "linkAdd", - props: { title: "폴더에 추가", subTitle: "링크 주소" }, + props: { title: "폴더에 추가", subTitle: linkText }, + component: , }); }; + const changeLinkText = (e: React.ChangeEvent) => { + setLinkText(e.target.value); + }; + return (
    링크 추가 아이콘 - + diff --git a/components/folder/NavBox.tsx b/components/folder/NavBox.tsx index b907fa98b..251a0edac 100644 --- a/components/folder/NavBox.tsx +++ b/components/folder/NavBox.tsx @@ -12,6 +12,10 @@ import { ModalParam } from "@/types/Modal"; import { INavItem } from "@/types/FolderNav"; import Image from "next/image"; import { UserInfoContext } from "@/context/User"; +import FolderAddModalContent from "../common/modal/modalContent/FolderAddModalContent"; +import ShareModalContent from "../common/modal/modalContent/ShareModalContent"; +import FolderNameChangeContent from "../common/modal/modalContent/FolderNameChangeContent"; +import FolderDeleteContent from "../common/modal/modalContent/FolderDeleteContent"; interface FolderNav { pageNavId?: string | string[]; @@ -28,7 +32,7 @@ const NavBox = ({ pageNavId }: FolderNav) => { const folderNavInfo = await getFolderNavInfo(userInfo.id); if (folderNavInfo !== null) { - setNavList(folderNavInfo.data.folder); + setNavList(folderNavInfo); } }; @@ -40,19 +44,19 @@ const NavBox = ({ pageNavId }: FolderNav) => { if (!pageNavId) { setCurrentNav("전체"); } - navList.forEach((navListItem: INavItem) => { if ( navListItem.name && pageNavId && navListItem.id === parseInt(pageNavId[0]) - ) + ) { setCurrentNav(navListItem.name); + } }); - }, [pageNavId]); + }, [pageNavId, navList]); - const handleOpenModal = ({ type, props }: ModalParam) => { - openModal({ type, props }); + const handleOpenModal = ({ props, component }: ModalParam) => { + openModal({ props, component: component }); }; return ( @@ -79,8 +83,8 @@ const NavBox = ({ pageNavId }: FolderNav) => {