diff --git a/api/api.ts b/api/api.ts index 161c137b76..f1a059d8c4 100644 --- a/api/api.ts +++ b/api/api.ts @@ -2,11 +2,7 @@ import { Folder, FolderInfo, Link, User } from "@/types"; import { getCookie } from "@/utils/cookie"; import { instance } from "./axios"; -const BASE_URL = "https://bootcamp-api.codeit.kr/api"; - -export async function getUserFolderList(): Promise<{ - data: { folder: Folder[] }; -}> { +export async function getUserFolderList(): Promise { return (await instance.get("/folders")).data; } @@ -14,8 +10,10 @@ export async function getUserLinks({ folderId, }: { folderId: string | null; -}): Promise<{ data: { folder: Link[] } }> { - const query = folderId ? `?folderId=${folderId}` : ""; +}): Promise { + if (!folderId) { + return (await instance.get(`/links`)).data; + } - return (await instance.get(`/links${query}`)).data; + return (await instance.get(`/folders/${folderId}/links`)).data; } diff --git a/api/auth.ts b/api/auth.ts index efa382d445..2b36e5c91b 100644 --- a/api/auth.ts +++ b/api/auth.ts @@ -1,4 +1,4 @@ -const BASE_URL = "https://bootcamp-api.codeit.kr/api"; +const BASE_URL = "https://bootcamp-api.codeit.kr/api/linkbrary/v1"; export async function postUserSignin({ email, @@ -7,7 +7,7 @@ export async function postUserSignin({ email: string; password: string; }) { - const response = await fetch(`${BASE_URL}/sign-in`, { + const response = await fetch(`${BASE_URL}/auth/sign-in`, { method: "POST", body: JSON.stringify({ email, password }), headers: { @@ -30,7 +30,7 @@ export async function postUserSignUp({ email: string; password: string; }) { - const response = await fetch(`${BASE_URL}/sign-up`, { + const response = await fetch(`${BASE_URL}/auth/sign-up`, { method: "POST", body: JSON.stringify({ email, password }), headers: { @@ -47,7 +47,7 @@ export async function postUserSignUp({ } export async function postCheckEmail({ email }: { email: string }) { - const response = await fetch(`${BASE_URL}/check-email`, { + const response = await fetch(`${BASE_URL}/users/check-email`, { method: "POST", body: JSON.stringify({ email }), headers: { diff --git a/api/axios.ts b/api/axios.ts index 9bb1a52647..a53312d5e0 100644 --- a/api/axios.ts +++ b/api/axios.ts @@ -2,8 +2,7 @@ import { getCookie } from "@/utils/cookie"; import axios from "axios"; export const instance = axios.create({ - baseURL: "https://bootcamp-api.codeit.kr/api", - timeout: 1000, + baseURL: " https://bootcamp-api.codeit.kr/api/linkbrary/v1", }); instance.interceptors.request.use( diff --git a/api/folder.ts b/api/folder.ts new file mode 100644 index 0000000000..d8f63ec0e7 --- /dev/null +++ b/api/folder.ts @@ -0,0 +1,13 @@ +import { instance } from "./axios"; + +export async function addFolder(name: string) { + return instance.post("/folders", { name }); +} + +export async function modifyFolder(id: string, name: string) { + return instance.put(`/folders/${id}`, { name }); +} + +export async function deleteFolder(id: string) { + return instance.delete(`/folders/${id}`); +} diff --git a/api/links.ts b/api/links.ts new file mode 100644 index 0000000000..5fcf9c5c83 --- /dev/null +++ b/api/links.ts @@ -0,0 +1,9 @@ +import { instance } from "./axios"; + +export async function addLink(url: string, folderId: number) { + return instance.post("/links", { url, folderId }); +} + +export async function deleteLink(linkId: number) { + return instance.delete(`/links/${linkId}`); +} diff --git a/api/share.ts b/api/share.ts index 9b18329d2d..2ad4792f48 100644 --- a/api/share.ts +++ b/api/share.ts @@ -5,15 +5,11 @@ export async function getFolder({ folderId, }: { folderId: string; -}): Promise<{ data: Omit[] }> { +}): Promise[]> { return (await instance.get(`/folders/${folderId}`)).data; } -export async function getUser({ - userId, -}: { - userId: number; -}): Promise<{ data: User[] }> { +export async function getUser({ userId }: { userId: number }): Promise { return (await instance.get(`/users/${userId}`)).data; } @@ -21,9 +17,9 @@ export async function getLinks({ userId, folderId, }: { - userId: string; + userId: number; folderId: string; -}): Promise<{ data: Link[] }> { +}): Promise { return (await instance.get(`/users/${userId}/links?folderId=${folderId}`)) .data; } diff --git a/api/user.ts b/api/user.ts index cd9083830e..40198b3cc9 100644 --- a/api/user.ts +++ b/api/user.ts @@ -1,5 +1,6 @@ +import { User } from "@/types"; import { instance } from "./axios"; -export async function getUser() { +export async function getUser(): Promise { return (await instance.get("/users")).data; } diff --git a/components/AddLinkModal/index.tsx b/components/AddLinkModal/index.tsx index 42419ff86a..0825daf01d 100644 --- a/components/AddLinkModal/index.tsx +++ b/components/AddLinkModal/index.tsx @@ -1,25 +1,44 @@ import { useState } from "react"; import styles from "./styles.module.css"; import { Folder } from "../../types"; +import { QueryClient, useMutation } from "@tanstack/react-query"; +import { addLink } from "@/api/links"; interface Props { + folderId: string; folders: Folder[]; linkUrl: string; } -export const AddLinkModal = ({ folders, linkUrl }: Props) => { +export const AddLinkModal = ({ folderId, folders, linkUrl }: Props) => { const [selectedId, setSelectedId] = useState(null); + const queryClient = new QueryClient(); + const mutation = useMutation({ + mutationFn: (folder: { url: string; folderId: number }) => + addLink(folder.url, folder.folderId), + onError: () => { + alert("링크 추가에 실패하였습니다."); + }, + onSuccess: () => { + alert("링크를 성공적으로 추가했습니다."); + queryClient.invalidateQueries({ queryKey: [`links-${selectedId}`] }); + }, + }); const handleFolderClick = (id: string) => { setSelectedId(id); }; + const handleAddLinks = () => { + mutation.mutate({ url: linkUrl.trim(), folderId: Number(selectedId) }); + }; + return (

폴더에 추가

{linkUrl}

    - {folders.map((folder) => ( + {folders?.map((folder) => (
  • {
    {folder.name} - {folder.link.count}개 링크 + {folder.link_count}개 링크
  • ))}
- +
); }; diff --git a/components/AddModal/index.tsx b/components/AddModal/index.tsx index 357a86f961..8f6858c59a 100644 --- a/components/AddModal/index.tsx +++ b/components/AddModal/index.tsx @@ -1,11 +1,43 @@ +import { + QueryClient, + useMutation, + useQueryClient, +} from "@tanstack/react-query"; import styles from "./styles.module.css"; +import { addFolder } from "@/api/folder"; +import { useState } from "react"; export const AddModal = () => { + const [name, setName] = useState(""); + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: (name: string) => addFolder(name), + onError: () => { + alert("폴더를 추가하는데 실패하였습니다."); + }, + onSuccess: () => { + alert("폴더를 성공적으로 추가했습니다."); + queryClient.invalidateQueries({ queryKey: ["folders"] }); + }, + }); + + const handleAddFolder = () => { + mutation.mutate(name); + }; + return (

폴더 추가

- - + setName(e.target.value)} + type="text" + placeholder="내용 입력" + /> +
); }; diff --git a/components/DeleteLinkModal/index.tsx b/components/DeleteLinkModal/index.tsx new file mode 100644 index 0000000000..48874dadf9 --- /dev/null +++ b/components/DeleteLinkModal/index.tsx @@ -0,0 +1,37 @@ +import { deleteFolder } from "@/api/folder"; +import styles from "./styles.module.css"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { deleteLink } from "@/api/links"; + +interface Props { + linkId: number; + linkUrl: string; +} + +export const DeleteLinkModal = ({ linkUrl, linkId }: Props) => { + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: (linkId: number) => deleteLink(linkId), + onError: () => { + alert("링크를 삭제하는데 실패하였습니다."); + }, + onSuccess: () => { + alert("링크를 성공적으로 삭제했습니다."); + }, + }); + + const handleDeleteLink = () => { + mutation.mutate(linkId); + }; + + return ( +
+

링크 삭제

+

{linkUrl}

+ +
+ ); +}; diff --git a/components/DeleteLinkModal/styles.module.css b/components/DeleteLinkModal/styles.module.css new file mode 100644 index 0000000000..dd1321f4b3 --- /dev/null +++ b/components/DeleteLinkModal/styles.module.css @@ -0,0 +1,24 @@ +.container { + text-align: center; +} + +.title { + font-weight: bold; + font-size: 1.9rem; + margin-bottom: 10px; +} + +.description { + font-size: 1.4rem; + margin-bottom: 20px; + color: #9fa6b2; +} + +.btn { + width: 100%; + padding: 15px 0; + color: white; + background-color: #ff5b56; + font-size: 1.5rem; + border-radius: 10px; +} diff --git a/components/DeleteModal/index.tsx b/components/DeleteModal/index.tsx index 8501955f0e..b1aa21a4a0 100644 --- a/components/DeleteModal/index.tsx +++ b/components/DeleteModal/index.tsx @@ -1,16 +1,38 @@ +import { deleteFolder } from "@/api/folder"; import styles from "./styles.module.css"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; interface Props { title: string; description: string; + folderId: string; } -export const DeleteModal = ({ title, description }: Props) => { +export const DeleteModal = ({ folderId, title, description }: Props) => { + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: (folderId: string) => deleteFolder(folderId), + onError: () => { + alert("폴더를 삭제하는데 실패하였습니다."); + }, + onSuccess: () => { + alert("폴더를 성공적으로 삭제했습니다."); + queryClient.invalidateQueries({ queryKey: ["folders"] }); + }, + }); + + const handleDeleteFolder = () => { + mutation.mutate(folderId); + }; + return (

{title}

{description}

- +
); }; diff --git a/components/FolderAddLinkArea/index.tsx b/components/FolderAddLinkArea/index.tsx index 2c05dbdd79..fc71eb031f 100644 --- a/components/FolderAddLinkArea/index.tsx +++ b/components/FolderAddLinkArea/index.tsx @@ -8,15 +8,19 @@ import { ModalDispatchContext } from "@/context/modalContext"; interface Props { folders: Folder[]; isFloating: boolean; + folderId: string; } -export function FolderAddLinkArea({ folders, isFloating }: Props) { +export function FolderAddLinkArea({ folderId, folders, isFloating }: Props) { const [linkUrl, setLinkUrl] = useState(""); const dispatch = useContext(ModalDispatchContext)!; const handleAddLinkClick = () => { dispatch({ type: "showModal", - payload: { modalType: "AddLinkModal", data: { folders, linkUrl } }, + payload: { + modalType: "AddLinkModal", + data: { folderId, folders, linkUrl }, + }, }); }; diff --git a/components/FolderCard/index.tsx b/components/FolderCard/index.tsx index fc08d2815c..00b47fdb6c 100644 --- a/components/FolderCard/index.tsx +++ b/components/FolderCard/index.tsx @@ -44,6 +44,8 @@ export function FolderCard({ link, folders }: Props) {
diff --git a/components/FolderCategory/index.tsx b/components/FolderCategory/index.tsx index 1702c30fc8..f6e3350897 100644 --- a/components/FolderCategory/index.tsx +++ b/components/FolderCategory/index.tsx @@ -1,9 +1,8 @@ -import { useModal } from "@/hooks/useModal"; import styles from "./styles.module.css"; -import { AddModal } from "@/components"; import { Folder } from "@/types"; import Link from "next/link"; -import { Dispatch, SetStateAction } from "react"; +import { useContext } from "react"; +import { ModalDispatchContext } from "@/context/modalContext"; interface Props { folders: Folder[]; @@ -11,7 +10,17 @@ interface Props { } export function FolderCategory({ folders, selectedId }: Props) { - const handleFolderAddClick = (e: React.MouseEvent) => {}; + const dispatch = useContext(ModalDispatchContext)!; + + const handleFolderAddClick = (e: React.MouseEvent) => { + dispatch({ + type: "showModal", + payload: { + modalType: "AddModal", + data: null, + }, + }); + }; return ( <> diff --git a/components/FolderControl/index.tsx b/components/FolderControl/index.tsx index 35ef7511d1..45ff5eb36a 100644 --- a/components/FolderControl/index.tsx +++ b/components/FolderControl/index.tsx @@ -8,7 +8,13 @@ import { DeleteModal, ModifyModal, ShareModal } from "@/components"; import { useContext } from "react"; import { ModalDispatchContext } from "@/context/modalContext"; -export function FolderControl({ folderName }: { folderName: string }) { +export function FolderControl({ + folderName, + folderId, +}: { + folderName: string; + folderId: string; +}) { const dispatch = useContext(ModalDispatchContext)!; const handleDeleteClick = () => { @@ -16,12 +22,20 @@ export function FolderControl({ folderName }: { folderName: string }) { type: "showModal", payload: { modalType: "DeleteModal", - data: { title: "링크 삭제", description: folderName }, + data: { title: "링크 삭제", description: folderName, folderId }, }, }); }; - const handleModifyClick = () => {}; + const handleModifyClick = () => { + dispatch({ + type: "showModal", + payload: { + modalType: "ModifyModal", + data: { folderName, folderId }, + }, + }); + }; const handleShareClick = () => {}; diff --git a/components/FolderHeader/index.tsx b/components/FolderHeader/index.tsx index 6e1f8dde94..38c0b478a6 100644 --- a/components/FolderHeader/index.tsx +++ b/components/FolderHeader/index.tsx @@ -6,28 +6,21 @@ import { User } from "@/types"; import Image from "next/image"; import Link from "next/link"; import { getUser } from "@/api/user"; +import { useQuery } from "@tanstack/react-query"; export function FolderHeader() { - const [user, setUser] = useState({} as User); - const [loading, error, getUserAsync] = useAsync(getUser); + const { isLoading, error, data } = useQuery({ + queryKey: ["fh-user"], + queryFn: getUser, + retry: false, + }); - const handleLoadUser = async () => { - const userData = await getUserAsync(); - setUser(userData.data[0]); - }; - - useEffect(() => { - handleLoadUser(); - }, []); - - if (loading) { - return <>로딩중이다; + if (isLoading) { + return <>로딩중; } return (
- {error &&
네트워크 오류입니다. 인터넷 연결상태를 확인해주세요
} - {loading &&
로딩중
}

@@ -35,16 +28,16 @@ export function FolderHeader() {

- {Object.keys(user).length > 0 ? ( + {data ? ( <>
profileImg

- {user.email} + {data[0]?.email}

diff --git a/components/FolderMain/index.tsx b/components/FolderMain/index.tsx deleted file mode 100644 index 9f02a3651d..0000000000 --- a/components/FolderMain/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { CardList, SearchInput } from "@/components"; -import { FolderAddLinkArea, FolderCategory, FolderControl } from "../"; -import styles from "./styles.module.css"; -import { Suspense, useEffect, useRef, useState } from "react"; -import { getUserFolderList, getUserLinks } from "@/api/api"; -import { useSearch } from "@/hooks/useSearch"; -import { useAsync } from "@/hooks/useAsync"; -import { Folder, Link } from "@/types"; -import useIntersectionObserver from "@/hooks/useIntersectionObserver"; -import { useRouter } from "next/router"; - -export function FolderMain() { - const [folders, setFolders] = useState([] as Folder[]); - const router = useRouter(); - - const { id, name } = router.query; - const selectedId = (id && id[0]) || null; - const selectedName = name || "전체"; - - const [links, setLinks] = useState([] as Link[]); - const [folderListLoading, folderListError, getFolderListAsync] = - useAsync(getUserFolderList); - const [linksLoading, linksError, getLinksAsync] = useAsync(getUserLinks); - const search = useSearch(); - const { isVisible: headerVisible, ref: headerBoundaryRef } = - useIntersectionObserver(); - - const { isVisible: footerVisible, ref: footerBoundaryRef } = - useIntersectionObserver(); - - const loadFolderList = async () => { - const folders = await getFolderListAsync(); - if (!folders) return; - setFolders(folders.data.folder); - }; - - const loadLinks = async (option: { folderId: string | null }) => { - const links = await getLinksAsync(option); - if (!links) return; - setLinks(links.data.folder); - }; - - useEffect(() => { - loadLinks({ folderId: selectedId }); - }, [selectedId]); - - useEffect(() => { - loadFolderList(); - }, []); - - return ( -
-
- -
-
-
- - {search.query[0] && ( -

- {search.query[0]}로 검색한 - 결과입니다 -

- )} - - - - {!linksLoading ? ( - links?.length === 0 ? ( -
저장된 링크가 없습니다
- ) : ( - - ) - ) : ( -
로딩중
- )} -
-
-
- ); -} diff --git a/components/FolderMain/styles.module.css b/components/FolderMain/styles.module.css deleted file mode 100644 index 856ba1842f..0000000000 --- a/components/FolderMain/styles.module.css +++ /dev/null @@ -1,35 +0,0 @@ -.mainContainer { - max-width: 1380px; - margin: 0 auto; - padding: 40px 32px 80px; - min-height: 1200px; -} - -.searchBox { - width: 100%; - background-color: #f5f5f5; - padding: 15px; - border-radius: 10px; - display: flex; - gap: 10px; -} - -.searchBox input { - background-color: #f5f5f5; - border: none; - width: 100%; -} - -.searchResult { - font-size: 26px; - font-weight: bold; - color: #9fa6b2; -} - -.emptyArea { - display: flex; - align-items: center; - justify-content: center; - font-size: 1.3rem; - height: 230px; -} diff --git a/components/Modal/ModalType.ts b/components/Modal/ModalType.ts index 47b48d879a..f0631e0e56 100644 --- a/components/Modal/ModalType.ts +++ b/components/Modal/ModalType.ts @@ -1,8 +1,10 @@ import { Folder } from "@/types"; +import { DeleteLinkModal } from "../DeleteLinkModal"; export type AddLinkModalType = { modalType: "AddLinkModal"; data: { + folderId: string; folders: Folder[]; linkUrl: string; }; @@ -16,15 +18,25 @@ export type AddModalType = { export type DeleteModalType = { modalType: "DeleteModal"; data: { + folderId: string; title: string; description: string; }; }; +export type DeleteLinkModalType = { + modalType: "DeleteLinkModal"; + data: { + linkId: number; + linkUrl: string; + }; +}; + export type ModifyModalType = { modalType: "ModifyModal"; data: { folderName: string; + folderId: string; }; }; @@ -40,7 +52,8 @@ export type PayloadType = | AddModalType | DeleteModalType | ModifyModalType - | ShareModalType; + | ShareModalType + | DeleteLinkModalType; export type ActionType = | { type: "showModal"; payload: PayloadType } diff --git a/components/Modal/index.tsx b/components/Modal/index.tsx index 63d17844da..d07ccd40b8 100644 --- a/components/Modal/index.tsx +++ b/components/Modal/index.tsx @@ -9,12 +9,14 @@ import { ModifyModal } from "../ModifyModal"; import { ShareModal } from "../ShareModal"; import { ModalDispatchContext, ModalContext } from "@/context/modalContext"; import { AddLinkModalType, AddModalType, ModalType } from "./ModalType"; +import { DeleteLinkModal } from "../DeleteLinkModal"; const renderComponent = (modalData: ModalType): ReactNode => { switch (modalData.type) { case "AddLinkModal": return ( @@ -24,12 +26,25 @@ const renderComponent = (modalData: ModalType): ReactNode => { case "DeleteModal": return ( ); + case "DeleteLinkModal": + return ( + + ); case "ModifyModal": - return ; + return ( + + ); case "ShareModal": return ; default: diff --git a/components/ModifyModal/index.tsx b/components/ModifyModal/index.tsx index cf3eb933c5..6c1289dee7 100644 --- a/components/ModifyModal/index.tsx +++ b/components/ModifyModal/index.tsx @@ -1,15 +1,45 @@ +import { useState } from "react"; import styles from "./styles.module.css"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { modifyFolder } from "@/api/folder"; interface Props { + folderId: string; folderName: string; } -export const ModifyModal = ({ folderName }: Props) => { +export const ModifyModal = ({ folderId, folderName }: Props) => { + const [name, setName] = useState(""); + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: (folder: { id: string; name: string }) => + modifyFolder(folder.id, folder.name), + onError: () => { + alert("폴더명 변경에 실패하였습니다."); + }, + onSuccess: () => { + alert("폴더명을 성공적으로 변경했습니다."); + queryClient.invalidateQueries({ queryKey: ["folders"] }); + }, + }); + + const handleModifyFolder = () => { + mutation.mutate({ id: folderId, name }); + }; + return (

폴더 이름 변경

- - + setName(e.target.value)} + type="text" + defaultValue={folderName} + /> +
); }; diff --git a/components/PopOver/index.tsx b/components/PopOver/index.tsx index 78046e8215..1efdca50ba 100644 --- a/components/PopOver/index.tsx +++ b/components/PopOver/index.tsx @@ -1,19 +1,35 @@ +import { ModalDispatchContext } from "@/context/modalContext"; import useHandleOutsideClick from "../../hooks/useHandleOutsideClick"; import styles from "./styles.module.css"; -import { useRef } from "react"; +import { useContext, useRef } from "react"; interface Props { openPopOver: boolean; handlePopOverClose: () => void; + linkUrl: string; + linkId: number; } -export function PopOver({ openPopOver, handlePopOverClose }: Props) { +export function PopOver({ + linkId, + linkUrl, + openPopOver, + handlePopOverClose, +}: Props) { const ref = useRef(null); + const dispatch = useContext(ModalDispatchContext)!; useHandleOutsideClick(ref, handlePopOverClose); const handleLinkDeleteClick = (e: React.MouseEvent) => { e.preventDefault(); + dispatch({ + type: "showModal", + payload: { + modalType: "DeleteLinkModal", + data: { linkUrl, linkId }, + }, + }); handlePopOverClose(); }; diff --git a/components/index.tsx b/components/index.tsx index ca99d5e63c..7185a8ed87 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -16,7 +16,6 @@ import { FolderCard } from "./FolderCard"; import { FolderCategory } from "./FolderCategory"; import { FolderControl } from "./FolderControl"; import { FolderHeader } from "./FolderHeader"; -import { FolderMain } from "./FolderMain"; export { Header, @@ -37,5 +36,4 @@ export { FolderCategory, FolderControl, FolderHeader, - FolderMain, }; diff --git a/package-lock.json b/package-lock.json index 92304c073d..aba95ecfde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@nanostores/react": "^0.7.2", + "@tanstack/react-query": "^5.35.1", "axios": "^1.6.8", "nanostore": "^1.2.1", "next": "13.5.6", @@ -353,6 +354,30 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/query-core": { + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.35.1.tgz", + "integrity": "sha512-0Dnpybqb8+ps6WgqBnqFEC+1F/xLvUosRAq+wiGisTgolOZzqZfkE2995dEXmhuzINiTM7/a6xSGznU0NIvBkw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.35.1.tgz", + "integrity": "sha512-i2T7m2ffQdNqlX3pO+uMsnQ0H4a59Ens2GxtlMsRiOvdSB4SfYmHb27MnvFV8rGmtWRaa4gPli0/rpDoSS5LbQ==", + "dependencies": { + "@tanstack/query-core": "5.35.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", diff --git a/package.json b/package.json index 262b99e6c2..5a5b51c21d 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@nanostores/react": "^0.7.2", + "@tanstack/react-query": "^5.35.1", "axios": "^1.6.8", "nanostore": "^1.2.1", "next": "13.5.6", diff --git a/pages/_app.tsx b/pages/_app.tsx index 021681f4dd..9e4038e252 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,6 +1,13 @@ -import '@/styles/globals.css' -import type { AppProps } from 'next/app' +import "@/styles/globals.css"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import type { AppProps } from "next/app"; + +const queryClient = new QueryClient(); export default function App({ Component, pageProps }: AppProps) { - return + return ( + + + + ); } diff --git a/pages/folder/[[...id]]/index.tsx b/pages/folder/[[...id]]/index.tsx index 4ebe9b1018..87a2ffebc9 100644 --- a/pages/folder/[[...id]]/index.tsx +++ b/pages/folder/[[...id]]/index.tsx @@ -5,52 +5,39 @@ import { ModalContextProvider } from "@/context/modalContext"; import { CardList, SearchInput } from "@/components"; import { FolderAddLinkArea, FolderCategory, FolderControl } from "@/components"; import styles from "./styles.module.css"; -import { useEffect, useState } from "react"; import { getUserFolderList, getUserLinks } from "@/api/api"; import { useSearch } from "@/hooks/useSearch"; -import { useAsync } from "@/hooks/useAsync"; import { Folder, Link } from "@/types"; import useIntersectionObserver from "@/hooks/useIntersectionObserver"; import { useRouter } from "next/router"; +import { useQuery } from "@tanstack/react-query"; export default function FolderPage() { - const [folders, setFolders] = useState([] as Folder[]); - const router = useRouter(); - - const { id, name } = router.query; - const selectedId = (id && id[0]) || null; - const selectedName = name || "전체"; - - const [links, setLinks] = useState([] as Link[]); - const [folderListLoading, folderListError, getFolderListAsync] = - useAsync(getUserFolderList); - const [linksLoading, linksError, getLinksAsync] = useAsync(getUserLinks); - const search = useSearch(); const { isVisible: headerVisible, ref: headerBoundaryRef } = useIntersectionObserver(); - const { isVisible: footerVisible, ref: footerBoundaryRef } = useIntersectionObserver(); + const search = useSearch(); + const router = useRouter(); - const loadFolderList = async () => { - const folders = await getFolderListAsync(); - if (!folders) return; - setFolders(folders.data.folder); - }; - - const loadLinks = async (option: { folderId: string | null }) => { - const links = await getLinksAsync(option); - if (!links) return; - setLinks(links.data.folder); - }; + const { id, name } = router.query; + const selectedName = name || "전체"; + const selectedId = (id && id[0]) || null; - useEffect(() => { - loadLinks({ folderId: selectedId }); - }, [selectedId]); + const { + isLoading: folderListLoading, + error: folderListError, + data: folders, + } = useQuery({ queryKey: ["folders"], queryFn: getUserFolderList }); - useEffect(() => { - loadFolderList(); - }, []); + const { + isLoading: linksLoading, + error: linksError, + data: links, + } = useQuery({ + queryKey: [`links-${selectedId}`], + queryFn: () => getUserLinks({ folderId: selectedId }), + }); return ( @@ -59,7 +46,8 @@ export default function FolderPage() {
@@ -71,14 +59,29 @@ export default function FolderPage() { 결과입니다

)} - - + {folderListLoading ? ( +
로딩중입니다...
+ ) : folderListError ? ( +
폴더 정보들을 가져오는데 실패했습니다.
+ ) : ( + //folders를 잘 받아온게 확실 + + )} + {!linksLoading ? ( links?.length === 0 ? (
저장된 링크가 없습니다
) : ( - +
+ +
) ) : (
로딩중
diff --git a/pages/share/[folderId]/index.tsx b/pages/share/[folderId]/index.tsx index 875e396f7d..b22b613026 100644 --- a/pages/share/[folderId]/index.tsx +++ b/pages/share/[folderId]/index.tsx @@ -1,64 +1,49 @@ -import { Header, Footer, CardList, FolderHeader } from "../../../components"; +import { Footer, CardList, FolderHeader } from "../../../components"; import styles from "./styles.module.css"; -import { useEffect, useState } from "react"; -import { useAsync } from "@/hooks/useAsync"; -import { FolderInfo, Link, User } from "@/types"; import { SearchInput } from "@/components/SearchInput"; import { getFolder, getLinks, getUser } from "@/api/share"; import { useRouter } from "next/router"; +import { useQuery } from "@tanstack/react-query"; export default function Share() { - const [folderLoading, folderError, getFolderAsync] = useAsync(getFolder); - const [userLoading, userError, getUserAsync] = useAsync(getUser); - const [linksLoading, linksError, getLinksAsync] = useAsync(getLinks); - const [folderName, setFolderName] = useState(""); - const [userId, setUserId] = useState(); - const [user, setUser] = useState(); - const [links, setLinks] = useState(); const router = useRouter(); - const { folderId } = router.query; - - const handleLoadFolder = async (options: { folderId: string }) => { - const data = await getFolderAsync(options); - if (!data || data.data.length === 0) { - return; - } - setFolderName(data.data[0].name); - setUserId(data.data[0].user_id); - }; - - const handleLoadUser = async (options: { userId: number }) => { - const data = await getUserAsync(options); - if (!data) { - return; - } - setUser(data.data[0]); - }; - const handleLoadLinks = async (options: { - userId: string; - folderId: string; - }) => { - const data = await getLinksAsync(options); - if (!data) { - return; - } - setLinks(data.data); - }; + const { folderId } = router.query; - useEffect(() => { - if (!folderId) return; - handleLoadFolder({ folderId: folderId as string }); - }, [folderId]); + const { isLoading, error, data } = useQuery({ + queryKey: ["s-folderInfo"], + queryFn: () => getFolder({ folderId: folderId as string }), + enabled: !!folderId, + }); - useEffect(() => { - if (!userId) { - return; - } + const { + isLoading: isUserLoading, + error: isUserError, + data: userData, + } = useQuery({ + queryKey: ["s-user"], + queryFn: () => { + if (!data) return; + return getUser({ userId: data[0].user_id }); + }, + enabled: !!data, + }); - handleLoadUser({ userId }); - handleLoadLinks({ userId: String(userId), folderId: folderId as string }); - }, [userId]); + const { + isLoading: isLinksLoading, + error: isLinksError, + data: linksData, + } = useQuery({ + queryKey: ["s-links"], + queryFn: () => { + if (!data) return; + return getLinks({ + userId: data[0].user_id, + folderId: folderId as string, + }); + }, + enabled: !!folderId && !!data, + }); return ( <> @@ -66,17 +51,27 @@ export default function Share() {
- {userLoading &&
loading
} - profile -
- @{user?.name || "It doesnt exist "} -
+ {isUserLoading ? ( + "로딩중" + ) : ( + <> + profile +
+ @ + {(userData && userData[0].name) || + "존재하지 않는 사용자입니다"} +
+ + )} +

- {folderName || "존재하지 않는 폴더입니다"} + {isLoading + ? "로딩중..." + : (data && data[0].name) || "존재하지 않는 폴더입니다"}

@@ -84,9 +79,9 @@ export default function Share() {
- {!linksLoading ? ( - links ? ( - + {!isLinksLoading ? ( + linksData ? ( + ) : (
링크 정보들이 없습니다.
) diff --git a/pages/signin/index.tsx b/pages/signin/index.tsx index 588e819319..dcbfab448e 100644 --- a/pages/signin/index.tsx +++ b/pages/signin/index.tsx @@ -19,6 +19,7 @@ import { } from "@/constants/placeholderMessage"; import { useEffect } from "react"; import { setCookie } from "@/utils/cookie"; +import { useMutation } from "@tanstack/react-query"; type FormType = { email: string; @@ -27,6 +28,15 @@ type FormType = { export default function SignIn() { const router = useRouter(); + const mutation = useMutation({ + mutationFn: (user: FormType) => + postUserSignin({ email: user.email, password: user.password }), + onSuccess: (data) => { + setCookie("accessToken", data.accessToken); + setCookie("refreshToken", data.refreshToken); + router.push("/folder", undefined, { shallow: true }); + }, + }); const { register, @@ -40,28 +50,20 @@ export default function SignIn() { }); const onSubmit = async (user: { email: string; password: string }) => { - try { - const data = await postUserSignin({ - email: user.email, - password: user.password, - }); - - setCookie("accessToken", data.data.accessToken); - setCookie("refreshToken", data.data.refreshToken); - - router.push("/folder"); - } catch (e) { - setError("email", { - type: "emailInValid", - message: EMAIL_ERROR_MESSAGE.inValid, - }); - setError("password", { - type: "passwordInValid", - message: PASSWORD_ERROR_MESSAGE.inValid, - }); - } + mutation.mutate({ email: user.email, password: user.password }); }; + if (mutation.isError) { + setError("email", { + type: "emailInValid", + message: EMAIL_ERROR_MESSAGE.inValid, + }); + setError("password", { + type: "passwordInValid", + message: PASSWORD_ERROR_MESSAGE.inValid, + }); + } + return (
diff --git a/pages/signup/index.tsx b/pages/signup/index.tsx index 124adaf171..aa528dd3a9 100644 --- a/pages/signup/index.tsx +++ b/pages/signup/index.tsx @@ -21,6 +21,8 @@ import { PASSWORD_PLACEHOLDER, } from "@/constants/placeholderMessage"; import { useCallback, useEffect } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { setCookie } from "@/utils/cookie"; type FormType = { email: string; @@ -46,6 +48,19 @@ async function checkIsDuplicated(email: string) { export default function SignUp() { const router = useRouter(); + const mutation = useMutation({ + mutationFn: (user: Omit) => + postUserSignUp({ email: user.email, password: user.password }), + onSuccess: (data) => { + setCookie("accessToken", data.accessToken); + setCookie("refreshToken", data.refreshToken); + router.push("/folder", undefined, { shallow: true }); + }, + onError: () => { + console.log("회원가입 실패"); + }, + }); + const { register, formState: { errors }, @@ -91,20 +106,7 @@ export default function SignUp() { ); const onSubmit = async (user: { email: string; password: string }) => { - try { - const data = await postUserSignUp({ - email: user.email, - password: user.password, - }); - localStorage.setItem("accessToken", data.data.accessToken); - localStorage.setItem("refreshToken", data.data.refreshToken); - - if (localStorage.getItem("accessToken")) { - router.push("/folder"); - } - } catch (e) { - console.error(e); - } + mutation.mutate({ email: user.email, password: user.password }); }; useEffect(() => { diff --git a/types/index.ts b/types/index.ts index 910bf5bb59..4dcd5f5e3f 100644 --- a/types/index.ts +++ b/types/index.ts @@ -23,9 +23,7 @@ export interface Folder { created_at: string; favorite: boolean; name: string; - link: { - count: number; - }; + link_count: number; user_id: number; }