diff --git a/.github/workflows/delete-merged-branch-config.yml b/.github/workflows/delete-merged-branch-config.yml new file mode 100644 index 000000000..d54933615 --- /dev/null +++ b/.github/workflows/delete-merged-branch-config.yml @@ -0,0 +1,14 @@ +name: delete branch on close pr + +on: + pull_request: + types: [closed] + +jobs: + delete-branch: + runs-on: ubuntu-latest + steps: + - name: delete branch + uses: SvanBoxel/delete-merged-branch@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/next.config.js b/next.config.js index 518c410a9..e9b26889d 100644 --- a/next.config.js +++ b/next.config.js @@ -11,6 +11,14 @@ const nextConfig = { protocol: "http", hostname: "localhost", }, + { + protocol: "https", + hostname: "avatars.githubusercontent.com", + }, + { + protocol: "https", + hostname: "ca.slack-edge.com", + }, ], }, }; diff --git a/package-lock.json b/package-lock.json index 697fb224a..888d124a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,17 @@ "name": "1-weekly-mission", "version": "0.1.0", "dependencies": { + "@tanstack/react-query": "^5.17.10", + "@tanstack/react-query-devtools": "^5.17.10", "axios": "^1.6.2", "next": "14.0.3", "react": "^18", + "react-cookie": "^7.0.1", "react-dom": "^18", - "sass": "^1.69.5" + "react-hook-form": "^7.49.3", + "react-hot-toast": "^2.4.1", + "sass": "^1.69.5", + "zustand": "^4.4.7" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", @@ -781,6 +787,55 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/query-core": { + "version": "5.17.10", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.17.10.tgz", + "integrity": "sha512-bJ2oQUDBftvHcEkLS3gyzzShSeZpJyzNNRu8oHK13iNdsofyaDXtNO/c1Zy/PZYVX+PhqOXwoT42gMiEMRSSfQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.17.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.17.7.tgz", + "integrity": "sha512-TfgvOqza5K7Sk6slxqkRIvXlEJoUoPSsGGwpuYSrpqgSwLSSvPPpZhq7hv7hcY5IvRoTNGoq6+MT01C/jILqoQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.17.10", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.17.10.tgz", + "integrity": "sha512-TNmJN7LkSLzmv01Jen3JbcvhXZyRhc/ETJNjssmmlyMB8IoNvicfgvDRX2gX3q1FTONq+mfsmWintwI+ejmEUw==", + "dependencies": { + "@tanstack/query-core": "5.17.10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.17.10", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.17.10.tgz", + "integrity": "sha512-bdH1eOTvXZj3nvdSTYMyAKSoKnoYtyV+YTuP6japifbtWlqEZvHWJ8KbazAt7rLVfyn/5OhtiHPelTU8DYWgrA==", + "dependencies": { + "@tanstack/query-devtools": "5.17.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.17.10", + "react": "^18.0.0" + } + }, "node_modules/@trivago/prettier-plugin-sort-imports": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz", @@ -804,6 +859,20 @@ } } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -822,14 +891,12 @@ "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", - "dev": true + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/react": { "version": "18.2.39", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", - "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -848,8 +915,7 @@ "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "node_modules/@typescript-eslint/parser": { "version": "6.13.1", @@ -1418,6 +1484,14 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1435,8 +1509,7 @@ "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -2435,6 +2508,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -2539,6 +2620,14 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -3621,6 +3710,19 @@ "node": ">=0.10.0" } }, + "node_modules/react-cookie": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.0.1.tgz", + "integrity": "sha512-SmDjy2TFp+vS6BGOqW7HyaWKyJzVmIH74uP3mxq6kswlwLJEBtIbhkrioozdvQL9r81yprHYFQkSmcO4HiXPdA==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.5", + "hoist-non-react-statics": "^3.3.2", + "universal-cookie": "^7.0.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -3633,11 +3735,41 @@ "react": "^18.2.0" } }, + "node_modules/react-hook-form": { + "version": "7.49.3", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.49.3.tgz", + "integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==", + "engines": { + "node": ">=18", + "pnpm": "8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "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==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/readdirp": { "version": "3.6.0", @@ -4276,6 +4408,15 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, + "node_modules/universal-cookie": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.0.1.tgz", + "integrity": "sha512-6OuX9xELF6dsVJeADJAYNDOxQf/NR3Na5bGCRd+hkysMDkSt79jJ4tdv5OBe+ZgAks3ExHBdCXkD2SjqLyK59w==", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4285,6 +4426,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -4411,6 +4560,33 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.7.tgz", + "integrity": "sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 3553f3b81..610726110 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,17 @@ "lint": "next lint" }, "dependencies": { + "@tanstack/react-query": "^5.17.10", + "@tanstack/react-query-devtools": "^5.17.10", "axios": "^1.6.2", "next": "14.0.3", "react": "^18", + "react-cookie": "^7.0.1", "react-dom": "^18", - "sass": "^1.69.5" + "react-hook-form": "^7.49.3", + "react-hot-toast": "^2.4.1", + "sass": "^1.69.5", + "zustand": "^4.4.7" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", diff --git a/src/api/axiosInstance.ts b/src/api/axiosInstance.ts new file mode 100644 index 000000000..c4e927b31 --- /dev/null +++ b/src/api/axiosInstance.ts @@ -0,0 +1,44 @@ +import axios from "axios"; +import { getCookie, setCookie } from "@/utils/manageCookie"; +import { getNewToken } from "./getAuthApi"; + +export const axiosInstance = axios.create({ + baseURL: "https://bootcamp-api.codeit.kr/api/linkbrary/v1/", + headers: { + "Content-Type": "application/json", + withCredentials: true, + }, +}); + +axiosInstance.interceptors.request.use( + (config) => { + const accessToken = getCookie("accessToken"); + config.headers["Authorization"] = `Bearer ${accessToken}`; + return config; + }, + (error) => { + return Promise.reject(error); + }, +); + +axios.interceptors.response.use( + (res) => res, + async (error) => { + const { + config, + response: { status }, + } = error; + + if (status === 401) { + const originRequest = config; + const { data } = await getNewToken(); + if (data) { + setCookie("accessToken", data.accessToken); + setCookie("refreshToken", data.refreshToken); + originRequest.headers["Authorization"] = `Bearer ${data.accessToken}`; + return axios(originRequest); + } + } + return Promise.reject(error); + }, +); diff --git a/src/api/axiosInstance.tsx b/src/api/axiosInstance.tsx deleted file mode 100644 index 740b5e5fc..000000000 --- a/src/api/axiosInstance.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import axios from "axios"; - -export const axiosInstance = axios.create({ - baseURL: "https://bootcamp-api.codeit.kr/api/", - headers: { - "Content-Type": "application/json", - withCredentials: true, - }, -}); - -axiosInstance.interceptors.request.use((config) => { - if (!config.headers) return config; - if (typeof window !== "undefined") { - const accessToken = localStorage.getItem("accessToken"); - if (accessToken && config.headers) { - config.headers["Authorization"] = accessToken; - } - } - return config; -}); - -axiosInstance.interceptors.response.use( - (res) => { - return res; - }, - async (error) => { - if (error.config && error.response && error.response.status === 401) { - error.config._retry = true; - const refreshtoken = localStorage.getItem("refreshToken"); - error.config.headers.RefreshToken = `${refreshtoken}`; - return axios - .get(`${process.env.NEXT_PUBLIC_API_URL}/auth/me`, { - headers: { - RefreshToken: `${refreshtoken}`, - "Content-Type": "application/json", - withCredentials: true, - }, - }) - .then(async (res) => { - if (res.status === 200 && res.data.accessToken) { - localStorage.setItem("accessToken", res.data.accessToken); - const accesstoken = localStorage.getItem("accessToken"); - error.config.headers["Authorization"] = `${accesstoken}`; - return axiosInstance(error.config); - } - }); - } - return Promise.reject(error); - }, -); diff --git a/src/api/constants.tsx b/src/api/constants.ts similarity index 100% rename from src/api/constants.tsx rename to src/api/constants.ts diff --git a/src/api/getAllCards.tsx b/src/api/getAllCards.tsx deleted file mode 100644 index 04f003e23..000000000 --- a/src/api/getAllCards.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { axiosInstance } from "./axiosInstance"; - -export default async function getAllCards(userId = "", folderId = "") { - let url = `${userId}/links?`; - const searchUrl = new URLSearchParams(); - if (folderId !== "") { - searchUrl.append("folderId", folderId); - url += searchUrl.toString(); - } - console.log(url); - const response = await axiosInstance.get(`${url}`); - const data = response?.data?.data; - return data?.folder; -} diff --git a/src/api/getAuthApi.ts b/src/api/getAuthApi.ts new file mode 100644 index 000000000..0348b8017 --- /dev/null +++ b/src/api/getAuthApi.ts @@ -0,0 +1,39 @@ +/* signin, signup에 필요한 api*/ + +import { axiosInstance } from "./axiosInstance"; +import { getCookie } from "@/utils/manageCookie"; + +// signup 페이지에서 쓰이는 이메일 중복 체크 api. +export async function getEmailCheck(email = "") { + const response = await axiosInstance.post(`users/check-email`, { + email: email, + }); + return response; +} + +// signup 페이지에서 쓰이는 회원가입 api +export async function getSignUp(email = "", password = "") { + const response = await axiosInstance.post("auth/sign-up", { + email: email, + password: password, + }); + return response; +} + +// signin 페이지에서 쓰이는 로그인 api +export async function getSignIn(email = "", password = "") { + const response = await axiosInstance.post("auth/sign-in", { + email: email, + password: password, + }); + return response; +} + +// refresh token으로 새 token 발급받는 api +export async function getNewToken() { + const refreshToken = getCookie("refreshToken"); + const response = await axiosInstance.post(`/auth/refresh-token`, { + refresh_token: refreshToken, + }); + return response; +} diff --git a/src/api/getCardCRUDApi.ts b/src/api/getCardCRUDApi.ts new file mode 100644 index 000000000..29ee355df --- /dev/null +++ b/src/api/getCardCRUDApi.ts @@ -0,0 +1,38 @@ +/*card에 관한 api*/ + +import { axiosInstance } from "./axiosInstance"; + +// 로그인한 유저의 전체 카드 리스트를 조회하는 api +export async function getAllCards() { + const response = await axiosInstance.get(`/links`); + return response.data; +} + +// 외부 유저의 전체 카드 리스트를 조회하는 api +export async function getSharedAllCards(userId: string) { + const response = await axiosInstance.get(`/users/${userId}/links`); + return response.data; +} + +// 한 폴더 내의 카드 리스트를 조회하는 api +export async function getCards(folderId: string) { + const response = await axiosInstance.get(`/folders/${folderId}/links`); + return response.data; +} + +// card Create api +export async function createCard(url: string, folderId: string) { + const response = await axiosInstance.post(`/links`, { + url: url, + folderId: folderId, + }); + return response; +} + +// card Delete api +export async function deleteCard(cardId: string) { + const response = await axiosInstance.delete(`/links/${cardId}`); + return response; +} + +// TODO - card update api diff --git a/src/api/getEmailCheck.tsx b/src/api/getEmailCheck.tsx deleted file mode 100644 index 01c483b91..000000000 --- a/src/api/getEmailCheck.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { axiosInstance } from "./axiosInstance"; - -export default async function getEmailCheck(email = "") { - try { - const response = await axiosInstance.post(`check-email`, { - email: email, - }); - return response.data; - } catch (e) { - return; - } -} diff --git a/src/api/getFolderCRUDApi.ts b/src/api/getFolderCRUDApi.ts new file mode 100644 index 000000000..f667bc3a8 --- /dev/null +++ b/src/api/getFolderCRUDApi.ts @@ -0,0 +1,40 @@ +/*folder에 관한 api*/ + +import { FolderType } from "@/types/FolderType"; +import { axiosInstance } from "./axiosInstance"; + +// 유저가 가진 모든 folders 리스트를 조회하는 api +export async function getFolderList() { + const response = await axiosInstance.get(`/folders`); + return response.data as [FolderType] | []; +} + +// folder의 상세 정보를 조회하는 api +export async function getFolderInfo(folderId: string) { + const response = await axiosInstance.get(`/folders/${folderId}`); + return response.data; +} + +// folder Create api +export async function createFolder(name: string) { + const response = await axiosInstance.post(`/folders`, { + name: name, + }); + return response; +} + +// folder Update api +export async function editFolder(folderId: string, name: string) { + const response = await axiosInstance.put(`/folders/${folderId}`, { + name: name, + }); + return response; +} + +// folder Delete api +export async function deleteFolder(folderId: string) { + const response = await axiosInstance.delete(`/folders/${folderId}`); + return response; +} + +// TODO - userId 유저의 모든 폴더 조회 api diff --git a/src/api/getFolderList.tsx b/src/api/getFolderList.tsx deleted file mode 100644 index f94f259e7..000000000 --- a/src/api/getFolderList.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { axiosInstance } from "./axiosInstance"; - -export default async function getFolderList(userId = "") { - const response = await axiosInstance.get(`${userId}/folders`); - return response?.data; -} diff --git a/src/api/getNewToken.tsx b/src/api/getNewToken.tsx deleted file mode 100644 index f91d47940..000000000 --- a/src/api/getNewToken.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { axiosInstance } from "./axiosInstance"; - -export default async function getNewToken(refreshToken = "") { - if (!refreshToken) return; - - const response = await axiosInstance.post("/refresh-token", { - refresh_token: refreshToken, - }); - return response?.data; -} diff --git a/src/api/getSharedFolder.tsx b/src/api/getSharedFolder.tsx deleted file mode 100644 index 68fcb308c..000000000 --- a/src/api/getSharedFolder.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { axiosInstance } from "./axiosInstance"; -import { SHARED_USER_ID } from "./constants"; - -export default async function getSharedFolder() { - const response = await axiosInstance.get(`${SHARED_USER_ID}/folder`); - return response?.data; -} diff --git a/src/api/getSharedUser.tsx b/src/api/getSharedUser.tsx deleted file mode 100644 index d2786675b..000000000 --- a/src/api/getSharedUser.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { axiosInstance } from "./axiosInstance"; -import { SHARED_USER_ID } from "./constants"; - -export default async function getSharedUser() { - const response = await axiosInstance.get(`${SHARED_USER_ID}/user`); - return response?.data; -} diff --git a/src/api/getSignUp.tsx b/src/api/getSignUp.tsx deleted file mode 100644 index fa60b00ac..000000000 --- a/src/api/getSignUp.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { axiosInstance } from "./axiosInstance"; - -export default async function getSignUp(email = "", password = "") { - try { - const response = await axiosInstance.post("/sign-up", { - email: email, - password: password, - }); - return response.data; - } catch (e) { - return; - } -} diff --git a/src/api/getSignin.tsx b/src/api/getSignin.tsx deleted file mode 100644 index 5a0ad55f2..000000000 --- a/src/api/getSignin.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { axiosInstance } from "./axiosInstance"; - -export default async function getSignIn(email = "", password = "") { - try { - const response = await axiosInstance.post("/sign-in", { - email: email, - password: password, - }); - return response.data; - } catch (e) { - console.log(e); - return; - } -} diff --git a/src/api/getUser.ts b/src/api/getUser.ts new file mode 100644 index 000000000..f4e497c08 --- /dev/null +++ b/src/api/getUser.ts @@ -0,0 +1,12 @@ +import { UserType } from "@/types/UserType"; +import { axiosInstance } from "./axiosInstance"; + +export async function getUser() { + const response = await axiosInstance.get<[UserType]>(`/users`); + return response.data[0]; +} + +export async function getSharedUser(userId: string) { + const response = await axiosInstance.get<[UserType]>(`/users/${userId}`); + return response.data[0]; +} diff --git a/src/api/getUser.tsx b/src/api/getUser.tsx deleted file mode 100644 index cc41f1e21..000000000 --- a/src/api/getUser.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { axiosInstance } from "./axiosInstance"; -import { USER_ID } from "./constants"; - -export default async function getUser() { - const response = await axiosInstance.get(`${USER_ID}`); - return response?.data; -} diff --git a/src/api/index.tsx b/src/api/index.tsx deleted file mode 100644 index bc01c2bc8..000000000 --- a/src/api/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import getAllCards from "./getAllCards"; -import getFolderList from "./getFolderList"; -import getUser from "./getUser"; -import getNewToken from "./getNewToken"; - -import getSharedFolder from "./getSharedFolder"; -import getSharedUser from "./getSharedUser"; - -import getEmailCheck from "./getEmailCheck"; -import getSignIn from "./getSignin"; -import getSignUp from "./getSignUp"; - -export { - getAllCards, - getFolderList, - getNewToken, - getUser, - getSharedFolder, - getSharedUser, - getEmailCheck, - getSignIn, - getSignUp, -}; diff --git a/src/components/common/Card/Card.module.scss b/src/components/CardList/Card/Card.module.scss similarity index 100% rename from src/components/common/Card/Card.module.scss rename to src/components/CardList/Card/Card.module.scss diff --git a/src/components/CardList/Card/Card.tsx b/src/components/CardList/Card/Card.tsx new file mode 100644 index 000000000..6403a0e69 --- /dev/null +++ b/src/components/CardList/Card/Card.tsx @@ -0,0 +1,53 @@ +/*Card 컴포넌트*/ + +import Link from "next/link"; + +import formatDate from "@/utils/formatDate"; +import calcDate from "@/utils/calcDate"; +import { CardType } from "@/types/CardType"; +import Kebab from "@/components/CardList/Kebab/Kebab"; + +import styles from "./Card.module.scss"; +import Favorite from "../Favorite/Favorite"; + +interface CardProps { + card: CardType; + isShared?: boolean; +} + +export default function Card({ card, isShared }: CardProps) { + const str = calcDate(card.created_at); + + return ( + <> +
+ {!isShared && ( + <> + + + + )} + +
+ {card?.title +
+
+

{str}

+

{card?.description}

+

+ {formatDate(card.created_at)} +

+
+ +
+ + ); +} diff --git a/src/components/common/CardList/CardList.module.scss b/src/components/CardList/CardList.module.scss similarity index 100% rename from src/components/common/CardList/CardList.module.scss rename to src/components/CardList/CardList.module.scss diff --git a/src/components/common/CardList/CardList.tsx b/src/components/CardList/CardList.tsx similarity index 55% rename from src/components/common/CardList/CardList.tsx rename to src/components/CardList/CardList.tsx index 9f7ca9057..7947a03d4 100644 --- a/src/components/common/CardList/CardList.tsx +++ b/src/components/CardList/CardList.tsx @@ -6,32 +6,40 @@ keyword: keyword 값을 받아 keyword가 포함된 card 컴포넌트들만 filt onClick: Card 컴포넌트에게 내려줌 folderList: Card 컴포넌트에게 내려줌 */ +import Card from "@/components/CardList/Card/Card"; +import { useQuery } from "@tanstack/react-query"; + +import { getAllCards, getCards } from "@/api/getCardCRUDApi"; -import Card from "@/components/common/Card/Card"; -import { CardInterface, FolderInterface, ModalInterface } from "@/types"; import styles from "./CardList.module.scss"; -function CardList({ - cardList, - onClick, - folderList, - keyword = "", -}: { - cardList?: CardInterface[]; - onClick?: (m: ModalInterface) => void; - folderList?: FolderInterface[]; +interface CardListProps { + folderId?: string; keyword?: string; -}) { +} + +function CardList({ folderId, keyword = "" }: CardListProps) { + const { data: cardList } = useQuery({ + queryKey: ["card-list", folderId], + queryFn: () => { + if (!folderId) return getAllCards(); + else return getCards(folderId); + }, + staleTime: 1000 * 60, + }); + if (!cardList || cardList.length === 0) { return (
저장된 링크가 없습니다.
); } + let currentCardList = cardList; + if (keyword) { const searchTerm = keyword.toLowerCase().split(" ").join(""); - cardList = cardList.filter((card) => { - const searchText = `${card.description}${card.url}${card.title}` + currentCardList = cardList.filter((card) => { + const searchText = `${card?.description}${card?.url}${card?.title}` .toLowerCase() .split(" ") .join(""); @@ -40,15 +48,8 @@ function CardList({ } return (
- {cardList?.map((card) => { - return ( - - ); + {currentCardList?.map((card) => { + return ; })}
); diff --git a/src/components/common/StarButton/StarButton.module.scss b/src/components/CardList/Favorite/Favorite.module.scss similarity index 100% rename from src/components/common/StarButton/StarButton.module.scss rename to src/components/CardList/Favorite/Favorite.module.scss diff --git a/src/components/common/StarButton/StarButton.tsx b/src/components/CardList/Favorite/Favorite.tsx similarity index 54% rename from src/components/common/StarButton/StarButton.tsx rename to src/components/CardList/Favorite/Favorite.tsx index bf82fe398..2d77307a2 100644 --- a/src/components/common/StarButton/StarButton.tsx +++ b/src/components/CardList/Favorite/Favorite.tsx @@ -2,29 +2,31 @@ Card 컴포넌트의 좌측 상단 별모양 버튼. */ -import { useState } from "react"; import Image from "next/image"; -import { CardInterface } from "@/types"; -import styles from "./StarButton.module.scss"; -function StarButton({ card }: { card: CardInterface }) { - const [isFilled, setIsFilled] = useState(false); - const handleStarButton = () => { - setIsFilled(!isFilled); - }; +import styles from "./Favorite.module.scss"; + +interface FavoriteProps { + cardId: string; + isFilled: boolean; +} + +export default function Favorite({ cardId, isFilled }: FavoriteProps) { + const handleStarButton = () => {}; + return ( <> + + + + ); +} + +export default function Kebab({ cardId, cardUrl }: KebabProps) { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const handleMenuOpen = () => { + setIsMenuOpen(!isMenuOpen); + }; + + return ( + <> +
+ + {isMenuOpen && } +
+ + ); +} diff --git a/src/components/CardList/SharedCardList.tsx b/src/components/CardList/SharedCardList.tsx new file mode 100644 index 000000000..de50404ce --- /dev/null +++ b/src/components/CardList/SharedCardList.tsx @@ -0,0 +1,55 @@ +/*CardList 컴포넌트*/ + +import Card from "@/components/CardList/Card/Card"; +import { useQuery } from "@tanstack/react-query"; + +import { getCards, getSharedAllCards } from "@/api/getCardCRUDApi"; + +import styles from "./CardList.module.scss"; + +interface CardListProps { + userId: string; + folderId?: string; + keyword?: string; +} + +export default function SharedCardList({ + userId, + folderId, + keyword = "", +}: CardListProps) { + const { data: cardList } = useQuery({ + queryKey: ["card-list", folderId], + queryFn: () => { + if (!folderId) return getSharedAllCards(userId); + else return getCards(folderId); + }, + staleTime: 1000 * 60, + }); + + if (!cardList || cardList.length === 0) { + return ( +
저장된 링크가 없습니다.
+ ); + } + let currentCardList = cardList; + + if (keyword) { + const searchTerm = keyword.toLowerCase().split(" ").join(""); + + currentCardList = cardList.filter((card) => { + const searchText = `${card?.description}${card?.url}${card?.title}` + .toLowerCase() + .split(" ") + .join(""); + return searchText.includes(searchTerm); + }); + } + return ( +
+ {currentCardList?.map((card) => { + return ; + })} +
+ ); +} diff --git a/src/components/common/Footer/Footer.module.scss b/src/components/Footer/Footer.module.scss similarity index 100% rename from src/components/common/Footer/Footer.module.scss rename to src/components/Footer/Footer.module.scss diff --git a/src/components/common/Footer/Footer.tsx b/src/components/Footer/Footer.tsx similarity index 88% rename from src/components/common/Footer/Footer.tsx rename to src/components/Footer/Footer.tsx index 0313846c7..786400713 100644 --- a/src/components/common/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -12,7 +12,7 @@ function Footer() { name: "facebook", comp: ( facebook icon getUser(), + staleTime: 1000 * 60, + enabled: !!accessToken, + }); + + const userInfo = useUserInfoStore((state) => state.userInfo); + const addUser = useUserInfoStore((state) => state.addUser); + const deleteUser = useUserInfoStore((state) => state.deleteUser); + + useEffect(() => { + if (userInfo) return; + + if (data) addUser(data); + else deleteUser(); + }, [data]); + + return ( + <> + {!noNavPage.includes(route) &&