diff --git a/components/Button.tsx b/components/Button.tsx index 883c7213..94bb8c88 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -1,16 +1,28 @@ +import clsx from "clsx"; import { ReactNode } from "react"; // interface Props { - onClick: onClick; + onClick?: onClick; children: ReactNode; disabled?: boolean; + className?: string; } -export default function Button({ onClick, children, ...props }: Props) { +export default function Button({ + onClick, + children, + className, + disabled, + ...props +}: Props) { return ( <> diff --git a/components/Card.tsx b/components/Card.tsx index a6fc4e65..ac986618 100644 --- a/components/Card.tsx +++ b/components/Card.tsx @@ -4,9 +4,11 @@ import ProfileImg from "@/public/assets/icons/default.profile.icon.svg"; import { useFormatDate } from "@/hooks/useFormatting"; import BtnHeart from "./BtnHeart"; +import Profile from "./common/Profile"; // interface Props { article: Article; + isDetailArticle?: boolean; } export function BestArticle({ article }: Props) { const formattedDate = useFormatDate(article.createdAt); @@ -41,27 +43,23 @@ export function BestArticle({ article }: Props) { ); } -export function Articles({ article }: Props) { - const formattedDate = useFormatDate(article.createdAt); +export function Articles({ article, isDetailArticle }: Props) { const articleImg = article.image || ProfileImg; return (

{article.content}

- 상품이미지 + {!isDetailArticle && ( + 상품이미지 + )}
-
- 프로필 - -
{article.writer.nickname}
- {formattedDate} -
+
diff --git a/components/Input.tsx b/components/Input.tsx index ef448c09..fad66dd4 100644 --- a/components/Input.tsx +++ b/components/Input.tsx @@ -2,11 +2,13 @@ import PlusIcon from "@/public/assets/icons/plusIcon.svg"; import DeleteIcon from "@/public/assets/icons/DeleteIcon.svg"; import SearchIcon from "@/public/assets/icons/search.icon.svg"; import Image from "next/image"; -import { ChangeEvent, useRef, useState } from "react"; +import { useRef, useState } from "react"; +import { uploadImage } from "@/lib/image"; +import { renameFile } from "@/hooks/useRenameFile"; // interface Props { - onChange: (e: ChangeEvent) => void; + onChange: (value: string) => void; label?: string; placeholder?: string; name: string; @@ -17,8 +19,16 @@ interface Props { $edit?: boolean; onKeyUp?: (e: React.KeyboardEvent) => void; type?: "text" | "number"; + isAsterisk?: boolean; } -export function Input({ onChange, type = "text", ...props }: Props) { + +export function Input({ + onChange, + placeholder, + type = "text", + isAsterisk, + ...props +}: Props) { const { label, onKeyUp, $edit, $textArea, $comment } = props; return ( @@ -28,6 +38,7 @@ export function Input({ onChange, type = "text", ...props }: Props) { $comment && "text-H5Bold" } w-full h-[26px] font-Pretendard text-gray-800 text-H4Bold`} > + {isAsterisk && *} {label} { + if (e.nativeEvent.composed) return onChange(e.target.value); + }} onKeyUp={onKeyUp || undefined} + placeholder={placeholder} /> ); } + // interface ImgProps extends Omit { - onChange: (value: File | string) => void; + onChange: (value: null | string) => void; } -export function ImgInput({ onChange, ...props }: ImgProps) { +export function ImgInput({ onChange, label, ...props }: ImgProps) { const imgRef = useRef(null); const [imgPreview, setImgPreview] = useState(""); const handlePreviewImg = () => { if (!imgRef.current || !imgRef.current.files?.length) return; const file = imgRef.current.files[0]; + + const renamedFile = renameFile(file); + const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onloadend = () => { + reader.readAsDataURL(renamedFile); + + reader.onloadend = async () => { if (typeof reader.result === "string") { setImgPreview(reader.result); - onChange(file); + const formattedImage = await uploadImage(renamedFile); + onChange(formattedImage); } }; }; - // + const handleClickImgDelete = () => { setImgPreview(""); - onChange(""); + onChange(null); }; + return ( -
-
- +

{label}

+
+ {!imgPreview ? ( + <> + - + type="file" + id="fileUpload" + accept="image/*" + ref={imgRef} + onChange={handlePreviewImg} + {...props} + /> + + + ) : ( +
+ 이미지 미리보기 + x +
+ )}
- - {imgPreview && ( -
- 이미지 미리보기 - x -
- )}
); } @@ -117,7 +139,9 @@ export function SearchInput({ onChange, ...props }: Props) { return (
{ + onChange(e.target.value); + }} className=" rounded-xl border-none px-[44px] py-[16px] w-full h-[42px] bg-gray-100 font-Pretendard text-gray-800 text-H5Regular diff --git a/components/Nav.tsx b/components/Nav.tsx index 70ebc934..623347c8 100644 --- a/components/Nav.tsx +++ b/components/Nav.tsx @@ -3,9 +3,22 @@ import pandaLogo from "@/public/assets/Logo/pandaLogo.svg"; import textLogo from "@/public/assets/Logo/textLogo.svg"; import myLogo from "@/public/assets/icons/default.profile.icon.svg"; import Image from "next/image"; -import useWindowSize from "@/hooks/useWindowSize"; +import { useEffect, useState } from "react"; +import Button from "./Button"; +import { signIn } from "@/lib/auth"; + export default function Nav() { - const device: string = useWindowSize(); + const [isLogin, setIsLogin] = useState(false); + useEffect(() => { + const hasAccessToken = Boolean(localStorage.getItem("accessToken")); + setIsLogin(hasAccessToken); + }, []); + + const handleClickLogin = async () => { + await signIn(); + setIsLogin(true); + }; + return (
@@ -27,11 +40,15 @@ export default function Nav() { />
- 자유게시판 - 중고마켓 + 자유게시판 + 중고마켓
- + {!isLogin ? ( +
+ +
+ ) : ( - + )}
); diff --git a/components/Select.tsx b/components/Select.tsx index 52ca1a99..a02c78ad 100644 --- a/components/Select.tsx +++ b/components/Select.tsx @@ -80,7 +80,7 @@ export function EditSelect({ onChange, ...props }: Props) { onClick={() => setIsOpen(!isOpen)} className="h-6 bg-white border-none cursor-pointer" > - 케밥 + 케밥
    { + switch (option) { + case "수정하기": { + setIsEdit(true); + break; + } + case "삭제하기": { + await deleteArticleComment(comment.id); + break; + } + } + }; + const handleChange = async (e: KeyboardEvent) => { + if (e.key === "Enter") { + await editArticleComment(comment.id, currentValue); + setCurrentValue(currentValue); + } + }; + return ( + <> +
    +
    + {isEdit ? ( + { + setCurrentValue(e.currentTarget.value); + }} + onKeyDown={(e) => handleChange(e)} + /> + ) : ( +

    {currentValue}

    + )} + handleSelect(option)} /> +
    +
    + +
    +
    +
    + + ); +} diff --git a/components/common/Profile.tsx b/components/common/Profile.tsx new file mode 100644 index 00000000..17d5e70d --- /dev/null +++ b/components/common/Profile.tsx @@ -0,0 +1,26 @@ +import Image from "next/image"; +import ProfileImg from "@/public/assets/icons/default.profile.icon.svg"; +import { useFormatDate, useFormatUpDate } from "@/hooks/useFormatting"; +import clsx from "clsx"; +interface Props { + value: { + writer: { nickname: string }; + createdAt: string; + }; + isComment?: boolean; +} +export default function Profile({ value, isComment }: Props) { + const formattedDate = isComment + ? useFormatUpDate(value.createdAt) + : useFormatDate(value.createdAt); + + return ( +
    + 프로필 +
    +
    {value.writer?.nickname}
    +

    {formattedDate}

    +
    +
    + ); +} diff --git a/hooks/useRenameFile.ts b/hooks/useRenameFile.ts new file mode 100644 index 00000000..70243f42 --- /dev/null +++ b/hooks/useRenameFile.ts @@ -0,0 +1,6 @@ +export const renameFile = (file: File) => { + const extension = file.name.split(".").pop(); + return new File([file], `upload_${Date.now()}.${extension}`, { + type: file.type, + }); +}; diff --git a/lib/Articles.tsx b/lib/Articles.tsx index 1e3a70cf..dc66f7c3 100644 --- a/lib/Articles.tsx +++ b/lib/Articles.tsx @@ -31,3 +31,28 @@ export async function getArticles({ throw new Error(" 게시글 불러오기 실패"); } } + +export async function getArticleDetail(articleId: number) { + const res = await instance.get(`/articles/${articleId}`); + return res.data; +} + +export async function deleteArticle(articleId: number) { + await instance.delete(`/articles/${articleId}`); +} + +interface articleEditedRequestParams { + articleId: number; + articleData: ArticleData; +} +export async function editArticle({ + articleId, + articleData, +}: articleEditedRequestParams) { + await instance.delete(`/articles/${articleId}`); +} + +export async function createArticle(articleData: ArticleData) { + console.log(articleData); + const res = await instance.post(`/articles`, { ...articleData }); +} diff --git a/lib/api.tsx b/lib/api.tsx index 76c18f34..7b9f4669 100644 --- a/lib/api.tsx +++ b/lib/api.tsx @@ -1,4 +1,4 @@ -import axios from "axios"; +import axios, { AxiosResponse, InternalAxiosRequestConfig } from "axios"; const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL; @@ -6,7 +6,28 @@ const instance = axios.create({ baseURL: BASE_URL, headers: { "Content-Type": "application/json", + withCredentials: true, }, }); export default instance; + +instance.interceptors.request.use( + (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => { + if (typeof window !== "undefined") { + const accessToken = localStorage.getItem("accessToken"); + if (!accessToken) return config; + config.headers.set("Authorization", `Bearer ${accessToken}`); + } + return config; + } +); + +instance.interceptors.response.use((response: AxiosResponse) => { + const accessToken = response.data.accessToken; + if (typeof window !== "undefined" && accessToken) { + localStorage.setItem("accessToken", accessToken); + localStorage.setItem("refreshToken", response.data.refreshToken); + } + return response; +}); diff --git a/lib/auth.tsx b/lib/auth.tsx new file mode 100644 index 00000000..aecb3d37 --- /dev/null +++ b/lib/auth.tsx @@ -0,0 +1,25 @@ +import instance from "./api"; +import Cookies from "js-cookie"; + +export const setAccessToken = (token: string) => { + Cookies.set("accessToken", token, { + path: "/", + expires: 7, + sameSite: "Strict", + }); +}; + +export const removeAccessToken = () => { + Cookies.remove("accessToken", { path: "/" }); +}; + +const TEST_EMAIL = "test98@email.com"; +const TEST_PASSWORD = "12345678"; + +export async function signIn() { + const res = await instance.post(`/auth/signIn`, { + email: TEST_EMAIL, + password: TEST_PASSWORD, + }); + setAccessToken(res.data.accessToken); +} diff --git a/lib/comments.api.tsx b/lib/comments.api.tsx new file mode 100644 index 00000000..92ec62cb --- /dev/null +++ b/lib/comments.api.tsx @@ -0,0 +1,29 @@ +import instance from "./api"; + +export async function getArticleComment( + articleId: number, + limit: number = 5, + cursor: number +) { + const res = await instance.get(`/articles/${articleId}/comments`, { + params: { limit: limit, cursor: cursor }, + }); + return res.data; +} + +export async function postArticleComment(articleId: number, content: string) { + const res = await instance.post(`/articles/${articleId}/comments`, { + content, + }); + return res.data; +} + +export async function editArticleComment(commentId: number, content: string) { + const res = await instance.patch(`/comments/${commentId}`, { content }); + return res.data.content; +} + +export async function deleteArticleComment(commentId: number) { + const res = await instance.delete(`/comments/${commentId}`, {}); + return res.data.content; +} diff --git a/lib/image.tsx b/lib/image.tsx new file mode 100644 index 00000000..67489535 --- /dev/null +++ b/lib/image.tsx @@ -0,0 +1,12 @@ +import { headers } from "next/headers"; +import instance from "./api"; + +export async function uploadImage(image: File) { + const formData = new FormData(); + formData.append("image", image); + + const res = await instance.post(`/images/upload`, formData, { + headers: { " Content-Type": "multipart/form-data" }, + }); + return res.data.url; +} diff --git a/package-lock.json b/package-lock.json index 30cf419a..f696d0b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,16 @@ "version": "0.1.0", "dependencies": { "axios": "^1.8.1", + "clsx": "^2.1.1", + "js-cookie": "^3.0.5", "lodash.throttle": "^4.1.1", "next": "^15.2.1", + "nookies": "^2.5.2", "react": "^18", - "react-dom": "^18", - "styled-components": "^6.1.15" + "react-dom": "^18" }, "devDependencies": { + "@types/js-cookie": "^3.0.6", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", @@ -48,24 +51,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" - }, - "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -807,6 +792,13 @@ "tslib": "^2.8.0" } }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -847,11 +839,6 @@ "@types/react": "^18.0.0" } }, - "node_modules/@types/stylis": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", - "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" - }, "node_modules/@typescript-eslint/parser": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", @@ -1491,14 +1478,6 @@ "node": ">= 6" } }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001702", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001702.tgz", @@ -1575,6 +1554,15 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -1642,6 +1630,15 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1656,24 +1653,6 @@ "node": ">= 8" } }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1689,7 +1668,8 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -3529,6 +3509,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3894,6 +3883,16 @@ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, + "node_modules/nookies": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/nookies/-/nookies-2.5.2.tgz", + "integrity": "sha512-x0TRSaosAEonNKyCrShoUaJ5rrT5KHRNZ5DwPCuizjgrnkpE5DRf3VL7AyyQin4htict92X1EQ7ejDbaHDVdYA==", + "license": "MIT", + "dependencies": { + "cookie": "^0.4.1", + "set-cookie-parser": "^2.4.6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4380,7 +4379,8 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -4687,6 +4687,12 @@ "node": ">=10" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -4733,11 +4739,6 @@ "node": ">= 0.4" } }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, "node_modules/sharp": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", @@ -5140,65 +5141,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/styled-components": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.15.tgz", - "integrity": "sha512-PpOTEztW87Ua2xbmLa7yssjNyUF9vE7wdldRfn1I2E6RTkqknkBYpj771OxM/xrvRGinLy2oysa7GOd7NcZZIA==", - "dependencies": { - "@emotion/is-prop-valid": "1.2.2", - "@emotion/unitless": "0.8.1", - "@types/stylis": "4.2.5", - "css-to-react-native": "3.2.0", - "csstype": "3.1.3", - "postcss": "8.4.49", - "shallowequal": "1.1.0", - "stylis": "4.3.2", - "tslib": "2.6.2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" - } - }, - "node_modules/styled-components/node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/styled-components/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -5221,11 +5163,6 @@ } } }, - "node_modules/stylis": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", - "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" - }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", diff --git a/package.json b/package.json index 8a2f5344..a8863ccd 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,16 @@ }, "dependencies": { "axios": "^1.8.1", + "clsx": "^2.1.1", + "js-cookie": "^3.0.5", "lodash.throttle": "^4.1.1", "next": "^15.2.1", + "nookies": "^2.5.2", "react": "^18", - "react-dom": "^18", - "styled-components": "^6.1.15" + "react-dom": "^18" }, "devDependencies": { + "@types/js-cookie": "^3.0.6", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/pages/_app.tsx b/pages/_app.tsx index 5c8e310f..1e7e0445 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -6,7 +6,7 @@ export default function App({ Component, pageProps }: AppProps) { <>
    - + + +
    handleSearchChange(value)} />
    {articles.length > 0 && articles.map((article) => ( - + + + ))}
    diff --git a/public/assets/icons/previous.icon.svg b/public/assets/icons/previous.icon.svg new file mode 100644 index 00000000..bbdaa5fa --- /dev/null +++ b/public/assets/icons/previous.icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/noComment.svg b/public/assets/noComment.svg new file mode 100644 index 00000000..dbac5014 --- /dev/null +++ b/public/assets/noComment.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/types/global.d.ts b/types/global.d.ts index 22b0ab9f..f68bc494 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -45,4 +45,10 @@ declare global { id: number; } type onClick = (e: MouseEvent) => void; + + interface ArticleData { + image: string | null; + content: string; + title: string; + } }