diff --git a/api/itemApi.ts b/api/itemApi.ts new file mode 100644 index 00000000..4b327208 --- /dev/null +++ b/api/itemApi.ts @@ -0,0 +1,78 @@ +import { ProductListFetcherParams } from '@/types/productTypes'; + +export async function getProducts({ + orderBy, + pageSize, + page = 1, +}: ProductListFetcherParams) { + const params = new URLSearchParams({ + orderBy, + pageSize: String(pageSize), + page: String(page), + }); + + try { + const response = await fetch( + `http://panda-market-api.vercel.app/products?${params}` + ); + if (!response.ok) { + throw new Error(`HTTP error: ${response.status}`); + } + + const body = await response.json(); + return body; + } catch (error) { + console.error('Failed to fetch products:', error); + throw error; + } +} + +export async function getProductDetail(productId: number) { + if (!productId) { + throw new Error('상품 아이디가 유효가지 않습니다'); + } + + try { + const response = await fetch( + `https://panda-market-api.vercel.app/products/${productId}` + ); + if (!response.ok) { + throw new Error(`HTTP error: ${response.status}`); + } + + const body = await response.json(); + return body; + } catch (error) { + console.error('Failed to fetch product detail:', error); + throw error; + } +} + +export async function getProductComments({ + productId, + limit = 10, +}: { + productId: number; + limit?: number; +}) { + const params = new URLSearchParams({ + limit: String(limit), + productId: String(productId), + }); + + try { + const query = new URLSearchParams(params).toString(); + const response = await fetch( + `https://panda-market-api.vercel.app/products/${productId}/comments?${query}` + ); + if (!response.ok) { + throw new Error(`HTTP error: ${response.status}`); + } + + const body = await response.json(); + return body; + } catch (error) { + console.error('Failed to fetch product comments:', error); + throw error; + } +} diff --git a/components/boards/BestArticlesSection.tsx b/components/boards/BestArticlesSection.tsx index d6c7ebd7..34db731a 100644 --- a/components/boards/BestArticlesSection.tsx +++ b/components/boards/BestArticlesSection.tsx @@ -49,7 +49,8 @@ const BestArticleCard = ({ article }: { article: Article }) => { return ( - + {/* */} + 베스트 게시글 Best diff --git a/components/items/itemPage/CommentThread.tsx b/components/items/itemPage/CommentThread.tsx index 5457fe76..8775159d 100644 --- a/components/items/itemPage/CommentThread.tsx +++ b/components/items/itemPage/CommentThread.tsx @@ -10,6 +10,7 @@ import { ProductCommentListResponse, } from "@/types/commentTypes"; import EmptyState from "@/components/ui/EmptyState"; +import Image from "next/image"; const CommentContainer = styled.div` padding: 24px 0; @@ -64,7 +65,8 @@ const CommentItem: React.FC = ({ item }) => { {/* 참고: 더보기 버튼 기능은 추후 요구사항에 따라 추가 예정 */} - + {/* */} + 더보기 {item.content} diff --git a/components/items/itemPage/ItemProfileSection.tsx b/components/items/itemPage/ItemProfileSection.tsx index cbfc8732..1815b2b5 100644 --- a/components/items/itemPage/ItemProfileSection.tsx +++ b/components/items/itemPage/ItemProfileSection.tsx @@ -126,7 +126,8 @@ const ItemProfileSection: React.FC = ({ product }) => { {/* 참고: 더보기 버튼 기능은 추후 요구사항에 따라 추가 예정 */} - + {/* */} + 더보기
diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx index 17feebbc..f50533aa 100644 --- a/components/layout/Header.tsx +++ b/components/layout/Header.tsx @@ -1,8 +1,11 @@ -import Logo from "@/public/images/logo/logo.svg"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import styled from "styled-components"; -import { StyledLink } from "@/styles/CommonStyles"; +import Logo from '@/public/images/logo/logo.svg'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import styled from 'styled-components'; +import { StyledLink } from '@/styles/CommonStyles'; +import Image from 'next/image'; +import { useEffect, useState } from 'react'; +import DefaultProfile from '@/public/images/ui/ic_profile.svg'; const GlobalHeader = styled.header` display: flex; @@ -15,8 +18,7 @@ const HeaderLeft = styled.div` align-items: center; `; -// Next.js에서는 next/link의 Link 컴포넌트를 사용해 주세요. -// 참고: Next.js 버전 13부터는 Link 자체가 anchor 태그의 역할을 해요. Link 요소 내에 태그가 중첩되어 있으면 hydration 오류가 발생하니 주의해 주세요. + const HeaderLogo = styled(Link)` margin-right: 16px; @@ -52,31 +54,38 @@ const NavItem = styled.li` const LoginLink = styled(StyledLink)``; function getLinkStyle(isActive: boolean) { - return { color: isActive ? "var(--blue)" : undefined }; + return { color: isActive ? 'var(--blue)' : undefined }; } const Header: React.FC = () => { const { pathname } = useRouter(); + const [isLoggedIn, setIsLoggedIn] = useState(false); + + useEffect(() => { + // 컴포넌트가 마운트될 때 sessionStorage에서 토큰을 확인 + const token = sessionStorage.getItem('token'); + setIsLoggedIn(!!token); // 토큰이 있으면 true, 없으면 false 설정 + }, []); return ( - - + + leftBtn - - 로그인 + {isLoggedIn ? ( + // 로그인이 되어 있으면 판다 이미지 렌더링 + 판다마켓 기본 프로필 + ) : ( + 로그인 + )} ); }; diff --git a/components/ui/DropdownMenu.tsx b/components/ui/DropdownMenu.tsx index 0070736c..589fa3fa 100644 --- a/components/ui/DropdownMenu.tsx +++ b/components/ui/DropdownMenu.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import styled from "styled-components"; import SortIcon from "@/public/images/icons/ic_sort.svg"; +import Image from "next/image"; const SortButtonWrapper = styled.div` position: relative; @@ -54,7 +55,8 @@ const DropdownMenu: React.FC = ({ return ( - + {/* */} + 정렬 {isDropdownVisible && ( diff --git a/components/ui/Icon.tsx b/components/ui/Icon.tsx index 578ffb0c..9c081aec 100644 --- a/components/ui/Icon.tsx +++ b/components/ui/Icon.tsx @@ -1,4 +1,5 @@ -import styled from "styled-components"; +import Image from 'next/image'; +import styled from 'styled-components'; // SVG 이미지 형식을 이용하는 큰 장점 중 하나는 비슷한 아이콘을 색상별로 저장할 필요 없이 하나의 파일로 유연하게 변경할 수 있다는 거예요. // SVG 파일 내에 정의된 크기 또는 선 및 배경색을 동적으로 변경할 수 있는 컴포넌트를 만들어 볼게요. @@ -15,21 +16,21 @@ const IconWrapper = styled.div` justify-content: center; svg { - fill: ${({ $fillColor }) => $fillColor || "current"}; // 색 채움 - width: ${({ $size }) => ($size ? `${$size}px` : "auto")}; - height: ${({ $size }) => ($size ? `${$size}px` : "auto")}; + fill: ${({ $fillColor }) => $fillColor || 'current'}; // 색 채움 + width: ${({ $size }) => ($size ? `${$size}px` : 'auto')}; + height: ${({ $size }) => ($size ? `${$size}px` : 'auto')}; } /* 선(stroke)의 색상 변경은 svg 내의 path 요소에 넣어줘야 적용돼요 */ /* - 채움색이 있을 때는 아웃라인 색상도 함께 바꿔주는 것이 일반적이기 때문에 선에 대한 속성이지만 fillColor를 outlineColor보다 우선적으로 적용하도록 했어요 */ svg path { stroke: ${({ $fillColor, $outlineColor }) => - $fillColor || $outlineColor || "currentColor"}; + $fillColor || $outlineColor || 'currentColor'}; } `; interface IconProps { - iconComponent: React.FunctionComponent>; + iconComponent: string; size?: number; fillColor?: string; outlineColor?: string; @@ -40,11 +41,12 @@ const Icon: React.FC = ({ // - 통일성을 위해 prop 이름에는 camelCase를 사용했지만, SVG를 ReactComponent 형태로 전달하고 있기 때문에 PascalCase으로 바꿔 사용 iconComponent: IconComponent, size, // Optional props이며, prop이 생략될 경우 SVG 파일의 기본 크기를 사용함 - fillColor = "currentColor", - outlineColor = "currentColor", + fillColor = 'currentColor', + outlineColor = 'currentColor', }) => ( - + {/* */} + 더보기 ); diff --git a/components/ui/LikeCountDisplay.tsx b/components/ui/LikeCountDisplay.tsx index 06a37e9e..ca1c699c 100644 --- a/components/ui/LikeCountDisplay.tsx +++ b/components/ui/LikeCountDisplay.tsx @@ -2,6 +2,7 @@ import React from "react"; import styled from "styled-components"; import HeartIcon from "@/public/images/icons/ic_heart.svg"; import { FlexRowCentered } from "@/styles/CommonStyles"; +import Image from "next/image"; const LikeCountWrapper = styled(FlexRowCentered)<{ $fontSize: number; @@ -37,7 +38,8 @@ const LikeCountDisplay: React.FC = ({ return ( - + {/* */} + 좋아요 아이콘 {displayCount} ); diff --git a/components/ui/PaginationBar.tsx b/components/ui/PaginationBar.tsx index 92255f6c..30952d01 100644 --- a/components/ui/PaginationBar.tsx +++ b/components/ui/PaginationBar.tsx @@ -1,6 +1,7 @@ import styled from "styled-components"; import LeftArrow from "@/public/images/icons/arrow_left.svg"; import RightArrow from "@/public/images/icons/arrow_right.svg"; +import Image from "next/image"; const PaginationBarContainer = styled.div` display: flex; @@ -59,7 +60,8 @@ const PaginationBar: React.FC = ({ onClick={() => onPageChange(activePageNum - 1)} aria-label="이전 페이지로 이동 버튼" > - + {/* */} + 이전 페이지로 이동 버튼 이미지 {pages.map((page) => ( = ({ onClick={() => onPageChange(activePageNum + 1)} aria-label="다음 페이지로 이동 버튼" > - + {/* */} + 다음페이지로 이동버튼 이미지 ); diff --git a/components/ui/SearchBar.tsx b/components/ui/SearchBar.tsx index 16f75d04..d5a14f0a 100644 --- a/components/ui/SearchBar.tsx +++ b/components/ui/SearchBar.tsx @@ -3,6 +3,7 @@ import { FlexRowCentered } from "@/styles/CommonStyles"; import SearchIcon from "@/public/images/icons/ic_search.svg"; import { useEffect, useState } from "react"; import { useRouter } from "next/router"; +import Image from "next/image"; const Container = styled(FlexRowCentered)` background-color: var(--gray-100); @@ -57,7 +58,8 @@ const SearchBar: React.FC = ({ return ( - + {/* */} + 검색 >; - - // 타입스크립트는 SVG 모듈의 ReactComponent를 default export로 처리하지 않기 때문에, ReactComponent와 SVG 파일의 경로(src)를 함께 export 해주어야 합니다. - const src: string; - export default src; -} - declare module "*.png" { const value: string; export default value; diff --git a/hooks/useImageUpload.ts b/hooks/useImageUpload.ts new file mode 100644 index 00000000..74edb52a --- /dev/null +++ b/hooks/useImageUpload.ts @@ -0,0 +1,57 @@ +import axios from 'axios'; +import { ChangeEvent, useState } from 'react'; + +const useImageUpload = () => { + const [previewImageSrc, setPreviewImageSrc] = useState(''); + const [imageFile, setImageFile] = useState(null); + + const encodeFileToBase64 = (fileBlob: File) => { + const reader = new FileReader(); + + reader.readAsDataURL(fileBlob); + + return new Promise((resolve) => { + reader.onload = () => { + if (typeof reader.result === 'string') { + setPreviewImageSrc(reader.result); + } + + resolve(reader.result); + }; + }); + }; + + const handleImagePreview = (e: ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + const selectedFile = e.target.files[0]; + setImageFile(selectedFile); + encodeFileToBase64(selectedFile); + } + }; + + const uploadImage = async (file: File): Promise => { + try { + const formData = new FormData(); + formData.append('image', file); + + const res = await axios.post( + 'https://panda-market-api.vercel.app/images/upload', + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + Authorization: `Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`, + }, + } + ); + + return res.data.url; + } catch (error) { + console.error('이미지 업로드에 실패했습니다......', error); + return null; + } + }; + return { previewImageSrc, handleImagePreview, uploadImage, imageFile }; +}; + +export default useImageUpload; \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index ff835eb9..2f7099ff 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -10,15 +10,6 @@ const nextConfig = { images: { domains: ["sprint-fe-project.s3.ap-northeast-2.amazonaws.com"], }, - - // SVG 이미지를 리액트 컴포넌트로 변환해주는 라이브러리 설정 추가 - webpack(config) { - config.module.rules.push({ - test: /\.svg$/, - use: ["@svgr/webpack"], - }); - return config; - }, }; export default nextConfig; diff --git a/package.json b/package.json index c44b55cd..4ec390e4 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,13 @@ "lint": "next lint" }, "dependencies": { + "@emotion/is-prop-valid": "^1.3.1", "@types/styled-components": "^5.1.34", "axios": "^1.7.7", "date-fns": "^3.6.0", "next": "14.2.3", - "react": "^18", - "react-dom": "^18", + "react": "18.2.0", + "react-dom": "18.2.0", "react-spinners": "^0.13.8", "styled-components": "^6.1.11" }, diff --git a/pages/addboard/index.tsx b/pages/addboard/index.tsx index 8f895451..4231f2a8 100644 --- a/pages/addboard/index.tsx +++ b/pages/addboard/index.tsx @@ -1,64 +1,20 @@ -import IconPlus from '@/public/images/icons/ic_plus.svg'; -import { Container, SectionHeader, SectionTitle } from '@/styles/CommonStyles'; import axios, { HttpStatusCode } from 'axios'; import Image from 'next/image'; import { useRouter } from 'next/router'; -import { ChangeEvent, MouseEvent, useState } from 'react'; +import { MouseEvent, useState } from 'react'; import styled from 'styled-components'; +import useImageUpload from '@/hooks/useImageUpload'; +import IconPlus from '@/public/images/icons/ic_plus.svg'; +import { Container, SectionHeader, SectionTitle } from '@/styles/CommonStyles'; + const AddBoardPage = () => { const router = useRouter(); const [title, setTitle] = useState(''); const [content, setContent] = useState(''); - const [previewImageSrc, setPreviewImageSrc] = useState(''); - const [imageFile, setImageFile] = useState(null); - - const encodeFileToBase64 = (fileBlob: File) => { - const reader = new FileReader(); - - reader.readAsDataURL(fileBlob); - - return new Promise((resolve) => { - reader.onload = () => { - if (typeof reader.result === 'string') { - setPreviewImageSrc(reader.result); - } - resolve(reader.result); - }; - }); - }; - - const uploadImage = async (file: File): Promise => { - try { - const formData = new FormData(); - formData.append('image', file); - - const res = await axios.post( - 'https://panda-market-api.vercel.app/images/upload', - formData, - { - headers: { - 'Content-Type': 'multipart/form-data', - Authorization: `Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`, - }, - } - ); - - return res.data.url; - } catch (error) { - console.error('이미지 업로드에 실패했습니다......', error); - return null; - } - }; - - const handleImagePreview = (e: ChangeEvent) => { - if (e.target.files && e.target.files[0]) { - const selectedFile = e.target.files[0]; - setImageFile(selectedFile); - encodeFileToBase64(selectedFile); - } - }; + const { previewImageSrc, handleImagePreview, uploadImage, imageFile } = + useImageUpload(); const formSubmit = async (e: MouseEvent) => { e.preventDefault(); @@ -131,11 +87,11 @@ const AddBoardPage = () => { /> 이미지 - + {!previewImageSrc ? ( <> - + icon-plus 이미지 등록 ) : ( @@ -224,7 +180,7 @@ const StyledBox = styled.div` } `; -const StyledFileUpload = styled.label<{ hasImage: boolean }>` +const StyledFileUpload = styled.label<{ $hasImage: boolean }>` > input[type='file'] { display: none; } @@ -241,8 +197,9 @@ const StyledFileUpload = styled.label<{ hasImage: boolean }>` align-items: center; justify-content: center; flex-direction: column; - border: ${({ hasImage, theme }) => - hasImage ? `1px solid ${theme.colors.gray[200]}` : 'none'}; + border: 1px solid + ${({ $hasImage, theme }) => + $hasImage ? theme.colors.gray[200] : 'transparent'}; `; export default AddBoardPage; diff --git a/pages/items/[id].tsx b/pages/items/[id].tsx index 4ca6af95..f07d4298 100644 --- a/pages/items/[id].tsx +++ b/pages/items/[id].tsx @@ -8,6 +8,7 @@ import BackIcon from "@/public/images/icons/ic_back.svg"; import LoadingSpinner from "@/components/ui/LoadingSpinner"; import { Product } from "@/types/productTypes"; import { useRouter } from "next/router"; +import Image from "next/image"; const BackToMarketPageLink = styled(StyledLink)` display: flex; @@ -80,7 +81,8 @@ const ItemPage: React.FC = () => { 목록으로 돌아가기 - + {/* */} + 목록으로 돌아가기 diff --git a/pages/login/index.tsx b/pages/login/index.tsx new file mode 100644 index 00000000..dae6e113 --- /dev/null +++ b/pages/login/index.tsx @@ -0,0 +1,98 @@ +import axios from 'axios'; +import Image from 'next/image'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { useEffect, useState } from 'react'; + +import IconEyeInVisible from '@/public/images/icons/eye-invisible.svg'; +import IconEyeVisible from '@/public/images/icons/eye-visible.svg'; +import Logo from '@/public/images/logo/logo.svg'; + +function LoginPage(): React.ReactElement { + const router = useRouter(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [loginCheck, setLoginCheck] = useState(false); // 로그인 상태 체크 + + useEffect(() => { + const accessToken = sessionStorage.getItem('token'); + if (accessToken) { + router.push('/'); + } + }, [router]); + + const handleLogin = async (e: React.MouseEvent) => { + e.preventDefault(); + + try { + const res = await axios.post( + 'https://panda-market-api.vercel.app/auth/signIn', + { + email, + password, + } + ); + const result = await res.data; + + if (res.status === 200) { + alert('로그인 성공!'); + setLoginCheck(true); + // 여기서 토큰을 저장합니다. + sessionStorage.setItem('token', result.accessToken); + + router.push('/'); + } else { + setLoginCheck(false); + } + } catch (err) { + console.error('로그인 실패: ' + err); + } + }; + + return ( +
+

+ + 판다마켓 로고 + +

+
+ + setEmail(e.target.value)} + value={email} + type='email' + id='email' + placeholder='이메일을 입력해주세요' + required + /> + + setPassword(e.target.value)} + value={password} + type='password' + name='password' + id='password' + placeholder='비밀번호를 입력해주세요' + required + /> + + +
+
+ ); +} + +export default LoginPage; diff --git a/pages/signup/index.tsx b/pages/signup/index.tsx new file mode 100644 index 00000000..c49e9baf --- /dev/null +++ b/pages/signup/index.tsx @@ -0,0 +1,7 @@ +function SignUpPage(): React.ReactElement { + return ( + <>SignUp 페이지 + ) +} + +export default SignUpPage \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d8213efa..5b4713f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -896,11 +896,23 @@ dependencies: "@emotion/memoize" "^0.8.1" +"@emotion/is-prop-valid@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz#8d5cf1132f836d7adbe42cf0b49df7816fc88240" + integrity sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw== + dependencies: + "@emotion/memoize" "^0.9.0" + "@emotion/memoize@^0.8.1": version "0.8.1" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== + "@emotion/unitless@0.8.1": version "0.8.1" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" @@ -1237,9 +1249,9 @@ integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/node@^20": - version "20.17.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.4.tgz#98bca755e4551b2a8e7327242a048e5a8eacb719" - integrity sha512-Fi1Bj8qTJr4f1FDdHFR7oMlOawEYSzkHNdBJK+aRjcDDNHwEV3jPPjuZP2Lh2QNgXeqzM8Y+U6b6urKAog2rZw== + version "20.17.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.6.tgz#6e4073230c180d3579e8c60141f99efdf5df0081" + integrity sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ== dependencies: undici-types "~6.19.2" @@ -1621,9 +1633,9 @@ camelize@^1.0.0: integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001669: - version "1.0.30001676" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001676.tgz#fe133d41fe74af8f7cc93b8a714c3e86a86e6f04" - integrity sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw== + version "1.0.30001678" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001678.tgz#b930b04cd0b295136405634aa32ad540d7eeb71e" + integrity sha512-RR+4U/05gNtps58PEBDZcPWTgEO2MBeoPZ96aQcjmfkBWRIDfN451fW2qyDA9/+HohLLIL5GqiMwA+IB1pWarw== chalk@^4.0.0: version "4.1.2" @@ -1690,9 +1702,9 @@ cosmiconfig@^8.1.3: path-type "^4.0.0" cross-spawn@^7.0.0, cross-spawn@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.4" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.4.tgz#36d9cb36c32ae7a0df935f0191f79959962a2165" + integrity sha512-9KdyVPPtLHjPAD7tcuzSFs64UfHlLJt7U6qP4/bFVLyjLceyizj6s6jO6YBaV5d0G7g/9KnY/dOpLR4Rcg8YDg== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" @@ -1905,9 +1917,9 @@ eastasianwidth@^0.2.0: integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== electron-to-chromium@^1.5.41: - version "1.5.49" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.49.tgz#9358f514ab6eeed809a8689f4b39ea5114ae729c" - integrity sha512-ZXfs1Of8fDb6z7WEYZjXpgIRF6MEu8JdeGA0A40aZq6OQbS+eJpnnV49epZRna2DU/YsEjSQuGtQPPtvt6J65A== + version "1.5.52" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.52.tgz#2bed832c95a56a195504f918150e548474687da8" + integrity sha512-xtoijJTZ+qeucLBDNztDOuQBE1ksqjvNjvqFoST3nGC7fSpqJ+X6BdTBaY5BHG+IhWWmpc6b/KfpeuEDupEPOQ== emoji-regex@^8.0.0: version "8.0.0" @@ -2004,9 +2016,9 @@ es-errors@^1.2.1, es-errors@^1.3.0: integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-iterator-helpers@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.1.0.tgz#f6d745d342aea214fe09497e7152170dc333a7a6" - integrity sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw== + version "1.2.0" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz#2f1a3ab998b30cb2d10b195b587c6d9ebdebf152" + integrity sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q== dependencies: call-bind "^1.0.7" define-properties "^1.2.1" @@ -2016,6 +2028,7 @@ es-iterator-helpers@^1.1.0: function-bind "^1.1.2" get-intrinsic "^1.2.4" globalthis "^1.0.4" + gopd "^1.0.1" has-property-descriptors "^1.0.2" has-proto "^1.0.3" has-symbols "^1.0.3" @@ -3296,13 +3309,13 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -react-dom@^18: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" - integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== +react-dom@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== dependencies: loose-envify "^1.1.0" - scheduler "^0.23.2" + scheduler "^0.23.0" react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" @@ -3314,10 +3327,10 @@ react-spinners@^0.13.8: resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.13.8.tgz#5262571be0f745d86bbd49a1e6b49f9f9cb19acc" integrity sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA== -react@^18: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" - integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== +react@18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" @@ -3458,7 +3471,7 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" -scheduler@^0.23.2: +scheduler@^0.23.0: version "0.23.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== @@ -3768,9 +3781,9 @@ tslib@2.6.2: integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== tslib@^2.0.3, tslib@^2.4.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b" - integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA== + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== type-check@^0.4.0, type-check@~0.4.0: version "0.4.0"