diff --git a/package-lock.json b/package-lock.json index b37997c1..0159a2b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "@types/styled-components": "^5.1.34", "axios": "^1.7.9", "firebase": "^11.1.0", "lodash.throttle": "^4.1.1", @@ -21,6 +22,11 @@ "react-select": "^5.9.0", "styled-components": "^6.1.14", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "typescript": "^4.9.5" } }, "node_modules/@adobe/css-tools": { @@ -4572,6 +4578,24 @@ "node": ">=12" } }, + "node_modules/@testing-library/react/node_modules/@types/react": { + "version": "18.3.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", + "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@testing-library/react/node_modules/@types/react-dom": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", + "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@testing-library/react/node_modules/aria-query": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", @@ -4756,6 +4780,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -4896,21 +4929,20 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { - "version": "18.3.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", - "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", - "peer": true, + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", - "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", + "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", + "dev": true, "peerDependencies": { - "@types/react": "^18.0.0" + "@types/react": "^19.0.0" } }, "node_modules/@types/react-transition-group": { @@ -4979,6 +5011,16 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "node_modules/@types/styled-components": { + "version": "5.1.34", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", + "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==", + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/stylis": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", @@ -17885,7 +17927,6 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 3fbc0b7a..09c65e00 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "@types/styled-components": "^5.1.34", "axios": "^1.7.9", "firebase": "^11.1.0", "lodash.throttle": "^4.1.1", @@ -40,5 +41,10 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "typescript": "^4.9.5" } } diff --git a/src/Main.jsx b/src/Main.jsx index 7ac1c86c..0b4bf2d1 100644 --- a/src/Main.jsx +++ b/src/Main.jsx @@ -1,53 +1,11 @@ import { Route, BrowserRouter, Routes } from "react-router-dom"; -import { createGlobalStyle } from "styled-components"; +import GlobalStyle from "./style/globalStyle.js"; // import LandingPage from "./pages/LandingPage/LandingPage.jsx"; import App from "./App.js"; -import HomePage from "./pages/HomePage/HomePage.jsx"; -import AddItem from "./pages/AddItem/AddItem.jsx"; -import Product from "./pages/ProductPage/Product.jsx"; -import Test from "./components/TestPage.jsx"; -// -// -const GlobalStyle = createGlobalStyle` - * { - box-sizing: border-box; - } - body { - font-family: 'Pretendard', sans-serif; - font-display: swap; - margin: 0; - padding: 0; - } - html { - margin: 0; - padding: 0; - } - a { - text-decoration: none; - color: #ffffff; - } - p{ - margin: 0px; - } - @font-face { - font-family: 'Pretendard'; - src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff2') format('woff2'); - src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') format('woff'); - font-display: swap; - font-weight: 400; - font-style: normal; -} - -@font-face { -font-family: "Pretendard"; -src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Bold.woff2') format('woff2'); -src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Bold.woff') format('woff'); - font-display: swap; - font-weight: 600; - font-style: normal; -} -`; +import ItemsPage from "./pages/ItemsPage/ItemsPage.tsx"; +import AddItem from "./pages/AddItem/AddItem.tsx"; +import Product from "./pages/ProductPage/Product.tsx"; // function Main() { return ( @@ -57,8 +15,7 @@ function Main() { } /> }> - } /> - } /> + } /> } /> } /> diff --git a/src/api/comment.api.jsx b/src/api/comment.api.tsx similarity index 70% rename from src/api/comment.api.jsx rename to src/api/comment.api.tsx index 2c861071..a2718b42 100644 --- a/src/api/comment.api.jsx +++ b/src/api/comment.api.tsx @@ -1,7 +1,7 @@ import axios from "axios"; const BASE_URL = "https://panda-market-api.vercel.app"; -export async function getProductComments(productId, limit = 3) { +export async function getProductComments({ productId }: Params, limit = 3) { try { const res = await axios.get( `${BASE_URL}/products/${productId}/comments?limit=${limit}` @@ -9,9 +9,9 @@ export async function getProductComments(productId, limit = 3) { if (!res) { throw new Error("리뷰 불러오기 실패"); } - return res.data; + const data: Comment[] = res.data.list; + return data; } catch (error) { console.error(error); - return null; } } diff --git a/src/api/product.api.jsx b/src/api/product.api.jsx deleted file mode 100644 index 0fc94123..00000000 --- a/src/api/product.api.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import axios from "axios"; -const BASE_URL = "https://panda-market-api.vercel.app"; - -export async function getProducts({ - device = "desktop", - page = 1, - selectedOrder = "최신순", -}) { - const order = selectedOrder === "최신순" ? "recent" : "favorite"; - const pageSize = device === "mobile" ? 4 : device === "tablet" ? 6 : 10; - const query = `?orderBy=${order}&page=${page}&pageSize=${pageSize}`; - const response = await fetch(`${BASE_URL}/products${query}`); - - if (!response.ok) { - throw new Error("상품을 불러오지 못했습니다. 다시 시도해주세요"); - } - const body = await response.json(); - return body; -} - -export async function bestProducts({ device }) { - const pageSize = device === "mobile" ? 1 : device === "tablet" ? 2 : 4; - const response = await fetch( - `${BASE_URL}/products?orderBy=favorite&pageSize=${pageSize}` - ); - if (!response.ok) { - throw new Error("베스트 상품을 불러오지 못했습니다. 다시 시도해주세요"); - } - const body = await response.json(); - return body; -} - -export async function getProductInfo(productId) { - try { - const response = await axios.get(`${BASE_URL}/products/${productId}`); - if (!response) throw new Error("제품정보 get api 실패"); - return response.data; - } catch (error) { - console.error(error, "제품정보 api 실패"); - return null; - } -} diff --git a/src/api/product.api.tsx b/src/api/product.api.tsx new file mode 100644 index 00000000..0ff07fb8 --- /dev/null +++ b/src/api/product.api.tsx @@ -0,0 +1,60 @@ +import axios from "axios"; +const BASE_URL = "https://panda-market-api.vercel.app"; +// + +export interface ResponseData extends Product { + createdAt: string; + favoriteCount: number; + ownerNickname: string; + ownerId: number; + id: number; + isFavorite: boolean; +} +export type Item = Omit; + +export async function getProducts({ + device = "desktop", + page = 1, + selectedOrder = "최신순", +}) { + const order = selectedOrder === "최신순" ? "recent" : "favorite"; + const pageSize = device === "mobile" ? 4 : device === "tablet" ? 6 : 10; + const query = `?orderBy=${order}&page=${page}&pageSize=${pageSize}`; + + try { + const res = await axios.get(`${BASE_URL}/products${query}`); + if (!res) throw new Error("상품 불러오기 실패"); + const data: Item[] = res.data.list; + return data; + } catch (error) { + console.error(error, "상품 불러오기 실패"); + } +} + +export async function bestProducts({ device }: Device) { + const pageSize = device === "mobile" ? 1 : device === "tablet" ? 2 : 4; + try { + const res = await axios.get( + `${BASE_URL}/products?orderBy=favorite&pageSize=${pageSize}` + ); + if (!res) throw new Error("베스트상품 api 실패"); + const data: Item[] = res.data.list; + return data; + } catch (error) { + console.error( + error, + "베스트 상품을 불러오지 못했습니다. 다시 시도해주세요" + ); + } +} + +export async function getProductInfo({ productId }: Params) { + try { + const response = await axios.get(`${BASE_URL}/products/${productId}`); + if (!response) throw new Error("제품정보 get api 실패"); + const data: ResponseData = response.data; + return data; + } catch (error) { + console.error(error, "제품정보 api 실패"); + } +} diff --git a/src/assets/icons/nextPage.icon.svg b/src/assets/icons/nextPage.icon.svg new file mode 100644 index 00000000..368742c9 --- /dev/null +++ b/src/assets/icons/nextPage.icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/previousPage.icon.svg b/src/assets/icons/previousPage.icon.svg new file mode 100644 index 00000000..040e81c2 --- /dev/null +++ b/src/assets/icons/previousPage.icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/search.icon.svg b/src/assets/icons/search.icon.svg new file mode 100644 index 00000000..d9cc31ea --- /dev/null +++ b/src/assets/icons/search.icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/sort.icon.svg b/src/assets/icons/sort.icon.svg new file mode 100644 index 00000000..657b44f9 --- /dev/null +++ b/src/assets/icons/sort.icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/CommentCard/CommentCard.style.jsx b/src/components/CommentCard/CommentCard.style.tsx similarity index 100% rename from src/components/CommentCard/CommentCard.style.jsx rename to src/components/CommentCard/CommentCard.style.tsx diff --git a/src/components/CommentCard/CommentCard.jsx b/src/components/CommentCard/CommentCard.tsx similarity index 64% rename from src/components/CommentCard/CommentCard.jsx rename to src/components/CommentCard/CommentCard.tsx index 486ad262..8baadc4a 100644 --- a/src/components/CommentCard/CommentCard.jsx +++ b/src/components/CommentCard/CommentCard.tsx @@ -1,40 +1,45 @@ import { useEffect, useState } from "react"; // -import * as S from "./CommentCard.style.jsx"; +import * as S from "./CommentCard.style"; import defaultImg from "../../assets/icons/default.profile.icon.svg"; -import { Input } from "../common/Input/Input.jsx"; -import { EditSelect } from "../common/Select/Select.jsx"; +import { Input } from "../common/Input/Input"; +import { EditSelect } from "../common/Select/Select"; import { button } from "../../constants/globalConstant.jsx"; import { useFormatUpDate } from "../../hooks/useFormatting.jsx"; // -export default function CommentCard({ data }) { + +interface Props { + data: Comment; +} +export default function CommentCard({ data }: Props) { const [initialValue, setInitialValue] = useState(data.content); - const [isEditing, setIsEditing] = useState(null); - const [isDelete, setIsDelete] = useState(null); + const [EditingId, setEditingId] = useState(0); + const [DeleteId, setDeleteId] = useState(0); const formattedUpdate = useFormatUpDate(data.updatedAt); // - const handleOnChange = (option) => { + const handleOnChange = (option: string) => { if (option === button.edit) { - setIsEditing(data.id); + setEditingId(data.id); } if (option === button.delete) { - setIsDelete(data.id); + setDeleteId(data.id); } return; }; useEffect(() => { //삭제 리퀘스트 예정 - }, [isDelete]); + }, [DeleteId]); // return ( - {data.id === isEditing ? ( + {data.id === EditingId ? ( setInitialValue(target.value)} + onChange={(e) => setInitialValue(e.target.value)} /> ) : ( {initialValue} @@ -53,19 +58,19 @@ export default function CommentCard({ data }) { {formattedUpdate} - {data.id === isEditing && ( + {data.id === EditingId && (
- setIsEditing("")}> + setEditingId(0)}> {button.cancel} - setIsEditing("")}> + setEditingId(0)}> {button.editConfirm}
)}
- {data.id !== isEditing && } + {data.id !== EditingId && }
); } diff --git a/src/components/ItemsList/ItemsList.style.tsx b/src/components/ItemsList/ItemsList.style.tsx new file mode 100644 index 00000000..af665f9f --- /dev/null +++ b/src/components/ItemsList/ItemsList.style.tsx @@ -0,0 +1,101 @@ +import styled from "styled-components"; +import theme from "../../style/theme"; + +interface Props { + value: string; + $device: string; +} + +interface Device { + gap: string; + gridTemplate: string; + gridRow: string; + height: string; +} + +interface DeviceStyle { + [key: string]: { + mobile: Device; + tablet: Device; + desktop: Device; + [key: string]: Device; + }; +} +const ByDevice: DeviceStyle = { + best: { + mobile: { + gap: "none", + gridTemplate: "repeat(1, 343px)", + gridRow: "434px", + height: "343px", + }, + tablet: { + gap: "10px", + gridTemplate: "repeat(3, 343px)", + gridRow: "434px", + height: "343px", + }, + desktop: { + gap: "24px", + gridTemplate: "repeat(4, 282px)", + gridRow: "378px", + height: "282px", + }, + }, + products: { + mobile: { + gap: "32px 8px", + gridTemplate: "repeat(2, 168px)", + gridRow: "264px", + height: "168px", + }, + tablet: { + gap: "40px 24px", + gridTemplate: "repeat(3, 221px)", + gridRow: "317px", + height: "221px", + }, + desktop: { + gap: "40px 24px", + gridTemplate: "repeat(5, 221px)", + height: "221px", + gridRow: "317px", + }, + }, +}; +export const Item = styled.div` + width: 100%; + display: flex; + flex-direction: column; + gap: 16px; +`; +export const ProductImg = styled.img` + aspect-ratio: 1/1; + height: ${({ $device, value }) => ByDevice[value][$device].height || "auto"}; + border-radius: 16px; +`; + +export const FlexContent = styled.div` + display: flex; + flex-direction: column; + gap: 6px; +`; +export const Title = styled.h4` + font: ${theme.font.H7Medium}; + text-align: left; + color: ${theme.color.gray800}; +`; +export const Price = styled.div` + font: ${theme.font.H5Bold}; + text-align: left; + color: ${theme.color.gray800}; +`; + +export const ItemListStyle = styled.div` + margin: 24px auto; + display: grid; + gap: ${({ $device, value }) => ByDevice[value][$device].gap}; + grid-template-columns: ${({ $device, value }) => + ByDevice[value][$device].gridTemplate}; + grid-auto-rows: ${({ value, $device }) => ByDevice[value][$device].gridRow}; +`; diff --git a/src/components/ItemsList/ItemsList.tsx b/src/components/ItemsList/ItemsList.tsx new file mode 100644 index 00000000..2783ef1a --- /dev/null +++ b/src/components/ItemsList/ItemsList.tsx @@ -0,0 +1,47 @@ +import { useNavigate } from "react-router-dom"; +import useWindowSize from "../../hooks/useWindowSize"; +// +import * as S from "./ItemsList.style"; +import BtnHeart from "../common/BtnHeart/BtnHeart"; +import { Item } from "../../api/product.api"; +// + +interface Prop { + value: string; + items: Item[]; +} +interface ListItemProps { + value: string; + item: Item; +} +function ListItem({ value, item }: ListItemProps) { + const navigate = useNavigate(); + const device = useWindowSize(); + return ( + navigate(`./${item.id}`)}> + + + {item.name} + {item.price} 원 + + + + ); +} +// +export default function ItemsList({ value, items, ...props }: Prop) { + const device = useWindowSize(); + + return ( + + {items.map((item) => { + return ; + })} + + ); +} diff --git a/src/components/PageNation/pageCount.style.tsx b/src/components/PageNation/pageCount.style.tsx new file mode 100644 index 00000000..fe1f94b6 --- /dev/null +++ b/src/components/PageNation/pageCount.style.tsx @@ -0,0 +1,45 @@ +import styled from "styled-components"; +import theme from "../../style/theme"; + +export interface StyleProps { + page: number; + value?: number; +} +export const PageBtn = styled.div` + display: flex; + gap: 4px; + margin: 43px auto 58px; + text-align: center; + width: 304px; +`; + +export const Btn = styled.button` + width: 40px; + height: 40px; + border-radius: 40px; + border: 1px solid ${theme.color.gray200}; + opacity: 0px; + font-size: 16px; + font-weight: 600; + line-height: 26px; + background-color: ${({ page, value }) => + page === value ? "#2f80ed" : "#ffffff"}; + color: ${({ page, value }) => (page === value ? "#f9fafb" : "#6b7280")}; + + &:active { + background-color: ${theme.color.blue}; + color: ${theme.color.gray50}; + } + + &:hover { + background-color: ${theme.color.blue}; + color: ${theme.color.gray50}; + } +`; +export const NextPrevBtn = styled.button` + width: 40px; + height: 40px; + border-radius: 40px; + border: 1px solid ${theme.color.gray200}; + background-color: ${theme.color.white}; +`; diff --git a/src/components/PageNation/pageCount.tsx b/src/components/PageNation/pageCount.tsx new file mode 100644 index 00000000..9047be0c --- /dev/null +++ b/src/components/PageNation/pageCount.tsx @@ -0,0 +1,60 @@ +import { MouseEvent, useState } from "react"; +import * as S from "./pageCount.style"; +import previousIcon from "../../assets/icons/previousPage.icon.svg"; +import nextIcon from "../../assets/icons/nextPage.icon.svg"; +// + +interface Props extends S.StyleProps { + onClick: onClick; +} +export function PageButton({ page, value, onClick }: Props) { + return ( + + {value} + + ); +} +// +type PageCountProps = Omit & { + onClick: (btn: number) => void; +}; +function PageCount({ page, onClick }: PageCountProps) { + const BtnArray = [1, 2, 3, 4, 5]; + const [currentPage, setCurrentPage] = useState(1); + + const handleClickBtn = (btn: number) => { + setCurrentPage(btn); + onClick(btn); + }; + + const handleClickPrev = () => { + setCurrentPage(currentPage - 1); + onClick(currentPage - 1); + }; + const handleClickNext = () => { + setCurrentPage(currentPage + 1); + onClick(currentPage + 1); + }; + + return ( + + + 이전 + + {BtnArray.map((btn) => { + return ( + handleClickBtn(btn)} + value={btn} + page={page} + /> + ); + })} + + 다음 + + + ); +} +export default PageCount; diff --git a/src/components/Tag/Tag.style.jsx b/src/components/Tag/Tag.style.tsx similarity index 76% rename from src/components/Tag/Tag.style.jsx rename to src/components/Tag/Tag.style.tsx index 2c24e7bf..f1c20ec0 100644 --- a/src/components/Tag/Tag.style.jsx +++ b/src/components/Tag/Tag.style.tsx @@ -1,7 +1,14 @@ import styled, { css } from "styled-components"; import theme from "../../style/theme"; -export const TagsContainer = styled.div` +export interface StyledProps { + $product?: boolean; +} +interface DeleteButtonProps { + tag: Tag; + onClick?: () => void; +} +export const TagsContainer = styled.div` width: 100%; display: flex; gap: 12px; @@ -38,7 +45,7 @@ export const Tag = styled.div` @media (min-width: 376px) and (max-width: 768px) { } `; -export const DeleteButton = styled.img` +export const DeleteButton = styled.img` width: 22px; height: 22px; `; diff --git a/src/components/Tag/Tag.jsx b/src/components/Tag/Tag.tsx similarity index 65% rename from src/components/Tag/Tag.jsx rename to src/components/Tag/Tag.tsx index f84891cf..94d6d811 100644 --- a/src/components/Tag/Tag.jsx +++ b/src/components/Tag/Tag.tsx @@ -1,21 +1,26 @@ import DeleteButton from "../../assets/icons/DeleteIcon.svg"; import * as S from "./Tag.style"; -export default function Tag({ tags, ...props }) { +// +interface Props extends S.StyledProps { + tags: Tag[]; + onClick?: (tag: Tag) => void; +} +export default function Tag({ tags, ...props }: Props) { const { onClick, ...rest } = props; - //...rest : $product(gap8px,items/id페이지) return ( - + {tags?.length > 0 && tags.map((tag) => { return ( #{tag} - {!rest.$product && ( + {!props.$product && ( onClick(tag)} src={DeleteButton} + onClick={onClick ? () => onClick(tag) : undefined} + {...rest} /> )} diff --git a/src/components/TestPage.jsx b/src/components/TestPage.jsx deleted file mode 100644 index e6a3ccb4..00000000 --- a/src/components/TestPage.jsx +++ /dev/null @@ -1,19 +0,0 @@ -/// 컴포넌트 테스트 페이지 완료후 삭제 예정 - -// import Button from "./common/Button/Button"; -// import BtnHeart from "./common/BtnHeart/BtnHeart"; -// import Tag from "../components/Tag/Tag"; -// import { Input } from "./common/Input/Input"; -export default function TestPage() { - // //태그 테스트 배열 - // const tag3 = []; - // const tag1 = ["태그1", "태그2"]; - // const tags = ["태그1", "태그2", "태그3", "태그4"]; - return ( - <> - {/* - - */} - - ); -} diff --git a/src/components/common/BtnHeart/BtnHeart.jsx b/src/components/common/BtnHeart/BtnHeart.jsx deleted file mode 100644 index 4ee40f3e..00000000 --- a/src/components/common/BtnHeart/BtnHeart.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import activeHeart from "../../../assets/icons/active.heart.icon.svg"; -import inactiveHeart from "../../../assets/icons/inactive.heart.icon.svg"; -// -import * as S from "./BtnHeart.style"; -// -export default function BtnHeart({ value, ...props }) { - const { border, active, onClick, small, ...rest } = props; - const onClickChange = (e) => { - onClick(e); - }; - return ( - <> - {active ? ( - - - {value} - - ) : ( - - - {value} - - )} - - ); -} diff --git a/src/components/common/BtnHeart/BtnHeart.style.jsx b/src/components/common/BtnHeart/BtnHeart.style.tsx similarity index 68% rename from src/components/common/BtnHeart/BtnHeart.style.jsx rename to src/components/common/BtnHeart/BtnHeart.style.tsx index 78db533a..b6a30a39 100644 --- a/src/components/common/BtnHeart/BtnHeart.style.jsx +++ b/src/components/common/BtnHeart/BtnHeart.style.tsx @@ -2,7 +2,12 @@ import styled, { css } from "styled-components"; import theme from "../../../style/theme"; // -export const InactiveBtnHeart = styled.button` +export interface StyledProps { + $small?: boolean; + $border?: boolean; + $items?: boolean; +} +export const InactiveBtnHeart = styled.button` display: flex; align-items: center; justify-content: center; @@ -11,10 +16,7 @@ export const InactiveBtnHeart = styled.button` height: 40px; border: none; border-radius: 35px; - font-weight: 500; - font-size: 16px; - line-height: 26px; - + font: ${theme.font.H5Regular}; background: ${theme.color.white}; color: ${theme.color.gray500}; ${(props) => @@ -28,9 +30,16 @@ export const InactiveBtnHeart = styled.button` css` border: 1px solid ${theme.color.gray200}; `}; + ${(props) => + props.$items && + css` + font: ${theme.font.H8}; + width: 42px; + height: 19px; + `}; `; -export const ActiveBtnHeart = styled.button` +export const ActiveBtnHeart = styled.button` display: flex; align-items: center; justify-content: center; @@ -57,7 +66,7 @@ export const ActiveBtnHeart = styled.button` `}; `; -export const HeartImg = styled.img` +export const HeartImg = styled.img` width: 26px; height: 23px; @@ -67,4 +76,10 @@ export const HeartImg = styled.img` width: 20px; height: 17px; `} + ${(props) => + props.$items && + css` + width: 16px; + height: 16px; + `} `; diff --git a/src/components/common/BtnHeart/BtnHeart.tsx b/src/components/common/BtnHeart/BtnHeart.tsx new file mode 100644 index 00000000..7ce2182a --- /dev/null +++ b/src/components/common/BtnHeart/BtnHeart.tsx @@ -0,0 +1,27 @@ +import activeHeart from "../../../assets/icons/active.heart.icon.svg"; +import inactiveHeart from "../../../assets/icons/inactive.heart.icon.svg"; +// +import * as S from "./BtnHeart.style"; +// +interface Props extends S.StyledProps { + value: number; + active?: boolean; +} +export default function BtnHeart({ value, ...props }: Props) { + const { active, ...rest } = props; + return ( + <> + {active ? ( + + + {value} + + ) : ( + + + {value} + + )} + + ); +} diff --git a/src/components/common/Button/Button.jsx b/src/components/common/Button/Button.jsx deleted file mode 100644 index 17e28380..00000000 --- a/src/components/common/Button/Button.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as S from "./Button.style"; -// -//버튼 컨테이너 필요 -export default function Button({ onClick, ...props }) { - const { - $small, - medium, - value, - toggle, - square, - circle, - heart, - children, - disabled, - ...rest - } = props; - //...rest: $square,$small,$medium,toggle,$circle,children,disabled - const onClickChange = (e) => { - onClick(e); - }; - return ( - <> - {toggle ? ( - + + + + {}} + /> + + + + ) : ( + + 전체 상품 + + + {}} + /> + + + + + + + + + )} + + + + + + + ); +} + +export default HomePage; diff --git a/src/pages/AddItem/AddItem.style.jsx b/src/pages/AddItem/AddItem.style.tsx similarity index 93% rename from src/pages/AddItem/AddItem.style.jsx rename to src/pages/AddItem/AddItem.style.tsx index 0fe592a5..a077cb7e 100644 --- a/src/pages/AddItem/AddItem.style.jsx +++ b/src/pages/AddItem/AddItem.style.tsx @@ -1,5 +1,6 @@ import styled from "styled-components"; - +import theme from "../../style/theme"; +// export const Background = styled.div` width: 100%; margin: 0; @@ -34,9 +35,7 @@ export const FlexDiv = styled.div` width: 100%; `; export const Title = styled.p` - font-weight: 700; - font-size: 20px; - line-height: 32px; + font: ${theme.font.H3Bold}; margin-top: 5px; margin-bottom: 5px; `; diff --git a/src/pages/AddItem/AddItem.jsx b/src/pages/AddItem/AddItem.tsx similarity index 72% rename from src/pages/AddItem/AddItem.jsx rename to src/pages/AddItem/AddItem.tsx index 257f32a9..a0c2c658 100644 --- a/src/pages/AddItem/AddItem.jsx +++ b/src/pages/AddItem/AddItem.tsx @@ -3,27 +3,34 @@ import { placeholder, button } from "../../constants/globalConstant"; import * as I from "../../components/common/Input/Input"; import * as S from "./AddItem.style"; import Tag from "../../components/Tag/Tag"; -import { useState } from "react"; +import { ChangeEvent, useState } from "react"; // -const INITIAL_DATA = { - img: "", +type ProductForm = Omit & { images: string | File }; + +const INITIAL_DATA: ProductForm = { + images: "", name: "", - content: "", + description: "", price: 0, tags: [], }; const REQUIRED_INPUT = ["name", "content", "tags", "price"]; // function AddItem() { - const [tag, setTag] = useState(""); + const [tag, setTag] = useState(""); const [formData, setFormData] = useState(INITIAL_DATA); // - const handleChange = (e) => { + const handleChange = (e: ChangeEvent) => { setFormData({ ...formData, [e.target.name]: e.target.value }); //ToDo: img 프로퍼티에는 imgInput에서 넘겨준 file 객체가 담기고 있음 }; - const CreateTag = (e) => { + const handleImgChange = (value: File | string) => { + setFormData({ ...formData, images: value }); + }; + + const CreateTag = (e: React.KeyboardEvent) => { + console.log("createTag 함수 들어옴"); if (e.key === "Enter" && tag.trim() !== "") { const notDuplicate = formData.tags.includes(tag.trim()); if (!notDuplicate) { @@ -33,17 +40,17 @@ function AddItem() { })); } setTag(""); - e.target.value = ""; + (e.target as HTMLInputElement).value = ""; } }; - const handleClickTagDelete = (tag) => { - const filterTags = formData.tags.filter((prev) => prev !== tag); + const handleClickTagDelete = (tag: string) => { + const filterTags = formData.tags.filter((prev: string) => prev !== tag); setFormData((prev) => ({ ...prev, tags: filterTags })); }; const isInputValid = REQUIRED_INPUT.every((field) => { - const value = formData[field]; + const value = formData[field]; //전역 타입 파일에 주석 단부분에서 인덱스 시그니쳐를 지우면 이부분이 오류가 납니다 ㅠㅠ switch (typeof value) { case "object": if (Array.isArray(value) && value.length === 0) { @@ -59,22 +66,25 @@ function AddItem() { return false; } }); + + const handleClickSubmit = () => {}; return ( 상품 등록하기 - + - { - const { label } = selected; - setSelectedOrder(label); - }; - - const handleLoad = async (options) => { - const { list: bestItems } = await bestProducts(options); - const { list } = await getProducts(options); - setItems(list); - setBestItems(bestItems); - }; - - const handleClickPageChange = (value) => { - setPage(Number(value)); - }; - - useEffect(() => { - handleLoad({ selectedOrder, device, page }); - }, [selectedOrder, page, device]); - - return ( - -
-

베스트 상품

- -
- -

전체 상품

- - - 상품 등록하기 - - -
- - -
- ); -} - -export default HomePage; diff --git a/src/pages/HomePage/HomePage.style.jsx b/src/pages/HomePage/HomePage.style.jsx deleted file mode 100644 index 57633dec..00000000 --- a/src/pages/HomePage/HomePage.style.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import styled from "styled-components"; - -export const Contents = styled.div` - width: ${({ device }) => - device === "desktop" ? "1200px" : device === "tablet" ? "696px" : "344px"}; - margin: 70px auto; -`; -export const InputDiv = styled.div` - display: flex; - justify-content: space-between; - width: 100%; - margin: 0 auto; - flex-wrap: wrap; - gap: 12px; -`; - -export const SearchBtn = styled.button` - height: 42px; - padding: 12px 23px; - border-radius: 8px; - color: #f3f4f6; - background-color: #3692ff; - border: none; - font-size: 16px; - font-weight: 600; - line-height: 26px; - order: ${({ device }) => (device === "mobile" ? 1 : "auto")}; -`; - -export const InputForm = styled.input` - width: ${({ device }) => - device === "desktop" ? "325px" : device === "tablet" ? "242px" : "288px"}; - height: 42px; - padding: 9px 20px 9px 16px; - border-radius: 12px; - border: none; - background-color: #f3f4f6; - color: #9ca3af; - font-size: 16px; - font-weight: 400; - line-height: 26px; - text-align: left; - order: ${({ device }) => (device === "mobile" ? 2 : "auto")}; - margin-left: ${({ device }) => - device === "desktop" ? "500px" : device === "tablet" ? "80px" : "0px"}; -`; diff --git a/src/pages/ItemsPage/ItemsList.js b/src/pages/ItemsPage/ItemsList.js deleted file mode 100644 index 65a37d41..00000000 --- a/src/pages/ItemsPage/ItemsList.js +++ /dev/null @@ -1,145 +0,0 @@ -import { useNavigate } from "react-router-dom"; -import styled from "styled-components"; -import useWindowSize from "../../hooks/useWindowSize"; -// -import BtnHeart from "../../components/common/BtnHeart/BtnHeart"; - -const ByDevice = { - best: { - mobile: { - gap: "none", - gridTemplate: "repeat(1, 343px)", - gridRow: "434px", - height: "343px", - ulWidth: "343px", - }, - tablet: { - gap: "10px", - gridTemplate: "repeat(3, 343px)", - gridRow: "434px", - height: "343px", - ulWidth: "696px", - }, - desktop: { - gap: "24px", - gridTemplate: "repeat(4, 282px)", - gridRow: "378px", - height: "282px", - ulWidth: "1200px", - }, - }, - products: { - mobile: { - gap: "32px 8px", - gridTemplate: "repeat(2, 168px)", - gridRow: "343px", - height: "168px", - ulWidth: "343px", - }, - tablet: { - gap: "40px 24px", - gridTemplate: "repeat(3, 221px)", - height: "221px", - ulWidth: "696px", - }, - desktop: { - gap: "40px 24px", - gridTemplate: "repeat(5, 221px)", - height: "221px", - ulWidth: "1200px", - }, - }, -}; -const Title = styled.h4` - font-size: 14px; - font-weight: 500; - line-height: 24px; - text-align: left; - color: #1f2937; - margin: 0px; -`; -const Price = styled.div` - font-size: 16px; - font-weight: 700; - line-height: 26px; - text-align: left; - color: #1f2937; - margin-top: 6px; - margin-bottom: 6px; -`; - -const FavoriteCount = styled.span` - color: #4b5563; - font-size: 12px; - font-weight: 500; - line-height: 18px; - text-align: left; -`; - -const ProductImg = styled.img` - width: 100%; - height: ${({ device, value }) => ByDevice[value][device].height || "auto"}; -`; - -const ItemListStyle = styled.ul` - margin: 24px auto; - padding: 0px; - width: ${({ device, value }) => ByDevice[value][device].ulWidth}; - display: grid; - list-style: none; - gap: ${({ device, value }) => ByDevice[value][device].gap}; - grid-template-columns: ${({ device, value }) => - ByDevice[value][device].gridTemplate}; - grid-auto-rows: ${({ value, device }) => ByDevice[value][device].gridRow}; -`; - -const Item = styled.div` - display: flex; - height: 317px; - flex-direction: column; - gap: 16px; -`; - -// - -function ListItem({ value, items }) { - const navigate = useNavigate(); - const device = useWindowSize(); - return ( - navigate(`./${items.id}`)}> - - -
- {items.name} - {items.price} 원 -
- -
-
-
- ); -} -function ItemsList({ value, items, device }) { - return ( -
-
- - {items.map((item) => { - return ( -
  • - -
  • - ); - })} -
    -
    -
    - ); -} - -export default ItemsList; diff --git a/src/pages/ProductPage/Comments.style.jsx b/src/pages/ProductPage/Comments.style.tsx similarity index 100% rename from src/pages/ProductPage/Comments.style.jsx rename to src/pages/ProductPage/Comments.style.tsx diff --git a/src/pages/ProductPage/Comments.jsx b/src/pages/ProductPage/Comments.tsx similarity index 75% rename from src/pages/ProductPage/Comments.jsx rename to src/pages/ProductPage/Comments.tsx index 49b80356..311cc89f 100644 --- a/src/pages/ProductPage/Comments.jsx +++ b/src/pages/ProductPage/Comments.tsx @@ -2,8 +2,8 @@ import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; // import * as S from "./Comments.style"; -import { placeholder } from "../../constants/globalConstant"; -import { button } from "../../constants/globalConstant"; +import { placeholder } from "../../constants/globalConstant.jsx"; +import { button } from "../../constants/globalConstant.jsx"; import noComment from "../../assets/no-comments.svg"; import returnIcon from "../../assets/icons/return.icon.svg"; // @@ -13,14 +13,14 @@ import Button from "../../components/common/Button/Button"; import CommentCard from "../../components/CommentCard/CommentCard"; // -export default function Comments({ productId }) { +export default function Comments({ productId }: Params) { const [isDisabled, setIsDisabled] = useState(true); - const [comments, setComments] = useState({}); - const [formData, setFormData] = useState(""); + const [comments, setComments] = useState([]); + // const [formData, setFormData] = useState(""); const navigate = useNavigate(); const handleLoad = async () => { - const data = await getProductComments(productId); - setComments(data); + const data = await getProductComments({ productId }); + setComments(data ?? []); }; const handleChange = () => { setIsDisabled(false); @@ -33,6 +33,7 @@ export default function Comments({ productId }) { - {comments?.list?.length === 0 ? ( + {comments?.length === 0 ? ( @@ -53,12 +54,12 @@ export default function Comments({ productId }) { ) : ( - {comments?.list?.map((data) => ( + {comments?.map((data) => ( ))} )} - diff --git a/src/pages/ProductPage/Product.style.jsx b/src/pages/ProductPage/Product.style.tsx similarity index 100% rename from src/pages/ProductPage/Product.style.jsx rename to src/pages/ProductPage/Product.style.tsx diff --git a/src/pages/ProductPage/Product.jsx b/src/pages/ProductPage/Product.tsx similarity index 61% rename from src/pages/ProductPage/Product.jsx rename to src/pages/ProductPage/Product.tsx index a13ff4ab..6c73be81 100644 --- a/src/pages/ProductPage/Product.jsx +++ b/src/pages/ProductPage/Product.tsx @@ -1,13 +1,19 @@ -import { useEffect, useState } from "react"; -import { useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; // import ProductInfo from "./ProductInfo"; import Comments from "./Comments"; import * as S from "./Product.style"; // + export default function Product() { - const { productId } = useParams(); + const navigate = useNavigate(); + const { productId } = useParams>(); + if (!productId) { + navigate("/items"); + alert("해당 상품을 찾을 수 없습니다."); + return; + } return ( <> diff --git a/src/pages/ProductPage/ProductInfo.style.jsx b/src/pages/ProductPage/ProductInfo.style.tsx similarity index 97% rename from src/pages/ProductPage/ProductInfo.style.jsx rename to src/pages/ProductPage/ProductInfo.style.tsx index 671e6105..5d252c4c 100644 --- a/src/pages/ProductPage/ProductInfo.style.jsx +++ b/src/pages/ProductPage/ProductInfo.style.tsx @@ -1,6 +1,9 @@ import styled from "styled-components"; import theme from "../../style/theme"; // +export interface Props { + src: string; +} export const ProductInfoWrapper = styled.div` display: flex; gap: 24px; @@ -22,7 +25,7 @@ export const ProductInfoWrapper = styled.div` } `; -export const ProductImg = styled.img` +export const ProductImg = styled.img` width: 100%; height: 100%; border-radius: 16px; diff --git a/src/pages/ProductPage/ProductInfo.jsx b/src/pages/ProductPage/ProductInfo.tsx similarity index 61% rename from src/pages/ProductPage/ProductInfo.jsx rename to src/pages/ProductPage/ProductInfo.tsx index f3089b26..ec93516b 100644 --- a/src/pages/ProductPage/ProductInfo.jsx +++ b/src/pages/ProductPage/ProductInfo.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; // import profile from "../../assets/icons/default.profile.icon.svg"; // -import { getProductInfo } from "../../api/product.api"; +import { getProductInfo, ResponseData } from "../../api/product.api"; import * as S from "./ProductInfo.style"; import Tag from "../../components/Tag/Tag"; import BtnHeart from "../../components/common/BtnHeart/BtnHeart"; @@ -10,13 +10,14 @@ import { EditSelect } from "../../components/common/Select/Select"; import { useFormatDate, useFormatPrice } from "../../hooks/useFormatting"; // -export default function ProductInfo({ productId }) { - const [product, setProduct] = useState({}); - const formattedDate = useFormatDate(product.createdAt); - const formattedPrice = useFormatPrice(product.price, "KRW"); +export default function ProductInfo({ productId }: Params) { + const [product, setProduct] = useState(); + const formattedDate = useFormatDate(product?.createdAt || ""); + const formattedPrice = useFormatPrice(product?.price || 0, "KRW"); + // const handleLoad = async () => { - const info = await getProductInfo(productId); + const info = await getProductInfo({ productId }); setProduct(info); }; @@ -28,7 +29,7 @@ export default function ProductInfo({ productId }) { {product?.images && product?.images?.length > 0 ? ( - + ) : ( 이미지가 없습니다. @@ -37,19 +38,23 @@ export default function ProductInfo({ productId }) { - {product.name} - {formattedPrice}원 + + {product?.name ?? "상품 이름 없음"} + + {formattedPrice || 0}원
    상품소개 - {product.description} + + {product?.description ?? "상품 설명 없음"} + 상품 태그 - +
    @@ -57,12 +62,12 @@ export default function ProductInfo({ productId }) {
    - {product.ownerNickname} - {formattedDate} + {product?.ownerNickname ?? "닉네임"} + {formattedDate || "날짜 정보 없음"}
    - + diff --git a/src/style/globalStyle.js b/src/style/globalStyle.js new file mode 100644 index 00000000..65ce3cd6 --- /dev/null +++ b/src/style/globalStyle.js @@ -0,0 +1,52 @@ +import { createGlobalStyle } from "styled-components"; + +const GlobalStyle = createGlobalStyle` + * { + box-sizing: border-box; + margin: 0px; + padding: 0px; + } + body { + font-family: 'Pretendard', sans-serif; + font-display: swap; + margin: 0; + padding: 0; + } + html { + margin: 0; + padding: 0; + } + + a { + text-decoration: none; + color: #ffffff; + } + p{ + margin: 0px; + } + button { + padding: 0px + } + ul{ + list-style: none; + + } + @font-face { + font-family: 'Pretendard'; + src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff2') format('woff2'); + src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') format('woff'); + font-display: swap; + font-weight: 400; + font-style: normal; +} + +@font-face { +font-family: "Pretendard"; +src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Bold.woff2') format('woff2'); +src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Bold.woff') format('woff'); + font-display: swap; + font-weight: 600; + font-style: normal; +}`; + +export default GlobalStyle; diff --git a/src/style/theme.jsx b/src/style/theme.jsx index b9d2a023..81e2cd49 100644 --- a/src/style/theme.jsx +++ b/src/style/theme.jsx @@ -18,10 +18,10 @@ const theme = { font: { //weight , size , height , fontfamily H1: "600 40px/47px 'Pretendard', sans-serif", - H2Bold: "600 24px/32px 'Pretendard', sans-serif", + H2Bold: "600 24px/36px 'Pretendard', sans-serif", H2Regular: "400 24px/36px 'Pretendard', sans-serif", - H3Bold: "600 20px/30px 'Pretendard', sans-serif", - H3Regular: "400 20px/30px 'Pretendard', sans-serif", + H3Bold: "600 20px/32px 'Pretendard', sans-serif", + H3Regular: "400 20px/32px 'Pretendard', sans-serif", H4Bold: "600 18px/28px 'Pretendard', sans-serif", H4Regular: "400 18px/28px 'Pretendard', sans-serif", H5Bold: "600 16px/26px 'Pretendard', sans-serif", diff --git a/src/types/declaration.d.ts b/src/types/declaration.d.ts new file mode 100644 index 00000000..5e52f805 --- /dev/null +++ b/src/types/declaration.d.ts @@ -0,0 +1,4 @@ +declare module "*.svg" { + const content: string; + export default content; +} diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 00000000..bfd0b615 --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,37 @@ +export {}; + +declare global { + type Tag = string; + + interface Params { + productId: string; + } + interface Device { + device: string; + } + + interface Product { + name: string; + price: number; + tags: Tag[]; + images: [string]; + description: string; + [key: string]: any; + //원래 any 부분에 string| boolean| [string]|number 를 적었었는데, + //유니온 타입에 이터러블이 아닌 값이 섞여있어서 includes랑 스프레드가 안되더라고요... ㅠㅠ + //인덱스 시그니쳐를 지우면 addItem 에서 에러가 나서 방법을 찾아보다가 도저히 모르겠어서 any 로 지정했습니다! (addItem에 에러 부분 주석 남겨놨습니다!) + } + interface Writer { + image: string; + nickname: string; + id: number; + } + interface Comment { + writer: Writer; + updatedAt: string; + createdAt: string; + content: string; + id: number; + } + type onClick = (e: MouseEvent) => void; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..f8019780 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,114 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "jsx": "react-jsx" /* Specify what JSX code is generated. */, + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs" /* Specify what module code is generated. */, + "rootDir": "./src" /* Specify the root folder within your source files. */, + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + "baseUrl": "./" /* Specify the base directory to resolve non-relative module names. */, + "paths": { + "@components/*": ["src/components/*"], + "@utils/*": ["src/utils/*"] + } /* Specify a set of entries that re-map imports to additional lookup locations. */, + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + "resolveJsonModule": true /* Enable importing .json files. */, + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "sourceMap": true /* Create source map files for emitted JavaScript files. */, + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist" /* Specify an output folder for all emitted files. */, + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */, + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}