diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..95edec7b --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,11 @@ +{ + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:prettier/recommended" + ], + "plugins": [ + "react", + "prettier" + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..47591372 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +.env + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Local Netlify folder +.netlify \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..33a8b8de --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": true, + "singleQuote": false, + "endOfLine": "lf", + "singleAttributePerLine": true, + "bracketSameLine": true, + "trailingComma": "none", + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/README.md b/README.md index c81804bf..61414e05 100644 --- a/README.md +++ b/README.md @@ -1,1319 +1,195 @@ -# 🀝 거래 API ν™œμš©, νŒ€ ν”„λ‘œμ νŠΈ +# 🏠 집 가ꡬ싢어 (SweetHome) -주어진 APIλ₯Ό 뢄석해 μ–΄λ–€ ν”„λ‘œμ νŠΈλ₯Ό 진행/μ™„μ„±ν•  것인지 νŒ€ λ‹¨μœ„λ‘œ 자유둭게 κ²°μ •ν•˜κ³  λ§Œλ“€μ–΄λ³΄μ„Έμš”. -TypeScriptλ₯Ό ν•„μˆ˜λ‘œ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€. -과제 μˆ˜ν–‰ 및 리뷰 기간은 별도 곡지λ₯Ό μ°Έκ³ ν•˜μ„Έμš”! +

React, TypeScript, Rest APIλ₯Ό ν™œμš©ν•œ 라이프 μŠ€νƒ€μΌ ν™ˆ λ°μ½”Β·μΈν…Œλ¦¬μ–΄ μ‡Όν•‘λͺ° ν”„λ‘œμ νŠΈμž…λ‹ˆλ‹€.
+성별과 연령에 상관없이 λͺ¨λ‘κ°€ 관심을 가지고 μ΄μš©ν•  수 μžˆλŠ” μΈν…Œλ¦¬μ–΄ κ΄€λ ¨ 주제둜 μ„ μ •ν•˜μ˜€μœΌλ©°,
+β€˜μ§‘(Home)’ + β€˜κ°€κ΅¬(Furniture)’ + β€˜κ°€κ³ μ‹Άλ‹€.(Want To Go)’ 의 ν•©μ„±μ–΄λ‘œ μ–Έμ œ μ–΄λ””μ„œλ‚˜ 집에 κ°€κ³ μ‹Άκ²Œ λ§Œλ“œλŠ” μ œν’ˆμ„ νŒλ§€ν•˜λ‹€λŠ” 의미의 μ‡Όν•‘λͺ°μž…λ‹ˆλ‹€.

-## 과제 μˆ˜ν–‰ 및 제좜 방법 +
-``` -KDT기수번호_이름 - -E.g, KDT0_ParkYoungWoong -``` - -1. ν˜„μž¬ μ €μž₯μ†Œλ₯Ό λ‘œμ»¬μ— 클둠(Clone)ν•©λ‹ˆλ‹€. -1. μžμ‹ μ˜ λ³Έλͺ…μœΌλ‘œ 브랜치λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.(ꡬ뢄 κ°€λŠ₯ν•˜λ„λ‘ λ³Έλͺ…을 κΌ­ νŒŒμŠ€μΉΌμΌ€μ΄μŠ€λ‘œ ν‘œμ‹œν•˜μ„Έμš”, `git branch KDTX_ParkYoungWoong`) -1. μžμ‹ μ˜ λ³Έλͺ… λΈŒλžœμΉ˜μ—μ„œ 과제λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€. -1. 과제 μˆ˜ν–‰μ΄ μ™„λ£Œλ˜λ©΄, μžμ‹ μ˜ λ³Έλͺ… 브랜치λ₯Ό 원격 μ €μž₯μ†Œμ— ν‘Έμ‹œ(Push)ν•©λ‹ˆλ‹€.(`main` λΈŒλžœμΉ˜μ— ν‘Έμ‹œν•˜μ§€ μ•Šλ„λ‘ κΌ­ μ£Όμ˜ν•˜μ„Έμš”, `git push origin KDTX_ParkYoungWoong`) -1. μ €μž₯μ†Œμ—μ„œ `main` 브랜치λ₯Ό λŒ€μƒμœΌλ‘œ Pull Request μƒμ„±ν•˜λ©΄, 과제 제좜이 μ™„λ£Œλ©λ‹ˆλ‹€!(E.g, `main` <== `KDTX_ParkYoungWoong`) - -- `main` ν˜Ήμ€ λ‹€λ₯Έ μ‚¬λžŒμ˜ 브랜치둜 μ ˆλŒ€ λ³‘ν•©ν•˜μ§€ μ•Šλ„λ‘ μ£Όμ˜ν•˜μ„Έμš”! -- Pull Requestμ—μ„œ λ³΄μ΄λŠ” μ„€λͺ…을 λ‹€λ₯Έ μ‚¬λžŒλ“€μ΄ μ΄ν•΄ν•˜κΈ° 쉽도둝 κΌΌκΌΌν•˜κ²Œ μž‘μ„±ν•˜μ„Έμš”! -- Pull Requestμ—μ„œ 과제 제좜 ν›„ μ ˆλŒ€ 병합(Merge)ν•˜μ§€ μ•Šλ„λ‘ μ£Όμ˜ν•˜μ„Έμš”! -- 과제 μˆ˜ν–‰ 및 제좜 κ³Όμ •μ—μ„œ λ¬Έμ œκ°€ λ°œμƒν•œ 경우, λ°”λ‘œ λ‹΄λ‹Ή λ©˜ν† λ‚˜ κ°•μ‚¬μ—μ„œ μ–˜κΈ°ν•˜μ„Έμš”! - -## API μ‚¬μš©λ²• - -λͺ¨λ“  API μš”μ²­(Request) `headers`에 μ•„λž˜ 정보가 κΌ­ 포함돼야 ν•©λ‹ˆλ‹€! -`username`은 `KDT5_TeamX`와 같이 λ³Έλͺ… ν˜Ήμ€ νŒ€ 이름을 포함해야 ν•©λ‹ˆλ‹€! -확인할 수 μ—†λŠ” μ‚¬μš©μžλ‚˜ νŒ€μ˜ DB μ •λ³΄λŠ” μž„μ˜λ‘œ μ‚­μ œλ  수 μžˆμŠ΅λ‹ˆλ‹€! - -```json -{ - "content-type": "application/json", - "apikey": "KDT5_nREmPe9B", - "username": "KDT5_TeamX" -} -``` - -
- -## 인증 - -'인증' κ΄€λ ¨ APIλŠ” λͺ¨λ‘ 일반 μ‚¬μš©μž μ „μš©μž…λ‹ˆλ‹€. - -### νšŒμ›κ°€μž… - -μ‚¬μš©μžκ°€ `username`에 μ’…μ†λ˜μ–΄ νšŒμ›κ°€μž…ν•©λ‹ˆλ‹€. - -- μ‚¬μš©μž λΉ„λ°€λ²ˆν˜ΈλŠ” μ•”ν˜Έν™”ν•΄ μ €μž₯ν•©λ‹ˆλ‹€.(κ΄€λ¦¬μžλŠ” 확인할 수 μ—†μŠ΅λ‹ˆλ‹€!) -- ν”„λ‘œν•„ μ΄λ―Έμ§€λŠ” 1MB μ΄ν•˜μ—¬μ•Ό ν•©λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/auth/signup - \ -X 'POST' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: +## ν”„λ‘œμ νŠΈ μ†Œκ°œ -```ts -interface RequestBody { - email: string // μ‚¬μš©μž 아이디 (ν•„μˆ˜!) - password: string // μ‚¬μš©μž λΉ„λ°€λ²ˆν˜Έ, 8자 이상 (ν•„μˆ˜!) - displayName: string // μ‚¬μš©μž 이름, 20자 μ΄ν•˜ (ν•„μˆ˜!) - profileImgBase64?: string // μ‚¬μš©μž ν”„λ‘œν•„ 이미지(base64) - jpg, jpeg, webp, png, gif, svg -} -``` +> **패슀트캠퍼슀 ν”„λ‘ νŠΈμ—”λ“œ 개발 λΆ€νŠΈμΊ ν”„ 5κΈ°**
+> **개발 κΈ°κ°„** : 2023. 05. 30 ~ 2023. 07. 01
+> **배포 μ£Όμ†Œ** : [DEMO](https://fe5-team9.github.io/sweethome) -```json -{ - "email": "thesecon@gmail.com", - "password": "********", - "displayName": "ParkYoungWoong", - "profileImgBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf...(μƒλž΅)" -} -``` +
-응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: +## 개발자 μ†Œκ°œ -```ts -interface ResponseValue { - user: { // νšŒμ›κ°€μž…ν•œ μ‚¬μš©μž 정보 - email: string // μ‚¬μš©μž 아이디 - displayName: string // μ‚¬μš©μž ν‘œμ‹œ 이름 - profileImg: string | null // μ‚¬μš©μž ν”„λ‘œν•„ 이미지(URL) - } - accessToken: string // μ‚¬μš©μž μ ‘κ·Ό 토큰 -} -``` - -```json -{ - "user": { - "email": "thesecon@gmail.com", - "displayName": "ParkYoungWoong", - "profileImg": "https://storage.googleapis.com/heropy-api/vjbtIrh5dGv163442.png" - }, - "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IlM3WDhpQ...(μƒλž΅)" -} -``` +| **[김쀀희](https://github.com/dev-junehee)** | **[μ†‘ν™λΉˆ](https://github.com/hbsongk)** | **[쑰은상](https://github.com/ChoEun-Sang)** | **[λ°•κ·Όμš°](https://github.com/SpeedGear)** | **[백동은](https://github.com/debeck6)** | +| :--------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | +|김쀀희 | μ†‘ν™λΉˆ | 쑰은상 | λ°•κ·Όμš° | 백동은 | +|νšŒμ›κ°€μž…
About νŽ˜μ΄μ§€
μƒν’ˆ νŽ˜μ΄μ§€
μž₯λ°”κ΅¬λ‹ˆ νŽ˜μ΄μ§€
결제 νŽ˜μ΄μ§€
κ΄€λ¦¬μž νŽ˜μ΄μ§€
Footer|둜그인/λ‘œκ·Έμ•„μ›ƒ
검색 κΈ°λŠ₯
메인 νŽ˜μ΄μ§€
μƒν’ˆ νŽ˜μ΄μ§€
μž₯λ°”κ΅¬λ‹ˆ νŽ˜μ΄μ§€
결제 νŽ˜μ΄μ§€
Header|νšŒμ›κ°€μž…
둜그인 인증
λ§ˆμ΄νŽ˜μ΄μ§€
μƒν’ˆ νŽ˜μ΄μ§€
μž₯λ°”κ΅¬λ‹ˆ νŽ˜μ΄μ§€
결제 νŽ˜μ΄μ§€
κ΄€λ¦¬μž νŽ˜μ΄μ§€|λ””μžμΈ μ°Έμ—¬|λ””μžμΈ μ°Έμ—¬| -### 둜그인 +
-- λ°œκΈ‰λœ `accessToken`은 24μ‹œκ°„ ν›„ λ§Œλ£Œλ©λ‹ˆλ‹€.(만료 ν›„ λ‹€μ‹œ 둜그인 ν•„μš”) +## μ‚¬μš©κΈ°μˆ  및 κ°œλ°œν™˜κ²½ -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/auth/login - \ -X 'POST' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - email: string // μ‚¬μš©μž 아이디 (ν•„μˆ˜!) - password: string // μ‚¬μš©μž λΉ„λ°€λ²ˆν˜Έ (ν•„μˆ˜!) -} -``` +### Development -```json -{ - "email": "thesecon@gmail.com", - "password": "********" -} -``` +

+ + + +
+ + + +

-응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: +### Config -```ts -interface ResponseValue { - user: { // νšŒμ›κ°€μž…ν•œ μ‚¬μš©μž 정보 - email: string // μ‚¬μš©μž 아이디 - displayName: string // μ‚¬μš©μž ν‘œμ‹œ 이름 - profileImg: string | null // μ‚¬μš©μž ν”„λ‘œν•„ 이미지(URL) - } - accessToken: string // μ‚¬μš©μž μ ‘κ·Ό 토큰 -} -``` +

+ + +

-```json -{ - "user": { - "email": "thesecon@gmail.com", - "displayName": "ParkYoungWoong", - "profileImg": "https://storage.googleapis.com/heropy-api/vAKjlJ-Gx5v163442.png" - }, - "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjlQS3I...(μƒλž΅)" -} -``` +### Deployment + + -### 인증 확인 +### Environment + +

+ + + +

+ +### Cowork Tools +

+ + + + + +

+ +
+ +### 전체 ν™”λ©΄ ꡬ성 +| **메인 νŽ˜μ΄μ§€** | **About νŽ˜μ΄μ§€** | +| :--------------------------------------------: | :--------------------------------------------: | +| 메인 | 어바웃 | + +| **μƒν’ˆ λͺ©λ‘ νŽ˜μ΄μ§€** | **μƒν’ˆ 상세 νŽ˜μ΄μ§€** | +| :--------------------------------------------: | :--------------------------------------------: | +| μƒν’ˆ λͺ©λ‘ | μƒν’ˆ 상세 | + +| **μž₯λ°”κ΅¬λ‹ˆ νŽ˜μ΄μ§€** | **결제 νŽ˜μ΄μ§€** | +| :--------------------------------------------: | :--------------------------------------------: | +| μž₯λ°”κ΅¬λ‹ˆ | 결제 + +| **마이 νŽ˜μ΄μ§€ (μ£Όλ¬Έ λ‚΄μ—­ 관리)** | **마이 νŽ˜μ΄μ§€ (μ£Όλ¬Έ λ‚΄μ—­ 상세)** | +| :--------------------------------------------: | :--------------------------------------------: | +| λ§ˆμ΄νŽ˜μ΄μ§€ μ£Όλ¬Έ λ‚΄μ—­ | λ§ˆμ΄νŽ˜μ΄μ§€ μ£Όλ¬Έ 상세 -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/auth/me - \ -X 'POST' - \ -H 'Authorization: Bearer ' -``` +| **마이 νŽ˜μ΄μ§€ (κ³„μ’Œ 관리)** | **마이 νŽ˜μ΄μ§€ (κ³„μ’Œ 등둝 λͺ¨λ‹¬)** | +| :--------------------------------------------: | :--------------------------------------------: | +| λ§ˆμ΄νŽ˜μ΄μ§€ κ³„μ’Œ 관리 | λ§ˆμ΄νŽ˜μ΄μ§€ κ³„μ’Œ 등둝 λͺ¨λ‹¬ -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: +| **마이 νŽ˜μ΄μ§€ (κ³„μ’Œ 확인/μ‚­μ œ)** | **마이 νŽ˜μ΄μ§€ (λΉ„λ°€λ²ˆν˜Έ μž¬ν™•μΈ)** | +| :--------------------------------------------: | :--------------------------------------------: | +| λ§ˆμ΄νŽ˜μ΄μ§€ κ³„μ’Œ 확인/μ‚­μ œ | λ§ˆμ΄νŽ˜μ΄μ§€ λΉ„λ°€λ²ˆν˜Έ μž¬ν™•μΈ -- μ—†μŒ +| **마이 νŽ˜μ΄μ§€ (κ°œμΈμ •λ³΄ μˆ˜μ •)** | **κ΄€λ¦¬μž νŽ˜μ΄μ§€ (μ‚¬μš©μž 관리)** | +| :--------------------------------------------: | :--------------------------------------------: | +| λ§ˆμ΄νŽ˜μ΄μ§€ κ°œμΈμ •λ³΄ μˆ˜μ • | bκ΄€λ¦¬μžνŽ˜μ΄μ§€ μ‚¬μš©μž 관리uy -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: +| **κ΄€λ¦¬μž νŽ˜μ΄μ§€ (μƒν’ˆ 관리)** | **κ΄€λ¦¬μž νŽ˜μ΄μ§€ (μ£Όλ¬Έ λ‚΄μ—­ 관리)** | +| :--------------------------------------------: | :--------------------------------------------: | +| κ΄€λ¦¬μž νŽ˜μ΄μ§€ μƒν’ˆ 관리 | κ΄€λ¦¬μž νŽ˜μ΄μ§€ μ£Όλ¬Έ λ‚΄μ—­ 관리 -```ts -interface ResponseValue { - email: string // μ‚¬μš©μž 아이디 - displayName: string // μ‚¬μš©μž ν‘œμ‹œ 이름 - profileImg: string | null // μ‚¬μš©μž ν”„λ‘œν•„ 이미지(URL) -} -``` +| **νšŒμ›κ°€μž… νŽ˜μ΄μ§€** | **둜그인 νŽ˜μ΄μ§€** | +| :--------------------------------------------: | :--------------------------------------------: | +| νšŒμ›κ°€μž… | 둜그인 -```json -{ - "email": "thesecon@gmail.com", - "displayName": "ParkYoungWoong", - "profileImg": "https://storage.googleapis.com/heropy-api/vAKjlJ-Gx5v163442.png" -} -``` +| **검색창** | **λ‘œλ”© μŠ€ν”Όλ„ˆ** | +| :--------------------------------------------: | :--------------------------------------------: | +| 검색창 | λ‘œλ”© -### λ‘œκ·Έμ•„μ›ƒ +
-```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/auth/logout - \ -X 'POST' - \ -H 'Authorization: Bearer ' -``` +## ν”„λ‘œμ νŠΈ 상세 κΈ°λŠ₯ -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: +### Layout +- React-Router-Dom의 outlet 쀑첩 λΌμš°νŒ…μ„ ν™œμš©ν•œ λ ˆμ΄μ•„μ›ƒ ꡬ성 +- Reduxλ₯Ό μ‚¬μš©ν•œ μ „μ—­ μƒνƒœ 관리 +- μž¬μ‚¬μš©μ„ 높이기 μœ„ν•œ μ„ΈλΆ€ μ»΄ν¬λ„ŒνŠΈ ꡬ성 +- 더 λ‚˜μ€ UXλ₯Ό μœ„ν•œ λ‘œλ”© μŠ€ν”Όλ„ˆ μΆ”κ°€ +### Head +- μΉ΄ν…Œκ³ λ¦¬ (Navbar) : useNavigate, NavLinkλ₯Ό ν™œμš©ν•œ νŽ˜μ΄μ§€ λΌμš°ν„° 관리 +- μ œν’ˆ 검색 κΈ°λŠ₯ : useState, useNavigate, ChangeEventλ₯Ό ν™œμš©ν•œ 검색 κΈ°λŠ₯ κ΅¬ν˜„ -- μ—†μŒ +### 메인 νŽ˜μ΄μ§€ (Home) +- 이미지 μŠ¬λΌμ΄λ” : React-sliderλ₯Ό ν™œμš©ν•œ 메인 νŽ˜μ΄μ§€ 이미지 μŠ¬λΌμ΄λ” κ΅¬ν˜„ + +### μƒν’ˆ νŽ˜μ΄μ§€ (Shop) +- μƒν’ˆ λͺ©λ‘ νŽ˜μ΄μ§€ : productItem μ»΄ν¬λ„ŒνŠΈλ‘œ μž¬μ‚¬μš©μ„±μ„ 높이고 MouseEvent와 useStateλ₯Ό ν™œμš©ν•œ μΉ΄ν…Œκ³ λ¦¬ ν•„ν„° κ΅¬ν˜„ +- μƒν’ˆ 상세 νŽ˜μ΄μ§€ : useParamsλ₯Ό ν™œμš©ν•œ μƒν’ˆ 상세 정보 쑰회(ID), Reduxλ₯Ό ν™œμš©ν•œ μž₯λ°”κ΅¬λ‹ˆ κΈ°λŠ₯ κ΅¬ν˜„ +- μž₯λ°”κ΅¬λ‹ˆ λ‹΄κΈ° κΈ°λŠ₯ : Redux, useDispatch, useSelector, useNavigate, useParamsλ₯Ό ν™œμš©ν•œ μž₯λ°”κ΅¬λ‹ˆ μƒνƒœ 관리 κ΅¬ν˜„ +- 결제 νŽ˜μ΄μ§€ : useNavigate, Reduxλ₯Ό ν™œμš©ν•œ νŽ˜μ΄μ§€ λΌμš°νŒ…, 데이터 전달 κ΅¬ν˜„ + +### μž₯λ°”κ΅¬λ‹ˆ νŽ˜μ΄μ§€ (Cart) +- μž₯λ°”κ΅¬λ‹ˆ λͺ©λ‘ 쑰회 : Redux, useSelector, useNavigateλ₯Ό ν™œμš©ν•œ μž₯λ°”κ΅¬λ‹ˆ μƒνƒœ 관리 κ΅¬ν˜„ +- 전체/선택 μƒν’ˆ ꡬ맀 : Reduxλ₯Ό ν™œμš©ν•˜μ—¬ μƒν’ˆ 선택 μœ λ¬΄μ— λ”°λ₯Έ 쑰건뢀 λ Œλ”λ§ κ΅¬ν˜„ + +### 결제 νŽ˜μ΄μ§€ (Buy) +- 결제 κΈ°λŠ₯ : Reduxλ₯Ό ν™œμš©ν•œ 결제 상세 정보 좜λ ₯ 및 μ‚¬μš©μž κ³„μ’Œ μœ λ¬΄μ— λ”°λ₯Έ 쑰건뢀 λ Œλ”λ§ κ΅¬ν˜„ -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: +### λ§ˆμ΄νŽ˜μ΄μ§€ (Mypage) +- μ£Όλ¬Έ λ‚΄μ—­ 쑰회 + - μƒν’ˆ ꡬ맀 μ·¨μ†Œ : State 변화에 λ”°λ₯Έ 쑰건뢀 λ Œλ”λ§ κ΅¬ν˜„ + - μƒν’ˆ ꡬ맀 ν™•μ • : State 변화에 λ”°λ₯Έ 쑰건뢀 λ Œλ”λ§ κ΅¬ν˜„ + - 상세 정보 쑰회 : μ£Όλ¬Έ μ‹œκ°„(μ΅œμ‹ μˆœ)에 λ”°λ₯Έ μƒν’ˆ ꡬ맀 λ‚΄μ—­ μ •λ ¬ κ΅¬ν˜„ +- κ³„μ’Œ 관리 + - 전체 κ³„μ’Œ 쑰회 + - κ³„μ’Œ 등둝 : Modal을 ν†΅ν•œ κ³„μ’Œ 등둝, 은행 μ„ νƒμ‹œ 은행 μ½”λ“œ μžλ™ μž…λ ₯ κ΅¬ν˜„, μžλ¦¬μˆ˜μ— λ”°λ₯Έ μ—¬λŸ¬ 개의 input μƒνƒœ 관리 + - κ³„μ’Œ μ‚­μ œ : μ»΄ν¬λ„ŒνŠΈλ₯Ό ν™œμš©ν•œ 등둝 κ³„μ’Œ 리슀트 좜λ ₯, 등둝 κ³„μ’Œ 여뢀에 따라 쑰건뢀 λ Œλ”λ§ κ΅¬ν˜„ +- 개인 정보 μˆ˜μ •: λΉ„λ°€λ²ˆν˜Έ μž¬ν™•μΈ κΈ°λŠ₯ κ΅¬ν˜„, React-Hook-form을 ν†΅ν•œ μ½”λ“œ λ‹¨μˆœν™” 및 μœ νš¨μ„± 검사 κ΅¬ν˜„ + +### κ΄€λ¦¬μž νŽ˜μ΄μ§€ (Admin) +- 전체 μ‚¬μš©μž λͺ©λ‘ 쑰회 +- μƒν’ˆ 관리 + - 전체 μƒν’ˆ λͺ©λ‘ 쑰회 + - μƒν’ˆ 등둝 : Select μ»΄ν¬λ„ŒνŠΈλ₯Ό μ΄μš©ν•œ μƒν’ˆ νƒœκ·Έ 등둝 κ΅¬ν˜„ + - μƒν’ˆ μ‚­μ œ : React-icons ν™œμš©ν•œ μ‚­μ œ λ²„νŠΌ 생성 + - μƒν’ˆ 정보 μˆ˜μ • : React-icons, Modal μ»΄ν¬λ„ŒνŠΈλ₯Ό ν†΅ν•œ μƒν’ˆ 정보 μˆ˜μ • 및 κΈ°μ‘΄ 정보 확인 κΈ°λŠ₯ κ΅¬ν˜„ +- μ£Όλ¬Έ λ‚΄μ—­ 관리 + - 전체 거래 λ‚΄μ—­ 쑰회 + - 거래 μ·¨μ†Œ : State 변화에 λ”°λ₯Έ 쑰건뢀 λ Œλ”λ§ κ΅¬ν˜„ + - 거래 ν™•μ • : State 변화에 λ”°λ₯Έ 쑰건뢀 λ Œλ”λ§ κ΅¬ν˜„ + +### 인증 & 인가 +- νšŒμ›κ°€μž… : React-Hook-Form을 μ΄μš©ν•œ λΉ„μ œμ–΄ μ»΄ν¬λ„ŒνŠΈ 관리 및 μœ νš¨μ„± 검사 κ΅¬ν˜„ +- 둜그인 : useStateλ₯Ό 톡해 둜그인 κΈ°λŠ₯ κ΅¬ν˜„ +- 둜그인 인증 : App.tsxμ—μ„œ useEffectλ₯Ό μ΄μš©ν•œ λͺ¨λ“  ν•˜μœ„ νŽ˜μ΄μ§€ 둜그인 인증 κ΅¬ν˜„ +- λ‘œκ·Έμ•„μ›ƒ : Reduxλ₯Ό μ‚¬μš©ν•΄ Header λ‘œκ·Έμ•„μ›ƒ UI κ΅¬ν˜„ -```ts -type ResponseValue = true // λ‘œκ·Έμ•„μ›ƒ 처리 μƒνƒœ -``` +
+ +## ν”„λ‘œμ νŠΈ ν…ŒμŠ€νŠΈ -### μ‚¬μš©μž 정보 μˆ˜μ • +### clone project -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/auth/user - \ -X 'PUT' - \ -H 'Authorization: Bearer ' +```bash +$ git clone git@github.com:FE5-TEAM9/sweethome.git ``` -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - displayName?: string // μƒˆλ‘œμš΄ ν‘œμ‹œ 이름 - profileImgBase64?: string // μ‚¬μš©μž ν”„λ‘œν•„ 이미지(base64) - jpg, jpeg, webp, png, gif, svg - oldPassword?: string // κΈ°μ‘΄ λΉ„λ°€λ²ˆν˜Έ - newPassword?: string // μƒˆλ‘œμš΄ λΉ„λ°€λ²ˆν˜Έ -} -``` - -```json -{ - "oldPassword": "********", - "newPassword": "**********" -} -``` - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface ResponseValue { - email: string // μ‚¬μš©μž 아이디 - displayName: string // μ‚¬μš©μž ν‘œμ‹œ 이름 - profileImg: string | null // μ‚¬μš©μž ν”„λ‘œν•„ 이미지(URL) -} -``` - -```json -{ - "email": "thesecon@gmail.com", - "displayName": "ParkYoungWoong", - "profileImg": "https://storage.googleapis.com/heropy-api/vAKjlJ-Gx5v163442.png" -} -``` - -### μ‚¬μš©μž λͺ©λ‘ 쑰회 - -- κ΄€λ¦¬μž μ „μš© APIμž…λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/auth/users - \ -X 'GET' - \ -H 'masterKey: true' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -- μ—†μŒ - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -type ResponseValue = User[] - -interface User { - email: string // μ‚¬μš©μž 아이디 - displayName: string // μ‚¬μš©μž ν‘œμ‹œ 이름 - profileImg: string // μ‚¬μš©μž ν”„λ‘œν•„ 이미지 URL -} -``` - -```json -[ - { - "email": "thesecon@gmail.com", - "displayName": "HEROPY", - "profileImg": null - }, - { - "email": "neo@zillinks.com", - "displayName": "λ°•μ˜μ›…", - "profileImg": "https://storage.googleapis.com/heropy-api/Z_una7lyijv074804.png" - }, - { - "email": "test@test.com", - "displayName": "κ΄€λ¦¬μž", - "profileImg": "https://storage.googleapis.com/heropy-api/ZXcXjwsB7nv121507.png" - } -] -``` - -
- -## κ³„μ’Œ - -'κ³„μ’Œ' κ΄€λ ¨ APIλŠ” λͺ¨λ‘ 일반 μ‚¬μš©μž μ „μš©μž…λ‹ˆλ‹€. - -### 선택 κ°€λŠ₯ν•œ 은행 λͺ©λ‘ 쑰회 - -- 은행 λ‹Ή ν•˜λ‚˜μ˜ κ³„μ’Œλ§Œ ν—ˆμš©λ©λ‹ˆλ‹€. -- μ‚¬μš©μžκ°€ κ³„μ’Œλ₯Ό μΆ”κ°€ν•˜λ©΄, ν•΄λ‹Ή 은행 정보 `disabled` 속성이 `true`둜 λ³€κ²½λ©λ‹ˆλ‹€. -- 은행 정보 `digits` μ†μ„±μ˜ 숫자λ₯Ό λͺ¨λ‘ λ”ν•˜λ©΄ 각 μ€ν–‰μ˜ μœ νš¨ν•œ κ³„μ’Œλ²ˆν˜Έ 길이가 λ©λ‹ˆλ‹€. -- `[3, 2, 4, 3]` => 123-12-1234-123 - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/account/banks - \ -X 'GET' - \ -H 'Authorization: Bearer ' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -- μ—†μŒ - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -type ResponseValue = Bank[] // 선택 κ°€λŠ₯ν•œ 은행 정보 λͺ©λ‘ - -interface Bank { // 선택 κ°€λŠ₯ν•œ 은행 정보 - name: string // 은행 이름 - code: string // 은행 μ½”λ“œ - digits: number[] // 은행 κ³„μ’Œ 자릿수 - disabled: boolean // μ‚¬μš©μžκ°€ μΆ”κ°€ν•œ κ³„μ’Œ μ—¬λΆ€ -} -``` - -```json -[ - { - "name": "KBꡭ민은행", - "code": "004", - "digits": [3, 2, 4, 3], - "disabled": false - }, - { - "name": "μ‹ ν•œμ€ν–‰", - "code": "088", - "digits": [3, 3, 6], - "disabled": true - }, - { - "name": "μš°λ¦¬μ€ν–‰", - "code": "020", - "digits": [4, 3, 6], - "disabled": true - }, - { - "name": "ν•˜λ‚˜μ€ν–‰", - "code": "081", - "digits": [3, 6, 5], - "disabled": false - }, - { - "name": "케이뱅크", - "code": "089", - "digits": [3, 3, 6], - "disabled": false - }, - { - "name": "μΉ΄μΉ΄μ˜€λ±…ν¬", - "code": "090", - "digits": [4, 2, 7], - "disabled": false - }, - { - "name": "NHλ†ν˜‘μ€ν–‰", - "code": "011", - "digits": [3, 4, 4, 2], - "disabled": false - } -] -``` +### go to project -### κ³„μ’Œ λͺ©λ‘ 및 μž”μ•‘ 쑰회 +```bash +$ cd sweethome +``` -- κ³„μ’Œλ²ˆν˜ΈλŠ” μΌλΆ€λ§Œ λ…ΈμΆœλ©λ‹ˆλ‹€. E.g. `"123-XXXX-XXXX-XX"` -- μž”μ•‘μ˜ λ‹¨μœ„λŠ” '원화(οΏ¦)'μž…λ‹ˆλ‹€. +### install npm -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/account - \ -X 'GET' - \ -H 'Authorization: Bearer ' +```bash +$ npm install ``` -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -- μ—†μŒ - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface ResponseValue { - totalBalance: number // μ‚¬μš©μž κ³„μ’Œ μž”μ•‘ 총합 - accounts: Bank[] // μ‚¬μš©μž κ³„μ’Œ 정보 λͺ©λ‘ -} - -interface Bank { // μ‚¬μš©μž κ³„μ’Œ 정보 - id: string // κ³„μ’Œ ID - bankName: string // 은행 이름 - bankCode: string // 은행 μ½”λ“œ - accountNumber: string // κ³„μ’Œ 번호 - balance: number // κ³„μ’Œ μž”μ•‘ -} -``` - -```json -{ - "totalBalance": 5999900, - "accounts": [ - { - "id": "jQMfKla8vOIFELA3mAXv", - "bankName": "NHλ†ν˜‘μ€ν–‰", - "bankCode": "011", - "accountNumber": "356-XXXX-XXXX-XX", - "balance": 2999900 - }, - { - "id": "wiPgsXvMAmcLw8AuRHIi", - "bankName": "KBꡭ민은행", - "bankCode": "004", - "accountNumber": "123-XX-XXXX-XXX", - "balance": 3000000 - } - ] -} -``` - -### κ³„μ’Œ μ—°κ²° - -- μ—°κ²°λœ κ³„μ’Œ μž”μ•‘μ—λŠ” μžλ™μœΌλ‘œ κΈ°λ³Έ '3λ°±λ§Œμ›'이 μΆ”κ°€λ©λ‹ˆλ‹€. -- μš”μ²­ν•˜λŠ” κ³„μ’Œλ²ˆν˜Έμ™€ μ „ν™”λ²ˆν˜Έμ—λŠ” `-` ꡬ뢄이 μ—†μ–΄μ•Ό ν•©λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/account - \ -X 'POST' - \ -H 'Authorization: Bearer ' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - bankCode: string // μ—°κ²°ν•  은행 μ½”λ“œ (ν•„μˆ˜!) - accountNumber: string // μ—°κ²°ν•  κ³„μ’Œλ²ˆν˜Έ (ν•„μˆ˜!) - phoneNumber: string // μ‚¬μš©μž μ „ν™”λ²ˆν˜Έ (ν•„μˆ˜!) - signature: boolean // μ‚¬μš©μž μ„œλͺ… (ν•„μˆ˜!) -} -``` - -```json -{ - "bankCode": "088", - "accountNumber": "123456789012", - "phoneNumber": "01012345678", - "signature": true -} -``` - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface ResponseValue { // μ—°κ²°λœ κ³„μ’Œ 정보 - id: string // κ³„μ’Œ ID - bankName: string // 은행 이름 - bankCode: string // 은행 μ½”λ“œ - accountNumber: string // κ³„μ’Œ 번호 - balance: number // κ³„μ’Œ μž”μ•‘ -} -``` - -```json -{ - "id": "1qRFC6Ey5VkSu6nyj5Ba", - "bankName": "μ‹ ν•œμ€ν–‰", - "bankCode": "088", - "accountNumber": "123-XXX-XXXXXX", - "balance": 3000000 -} -``` - -### κ³„μ’Œ 해지 - -- ν•΄μ§€ν•œ κ³„μ’ŒλŠ” λ‹€μ‹œ 연결해도 μž”μ•‘μ΄ λ°˜μ˜λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.(κΈ°λ³Έ κΈˆμ•‘μœΌλ‘œ μΆ”κ°€λ©λ‹ˆλ‹€) - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/account - \ -X 'DELETE' - \ -H 'Authorization: Bearer ' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - accountId: string // κ³„μ’Œ ID (ν•„μˆ˜!) - signature: boolean // μ‚¬μš©μž μ„œλͺ… (ν•„μˆ˜!) -} -``` - -```json -{ - "accountId": "jQMfKla8vOIFELA3mAXv", - "signature": true -} -``` - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -type ResponseValue = true // κ³„μ’Œ 해지 처리 μƒνƒœ -``` - -
- -## μ œν’ˆ - -'μ œν’ˆ' κ΄€λ ¨ APIλŠ” κ΄€λ¦¬μž μ „μš©κ³Ό 일반 μ‚¬μš©μž μ „μš©μœΌλ‘œ κ΅¬λΆ„λ©λ‹ˆλ‹€.
-곡용 API도 μžˆμœΌλ‹ˆ μ£Όμ˜ν•˜μ„Έμš”! - -### λͺ¨λ“  μ œν’ˆ 쑰회 - -- κ΄€λ¦¬μž μ „μš© APIμž…λ‹ˆλ‹€. -- 상세 정보가 μ•„λ‹Œ κΈ°λ³Έ μ •λ³΄μ˜ μ œν’ˆ μ„€λͺ…은 100μžκΉŒμ§€λ§Œ ν¬ν•¨λ©λ‹ˆλ‹€. -- 상세 정보가 μ•„λ‹Œ κΈ°λ³Έ μ •λ³΄μ˜ μ œν’ˆ 상세 사진은 ν¬ν•¨λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. -- μ œν’ˆ ν• μΈμœ¨(`discountRate`)은 μ œν’ˆ 가격과 직접 관계가 μ—†λŠ” λ‹¨μˆœ λ©”λͺ¨ μ†μ„±μž…λ‹ˆλ‹€. -- μ œν’ˆ ν• μΈμœ¨μ΄ μ—†λŠ” 경우, `0`으둜 ν‘œμ‹œλ©λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products - \ -X 'GET' - \ -H 'masterKey: true' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -- μ—†μŒ - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -type ResponseValue = Product[] // κ΄€λ¦¬ν•˜λŠ” λͺ¨λ“  μ œν’ˆμ˜ λͺ©λ‘ - -interface Product { // μ œν’ˆ 정보 - id: string // μ œν’ˆ ID - title: string // μ œν’ˆ 이름 - price: number // μ œν’ˆ 가격 - description: string // μ œν’ˆ μ„€λͺ…(μ΅œλŒ€ 100자) - tags: string[] // μ œν’ˆ νƒœκ·Έ - thumbnail: string | null // μ œν’ˆ 썸넀일 이미지(URL) - isSoldOut: boolean // μ œν’ˆ 맀진 μ—¬λΆ€ - discountRate: number // μ œν’ˆ ν• μΈμœ¨ -} -``` - -```json -[ - { - "id": "cFmeC7aY5KjZbBAdJE9y", - "title": "μ‚Όμ„±μ „μž 슀마트λͺ¨λ‹ˆν„° M7 S43AM700", - "price": 639000, - "description": "107.9cm(43인치) / μ™€μ΄λ“œ(16:9) / 평면 / VA / 3840 x 2160(4K UHD) / ν”½μ…€ν”ΌμΉ˜: 0.2451mm / 8ms(GTG) / 300cd / 5,00", - "tags": [ - "κ°€μ „", - "λͺ¨λ‹ˆν„°", - "컴퓨터" - ], - "thumbnail": "https://storage.googleapis.com/heropy-api/vBAK4MQdH5v195712.png", - "isSoldOut": false, - "discountRate": 20 - }, - { - "id": "nbqtQvEivYwEXTDet7YM", - "title": "MacBook Pro 16", - "price": 3360000, - "description": "μ—­λŒ€ κ°€μž₯ κ°•λ ₯ν•œ MacBook Proκ°€ λ“±μž₯ν–ˆμŠ΅λ‹ˆλ‹€. 졜초의 ν”„λ‘œμš© Apple Silicon인 M1 Pro λ˜λŠ” M1 Max 칩을 νƒ‘μž¬ν•΄ μœμ‚΄κ°™μ΄ λΉ λ₯Έ μ†λ„λŠ” λ¬Όλ‘ , 획기적인 μ„±", - "tags": [ - "κ°€μ „", - "λ…ΈνŠΈλΆ", - "컴퓨터" - ], - "thumbnail": "https://storage.googleapis.com/heropy-api/vIKMk_jy4Yv195256.png", - "isSoldOut": false, - "discountRate": 0 - } -] -``` - -### 전체 거래(판맀) λ‚΄μ—­ - -- κ΄€λ¦¬μž μ „μš© APIμž…λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/transactions/all - \ -X 'GET' - \ -H 'masterKey: true' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -- μ—†μŒ - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -type RequestValue = TransactionDetail[] // λͺ¨λ“  거래 λ‚΄μ—­μ˜ λͺ©λ‘ - -interface TransactionDetail { // 거래 λ‚΄μ—­ 정보 - detailId: string // 거래 λ‚΄μ—­ ID - user: { // κ±°λž˜ν•œ μ‚¬μš©μž 정보 - email: string - displayName: string - profileImg: string | null - } - account: { // κ±°λž˜ν•œ μ‚¬μš©μžμ˜ κ³„μ’Œ 정보 - bankName: string - bankCode: string - accountNumber: string - } - product: { // κ±°λž˜ν•œ μ œν’ˆ 정보 - productId: string - title: string - price: number - description: string - tags: string[] - thumbnail: string | null - discountRate: number - } - reservation: Reservation | null // κ±°λž˜ν•œ μ œν’ˆμ˜ μ˜ˆμ•½ 정보 - timePaid: string // μ œν’ˆμ„ κ±°λž˜ν•œ μ‹œκ°„ - isCanceled: boolean // 거래 μ·¨μ†Œ μ—¬λΆ€ - done: boolean // 거래 μ™„λ£Œ μ—¬λΆ€ -} - -interface Reservation { - start: string // μ˜ˆμ•½ μ‹œμž‘ μ‹œκ°„ - end: string // μ˜ˆμ•½ μ’…λ£Œ μ‹œκ°„ - isCanceled: boolean // μ˜ˆμ•½ μ·¨μ†Œ μ—¬λΆ€ - isExpired: boolean // μ˜ˆμ•½ 만료 μ—¬λΆ€ -} -``` - -```json -[ - { - "detailId": "dMhfxyrAupQP18OYmywy", - "user": { - "email": "thesecon@gmail.com", - "displayName": "ParkYoungWoong", - "profileImg": "https://storage.googleapis.com/heropy-api/vsLRqTlPO5v200111.png" - }, - "account": { - "bankName": "KBꡭ민은행", - "bankCode": "004", - "accountNumber": "123-XX-XXXX-XXX" - }, - "product": { - "productId": "cFmeC7aY5KjZbBAdJE9y", - "title": "μ‚Όμ„±μ „μž 슀마트λͺ¨λ‹ˆν„° M7 S43AM700", - "price": 639000, - "description": "107.9cm(43인치) / μ™€μ΄λ“œ(16:9) / 평면 / VA / 3840 x 2160(4K UHD) / ν”½μ…€ν”ΌμΉ˜: 0.2451mm / 8ms(GTG) / 300cd / 5,00", - "tags": [ - "κ°€μ „", - "λͺ¨λ‹ˆν„°", - "컴퓨터" - ], - "thumbnail": "https://storage.googleapis.com/heropy-api/vBAK4MQdH5v195712.png", - "discountRate": 0 - }, - "reservation": null, - "timePaid": "2021-11-07T20:01:49.100Z", - "isCanceled": false, - "done": false - } -] -``` - -μ˜ˆμ•½ 정보(`reservation`)κ°€ μžˆλŠ” 경우: - -```json -[ - { - "reservation": { - "start": "2021-11-12T06:00:00.000Z", - "end": "2021-11-12T07:00:00.000Z", - "isCanceled": false, - "isExpired": true - } - } -] -``` - -### 거래(판맀) λ‚΄μ—­ μ™„λ£Œ/μ·¨μ†Œ 및 ν•΄μ œ - -- κ΄€λ¦¬μž μ „μš© APIμž…λ‹ˆλ‹€. -- 거래 내역을 μ·¨μ†Œν•˜λ©΄, μ˜ˆμ•½λ„ 같이 μ·¨μ†Œλ©λ‹ˆλ‹€. -- 거래 내역을 μ·¨μ†Œ ν•΄μ œν•˜λ©΄, μ˜ˆμ•½λ„ 같이 μ·¨μ†Œκ°€ ν•΄μ œλ©λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/transactions/:detailId - \ -X 'PUT' - \ -H 'masterKey: true' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - isCanceled?: boolean // 거래 μ·¨μ†Œ μ—¬λΆ€ (μ‚¬μš©μžμ˜ 'μ œν’ˆ 거래(ꡬ맀) μ·¨μ†Œ' μƒνƒœμ™€ κ°™μŠ΅λ‹ˆλ‹€) - done?: boolean // 거래 μ™„λ£Œ μ—¬λΆ€ (μ‚¬μš©μžμ˜ 'μ œν’ˆ 거래(ꡬ맀) ν™•μ •' μƒνƒœμ™€ κ°™μŠ΅λ‹ˆλ‹€) -} -``` - -```json -{ - "isCanceled": true -} -``` - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - - -```ts -type ResponseValue = true // 거래 λ‚΄μ—­ μ™„λ£Œ/μ·¨μ†Œ 및 ν•΄μ œ 처리 μƒνƒœ -``` - -### μ œν’ˆ μΆ”κ°€ - -- κ΄€λ¦¬μž μ „μš© APIμž…λ‹ˆλ‹€. -- 파일(사진)은 Base64둜 μš”μ²­ν•΄μ•Ό ν•©λ‹ˆλ‹€. -- μ œν’ˆ 썸넀일 사진은 1MB μ΄ν•˜μ—¬μ•Ό ν•©λ‹ˆλ‹€. -- μ œν’ˆ 상세 사진은 4MB μ΄ν•˜μ—¬μ•Ό ν•©λ‹ˆλ‹€. -- μ œν’ˆ ν• μΈμœ¨(`discountRate`)은 μ œν’ˆ 가격과 직접 관계가 μ—†λŠ” λ‹¨μˆœ λ©”λͺ¨ μ†μ„±μž…λ‹ˆλ‹€. -- μ œν’ˆ ν• μΈμœ¨μ€ `0`~`99` 사이 숫자λ₯Ό μž…λ ₯ν•˜μ„Έμš”. λ§Œμ•½ ν• μΈμœ¨μ΄ '20%'인 경우, `20`으둜 μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€. -- μ œν’ˆ ν• μΈμœ¨μ„ μž…λ ₯ν•˜μ§€ μ•ŠμœΌλ©΄, `0`으둜 μ μš©λ©λ‹ˆλ‹€. - -```js -// 할인 μ „ 가격을 계산! -const priceBeforeDiscount = price * 100 / (100 - discountRate) -``` - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products - \ -X 'POST' - \ -H 'masterKey: true' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - title: string // μ œν’ˆ 이름 (ν•„μˆ˜!) - price: number // μ œν’ˆ 가격 (ν•„μˆ˜!) - description: string // μ œν’ˆ 상세 μ„€λͺ… (ν•„μˆ˜!) - tags?: string[] // μ œν’ˆ νƒœκ·Έ - thumbnailBase64?: string // μ œν’ˆ 썸넀일(λŒ€ν‘œ) 사진(base64) - jpg, jpeg, webp, png, gif, svg - photoBase64?: string // μ œν’ˆ 상세 사진(base64) - jpg, jpeg, webp, png, gif, svg - discountRate?: number // μ œν’ˆ ν• μΈμœ¨ -} -``` - -```json -{ - "title": "MacBook Pro 16", - "price": 3360000, - "description": "μ—­λŒ€ κ°€μž₯ κ°•λ ₯ν•œ MacBook Proκ°€ λ“±μž₯ν–ˆμŠ΅λ‹ˆλ‹€. 졜초의 ν”„λ‘œμš© Apple Silicon인 M1 Pro λ˜λŠ” M1 Max 칩을 νƒ‘μž¬ν•΄ μœμ‚΄κ°™μ΄ λΉ λ₯Έ μ†λ„λŠ” λ¬Όλ‘ , 획기적인 μ„±λŠ₯κ³Ό λ†€λΌμš΄ 배터리 μ‚¬μš© μ‹œκ°„μ„ μžλž‘ν•˜μ£ . 여기에 μ‹œμ„ μ„ μ‚¬λ‘œμž‘λŠ” Liquid Retina XDR λ””μŠ€ν”Œλ ˆμ΄, Mac λ…ΈνŠΈλΆ 사상 졜고의 카메라 및 μ˜€λ””μ˜€ 그리고 더할 λ‚˜μœ„ 없이 λ‹€μ–‘ν•œ ν¬νŠΈκΉŒμ§€. κΈ°μ‘΄ κ·Έ μ–΄λ–€ μΉ΄ν…Œκ³ λ¦¬μ—λ„ μ†ν•˜μ§€ μ•ŠλŠ” λ…ΈνŠΈλΆ. μƒˆλ‘œμš΄ MacBook ProλŠ” κ·Έμ•Όλ§λ‘œ μ•Όμˆ˜μž…λ‹ˆλ‹€.", - "tags": [ - "κ°€μ „", - "λ…ΈνŠΈλΆ", - "컴퓨터" - ], - "thumbnailBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...(μƒλž΅)" -} -``` - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface ResponseValue { // μΆ”κ°€ν•œ μ œν’ˆμ˜ 상세 λ‚΄μš© - id: string // μ œν’ˆ ID - title: string // μ œν’ˆ 이름 - price: number // μ œν’ˆ 가격 - description: string // μ œν’ˆ 상세 μ„€λͺ… - tags: string[] // μ œν’ˆ νƒœκ·Έ - thumbnail: string | null // μ œν’ˆ 썸넀일 이미지(URL) - photo: string | null // μ œν’ˆ 상세 이미지(URL) - isSoldOut: boolean // μ œν’ˆ 맀진 μ—¬λΆ€ - discountRate: number // μ œν’ˆ ν• μΈμœ¨ -} -``` - -```json -{ - "id": "nbqtQvEivYwEXTDet7YM", - "title": "MacBook Pro 16", - "price": 3360000, - "description": "μ—­λŒ€ κ°€μž₯ κ°•λ ₯ν•œ MacBook Proκ°€ λ“±μž₯ν–ˆμŠ΅λ‹ˆλ‹€. 졜초의 ν”„λ‘œμš© Apple Silicon인 M1 Pro λ˜λŠ” M1 Max 칩을 νƒ‘μž¬ν•΄ μœμ‚΄κ°™μ΄ λΉ λ₯Έ μ†λ„λŠ” λ¬Όλ‘ , 획기적인 μ„±λŠ₯κ³Ό λ†€λΌμš΄ 배터리 μ‚¬μš© μ‹œκ°„μ„ μžλž‘ν•˜μ£ . 여기에 μ‹œμ„ μ„ μ‚¬λ‘œμž‘λŠ” Liquid Retina XDR λ””μŠ€ν”Œλ ˆμ΄, Mac λ…ΈνŠΈλΆ 사상 졜고의 카메라 및 μ˜€λ””μ˜€ 그리고 더할 λ‚˜μœ„ 없이 λ‹€μ–‘ν•œ ν¬νŠΈκΉŒμ§€. κΈ°μ‘΄ κ·Έ μ–΄λ–€ μΉ΄ν…Œκ³ λ¦¬μ—λ„ μ†ν•˜μ§€ μ•ŠλŠ” λ…ΈνŠΈλΆ. μƒˆλ‘œμš΄ MacBook ProλŠ” κ·Έμ•Όλ§λ‘œ μ•Όμˆ˜μž…λ‹ˆλ‹€.", - "tags": [ - "κ°€μ „", - "λ…ΈνŠΈλΆ", - "컴퓨터" - ], - "thumbnail": "https://storage.googleapis.com/heropy-api/vIKMk_jy4Yv195256.png", - "photo": "https://storage.googleapis.com/heropy-api/voihKb3NLGcv195257.png", - "isSoldOut": false, - "discountRate": 0 -} -``` - -### μ œν’ˆ μˆ˜μ • - -- κ΄€λ¦¬μž μ „μš© APIμž…λ‹ˆλ‹€. -- μ‚¬μš©μžμ˜ ꡬ맀 λ‚΄μ—­ 확인을 μœ„ν•΄, μ œν’ˆμ„ μ‹€μ œλ‘œλŠ” μ‚­μ œν•˜μ§€ μ•Šκ³  맀진(Sold Out) μ²˜λ¦¬ν•΄μ•Ό ν•©λ‹ˆλ‹€. -- 맀진은 λ‹€μ‹œ ν•΄μ œν•  수 μžˆμŠ΅λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/:productId - \ -X 'PUT' - \ -H 'masterKey: true' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - title?: string // μ œν’ˆ 이름 - price?: number // μ œν’ˆ 가격 - description?: string // μ œν’ˆ 상세 μ„€λͺ… - tags?: string[] // μ œν’ˆ νƒœκ·Έ - thumbnailBase64?: string // μ œν’ˆ 썸넀일(λŒ€ν‘œ) 사진(base64) - jpg, jpeg, webp, png, gif, svg - photoBase64?: string // μ œν’ˆ 상세 사진(base64) - jpg, jpeg, webp, png, gif, svg - isSoldOut?: boolean // μ œν’ˆ 맀진 μ—¬λΆ€ - discountRate?: number // μ œν’ˆ ν• μΈμœ¨ -} -``` - -```json -{ - "price": 1500 -} -``` - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface ResponseValue { // μˆ˜μ •ν•œ μ œν’ˆμ˜ 상세 λ‚΄μš© - id: string // μ œν’ˆ ID - title: string // μ œν’ˆ 이름 - price: number // μ œν’ˆ 가격 - description: string // μ œν’ˆ 상세 μ„€λͺ… - tags: string[] // μ œν’ˆ νƒœκ·Έ - thumbnail: string | null // μ œν’ˆ 썸넀일 이미지(URL) - photo: string | null // μ œν’ˆ 상세 이미지(URL) - isSoldOut: boolean // μ œν’ˆ 맀진 μ—¬λΆ€ - discountRate: number // μ œν’ˆ ν• μΈμœ¨ -} -``` - -```json -{ - "id": "nbqtQvEivYwEXTDet7YM", - "title": "MacBook Pro 16", - "price": 1500, - "description": "μ—­λŒ€ κ°€μž₯ κ°•λ ₯ν•œ MacBook Proκ°€ λ“±μž₯ν–ˆμŠ΅λ‹ˆλ‹€. 졜초의 ν”„λ‘œμš© Apple Silicon인 M1 Pro λ˜λŠ” M1 Max 칩을 νƒ‘μž¬ν•΄ μœμ‚΄κ°™μ΄ λΉ λ₯Έ μ†λ„λŠ” λ¬Όλ‘ , 획기적인 μ„±λŠ₯κ³Ό λ†€λΌμš΄ 배터리 μ‚¬μš© μ‹œκ°„μ„ μžλž‘ν•˜μ£ . 여기에 μ‹œμ„ μ„ μ‚¬λ‘œμž‘λŠ” Liquid Retina XDR λ””μŠ€ν”Œλ ˆμ΄, Mac λ…ΈνŠΈλΆ 사상 졜고의 카메라 및 μ˜€λ””μ˜€ 그리고 더할 λ‚˜μœ„ 없이 λ‹€μ–‘ν•œ ν¬νŠΈκΉŒμ§€. κΈ°μ‘΄ κ·Έ μ–΄λ–€ μΉ΄ν…Œκ³ λ¦¬μ—λ„ μ†ν•˜μ§€ μ•ŠλŠ” λ…ΈνŠΈλΆ. μƒˆλ‘œμš΄ MacBook ProλŠ” κ·Έμ•Όλ§λ‘œ μ•Όμˆ˜μž…λ‹ˆλ‹€.", - "tags": [ - "κ°€μ „", - "λ…ΈνŠΈλΆ", - "컴퓨터" - ], - "thumbnail": "https://storage.googleapis.com/heropy-api/vIKMk_jy4Yv195256.png", - "photo": "https://storage.googleapis.com/heropy-api/voihKb3NLGcv195257.png", - "isSoldOut": false, - "discountRate": 0 -} -``` - -### μ œν’ˆ μ‚­μ œ - -- κ΄€λ¦¬μž μ „μš© APIμž…λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/:productId - \ -X 'DELETE' - \ -H 'masterKey: true' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -- μ—†μŒ - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -type ResponseValue = true // μ œν’ˆ μ‚­μ œ 처리 μƒνƒœ -``` - -### 단일 μ œν’ˆ 상세 쑰회 - -- 곡용 APIμž…λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/:productId - \ -X 'GET' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -- μ—†μŒ - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface ResponseValue { // μ œν’ˆμ˜ 상세 λ‚΄μš© - id: string // μ œν’ˆ ID - title: string // μ œν’ˆ 이름 - price: number // μ œν’ˆ 가격 - description: string // μ œν’ˆ 상세 μ„€λͺ… - tags: string[] // μ œν’ˆ νƒœκ·Έ - thumbnail: string | null // μ œν’ˆ 썸넀일 이미지(URL) - photo: string | null // μ œν’ˆ 상세 이미지(URL) - isSoldOut: boolean // μ œν’ˆ 맀진 μ—¬λΆ€ - reservations: Reservation[] // μ œν’ˆμ˜ λͺ¨λ“  μ˜ˆμ•½ 정보 λͺ©λ‘ - discountRate: number // μ œν’ˆ ν• μΈμœ¨ -} - -interface Reservation { - start: string // μ˜ˆμ•½ μ‹œμž‘ μ‹œκ°„ - end: string // μ˜ˆμ•½ μ’…λ£Œ μ‹œκ°„ - isCanceled: boolean // μ˜ˆμ•½ μ·¨μ†Œ μ—¬λΆ€ - isExpired: boolean // μ˜ˆμ•½ 만료 μ—¬λΆ€ -} -``` - -```json -{ - "id": "nbqtQvEivYwEXTDet7YM", - "title": "MacBook Pro 16", - "price": 3360000, - "description": "μ—­λŒ€ κ°€μž₯ κ°•λ ₯ν•œ MacBook Proκ°€ λ“±μž₯ν–ˆμŠ΅λ‹ˆλ‹€. 졜초의 ν”„λ‘œμš© Apple Silicon인 M1 Pro λ˜λŠ” M1 Max 칩을 νƒ‘μž¬ν•΄ μœμ‚΄κ°™μ΄ λΉ λ₯Έ μ†λ„λŠ” λ¬Όλ‘ , 획기적인 μ„±λŠ₯κ³Ό λ†€λΌμš΄ 배터리 μ‚¬μš© μ‹œκ°„μ„ μžλž‘ν•˜μ£ . 여기에 μ‹œμ„ μ„ μ‚¬λ‘œμž‘λŠ” Liquid Retina XDR λ””μŠ€ν”Œλ ˆμ΄, Mac λ…ΈνŠΈλΆ 사상 졜고의 카메라 및 μ˜€λ””μ˜€ 그리고 더할 λ‚˜μœ„ 없이 λ‹€μ–‘ν•œ ν¬νŠΈκΉŒμ§€. κΈ°μ‘΄ κ·Έ μ–΄λ–€ μΉ΄ν…Œκ³ λ¦¬μ—λ„ μ†ν•˜μ§€ μ•ŠλŠ” λ…ΈνŠΈλΆ. μƒˆλ‘œμš΄ MacBook ProλŠ” κ·Έμ•Όλ§λ‘œ μ•Όμˆ˜μž…λ‹ˆλ‹€.", - "tags": [ - "κ°€μ „", - "λ…ΈνŠΈλΆ", - "컴퓨터" - ], - "thumbnail": "https://storage.googleapis.com/heropy-api/vIKMk_jy4Yv195256.png", - "photo": "https://storage.googleapis.com/heropy-api/voihKb3NLGcv195257.png", - "isSoldOut": false, - "reservations": [], - "discountRate": 0 -} -``` - -μ˜ˆμ•½ 정보(`reservation`)κ°€ μžˆλŠ” 경우: - -```json -{ - "reservations": [ - { - "reservation": { - "start": "2021-11-12T06:00:00.000Z", - "end": "2021-11-12T07:00:00.000Z", - "isCanceled": false, - "isExpired": true - } - } - ] -} -``` - -### μ œν’ˆ 검색 - -- μ‚¬μš©μž μ „μš© APIμž…λ‹ˆλ‹€. -- μ œν’ˆ 이름과 νƒœκ·Έλ₯Ό λ™μ‹œμ— 검색할 수 있고, 'And'(κ²€μƒ‰ν•œ 이름과 νƒœκ·Έ λͺ¨λ‘ ν¬ν•¨λœ μ œν’ˆ) 쑰건으둜 κ²°κ³Όλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. -- μ œν’ˆ 이름과 νƒœκ·Έ λͺ¨λ‘ ν¬ν•¨ν•˜μ§€ μ•ŠμœΌλ©΄, λͺ¨λ“  μ œν’ˆμ˜ κ²°κ³Όλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. -- μ œν’ˆμ˜ κΈ°λ³Έ μ •λ³΄λ§Œ λ°˜ν™˜ν•©λ‹ˆλ‹€. -- λ§€μ§„λœ μ œν’ˆμ€ κ²€μƒ‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/search - \ -X 'POST' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - searchText?: string // 검색할 μ œν’ˆ 이름 - searchTags?: string[] // 검색할 μ œν’ˆ νƒœκ·Έ -} -``` - -```json -{ - "searchText": "μ‚Όμ„±μ „μž", - "searchTags": ["κ°€μ „"] -} -``` - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -type ResponseValue = Product[] // κ΄€λ¦¬ν•˜λŠ” λͺ¨λ“  μ œν’ˆμ˜ λͺ©λ‘ - -interface Product { // μ œν’ˆ 정보 - id: string // μ œν’ˆ ID - title: string // μ œν’ˆ 이름 - price: number // μ œν’ˆ 가격 - description: string // μ œν’ˆ μ„€λͺ…(μ΅œλŒ€ 100자) - tags: string[] // μ œν’ˆ νƒœκ·Έ - thumbnail: string | null // μ œν’ˆ 썸넀일 이미지(URL) - discountRate: number // μ œν’ˆ ν• μΈμœ¨ -} -``` - -```json -[ - { - "id": "cFmeC7aY5KjZbBAdJE9y", - "title": "μ‚Όμ„±μ „μž 슀마트λͺ¨λ‹ˆν„° M7 S43AM700", - "price": 639000, - "description": "107.9cm(43인치) / μ™€μ΄λ“œ(16:9) / 평면 / VA / 3840 x 2160(4K UHD) / ν”½μ…€ν”ΌμΉ˜: 0.2451mm / 8ms(GTG) / 300cd / 5,00", - "tags": [ - "κ°€μ „", - "λͺ¨λ‹ˆν„°", - "컴퓨터" - ], - "thumbnail": "https://storage.googleapis.com/heropy-api/vBAK4MQdH5v195712.png", - "discountRate": 0 - } -] -``` - -### μ œν’ˆ 거래(ꡬ맀) μ‹ μ²­ - -- μ‚¬μš©μž μ „μš© APIμž…λ‹ˆλ‹€. -- 거래(ꡬ맀) μ‹ μ²­μ‹œ μ—°κ²°λœ κ³„μ’Œμ—μ„œ κ²°μ œλ©λ‹ˆλ‹€. -- κ²°μ œν•  κ³„μ’Œ(ID)λ₯Ό κΌ­ 선택해야 ν•©λ‹ˆλ‹€.(`κ³„μ’Œ λͺ©λ‘ 및 μž”μ•‘ 쑰회` APIλ₯Ό μ‚¬μš©ν•˜μ„Έμš”) -- μ„ νƒν•œ κ³„μ’Œμ˜ μž”μ•‘λ³΄λ‹€ 결제 κΈˆμ•‘μ΄ 크면 κ²°μ œκ°€ μ²˜λ¦¬λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.(μ—λŸ¬ λ°˜ν™˜) - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/buy - \ -X 'POST' - \ -H 'Authorization: Bearer ' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - productId: string // κ±°λž˜ν•  μ œν’ˆ ID (ν•„μˆ˜!) - accountId: string // κ²°μ œν•  μ‚¬μš©μž κ³„μ’Œ ID (ν•„μˆ˜!) - reservation?: { // μ˜ˆμ•½ 정보(μ˜ˆμ•½ μ‹œμŠ€ν…œμ„ μ‚¬μš©ν•˜λŠ” 경우만 ν•„μš”) - start: string // μ˜ˆμ•½ μ‹œμž‘ μ‹œκ°„(ISO) - end: string // μ˜ˆμ•½ μ’…λ£Œ μ‹œκ°„(ISO) - } -} -``` - -```js -const isoString = new Date().toISOString() -``` - -```json -{ - "productId": "nbqtQvEivYwEXTDet7YM", - "accountId": "Mq2KKHk8vlmr6Xkg58Fa", - "reservation": { - "start": "2021-11-12T06:00:00.000Z", - "end": "2021-11-12T07:00:00.000Z" - } -} -``` - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -type ResponseValue = true // 거래 μ‹ μ²­ 처리 μ—¬λΆ€ -``` - -### μ œν’ˆ 거래(ꡬ맀) μ·¨μ†Œ - -- μ‚¬μš©μž μ „μš© APIμž…λ‹ˆλ‹€. -- '거래 μ·¨μ†Œ'μ‹œ κ²°μ œν•œ μ‚¬μš©μž κ³„μ’Œλ‘œ κΈˆμ•‘μ΄ ν™˜λΆˆλ©λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/cancel - \ -X 'POST' - \ -H 'Authorization: Bearer ' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - detailId: string // μ·¨μ†Œν•  μ œν’ˆμ˜ 거래 λ‚΄μ—­ ID -} -``` - -```json -{ - "detailId": "dMhfxyrAupQP18OYmywy" -} -``` - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -type ResponseValue = true // 거래 μ·¨μ†Œ 처리 μ—¬λΆ€ -``` - -### μ œν’ˆ 거래(ꡬ맀) ν™•μ • - -- μ‚¬μš©μž μ „μš© APIμž…λ‹ˆλ‹€. -- '거래(ꡬ맀) ν™•μ •' ν›„μ—λŠ” '거래 μ·¨μ†Œ'λ₯Ό ν•  수 μ—†μŠ΅λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/ok - \ -X 'POST' - \ -H 'Authorization: Bearer ' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - detailId: string // 거래(ꡬ맀) ν™•μ •ν•  μ œν’ˆμ˜ 거래 λ‚΄μ—­ ID -} -``` - -```json -{ - "detailId": "dMhfxyrAupQP18OYmywy" -} -``` - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -type ResponseValue = true // 거래(ꡬ맀) ν™•μ • 처리 μ—¬λΆ€ -``` - -### μ œν’ˆ 전체 거래(ꡬ맀) λ‚΄μ—­ - -- μ‚¬μš©μž μ „μš© APIμž…λ‹ˆλ‹€. -- 거래 λ‚΄μ—­μ˜ κΈ°λ³Έ μ •λ³΄λ§Œ ν¬ν•¨λ©λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/transactions/details - \ -X 'GET' - \ -H 'Authorization: Bearer ' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -- μ—†μŒ - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -type RequestValue = TransactionDetail[] // λͺ¨λ“  거래 λ‚΄μ—­μ˜ λͺ©λ‘ - -interface TransactionDetail { // 거래 λ‚΄μ—­ 정보 - detailId: string // 거래 λ‚΄μ—­ ID - product: { // κ±°λž˜ν•œ μ œν’ˆ 정보 - productId: string - title: string - price: number - description: string - tags: string[] - thumbnail: string | null - discountRate: number // μ œν’ˆ ν• μΈμœ¨ - } - reservation: Reservation | null // κ±°λž˜ν•œ μ œν’ˆμ˜ μ˜ˆμ•½ 정보 - timePaid: string // μ œν’ˆμ„ κ±°λž˜ν•œ μ‹œκ°„ - isCanceled: boolean // 거래 μ·¨μ†Œ μ—¬λΆ€ - done: boolean // 거래 μ™„λ£Œ μ—¬λΆ€ -} - -interface Reservation { - start: string // μ˜ˆμ•½ μ‹œμž‘ μ‹œκ°„ - end: string // μ˜ˆμ•½ μ’…λ£Œ μ‹œκ°„ - isCanceled: boolean // μ˜ˆμ•½ μ·¨μ†Œ μ—¬λΆ€ - isExpired: boolean // μ˜ˆμ•½ 만료 μ—¬λΆ€ -} -``` - -```json -[ - { - "detailId": "9jAoagzrZBkSWI5NctEB", - "product": { - "productId": "nbqtQvEivYwEXTDet7YM", - "title": "MacBook Pro 16", - "price": 3360000, - "description": "μ—­λŒ€ κ°€μž₯ κ°•λ ₯ν•œ MacBook Proκ°€ λ“±μž₯ν–ˆμŠ΅λ‹ˆλ‹€. 졜초의 ν”„λ‘œμš© Apple Silicon인 M1 Pro λ˜λŠ” M1 Max 칩을 νƒ‘μž¬ν•΄ μœμ‚΄κ°™μ΄ λΉ λ₯Έ μ†λ„λŠ” λ¬Όλ‘ , 획기적인 μ„±", - "tags": [ - "κ°€μ „", - "λ…ΈνŠΈλΆ", - "컴퓨터" - ], - "thumbnail": "https://storage.googleapis.com/heropy-api/vIKMk_jy4Yv195256.png", - "discountRate": 0 - }, - "reservation": null, - "timePaid": "2021-11-07T20:17:32.112Z", - "isCanceled": true, - "done": false - }, - { - "detailId": "dMhfxyrAupQP18OYmywy", - "product": { - "productId": "cFmeC7aY5KjZbBAdJE9y", - "title": "μ‚Όμ„±μ „μž 슀마트λͺ¨λ‹ˆν„° M7 S43AM700", - "price": 639000, - "description": "107.9cm(43인치) / μ™€μ΄λ“œ(16:9) / 평면 / VA / 3840 x 2160(4K UHD) / ν”½μ…€ν”ΌμΉ˜: 0.2451mm / 8ms(GTG) / 300cd / 5,00", - "tags": [ - "κ°€μ „", - "λͺ¨λ‹ˆν„°", - "컴퓨터" - ], - "thumbnail": "https://storage.googleapis.com/heropy-api/vBAK4MQdH5v195712.png", - "discountRate": 0 - }, - "reservation": { - "start": "2021-11-12T06:00:00.000Z", - "end": "2021-11-12T07:00:00.000Z", - "isCanceled": false, - "isExpired": true - }, - "timePaid": "2021-11-07T20:01:49.100Z", - "isCanceled": false, - "done": true - } -] -``` - -### 단일 μ œν’ˆ 상세 거래(ꡬ맀) λ‚΄μ—­ - -- μ‚¬μš©μž μ „μš© APIμž…λ‹ˆλ‹€. - -```curl -curl https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/transactions/detail - \ -X 'POST' - \ -H 'Authorization: Bearer ' -``` - -μš”μ²­ 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface RequestBody { - detailId: string // 상세 λ‚΄μš©μ„ 확인할 거래(ꡬ맀) λ‚΄μ—­ ID -} -``` - -```json -{ - "detailId": "dMhfxyrAupQP18OYmywy" -} -``` - -응닡 데이터 νƒ€μž… 및 μ˜ˆμ‹œ: - -```ts -interface TransactionDetail { // 상세 거래 정보 - detailId: string // 거래 λ‚΄μ—­ ID - account: { // κ±°λž˜ν•œ μ‚¬μš©μžμ˜ κ³„μ’Œ 정보 - bankName: string - bankCode: string - accountNumber: string - } - product: { // κ±°λž˜ν•œ μ œν’ˆ 정보 - productId: string - title: string - price: number - description: string - tags: string[] - thumbnail: string | null - photo: string | null - discountRate: number // μ œν’ˆ ν• μΈμœ¨ - } - reservation: Reservation | null // κ±°λž˜ν•œ μ œν’ˆμ˜ μ˜ˆμ•½ 정보 - timePaid: string // μ œν’ˆμ„ κ±°λž˜ν•œ μ‹œκ°„ - isCanceled: boolean // 거래 μ·¨μ†Œ μ—¬λΆ€ - done: boolean // 거래 μ™„λ£Œ μ—¬λΆ€ -} - -interface Reservation { - start: string // μ˜ˆμ•½ μ‹œμž‘ μ‹œκ°„ - end: string // μ˜ˆμ•½ μ’…λ£Œ μ‹œκ°„ - isCanceled: boolean // μ˜ˆμ•½ μ·¨μ†Œ μ—¬λΆ€ - isExpired: boolean // μ˜ˆμ•½ 만료 μ—¬λΆ€ -} -``` +### start project -```json -{ - "detailId": "dMhfxyrAupQP18OYmywy", - "account": { - "bankName": "KBꡭ민은행", - "bankCode": "004", - "accountNumber": "123-XX-XXXX-XXX" - }, - "product": { - "productId": "cFmeC7aY5KjZbBAdJE9y", - "title": "μ‚Όμ„±μ „μž 슀마트λͺ¨λ‹ˆν„° M7 S43AM700", - "price": 639000, - "description": "107.9cm(43인치) / μ™€μ΄λ“œ(16:9) / 평면 / VA / 3840 x 2160(4K UHD) / ν”½μ…€ν”ΌμΉ˜: 0.2451mm / 8ms(GTG) / 300cd / 5,000:1 / μ΅œλŒ€ μ£Όμ‚¬μœ¨: 60Hz / HDMI 2.0 / USB Type-C / ν”Œλ¦¬μ»€ 프리 / λΈ”λ£¨λΌμ΄νŠΈ 차단 / κ²Œμž„λͺ¨λ“œ 지원 / μŠ€ν”Όμ»€ / 리λͺ¨μ»¨ / USBν—ˆλΈŒ / Wi-Fi(무선) / 슀마트TV / λΈ”λ£¨νˆ¬μŠ€ / ν‹ΈνŠΈ(μƒν•˜) / 200 x 200mm / HDR / HDR10 / 10.6kg κΈ°νšμ „ μ°¨μ„ΈλŒ€ κ²Œμž„ 라이프 PS5 맀λ ₯뢄석 관련기사 νμ†Œλ‹‰, 43인치 4K UHD 슀마트 λͺ¨λ‹ˆν„° β€˜μ‚Όμ„±μ „μž M7 S43AM700’ μΆœμ‹œ 및 할인 행사 μ‚¬μš©κΈ° μ‚Όμ„± 슀마트λͺ¨λ‹ˆν„° m7 s43am700", - "tags": [ - "κ°€μ „", - "λͺ¨λ‹ˆν„°", - "컴퓨터" - ], - "thumbnail": "https://storage.googleapis.com/heropy-api/vBAK4MQdH5v195712.png", - "photo": "https://storage.googleapis.com/heropy-api/vVLP-ox_zSDv195712.jpg", - "discountRate": 0 - }, - "reservation": null, - "timePaid": "2021-11-07T20:01:49.100Z", - "isCanceled": false, - "done": true -} +```bash +$ npm run dev ``` diff --git a/index.html b/index.html new file mode 100644 index 00000000..f4e546ea --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + + 집 가ꡬ싢어(SweetHome) + + +
+ + + diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 00000000..ff1c0508 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,4 @@ +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..4d0ff359 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3756 @@ +{ + "name": "sweethome", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sweethome", + "version": "0.0.0", + "dependencies": { + "@types/react-router-dom": "^5.3.3", + "esbuild": "^0.18.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.45.0", + "react-icons": "^4.9.0", + "react-redux": "^8.1.0", + "react-router-dom": "^6.12.1", + "react-table": "^7.8.0", + "redux": "^4.2.1", + "redux-persist": "^6.0.0", + "swiper": "^9.4.1", + "vite-plugin-remove-console": "^2.1.1" + }, + "devDependencies": { + "@types/node": "^20.2.5", + "@types/react": "^18.2.12", + "@types/react-dom": "^18.0.11", + "@typescript-eslint/eslint-plugin": "^5.59.0", + "@typescript-eslint/parser": "^5.59.0", + "@vitejs/plugin-react": "^4.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.3.4", + "prettier": "^2.8.8", + "sass": "^1.62.1", + "typescript": "^5.0.2", + "vite": "^4.3.9" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.3.tgz", + "integrity": "sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.1.tgz", + "integrity": "sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.22.0", + "@babel/helper-compilation-targets": "^7.22.1", + "@babel/helper-module-transforms": "^7.22.1", + "@babel/helpers": "^7.22.0", + "@babel/parser": "^7.22.0", + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.3.tgz", + "integrity": "sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.3", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz", + "integrity": "sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.0", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz", + "integrity": "sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz", + "integrity": "sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.1", + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", + "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", + "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", + "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.3.tgz", + "integrity": "sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.4.tgz", + "integrity": "sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.21.0.tgz", + "integrity": "sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz", + "integrity": "sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz", + "integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.21.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.21.9.tgz", + "integrity": "sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/parser": "^7.21.9", + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.4.tgz", + "integrity": "sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.22.3", + "@babel/helper-environment-visitor": "^7.22.1", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.22.4", + "@babel/types": "^7.22.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.4.tgz", + "integrity": "sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.1.tgz", + "integrity": "sha512-8+QS98jqdreHLvCojIke8NjcuelB+Osysazr15EhkUIuG0Ov5WK26XgPYWViCTjHnKQxbpS86/JryBOkEpyrBA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.1.tgz", + "integrity": "sha512-l5V0IWGTYfQMzl4ulgIHKMZPwabIS4a39ZvtkPwL6LYiX3UtL76sylA6eFKufJCB43mwEYqbXoBSMn++NpxILw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.1.tgz", + "integrity": "sha512-1y8/bRek6EYxQeGTUfwL2mmj6NAeXZ3h5YSc4W2Y/kduI1B8VhT4x5X0VxrcGkIKef4N5qCdziRxvei/YUfESg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.1.tgz", + "integrity": "sha512-FFT/on9qQTOntdloQvgfwFkRhNI5l/TCNXZS1CpH6JQd0boR637aThi9g9FYs4o31Ao72/jPZFDiRln5Cu6R2A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.1.tgz", + "integrity": "sha512-p/ZIUt+NlW8qRNVTXoKJgRuc49teazvmXBquoGOm5D6IAimTfWJVJrEivqpoMKyDS/0/PxDMRM2lrkxlSa7XeQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.1.tgz", + "integrity": "sha512-eYUDR3thO96ULRf4rJcG9TJ/sQc6Z/YNe16mC/KvVeAOtzmeTXiPMETEv/iMqTCxZhYkHyQG/mYbAxPBWC2mcg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.1.tgz", + "integrity": "sha512-w03zjxyg51qktv0JKsV+AbY3uSb1Awifs8IkKQSUXdP3sdPxxmPzZLrlJ1+LjKZRiSa4yP/Haayi/hriNVoIdQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.1.tgz", + "integrity": "sha512-d6FXeb8F/cuXtSZuVHQN0Rz3gs3g2Xy/M4KJJRzbKsBx3pwCQuRdSrYxcr7g0PFN8geIOspiLQnUzwONyMA3Bw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.1.tgz", + "integrity": "sha512-dHlvkKAVlYNt5LPg1GUha99QiaEGKEC21zpHVAxs7hhW6EkR8nN3iWmyndGXxVJm4K7e4lKAzl8ekPqSr5gAXQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.1.tgz", + "integrity": "sha512-QHS4duBPuAsLZP82sNeoqTXAJ1mNU4QcfmYtBN/jNvQJXb6n0im8F4ljFSAQbivt1jl1OnKL8HhlLUeKY75nLg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.1.tgz", + "integrity": "sha512-g4YSiF/qBvXvJhSowxaR7Ei/79otL48Qfjviuo+FpXREykA9nSe407T5ZvezFXryFgdf44Fe8lWpjvtQ+n42cQ==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.1.tgz", + "integrity": "sha512-/G1fzmaR5u2S9wgQhiQEhWRct0+GMpuNjhll59uv5Tjojlma9MUPinVnvpw9Re+Idb6gxe6kmzUxFP2YkC/svg==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.1.tgz", + "integrity": "sha512-NkDjIvleUc3lSV1VI3QE9Oh5mz3nY11H5TCbi4DJ8X09FGwHN5pDVXdAsQYPGjlt/frXZzq6x7vMmTOb5VyBog==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.1.tgz", + "integrity": "sha512-IhN7Nz+HyDRnMQOLcCl6m5BgQMITMhS9O1hOqgAUIy6FI0m/0zTSkZHtvMmSIpOy1uleaGqfNDA9SM3nBeTATQ==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.1.tgz", + "integrity": "sha512-u9iRg0eUUIyBbg5hANvRBYRaAnhVemAA2+pi3IgrzQTMeR/uPHQtJI3XInNZkNR6ACA4Fdl8N941p81XygeqWQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.1.tgz", + "integrity": "sha512-0QeWU0a0+RmxPCDt+plXS7/hVMJtfde/LaSzs6X3UTr4FYA0hYpnwDzGXxumcPLzt5c8ctugPuKat0tmRb7noQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.1.tgz", + "integrity": "sha512-eFL7sxibN8wKuwrRf3HRkcjELALlfl/TavJ8P4J+BJfnkXOITF7zx4tTmGkzK8OwAR9snT2kEfp1Ictc80atGw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.1.tgz", + "integrity": "sha512-riCQUnngF2xYUzr0XDdrGEEz0nqnocch0No7SBIQM22wTRi5gt5WqTQexGd/2u2Z19d0rVYQbKBelaJ1dwe9bg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.1.tgz", + "integrity": "sha512-6lTop2k+GMkWlrwMy2+55xIBXKfXOi6uzWYypXZZP8HxXG3Mb5N4O71z2KzisVNJtYK2VlQRNbmGtUzIQIhHAw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.1.tgz", + "integrity": "sha512-d6wt4g9GluZp7xCmgpm7gY6wy0mjcBHbKeeK9MYrlWNFJd8KBcD2uCil8kFuaH3Dt6AUz62D0wIoDETFsZ01Tg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.1.tgz", + "integrity": "sha512-z51DOtcwECu4WlqJUhu39AVnnpaVmTvXei0EQxc99QK7ZJyn4tj0EelYkMBZckpqzqB/GyGSLwEclKtRJ0l2uw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.1.tgz", + "integrity": "sha512-6tdeuCLT+l9QuCFaYsNtULO6xH2fgJObvICMCsOZvkqIey6FUXVVju5aO+OZjxswy7WgKadhI1k/nq2wQSmB+Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", + "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@remix-run/router": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.3.tgz", + "integrity": "sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.2.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", + "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.12.tgz", + "integrity": "sha512-ndmBMLCgn38v3SntMeoJaIrO6tGHYKMEBohCUmw8HoLLQdRMOIGXfeYaBTLe2lsFaSB3MOK1VXscYFnmLtTSmw==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", + "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", + "devOptional": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.8.tgz", + "integrity": "sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.8", + "@typescript-eslint/type-utils": "5.59.8", + "@typescript-eslint/utils": "5.59.8", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.8.tgz", + "integrity": "sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.59.8", + "@typescript-eslint/types": "5.59.8", + "@typescript-eslint/typescript-estree": "5.59.8", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.8.tgz", + "integrity": "sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.8", + "@typescript-eslint/visitor-keys": "5.59.8" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.8.tgz", + "integrity": "sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.59.8", + "@typescript-eslint/utils": "5.59.8", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.8.tgz", + "integrity": "sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.8.tgz", + "integrity": "sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.8", + "@typescript-eslint/visitor-keys": "5.59.8", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.8.tgz", + "integrity": "sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.8", + "@typescript-eslint/types": "5.59.8", + "@typescript-eslint/typescript-estree": "5.59.8", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz", + "integrity": "sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.8", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.0.tgz", + "integrity": "sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.4", + "@babel/plugin-transform-react-jsx-self": "^7.21.0", + "@babel/plugin-transform-react-jsx-source": "^7.19.6", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz", + "integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001489", + "electron-to-chromium": "^1.4.411", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001494", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001494.tgz", + "integrity": "sha512-sY2B5Qyl46ZzfYDegrl8GBCzdawSLT4ThM9b9F+aDYUrAG2zCOyMbd2Tq34mS1g4ZKBfjRlzOohQMxx28x6wJg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.419", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.419.tgz", + "integrity": "sha512-jdie3RiEgygvDTyS2sgjq71B36q2cDSBfPlwzUyuOrfYTNoYWyBxxjGJV/HAu3A2hB0Y+HesvCVkVAFoCKwCSw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.1.tgz", + "integrity": "sha512-ZUvsIx2wPjpj86b805UwbGJu47Kxgr2UTio9rGhWpBr1oOVk88exzoieOgTweX0UcVCwSAk3z2WvNALpTcpQZw==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.1", + "@esbuild/android-arm64": "0.18.1", + "@esbuild/android-x64": "0.18.1", + "@esbuild/darwin-arm64": "0.18.1", + "@esbuild/darwin-x64": "0.18.1", + "@esbuild/freebsd-arm64": "0.18.1", + "@esbuild/freebsd-x64": "0.18.1", + "@esbuild/linux-arm": "0.18.1", + "@esbuild/linux-arm64": "0.18.1", + "@esbuild/linux-ia32": "0.18.1", + "@esbuild/linux-loong64": "0.18.1", + "@esbuild/linux-mips64el": "0.18.1", + "@esbuild/linux-ppc64": "0.18.1", + "@esbuild/linux-riscv64": "0.18.1", + "@esbuild/linux-s390x": "0.18.1", + "@esbuild/linux-x64": "0.18.1", + "@esbuild/netbsd-x64": "0.18.1", + "@esbuild/openbsd-x64": "0.18.1", + "@esbuild/sunos-x64": "0.18.1", + "@esbuild/win32-arm64": "0.18.1", + "@esbuild/win32-ia32": "0.18.1", + "@esbuild/win32-x64": "0.18.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", + "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.42.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.3.5.tgz", + "integrity": "sha512-61qNIsc7fo9Pp/mju0J83kzvLm0Bsayu7OQSLEoJxLDCBjIIyb87bkzufoOvdDxLkSlMfkF7UxomC4+eztUBSA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "dev": true, + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-hook-form": { + "version": "7.45.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.45.0.tgz", + "integrity": "sha512-AbHeZ4ad+0dEIknSW9dBgIwcvRDfZ1O97sgj75WaMdOX0eg8TBiUf9wxzVkIjZbk76BBIE9lmFOzyD4PN80ZQg==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/react-icons": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.9.0.tgz", + "integrity": "sha512-ijUnFr//ycebOqujtqtV9PFS7JjhWg0QU6ykURVHuL4cbofvRCf3f6GMn9+fBktEFQOIVZnuAYLZdiyadRQRFg==", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/react-redux": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.0.tgz", + "integrity": "sha512-CtHZzAOxi7GQvTph4dVLWwZHAWUjV2kMEQtk50OrN8z3gKxpWg3Tz7JfDw32N3Rpd7fh02z73cF6yZkK467gbQ==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@reduxjs/toolkit": "^1 || ^2.0.0-beta.0", + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4 || ^5.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@reduxjs/toolkit": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.12.1.tgz", + "integrity": "sha512-evd/GrKJOeOypD0JB9e1r7pQh2gWCsTbUfq059Wm1AFT/K2MNZuDo19lFtAgIhlBrp0MmpgpqtvZC7LPAs7vSw==", + "dependencies": { + "@remix-run/router": "1.6.3" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.12.1.tgz", + "integrity": "sha512-POIZN9UDKWwEDga054LvYr2KnK8V+0HR4Ny4Bwv8V7/FZCPxJgsCjYxXGxqxzHs7VBxMKZfgvtKhafuJkJSPGA==", + "dependencies": { + "@remix-run/router": "1.6.3", + "react-router": "6.12.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-table": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", + "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17.0.0-0 || ^18.0.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "peerDependencies": { + "redux": ">4.0.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.23.1.tgz", + "integrity": "sha512-ybRdFVHOoljGEFILHLd2g/qateqUdjE6YS41WXq4p3C/WwD3xtWxV4FYWETA1u9TeXQc5K8L8zHE5d/scOvrOQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sass": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz", + "integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssr-window": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.2.tgz", + "integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/swiper": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-9.4.1.tgz", + "integrity": "sha512-1nT2T8EzUpZ0FagEqaN/YAhRj33F2x/lN6cyB0/xoYJDMf8KwTFT3hMOeoB8Tg4o3+P/CKqskP+WX0Df046fqA==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/swiperjs" + }, + { + "type": "open_collective", + "url": "http://opencollective.com/swiper" + } + ], + "dependencies": { + "ssr-window": "^4.0.2" + }, + "engines": { + "node": ">= 4.7.0" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", + "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/vite": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", + "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", + "dev": true, + "dependencies": { + "esbuild": "^0.17.5", + "postcss": "^8.4.23", + "rollup": "^3.21.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-remove-console": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vite-plugin-remove-console/-/vite-plugin-remove-console-2.1.1.tgz", + "integrity": "sha512-AQOsKl9+1YO82otwSchf+P8SRo4RhMvPjOvjm9DXOnkff0SBwBPAzazEn06IUjhsm/zX4miMgicCQH1hPdktrw==" + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..ebd10fbb --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name": "sweethome", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "start": "npm run build && node main.tsx", + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@types/react-router-dom": "^5.3.3", + "esbuild": "^0.18.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.45.0", + "react-icons": "^4.9.0", + "react-redux": "^8.1.0", + "react-router-dom": "^6.12.1", + "react-table": "^7.8.0", + "redux": "^4.2.1", + "redux-persist": "^6.0.0", + "swiper": "^9.4.1", + "vite-plugin-remove-console": "^2.1.1" + }, + "devDependencies": { + "@types/node": "^20.2.5", + "@types/react": "^18.2.12", + "@types/react-dom": "^18.0.11", + "@typescript-eslint/eslint-plugin": "^5.59.0", + "@typescript-eslint/parser": "^5.59.0", + "@vitejs/plugin-react": "^4.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.3.4", + "prettier": "^2.8.8", + "sass": "^1.62.1", + "typescript": "^5.0.2", + "vite": "^4.3.9" + } +} diff --git a/public/.DS_Store b/public/.DS_Store new file mode 100644 index 00000000..ff082eed Binary files /dev/null and b/public/.DS_Store differ diff --git a/public/assets/.DS_Store b/public/assets/.DS_Store new file mode 100644 index 00000000..286d035d Binary files /dev/null and b/public/assets/.DS_Store differ diff --git a/public/assets/Home/main1.jpeg b/public/assets/Home/main1.jpeg new file mode 100644 index 00000000..d3769882 Binary files /dev/null and b/public/assets/Home/main1.jpeg differ diff --git a/public/assets/Home/main2.jpeg b/public/assets/Home/main2.jpeg new file mode 100644 index 00000000..39b7ab00 Binary files /dev/null and b/public/assets/Home/main2.jpeg differ diff --git a/public/assets/Home/main3.jpeg b/public/assets/Home/main3.jpeg new file mode 100644 index 00000000..42ade555 Binary files /dev/null and b/public/assets/Home/main3.jpeg differ diff --git a/public/assets/README/01_main.png b/public/assets/README/01_main.png new file mode 100644 index 00000000..c6ed1970 Binary files /dev/null and b/public/assets/README/01_main.png differ diff --git a/public/assets/README/02_about.png b/public/assets/README/02_about.png new file mode 100644 index 00000000..39a632b8 Binary files /dev/null and b/public/assets/README/02_about.png differ diff --git a/public/assets/README/03_shop.png b/public/assets/README/03_shop.png new file mode 100644 index 00000000..95c2c1d8 Binary files /dev/null and b/public/assets/README/03_shop.png differ diff --git a/public/assets/README/04_shopdetail.png b/public/assets/README/04_shopdetail.png new file mode 100644 index 00000000..67aac1ac Binary files /dev/null and b/public/assets/README/04_shopdetail.png differ diff --git a/public/assets/README/05_cart.png b/public/assets/README/05_cart.png new file mode 100644 index 00000000..54c8495c Binary files /dev/null and b/public/assets/README/05_cart.png differ diff --git a/public/assets/README/06_buy.png b/public/assets/README/06_buy.png new file mode 100644 index 00000000..5087e95f Binary files /dev/null and b/public/assets/README/06_buy.png differ diff --git a/public/assets/README/07_mypage_order.png b/public/assets/README/07_mypage_order.png new file mode 100644 index 00000000..2f569ab0 Binary files /dev/null and b/public/assets/README/07_mypage_order.png differ diff --git a/public/assets/README/08_mypage_orderdetail.png b/public/assets/README/08_mypage_orderdetail.png new file mode 100644 index 00000000..cc6c800c Binary files /dev/null and b/public/assets/README/08_mypage_orderdetail.png differ diff --git a/public/assets/README/09_mypage_account.png b/public/assets/README/09_mypage_account.png new file mode 100644 index 00000000..458e3e27 Binary files /dev/null and b/public/assets/README/09_mypage_account.png differ diff --git a/public/assets/README/0_loading.png b/public/assets/README/0_loading.png new file mode 100644 index 00000000..58f42fb2 Binary files /dev/null and b/public/assets/README/0_loading.png differ diff --git a/public/assets/README/0_login.png b/public/assets/README/0_login.png new file mode 100644 index 00000000..e3c1cd3c Binary files /dev/null and b/public/assets/README/0_login.png differ diff --git a/public/assets/README/0_search.png b/public/assets/README/0_search.png new file mode 100644 index 00000000..82e75f45 Binary files /dev/null and b/public/assets/README/0_search.png differ diff --git a/public/assets/README/0_signup.png b/public/assets/README/0_signup.png new file mode 100644 index 00000000..166ab079 Binary files /dev/null and b/public/assets/README/0_signup.png differ diff --git a/public/assets/README/10_mypage_account_modal.png b/public/assets/README/10_mypage_account_modal.png new file mode 100644 index 00000000..834a03b5 Binary files /dev/null and b/public/assets/README/10_mypage_account_modal.png differ diff --git a/public/assets/README/11_mypage_accountlist.png b/public/assets/README/11_mypage_accountlist.png new file mode 100644 index 00000000..bc09ee68 Binary files /dev/null and b/public/assets/README/11_mypage_accountlist.png differ diff --git a/public/assets/README/12_mypage_info.png b/public/assets/README/12_mypage_info.png new file mode 100644 index 00000000..90de1b34 Binary files /dev/null and b/public/assets/README/12_mypage_info.png differ diff --git a/public/assets/README/13_mypage_infochange.png b/public/assets/README/13_mypage_infochange.png new file mode 100644 index 00000000..f45b90b4 Binary files /dev/null and b/public/assets/README/13_mypage_infochange.png differ diff --git a/public/assets/README/14_admin_user.png b/public/assets/README/14_admin_user.png new file mode 100644 index 00000000..e940bbc4 Binary files /dev/null and b/public/assets/README/14_admin_user.png differ diff --git a/public/assets/README/15_admin_product.png b/public/assets/README/15_admin_product.png new file mode 100644 index 00000000..404471ab Binary files /dev/null and b/public/assets/README/15_admin_product.png differ diff --git a/public/assets/README/16_admin_orderlist.png b/public/assets/README/16_admin_orderlist.png new file mode 100644 index 00000000..66d4a905 Binary files /dev/null and b/public/assets/README/16_admin_orderlist.png differ diff --git a/public/assets/about/about.jpeg b/public/assets/about/about.jpeg new file mode 100644 index 00000000..75e4c3d9 Binary files /dev/null and b/public/assets/about/about.jpeg differ diff --git a/public/assets/bank/bank004.svg b/public/assets/bank/bank004.svg new file mode 100644 index 00000000..bc771df0 --- /dev/null +++ b/public/assets/bank/bank004.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/bank/bank011.svg b/public/assets/bank/bank011.svg new file mode 100644 index 00000000..468fe384 --- /dev/null +++ b/public/assets/bank/bank011.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/assets/bank/bank020.svg b/public/assets/bank/bank020.svg new file mode 100644 index 00000000..ce2e479e --- /dev/null +++ b/public/assets/bank/bank020.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/bank/bank081.svg b/public/assets/bank/bank081.svg new file mode 100644 index 00000000..31680943 --- /dev/null +++ b/public/assets/bank/bank081.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/assets/bank/bank088.svg b/public/assets/bank/bank088.svg new file mode 100644 index 00000000..073094d3 --- /dev/null +++ b/public/assets/bank/bank088.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/bank/bank089.svg b/public/assets/bank/bank089.svg new file mode 100644 index 00000000..fa045f4f --- /dev/null +++ b/public/assets/bank/bank089.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/bank/bank090.svg b/public/assets/bank/bank090.svg new file mode 100644 index 00000000..4526c79c --- /dev/null +++ b/public/assets/bank/bank090.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/assets/products/kitchen1-detail.jpeg b/public/assets/products/kitchen1-detail.jpeg new file mode 100644 index 00000000..febc85f4 Binary files /dev/null and b/public/assets/products/kitchen1-detail.jpeg differ diff --git a/public/assets/products/kitchen1.jpg b/public/assets/products/kitchen1.jpg new file mode 100644 index 00000000..dfdb83e7 Binary files /dev/null and b/public/assets/products/kitchen1.jpg differ diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 00000000..9b824b54 Binary files /dev/null and b/public/favicon.png differ diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 00000000..9d327c6b Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/App.scss b/src/App.scss new file mode 100644 index 00000000..67232021 --- /dev/null +++ b/src/App.scss @@ -0,0 +1,4 @@ +.allSections { + min-height: calc(100vh - (100px + 150px)); + width: 100%; +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 00000000..a1c3b6c8 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,37 @@ +import { useCallback, useEffect } from 'react'; +import { Outlet, useLocation } from 'react-router-dom'; +import { useDispatch } from "react-redux"; +import { authenticate } from '~/api/requests'; +import TheHeader from '~/components/common/TheHeader'; +import TheFooter from '~/components/common/TheFooter'; +import '~/App.scss'; + +const App = () => { + const location = useLocation(); + const dispatch = useDispatch() + + useEffect(()=> { + authenticateHandler(); + }, [location]) + + const authenticateHandler = useCallback(async () => { + try { + const res = await authenticate() + dispatch({ type: "RETURN_USER", payload: res }) + } catch (err) { + console.log("둜그인 인증 μ‹€νŒ¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€.") + } + }, [location]) + + return ( + <> + +
+ +
+ + + ) +}; + +export default App; \ No newline at end of file diff --git a/src/api/requests.ts b/src/api/requests.ts new file mode 100644 index 00000000..8707c656 --- /dev/null +++ b/src/api/requests.ts @@ -0,0 +1,436 @@ +// Common Header +const headers = { + "content-type": "application/json", + apikey: import.meta.env.VITE_API_KEY, + username: import.meta.env.VITE_USER_NAME, +}; + +// Common Interface +interface User { + email: string; + password: string; +} + +// Transactions Interface +interface TransactionsBody { + detailId: string; +} + +// Sign-Up νšŒμ›κ°€μž… +interface SignUpBody extends User { + displayName: string; +} + +const signUp = async (body: SignUpBody) => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/auth/signup`, { + method: "POST", + headers, + body: JSON.stringify(body), + }); + if (res.status === 200) { + const data = await res.json(); + return data; + } else return false; +}; + +// Log-In 둜그인 +const logIn = async (body: User) => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/auth/login`, { + method: "POST", + headers, + body: JSON.stringify(body), + }); + + if (res.status === 200) { + const json = await res.json(); + localStorage.setItem("token", json.accessToken); + return json; + } else return false; +}; + +// Log-Out λ‘œκ·Έμ•„μ›ƒ +const logOut = async () => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/auth/logout`, { + method: "POST", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + if (res.status === 200) { + await res.json(); + window.localStorage.clear(); + } else return false; +}; + +// 둜그인 인증 +const authenticate = async () => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/auth/me`, { + method: "POST", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + if (res.status === 200) { + const json = await res.json(); + return json; + } else return false; +}; + +// Users μ‚¬μš©μž λͺ©λ‘ +const users = async () => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/auth/users`, { + method: "GET", + headers: { + ...headers, + masterKey: "true", + }, + }); + const data = await res.json(); + return data; +}; + +// Add-Product μƒν’ˆλ“±λ‘ +interface AddProductBody { + title: string; + price: number; + description: string; + tags?: string; + thumbnailBase64?: string; + photoBase64?: string; + discountRate?: number; +} + +const addProduct = async (body: AddProductBody) => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/products`, { + method: "POST", + headers: { + "content-type": "application/json", + apikey: "KDT5_nREmPe9B", + username: "KDT5_Team9", + masterKey: "true", + }, + body: JSON.stringify(body), + }); + if (res.status === 200) { + const data = await res.json(); + return data; + } else return false; +}; + +// Get-All-Products 전체 μƒν’ˆ 쑰회 +const getAllProducts = async () => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/products`, { + method: "GET", + headers: { + ...headers, + masterKey: "true", + }, + }); + const data = await res.json(); + return data.map((data: any) => ({ ...data, isChecked: false })); +}; + +// Get-Product 단일 μƒν’ˆ 쑰회 +const getProduct = async (id: string) => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/products/${id}`, { + method: "GET", + headers, + }); + const data = await res.json(); + return data; +}; + +// Edit-Product μƒν’ˆ μˆ˜μ • +interface EditProductBody { + title?: string; + price?: number; + description?: string; + tags?: string[]; + thumbnailBase64?: string; + photoBase64?: string; + isSoldOut?: boolean; + discountRate?: number; +} + +const editProduct = async (body: EditProductBody, id: string) => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/products/${id}`, { + method: "PUT", + headers: { + ...headers, + masterKey: "true", + }, + body: JSON.stringify(body), + }); + const data = await res.json(); + return data; +}; + +// Delete-Product μƒν’ˆ μ‚­μ œ +const deleteProduct = async (id: string) => { + const res = await fetch( + `https://asia-northeast3-heropy-api.cloudfunctions.net/api/products/${id}`, + { + method: "DELETE", + headers: { + ...headers, + masterKey: "true", + }, + } + ); + if (res.status === 200) { + const data = await res.json(); + return data; + } else return false; +}; + +// 개인 정보 μˆ˜μ • +interface EditInfoBody { + displayName?: string; + profileImgBase64?: string; + oldPassword?: string; + newPassword?: string; +} + +const editInfo = async (body: EditInfoBody) => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/auth/user`, { + method: "PUT", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(body), + }); + if (res.status === 200) { + const data = await res.json(); + return data; + } else return false; +}; + +// 선택 κ°€λŠ₯ν•œ 은행 λͺ©λ‘ 쑰회 +const getBankList = async () => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/account/banks`, { + method: "GET", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + const data = await res.json(); + return data; +}; + +// κ³„μ’Œ λͺ©λ‘ 및 μž”μ•‘ 쑰회 +const getAccountList = async () => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/account`, { + method: "GET", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + const data = await res.json(); + return data; +}; + +// κ³„μ’Œ μ—°κ²° +interface linkAccountBody { + bankCode: string; + accountNumber: string; + phoneNumber: string; + signature: boolean; +} + +const linkAccount = async (body: linkAccountBody) => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/account`, { + method: "POST", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(body), + }); + if (res.status === 200) { + const data = await res.json(); + return data; + } else return false; +}; + +// κ³„μ’Œ 해지 +interface DeleteAccountBody { + accountId: string; + signature: boolean; +} + +const deleteAccount = async (body: DeleteAccountBody) => { + await fetch(`${import.meta.env.VITE_API_BASE}/account`, { + method: "DELETE", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(body), + }); +}; + +// μƒν’ˆ 거래 μ‹ μ²­ (ꡬ맀) + +interface BuyProductBody { + productId: string; // κ±°λž˜ν•  μ œν’ˆ ID (ν•„μˆ˜!) + accountId: string; // κ²°μ œν•  μ‚¬μš©μž κ³„μ’Œ ID (ν•„μˆ˜!) + reservation?: { + // μ˜ˆμ•½ 정보(μ˜ˆμ•½ μ‹œμŠ€ν…œμ„ μ‚¬μš©ν•˜λŠ” 경우만 ν•„μš”) + start: string; // μ˜ˆμ•½ μ‹œμž‘ μ‹œκ°„(ISO) + end: string; // μ˜ˆμ•½ μ’…λ£Œ μ‹œκ°„(ISO) + }; +} +const buyProduct = async (body: BuyProductBody) => { + try { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/products/buy`, { + method: "POST", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(body), + }); + if (res.status === 200) { + const data = await res.json(); + return data; + } else return res.status; + } catch (error) { + alert("결제 μ‹€νŒ¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€.") + } +}; + +// μƒν’ˆ 거래 μ·¨μ†Œ +const cancelTransaction = async (body: TransactionsBody) => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/products/cancel`, { + method: "POST", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(body), + }); + if (res.status === 200) { + const data = await res.json(); + return data; + } else return false; +}; + +// μƒν’ˆ 거래 ν™•μ • +const confirmedTransaction = async (body: TransactionsBody) => { + const res = await fetch(`${import.meta.env.VITE_API_BASE}/products/ok`, { + method: "POST", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(body), + }); + + if (res.status === 200) { + const data = await res.json(); + return data; + } else return false; +}; + +// 전체 거래 λ‚΄μ—­ (μ‚¬μš©μž) +const getAllTransactions = async () => { + const res = await fetch( + `${import.meta.env.VITE_API_BASE}/products/transactions/details`, + { + method: "GET", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + } + ); + if (res.status === 200) { + const data = await res.json(); + return data; + } else return false; +}; + +// 단일 거래 λ‚΄μ—­ (μ‚¬μš©μž) +const getTransaction = async (body: TransactionsBody) => { + const res = await fetch( + `${import.meta.env.VITE_API_BASE}/products/transactions/detail`, + { + method: "POST", + headers: { + ...headers, + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(body), + } + ); + const data = await res.json(); + return data; +}; + +// 전체 거래 λ‚΄μ—­ (κ΄€λ¦¬μž) +const adminAllTransactions = async () => { + const res = await fetch( + `${import.meta.env.VITE_API_BASE}/products/transactions/all`, + { + method: "GET", + headers: { + ...headers, + masterKey: "true", + }, + } + ); + const data = await res.json(); + return data; +}; + +// 거래 λ‚΄μ—­ 관리 - μ™„λ£Œ, μ·¨μ†Œ, ν•΄μ œ (κ΄€λ¦¬μž) +interface adminTransactionsBody { + isCanceled?: boolean; + done?: boolean; +} + +const adminTransactions = async (id: string, body: adminTransactionsBody) => { + const res = await fetch( + `${import.meta.env.VITE_API_BASE}/products/transactions/${id}`, + { + method: "PUT", + headers: { + ...headers, + masterKey: "true", + }, + body: JSON.stringify(body), + } + ); + const data = await res.json(); + return data; +}; + +export { + signUp, + logIn, + logOut, + authenticate, + users, + addProduct, + getAllProducts, + getProduct, + editProduct, + deleteProduct, + editInfo, + getBankList, + getAccountList, + linkAccount, + deleteAccount, + buyProduct, + cancelTransaction, + confirmedTransaction, + getAllTransactions, + getTransaction, + adminAllTransactions, + adminTransactions, +}; diff --git a/src/components/.DS_Store b/src/components/.DS_Store new file mode 100644 index 00000000..daf165e2 Binary files /dev/null and b/src/components/.DS_Store differ diff --git a/src/components/Cart/CartList.tsx b/src/components/Cart/CartList.tsx new file mode 100644 index 00000000..612b0989 --- /dev/null +++ b/src/components/Cart/CartList.tsx @@ -0,0 +1,105 @@ +import { useEffect } from "react"; +import { Link } from "react-router-dom"; +import { useSelector, useDispatch } from "react-redux"; +import { TfiClose } from "react-icons/tfi"; +import { convertPrice, priceBeforeDiscount } from "~/utils/convert"; +import styles from "~/styles/Cart/CartList.module.scss"; + +const CartList = () => { + const cart = useSelector((state: any) => state.cart); + const selectedCart = useSelector((state: any) => state.selectedCart); + const myCart = [...cart]; + const mySelectedCart = [...selectedCart]; + const dispatch = useDispatch(); + + // μž₯λ°”κ΅¬λ‹ˆ μ‚­μ œ + const deleteCartItemHandler = (i: number, item: any) => { + myCart.splice(i, 1); + + dispatch({ type: "RETURN_CART", items: myCart }); + dispatch({ + type: "DELETE_SELECTED_CART", + items: mySelectedCart.filter(obj => obj.id !== item.id) + }); + return cart; + }; + + const checkedCartItemHandler = ( + e: React.ChangeEvent, + item: any + ) => { + e.target.checked + ? dispatch({ type: "CHECKED_SELECTED_CART", items: item }) + : dispatch({ + type: "DELETE_SELECTED_CART", + items: mySelectedCart.filter(obj => obj.id !== item.id) + }); + }; + + useEffect(() => { + dispatch({ type: "REFRESH" }); + }, []); + + return ( +
+
    + {myCart.map((item, i: number) => ( +
  • + checkedCartItemHandler(e, item)} + /> + +
    + {item.title} +
    + + +
    + {item.title} +
    +
    + {item.quantity}개 +
    +
    + + {item.discountRate !== 0 + ? `${convertPrice(item.price)}원` + : `${convertPrice( + priceBeforeDiscount(item.price, item.discountRate) + )}원`} + + + {item.discountRate !== 0 + ? `${convertPrice( + priceBeforeDiscount(item.price, item.discountRate) + )}원` + : ""} + +
    +
    + {item.discountRate !== 0 + ? convertPrice(item.price * item.quantity) + : convertPrice( + priceBeforeDiscount(item.price, item.discount) * item.quantity) + }원 +
    +
    + deleteCartItemHandler(i, item)} /> +
    +
  • + ))} +
+
+ ); +}; + +export default CartList; diff --git a/src/components/MyPage/MyBankAccount/Account.tsx b/src/components/MyPage/MyBankAccount/Account.tsx new file mode 100644 index 00000000..8f26786d --- /dev/null +++ b/src/components/MyPage/MyBankAccount/Account.tsx @@ -0,0 +1,52 @@ +import { SlClose } from 'react-icons/sl'; +import { deleteAccount } from '~/api/requests'; +import { convertPrice } from '~/utils/convert'; +import styles from '~/styles/Mypage/Account.module.scss'; + +interface AccountProps { + item: Bank; + watch: boolean; + setWatch: React.Dispatch>; +} +interface Bank { + id: string + bankName: string + bankCode: string + accountNumber: string + balance: number +} + +const Account = ({ + item, watch, setWatch +}: AccountProps) => { + + // κ³„μ’Œ 해지 + const deleteAcountHandler = async (id: string) => { + const body ={ + accountId: id, + signature: true + } + try { + await deleteAccount(body); + alert("ν•΄λ‹Ή κ³„μ’Œκ°€ μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.") + setWatch(!watch) + } catch (error) { + alert('κ³„μ’Œ 해지 μ‹€νŒ¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€.') + } + } + + return ( +
+
+
+

{item.bankName}

+

{item.accountNumber}

+ {`μž”μ•‘: ${convertPrice(item.balance)}원`} +
+ deleteAcountHandler(item.id)} /> +
+
+ ) +} + +export default Account; \ No newline at end of file diff --git a/src/components/MyPage/MyBankAccount/AccountModal.tsx b/src/components/MyPage/MyBankAccount/AccountModal.tsx new file mode 100644 index 00000000..f0fb6a08 --- /dev/null +++ b/src/components/MyPage/MyBankAccount/AccountModal.tsx @@ -0,0 +1,171 @@ +import { useState } from 'react'; +import { linkAccount } from '~/api/requests'; +import Loading from '~/components/common/Loading'; +import styles from '~/styles/Mypage/AccountModal.module.scss'; + +interface AccountModalProps { + bankList: Bank[]; + watch: boolean; + setWatch: React.Dispatch> + showModal: boolean; + setShowModal: React.Dispatch> + +} + +interface Bank { // 선택 κ°€λŠ₯ν•œ 은행 정보 + name: string // 은행 이름 + code: string // 은행 μ½”λ“œ + digits: number[] // 은행 κ³„μ’Œ 자릿수 + disabled: boolean // μ‚¬μš©μžκ°€ μΆ”κ°€ν•œ κ³„μ’Œ μ—¬λΆ€ +} + +interface InputState { + input0: string; + input1: string; + input2: string; + input3: string; + phone0: string; + phone1: string; + phone2: string; +} + +const AccountModal = ({ + bankList, + watch, + setWatch, + showModal, + setShowModal +}: AccountModalProps) => { + const [bankCode, setBankCode] = useState('') + const [isAgree, setIsAgree] = useState(false) + const [bankIDX, setBankIDX] = useState(0) + const [isLoading, setIsLoading] = useState(false) + const [accountInput, setAccountInput] = useState({ + input0: '', + input1: '', + input2: '', + input3: '', + phone0: '', + phone1: '', + phone2: '' + }) + + // Input onChange Handler + const accountOnChangeHandler = (e: React.ChangeEvent) => { + const { value, name } = e.target; + setAccountInput({ + ...accountInput, + [name]: value + }) +} + + // κ³„μ’Œ 등둝 + const enrollAccount = async (e: React.MouseEvent) => { + e.preventDefault(); + setIsLoading(true); + const body = { + bankCode, + accountNumber: accountInput.input0 + accountInput.input1 + accountInput.input2+ accountInput.input3, + phoneNumber: accountInput.phone0+ accountInput.phone1 + accountInput.phone2, + signature: isAgree, + } + const res = await linkAccount(body); + if (res) { + console.log('κ³„μ’Œ μ—°κ²° 정보', res); + alert('κ³„μ’Œκ°€ μ—°κ²°λ˜μ—ˆμŠ΅λ‹ˆλ‹€.'); + setShowModal(!showModal); + setWatch(!watch) + } else alert('κ³„μ’Œ 정보λ₯Ό λ‹€μ‹œ 확인해 μ£Όμ„Έμš”.') + setIsLoading(false); + } + + return ( + <> + {isLoading ? : null} +
+
+
+

κ³„μ’Œ μ—°κ²°

+
+
    + {bankList.map((bank, idx) => ( +
  • { + setBankIDX(idx); + setBankCode(bank.code); + }} + > + +

    {bank.name}

    +
  • + ) + )} +
+ { bankCode ? null :

은행을 선택해 μ£Όμ„Έμš”.

} +
+ + + +
+
+ + setIsAgree(!isAgree)} + /> +

μœ„ 약관에 λ™μ˜ν•©λ‹ˆλ‹€.

+
+
+ + +
+
+
+ + + ) +} + +export default AccountModal diff --git a/src/components/Shop/ProductItem.tsx b/src/components/Shop/ProductItem.tsx new file mode 100644 index 00000000..0fc8be0f --- /dev/null +++ b/src/components/Shop/ProductItem.tsx @@ -0,0 +1,35 @@ +import { Link } from "react-router-dom"; +import { priceBeforeDiscount, convertPrice } from "~/utils/convert"; +import styles from "~/styles/Shop/ProductItem.module.scss"; + + +const ProductItem = ({ product }: any) => { + return ( + +
  • +
    + +
    +
    + {product.title} +
    +
    +

    + {product.discountRate ? `${product.discountRate}%` : ""} +

    + + β‚©{convertPrice((product.price))} + +

    + {product.discountRate + ? `β‚©${convertPrice(priceBeforeDiscount(product.price, product.discountRate))}` + : "" + } +

    +
    +
  • + + ); +}; + +export default ProductItem; diff --git a/src/components/common/Loading.tsx b/src/components/common/Loading.tsx new file mode 100644 index 00000000..264e214a --- /dev/null +++ b/src/components/common/Loading.tsx @@ -0,0 +1,21 @@ +import { GiHouse } from 'react-icons/gi'; +import { LuCar } from 'react-icons/lu'; +import styles from '~/styles/Loading.module.scss'; + +const Loading = () => { + return ( +
    +
    +
    + +
    +
    + +
    +
    +
    + + ) +} + +export default Loading; \ No newline at end of file diff --git a/src/components/common/Select.tsx b/src/components/common/Select.tsx new file mode 100644 index 00000000..aa361337 --- /dev/null +++ b/src/components/common/Select.tsx @@ -0,0 +1,29 @@ +interface SelectProps { + name: string; + options: { name: string, value: string }[]; + onChange: (event: React.ChangeEvent) => void; + value: any; +} + +const Select = ({ + options, + onChange, + value, + name +}: SelectProps) => { + return ( + + ) +} + +export default Select; \ No newline at end of file diff --git a/src/components/common/SubNav.tsx b/src/components/common/SubNav.tsx new file mode 100644 index 00000000..246df62c --- /dev/null +++ b/src/components/common/SubNav.tsx @@ -0,0 +1,33 @@ +import styles from "~/styles/SubNav.module.scss"; + +interface SubNavProps { + subNav: string[] + setCategory: React.Dispatch> +} + +const SubNav = ({ subNav, setCategory }: SubNavProps) => { + + return ( + <> +
    +
    + {subNav.map((category: string) => ( +
    { + setCategory(category)} + } + > + + {category} + +
    + ))} +
    +
    + + ); +}; + + +export default SubNav; diff --git a/src/components/common/TheFooter.tsx b/src/components/common/TheFooter.tsx new file mode 100644 index 00000000..a2cf5023 --- /dev/null +++ b/src/components/common/TheFooter.tsx @@ -0,0 +1,54 @@ +import styles from '~/styles/TheFooter.module.scss'; + +const TheFooter = () => { + return ( + <> +
    +
    +
      +
    • +

      SWEET HOME

      +
    • + +
    • +
      +

      (μ£Ό)집 가ꡬ싢어(SweetHome)

      + μƒν˜Έλͺ…: 집 가ꡬ싢어(SweetHome) + μ‚¬μ—…μžλ“±λ‘λ²ˆν˜Έ: 123-45-678-91011 + μ‚¬μ—…μž: 김쀀희, λ°•κ·Όμš°, 백동은, μ†‘ν™λΉˆ, 쑰은상 +
      +
    • + +
    • +
      +

      BANK ACCOUNT

      + κ΅­λ―Ό 562101-01-020304 + λ†ν˜‘ 111-9876-5432-01 + 예금주: (μ£Ό)집가ꡬ싢어 +
      +
    • + +
    • +
      +

      CUSTOMER CENTER

      + 평일: 10:00 AM ~ 18:00 PM + ν† μš”μΌ: 13:00 PM ~ 17:00 PM + (μ μ‹¬μ‹œκ°„: 12:00 PM ~ 13:00 PM) + μΌμš”μΌ 및 κ³΅νœ΄μΌμ€ μš΄μ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. +
      +
    • + +
    • +
      +

      Tel. 1234-5678

      +
      +
    • +
    + +
    +
    + + ) +} + +export default TheFooter; \ No newline at end of file diff --git a/src/components/common/TheHeader.tsx b/src/components/common/TheHeader.tsx new file mode 100644 index 00000000..76020217 --- /dev/null +++ b/src/components/common/TheHeader.tsx @@ -0,0 +1,103 @@ +import { useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { NavLink, useNavigate } from "react-router-dom"; +import { AiOutlineSearch } from "react-icons/ai"; +import { FaShoppingBag, FaUserAlt } from "react-icons/fa"; +import { logOut } from "~/api/requests"; +import TheSearchBar from "~/components/common/TheSearchBar"; +import styles from "~/styles/TheHeader.module.scss"; + +const TheHeader = () => { + const [search, setSearch] = useState(""); + const [searchIsClicked, setSearchIsClicked] = useState(false); + + const logout = useSelector((state: any) => state.logout); + const dispatch = useDispatch(); + const navigate = useNavigate(); + + // λ‘œκ·Έμ•„μ›ƒ + const logOutHandler = async (e: React.MouseEvent) => { + e.preventDefault(); + dispatch({ type: "LOGOUT", state: false }); + dispatch({ type: "RETURN", account: {} }); + try { + await logOut(); + alert("λ‘œκ·Έμ•„μ›ƒ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.") + navigate("/sweethome") + } catch (error: any) { + alert(error.message); + } + }; + + return ( +
    +
    +
    + SWEET HOME +
    +
    +
      +
    • + + isActive ? styles.active : `menu` + }> + ABOUT + +
    • +
    • + (isActive ? styles.active : "")}> + SHOP + +
    • +
    +
    +
    +
    + {logout ? ( + + Logout + + ) : ( +
    + + Login + + + Sign-Up + +
    + )} +
    +
    + + + + + + +
    + {searchIsClicked && ( + ) => setSearch(e.target.value)} + /> + )} + setSearchIsClicked(!searchIsClicked)} + /> +
    +
    +
    +
    +
    + ); +}; +export default TheHeader; diff --git a/src/components/common/TheModal.tsx b/src/components/common/TheModal.tsx new file mode 100644 index 00000000..8b452922 --- /dev/null +++ b/src/components/common/TheModal.tsx @@ -0,0 +1,200 @@ +import { useState } from "react"; +import { TfiClose } from "react-icons/tfi"; +import { editProduct } from "~/api/requests"; +import { SELECT_TAGS } from "~/constants"; +import Loading from "~/components/common/Loading"; +import Select from "~/components/common/Select"; +import styles from "~/styles/TheModal.module.scss"; + +interface PropsType { + setModalOpen: React.Dispatch>; + title: string; + allProducts: Product[]; + setAllProducts: React.Dispatch> + productId: string; + watch: boolean; + setWatch: React.Dispatch> + productIDX: number; +} + +interface Product { + id: string + title: string + price: number + description: string + tags: string[] + thumbnail: string + isSoldOut: boolean + discountRate: number +} + +const TheModal = ({ + setModalOpen, + title, + allProducts, + setAllProducts, + productId, + watch, + setWatch, + productIDX, +}: PropsType) => { + const [isChecked, setIsChecked] = useState(false); + const [productThumb] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [editInfo, setEditInfo] = useState({ + title: allProducts[productIDX].title, + price: allProducts[productIDX].price, + description: allProducts[productIDX].description, + tags: allProducts[productIDX].tags, + thumbnail: productThumb, + discountRate: allProducts[productIDX].discountRate, + }); + + const closeModal = () => { + setModalOpen(false); + }; + + // 썸넀일 base64 인코딩 + // const thumbBase64Handler = (e: React.ChangeEvent) => { + // const target: any = e.target as HTMLInputElement; + // const file = target.files[0]; + // const reader = new FileReader(); + // reader.readAsDataURL(file); + + // return new Promise((resolve) => { + // reader.onload = () => { + // setProductThumb(reader.result as string); + // resolve(); + // }; + // }); + // }; + + const onChangeHandler = ( + e: + | React.ChangeEvent + | React.ChangeEvent + ) => { + const { value, name } = e.target; + setEditInfo({ + ...editInfo, + [name]: value, + }); + }; + + // μƒν’ˆ μˆ˜μ • + const editProductHandler = async (e: React.MouseEvent, id: string) => { + e.preventDefault(); + setIsLoading(true); + if (editInfo.title.trim() === "") { + alert("μƒν’ˆμ΄λ¦„, μƒν’ˆνƒœκ·ΈλŠ” ν•„μˆ˜μž…λ‹ˆλ‹€."); + return; + } + if (editInfo.discountRate < 0 || editInfo.discountRate >= 100) { + alert("ν• μΈμœ¨μ€ 0 ~ 99λ₯Ό μž…λ ₯ν•˜μ„Έμš”."); + return; + } + + const body = { + title: editInfo.title, + price: editInfo.price, + description: editInfo.description, + tags: editInfo.tags, + thumbnail: productThumb, + isSoldOut: isChecked, + discountRate: editInfo.discountRate, + }; + + try { + const res = await editProduct(body, id); + setAllProducts([...allProducts, res]); + setWatch(!watch); + setModalOpen(false); + } catch (error: any) { + alert(error.message); + } + setIsLoading(false); + }; + + return ( + <> + {isLoading ? : null} +
    +
    +
    +

    {title}

    +
    +
    + + + + */} + + + editProductHandler(e, productId)} + /> +
    +
    + +
    +
    +
    + + ); +}; + +export default TheModal; diff --git a/src/components/common/TheSearchBar.tsx b/src/components/common/TheSearchBar.tsx new file mode 100644 index 00000000..62a79c19 --- /dev/null +++ b/src/components/common/TheSearchBar.tsx @@ -0,0 +1,79 @@ +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { getAllProducts } from "~/api/requests"; +import Loading from "~/components/common/Loading"; +import styles from "~/styles/TheSearchBar.module.scss"; + +const TheSearchBar = ({ search, onChange }: any) => { + const [allProducts, setAllProducts] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + // 전체 μƒν’ˆ 쑰회 + const getAllProductsHandler = async () => { + setIsLoading(true); + try { + const res = await getAllProducts(); + setAllProducts(res); + } catch (error: any) { + alert(error.message); + } + setIsLoading(false); + }; + + const navigate = useNavigate(); + + useEffect(() => { + getAllProductsHandler(); + }, []); + + interface Product { + id: string; + title: string; + price: number; + description: string; + tags: string[]; + thumbnail: string | null; + isSoldOut: boolean; + discountRate: number; + } + + return ( + <> + {isLoading ? : null} +
    +
    + +
    + {} +
    + {allProducts.map((product:Product, index:number) => + search === "" ? ( +
    + ) : product.title + .replace(" ", "") + .toLocaleLowerCase() + .includes(search?.toLocaleLowerCase().replace(" ", "")) ? ( +
    { + navigate(`/sweethome/shop/${product.id}`, { replace: true }); + }}> + {product.title} +
    + ) : ( +
    + ) + )} +
    +
    + + ); +}; +export default TheSearchBar; diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 00000000..bdb7f137 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,6 @@ +export const SELECT_TAGS = [ + {name: 'νƒœκ·Έλ₯Ό μ„ νƒν•΄μ£Όμ„Έμš”.', value: ''}, + {name: 'FURNITURE', value: 'FURNITURE'}, + {name: 'KITCHEN', value: 'KITCHEN'}, + {name: 'BEDROOM', value: 'BEDROOM'} +] \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 00000000..559e6a39 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,16 @@ +import ReactDOM from "react-dom/client"; +import router from "./routes/index.tsx"; +import { RouterProvider } from "react-router-dom"; +import { store } from "~/store/index.ts"; +import { Provider } from "react-redux"; +import { persistStore } from "redux-persist"; +import { PersistGate } from "redux-persist/integration/react"; + +const persistor = persistStore(store); +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + + + +); \ No newline at end of file diff --git a/src/reducers/account.ts b/src/reducers/account.ts new file mode 100644 index 00000000..1ddf3587 --- /dev/null +++ b/src/reducers/account.ts @@ -0,0 +1,10 @@ +const account = (state = {}, action: any) => { + switch (action.type) { + case "GET_ACCOUNT_LIST": + return action.accountList; + default: + return state; + } +}; + +export default account; diff --git a/src/reducers/buyItem.ts b/src/reducers/buyItem.ts new file mode 100644 index 00000000..81b772e0 --- /dev/null +++ b/src/reducers/buyItem.ts @@ -0,0 +1,10 @@ +const buyItem = (state = {}, action: any) => { + switch (action.type) { + case "GET_BUYITEM": + return action.payload + default: + return state; + } +}; + +export default buyItem \ No newline at end of file diff --git a/src/reducers/cart.ts b/src/reducers/cart.ts new file mode 100644 index 00000000..1174a4b8 --- /dev/null +++ b/src/reducers/cart.ts @@ -0,0 +1,15 @@ +const cart = (state = [], action: any) => { + switch (action.type) { + case "RETURN_CART": + return action.items; + case "DELETE_ITEM": + let num = state.findIndex((obj:any)=>{ + return obj.id === action.items + }) + return state.splice(num,1) + default: + return state; + } +}; + +export default cart; \ No newline at end of file diff --git a/src/reducers/index.ts b/src/reducers/index.ts new file mode 100644 index 00000000..c258646f --- /dev/null +++ b/src/reducers/index.ts @@ -0,0 +1,28 @@ +import { combineReducers } from "redux"; +import { persistReducer } from "redux-persist"; +import info from "./info"; +import cart from "./cart"; +import logout from "./logout"; +import selectedCart from "./selectedCart"; +import account from "./account"; +import user from "./user" +import buyItem from "./buyItem" +import storage from "redux-persist/lib/storage"; + +const persistConfig = { + key: "root", + storage, + whiteList: ["info", "cart", "logout", "selectedCart", "account","user","buyItem"] +}; + +const rootReducer = combineReducers({ + info, + cart, + logout, + selectedCart, + account, + user, + buyItem +}); + +export default persistReducer(persistConfig, rootReducer); diff --git a/src/reducers/info.ts b/src/reducers/info.ts new file mode 100644 index 00000000..ac5a2b85 --- /dev/null +++ b/src/reducers/info.ts @@ -0,0 +1,10 @@ +const info = (state = 0, action: any) => { + switch (action.type) { + case "RETURN": + return action.account; + default: + return state; + } +}; + +export default info; diff --git a/src/reducers/logout.ts b/src/reducers/logout.ts new file mode 100644 index 00000000..d4a9ddfe --- /dev/null +++ b/src/reducers/logout.ts @@ -0,0 +1,10 @@ +const logout = (state = true, action: any) => { + switch (action.type) { + case "LOGOUT": + return action.state; + default: + return state; + } +}; + +export default logout; diff --git a/src/reducers/selectedCart.ts b/src/reducers/selectedCart.ts new file mode 100644 index 00000000..cf97a0a7 --- /dev/null +++ b/src/reducers/selectedCart.ts @@ -0,0 +1,15 @@ +const selectedCart = (state = [], action: any) => { + switch (action.type) { + case "CHECKED_SELECTED_CART": + action.items.isChecked = true; + return action.items.isChecked ? [...state, action.items] : [...state]; + case "DELETE_SELECTED_CART": + return action.items; + case "REFRESH": + return [] + default: + return state + } +}; + +export default selectedCart; diff --git a/src/reducers/user.ts b/src/reducers/user.ts new file mode 100644 index 00000000..4021ab41 --- /dev/null +++ b/src/reducers/user.ts @@ -0,0 +1,10 @@ +const user = (state = {}, action: any) => { + switch (action.type) { + case "RETURN_USER": + return action.payload + default: + return state; + } +}; + +export default user; diff --git a/src/routes/.DS_Store b/src/routes/.DS_Store new file mode 100644 index 00000000..818b3558 Binary files /dev/null and b/src/routes/.DS_Store differ diff --git a/src/routes/About/About.tsx b/src/routes/About/About.tsx new file mode 100644 index 00000000..bf992b8d --- /dev/null +++ b/src/routes/About/About.tsx @@ -0,0 +1,26 @@ +import about from '/public/assets/about/about.jpeg'; +import styles from '~/styles/About/About.module.scss'; + +const About = () => { + return ( +
    +
    +
    + +
    +
    +

    + SWEET HOME +

    +

    + λ‹Ήμ‹ μ—κ²Œ μ§‘μ΄λž€ μ–΄λ–€ μ˜λ―ΈμΈκ°€μš”?
    + μ–Έμ œ, μ–΄λ””μ„œλ‚˜ 집을 κ°€κ³ μ‹Άκ²Œ λ§Œλ“€ 수 μžˆλŠ” 첫 걸음
    + 집 κ°€κ΅¬μ‹Άμ–΄μ—μ„œ μ‹œμž‘ν•΄ λ³΄μ„Έμš”. +

    +
    +
    +
    + ); +}; + +export default About; \ No newline at end of file diff --git a/src/routes/Admin/Admin.tsx b/src/routes/Admin/Admin.tsx new file mode 100644 index 00000000..8b13f637 --- /dev/null +++ b/src/routes/Admin/Admin.tsx @@ -0,0 +1,50 @@ +import { useState, useEffect } from 'react' +import { useNavigate } from 'react-router-dom' +import { useSelector } from 'react-redux' +import AdminUser from '~/routes/Admin/AdminUser' +import AdminProduct from '~/routes/Admin/AdminProduct' +import AdminOrder from '~/routes/Admin/AdminOrder' +import SubNav from '~/components/common/SubNav' +import styles from '~/styles/Admin/Admin.module.scss'; + +const Admin = () => { + const subNav: string[] = ["μ‚¬μš©μž 관리", "μƒν’ˆ 관리", "μ£Όλ¬Έ λ‚΄μ—­ 관리"]; + const [category, setCategory] = useState(""); + + const navigate = useNavigate(); + + const user = useSelector((state: any) => state.user) + + useEffect(() => { + if (!localStorage.getItem('token')) { + alert('κ΄€λ¦¬μž μ „μš© νŽ˜μ΄μ§€μž…λ‹ˆλ‹€! βš™οΈ'); + navigate('/sweethome'); + } + },[]) + + return ( + <> + {localStorage.getItem('token') && user.email === `${import.meta.env.VITE_ADMIN_ACCOUNT}` ? +
    + +
    +
    +
    + { + (category === "μ‚¬μš©μž 관리" || category === "") + ? + : category === "μƒν’ˆ 관리" + ? + : + } +
    +
    +
    +
    + :
    κ΄€λ¦¬μž νŽ˜μ΄μ§€μž…λ‹ˆλ‹€. κ΄€λ¦¬μž κ³„μ •μœΌλ‘œ 둜그인 ν•΄μ£Όμ„Έμš”.
    + } + + ) +} + +export default Admin; \ No newline at end of file diff --git a/src/routes/Admin/AdminOrder.tsx b/src/routes/Admin/AdminOrder.tsx new file mode 100644 index 00000000..821d2f4e --- /dev/null +++ b/src/routes/Admin/AdminOrder.tsx @@ -0,0 +1,161 @@ +import { useEffect, useState } from "react"; +import { adminAllTransactions, adminTransactions } from "~/api/requests"; +import { convertDate, sortDate, convertPrice } from "~/utils/convert"; +import Loading from "~/components/common/Loading"; +import styles from "~/styles/admin/AdminOrder.module.scss"; + +const AdminOrder = () => { + const [allList, setAllList] = useState([]); + const [isCanceled, setIsCanceled] = useState(false); + const [isDone, setIsDone] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() =>{ + getAllTransactions(); + },[]) + + // 전체 거래 λ‚΄μ—­ 쑰회 + const getAllTransactions = async () => { + setIsLoading(true); + try { + const res = await adminAllTransactions(); + setAllList(res.sort((a: any, b: any) => sortDate(b.timePaid) - sortDate(a.timePaid))); + } catch (error: any) { + alert(error.message); + } + setIsLoading(false); + } + + // 거래 μ™„λ£Œ 및 μ·¨μ†Œ + const adminTransactionsHandler = async (e: React.MouseEvent, detailId: string) => { + e.preventDefault(); + const id = e.currentTarget.id; + try { + if (id === 'canceled') { + await adminTransactions(detailId, { isCanceled: !isCanceled }); + setIsCanceled(!isCanceled); + } else { + await adminTransactions(detailId, { done: !isDone }); + setIsDone(!isDone); + } + } catch (error: any) { + alert(error.message); + } + } + + interface TransactionDetail { + detailId: string + user: { + email: string + displayName: string + profileImg: string | null + } + account: { + bankName: string + bankCode: string + accountNumber: string + } + product: { + productId: string + title: string + price: number + description: string + tags: string[] + thumbnail: string | null + discountRate: number + } + timePaid: string + isCanceled: boolean + done: boolean + } + + return ( + <> + {isLoading ? : null} +
    +
    +
    +

    전체 거래 λ‚΄μ—­ 관리

    +
    + +
    +
    +
    NO.
    +
    거래 λ‚΄μ—­ ID
    +
    거래자 정보
    +
    κ³„μ’Œ 정보
    +
    μ œν’ˆ 정보
    +
    거래 μ‹œκ°„
    +
    μ·¨μ†Œ μ—¬λΆ€
    +
    μ™„λ£Œ μ—¬λΆ€
    +
    λΉ„κ³ 
    +
    +
    + +
    +
      + {allList.map((list: TransactionDetail, i) => ( +
    • +

      {i + 1}

      +
      + {list.detailId.slice(0, 8)} +
      +
      +

      {list.user.displayName}

      +

      {list.user.email}

      +
      +
      +

      {list.account.bankName}

      +

      {list.account.accountNumber}

      +
      +
      +

      {list.product.title}

      +

      {convertPrice(list.product.price)}

      +

      {list.product.tags}

      +
      +
      +

      {convertDate(list.timePaid)}

      +
      +
      +

      {list.isCanceled ? "βœ…" : "❌"}

      +
      +
      + {list.done ? "βœ…" : "❌"} +
      +
      + {(list.isCanceled || list.done) + ? "🏠" + : + adminTransactionsHandler(e, list.detailId)} + /> + } + {(list.done || list.isCanceled) + ? null + : + adminTransactionsHandler(e, list.detailId)} + disabled={list.done ? true : false}/> + } +
      +
    • + ))} +
    +
    +
    +
    + + ) +} + +export default AdminOrder; \ No newline at end of file diff --git a/src/routes/Admin/AdminProduct.tsx b/src/routes/Admin/AdminProduct.tsx new file mode 100644 index 00000000..5ef72f2f --- /dev/null +++ b/src/routes/Admin/AdminProduct.tsx @@ -0,0 +1,349 @@ +import { useState, useEffect } from "react"; +import { TiDeleteOutline } from "react-icons/ti"; +import { BsPencilSquare } from "react-icons/bs"; +import { addProduct, getAllProducts, deleteProduct } from "~/api/requests"; +import { SELECT_TAGS } from "~/constants"; +import TheModal from "~/components/common/TheModal"; +import Select from "~/components/common/Select"; +import Loading from "~/components/common/Loading"; +import styles from "~/styles/Admin/AdminProduct.module.scss"; + +type AllProduct = Product[]; + +interface Product { + id: string; + title: string; + price: number; + description: string; + tags: string[]; + thumbnail: string; + isSoldOut: boolean; + discountRate: number; +} + +interface AddProductBody { + title: string; + price: number; + description: string; + tags?: string; + thumbnailBase64?: string; + photoBase64?: string; + discountRate?: number; + isSoldOut?: boolean; +} + +const AdminProduct = () => { + const [allProducts, setAllProducts] = useState([]); + const [productThumb, setProductThumb] = useState(""); + const [productPhoto, setProductPhoto] = useState(""); + const [productId, setProductId] = useState(""); + const [product, setProduct] = useState({ + title: "", + price: "", + description: "", + tags: "", + discountRate: "" + }); + + const [isLoading, setIsLoading] = useState(false); + const [isChecked, setIsChecked] = useState(false); + const [modalOpen, setModalOpen] = useState(false); + const [watch, setWatch] = useState(false); + const [productIDX, setProductIDX] = useState(0); + + // μƒν’ˆ μˆ˜μ • λ²„νŠΌ 클릭 μ‹œ λͺ¨λ‹¬μ°½ μ˜€ν”ˆ + const showModal = (id: string) => { + setModalOpen(true); + setProductId(id); + }; + + useEffect(() => { + getAllProductsHandler(); + }, [watch]); + + const tableHead = [ + "NO", + "μƒν’ˆνƒœκ·Έ", + "μƒν’ˆμ΄λ¦„", + "μƒν’ˆκ°€κ²©", + "ν• μΈμœ¨", + "ν’ˆμ ˆμ—¬λΆ€" + ]; + + // 전체 μƒν’ˆ λͺ©λ‘ 쑰회 + const getAllProductsHandler = async () => { + setIsLoading(true); + try { + const res = await getAllProducts(); + setAllProducts(res); + } catch (error) { + alert("μƒν’ˆ 좜λ ₯ μ‹€νŒ¨!"); + } + setIsLoading(false); + }; + + // μƒν’ˆ μˆ˜μ • input ν•Έλ“€λŸ¬ + const onInputChangeHandler = ( + e: + | React.ChangeEvent + | React.ChangeEvent + ) => { + const { value, name } = e.target; + setProduct({ + ...product, + [name]: value + }); + }; + + // μƒν’ˆ 등둝 + const addProductHandler = async () => { + const body: AddProductBody = { + title: product.title, + price: Number(product.price), + description: product.description, + tags: product.tags, + thumbnailBase64: productThumb, + photoBase64: productPhoto, + discountRate: Number(product.discountRate), + isSoldOut: isChecked + }; + + try { + setIsLoading(true); + await addProduct(body); + setWatch(!watch); + alert("μƒν’ˆ 등둝 성곡!"); + setProduct({ + title: "", + price: "", + description: "", + tags: "", + discountRate: "" + }); + setProductThumb(""); + setProductPhoto(""); + } catch (error) { + alert("μƒν’ˆ 등둝 μ‹€νŒ¨!"); + } + setIsLoading(false); + }; + + // 썸넀일 base64 인코딩 + const thumbBase64Handler = (e: React.ChangeEvent) => { + const target: any = e.target as HTMLInputElement; + const file = target.files[0]; + const reader = new FileReader(); + reader.readAsDataURL(file); + + return new Promise(resolve => { + reader.onload = () => { + setProductThumb(reader.result as string); + resolve(); + }; + }); + }; + + // 상세사진 base64 인코딩 + const photoBase64Handler = (e: React.ChangeEvent) => { + const target: any = e.target as HTMLInputElement; + const file = target.files[0]; + const reader = new FileReader(); + reader.readAsDataURL(file); + return new Promise(resolve => { + reader.onload = () => { + setProductPhoto(reader.result as string); + resolve(); + }; + }); + }; + + // μƒν’ˆ μ‚­μ œ + const deleteProductHandler = async (id: any) => { + try { + const res = await deleteProduct(id); + if (res) { + const updateProduct = allProducts.filter(product => product.id !== id); + setAllProducts(updateProduct); + } else { + alert("μƒν’ˆ μ‚­μ œ μ‹€νŒ¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€."); + } + } catch (error) { + alert("μƒν’ˆ μ‚­μ œ μ‹€νŒ¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€."); + } + }; + + return ( + <> + {isLoading ? : null} +
    +
    +
    +
    +

    μƒν’ˆ 등둝

    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    + +
    +
    +

    λͺ¨λ“  μƒν’ˆ 쑰회

    +
    +
    +
    + + + + {tableHead.map(item => ( + + ))} + + + + {allProducts.map((product, i) => ( + + + + + + + +
    + { + showModal(product.id); + setProductIDX(i); + }} + /> + deleteProductHandler(product.id)} + /> + {modalOpen && ( + + )} +
    + + ))} + +
    {item}
    {i + 1}{product.tags}{product.title}{product.price}{product.discountRate}{product.isSoldOut ? "ν’ˆμ ˆ" : ""}
    +
    +
    +
    +
    +
    + + ); +}; + +export default AdminProduct; diff --git a/src/routes/Admin/AdminUser.tsx b/src/routes/Admin/AdminUser.tsx new file mode 100644 index 00000000..6c752947 --- /dev/null +++ b/src/routes/Admin/AdminUser.tsx @@ -0,0 +1,62 @@ +import { useState, useEffect } from "react"; +import { users } from "~/api/requests"; +import styles from "~/styles/Admin/AdminUser.module.scss"; + +const AdminUser = () => { + interface User { + email: string + displayName: string + profileImg: string + } + + const [allUsers, setAllUsers] = useState([]); + const tableHead = ["NO", "이름", "이메일"]; + + useEffect(() => { + getUsers(); + }, []); + + // μ‚¬μš©μž λͺ©λ‘ 쑰회 + const getUsers = async () => { + try { + const res = await users(); + setAllUsers(res); + } catch (error) { + alert("μ‚¬μš©μž λͺ©λ‘ 쑰회 μ‹€νŒ¨!") + } + }; + + return ( + <> +
    +
    +
    +

    μ‚¬μš©μž 관리

    +
    +
    + + + + {tableHead.map(item => ( + + ))} + + + + {allUsers.map((user: User, i) => ( + + + + + + ))} + +
    {item}
    {i + 1}{user.displayName}{user.email}
    +
    +
    +
    + + ); +}; + +export default AdminUser; diff --git a/src/routes/Buy/Buy.tsx b/src/routes/Buy/Buy.tsx new file mode 100644 index 00000000..115a03cf --- /dev/null +++ b/src/routes/Buy/Buy.tsx @@ -0,0 +1,287 @@ +import { useEffect, useState } from "react"; +import { useLocation, useNavigate } from "react-router"; +import { Link } from "react-router-dom"; +import { useSelector, useDispatch } from "react-redux"; +import { FaEquals, FaPlus } from "react-icons/fa"; +import { buyProduct, getAccountList } from "~/api/requests"; +import { convertPrice, priceBeforeDiscount } from "~/utils/convert"; +import styles from "~/styles/Buy/Buy.module.scss"; + +interface Bank { + id: string; + bankName: string; + bankCode: string; + accountNumber: string; + balance: number; +} + +const Buy = () => { + const user = useSelector((state: any) => state.user); + const cart = useSelector((state: any) => state.cart); + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const [accountChecked, setAccountChecked] = useState(true); + const [bankChecked, setBankChecked] = useState(false); + const [accountId, setAccountId] = useState(""); + const [bankName, setBankName] = useState("") + const [accountList, setAccountList] = useState([]); + const [isActive, setIsActive] = useState(false); + + useEffect(() => { + getAccountData(); + }, []); + + const getAccountData = async () => { + try { + const res = await getAccountList(); + setAccountList(res.accounts); + } catch (err) { + alert("κ³„μ’Œμ •λ³΄ μ‹€νŒ¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€."); + } + }; + + // λΌλ””μ˜€ λ²„νŠΌ ν•Έλ“€λŸ¬ + const radioBtnHandler = () => { + setAccountChecked(!accountChecked); + setBankChecked(!bankChecked); + }; + const location = useLocation(); + const order = [...location.state]; + + const totalQuantity = order.reduce((acc, cur) => (acc += cur.quantity), 0); + const totalPrice = order.reduce( + (acc, cur) => (acc += cur.price * cur.quantity), + 0 + ); + + // μƒν’ˆ 거래 μ‹ μ²­ ν•Έλ“€λŸ¬ + interface orderApplyBody { + productId: string; + accountId: string; + reservation?: { + start: string; + end: string; + }; + } + + const orderApplyHandler = async ( + e: React.MouseEvent, + order: any[], + accountId: string + ) => { + e.preventDefault(); + try { + order.map(async (item: any) => { + const body: orderApplyBody = { + productId: item.id, + accountId + }; + await buyProduct(body); + }); + if (confirm("κ²°μ œκ°€ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.\nμ£Όλ¬Έ 내역을 ν™•μΈν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?")) { + navigate("/sweethome/mypage"); + } else { + return; + } + order.map((item: any) => { + dispatch({ type: "GETBUYITEM", items: item.id }); + }); + dispatch({ + type: "RETURN_CART", + items: cart.filter((item: any) => { + return !order.includes(item); + }) + }); + } catch (error) { + console.log("결제 μ‹€νŒ¨", error); + alert("μƒν’ˆ 주문에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€."); + } + }; + + return ( + <> +
    +
    +
    +

    μ£Όλ¬Έμ„œ μž‘μ„± / 결제

    +
    + +
    +

    주문상세내역

    +
    +
    μƒν’ˆ 이미지
    +
    μƒν’ˆλͺ…
    +
    μˆ˜λŸ‰
    +
    가격
    +
    총 κΈˆμ•‘
    +
    +
    + +
    +
      + {order.map((item, i: number) => ( +
    • + +
      + {item.title} +
      + + +
      + {item.title} +
      +
      + {item.quantity}개 +
      +
      + + {item.discountRate !== 0 + ? `${convertPrice(item.price)}원` + : `${convertPrice( + priceBeforeDiscount(item.price, item.discountRate) + )}원`} + + + {item.discountRate !== 0 + ? `${convertPrice( + priceBeforeDiscount(item.price, item.discountRate) + )}원` + : ""} + +
      +
      + {item.discountRate !== 0 + ? convertPrice(item.price * item.quantity) + : convertPrice( + priceBeforeDiscount(item.price, item.discountRate) * + item.quantity + )} + 원 +
      +
    • + ))} +
    +
    + +
    +
    + 총 {totalQuantity}개의 μƒν’ˆ + β‚©{convertPrice(totalPrice)} +
    + +
    + 배솑비 + β‚©0 +
    + +
    + 합계 + β‚©{convertPrice(totalPrice)} +
    +
    + +
    +

    주문자 정보

    +
    +
    + 주문자λͺ…: + {user.displayName} +
    +
    + 이메일: + {user.email} +
    +
    +
    + +
    +

    결제 μˆ˜λ‹¨

    +
    +
    +

    결제 μˆ˜λ‹¨ 선택

    + + +
    + {accountChecked ? ( +
    + {accountList.length ? ( + accountList.map(account => ( +
    { + setAccountId(account.id); + setBankName(account.bankName); + setIsActive(true) + }}> +
    {account.bankName}
    +
    {account.accountNumber}
    +
    μž”μ•‘: {convertPrice(account.balance)}원
    +
    + )) + ) : ( +
    + κ³„μ’Œλ₯Ό λ¨Όμ € λ“±λ‘ν•΄μ£Όμ„Έμš”. +
    + )} +
    + ) : ( +
    +

    μž…κΈˆμ€ν–‰

    +

    ꡭ민은행 123-4565-11234

    +

    예금주: (μ£Ό)집가ꡬ싢어

    +
    + )} +
    +
    + +
    +

    μ΅œμ’… 결제 κΈˆμ•‘

    +
    + β‚©{convertPrice(totalPrice)} + {bankName ? (

    {bankName}μ—μ„œ 총 {convertPrice(totalPrice)}원 결제 μ˜ˆμ •μž…λ‹ˆλ‹€.

    ) : null} +
    +
    +
    +
    + orderApplyHandler(e, order, accountId)} + /> +
    +
    + + ); +}; + +export default Buy; diff --git a/src/routes/Cart/Cart.tsx b/src/routes/Cart/Cart.tsx new file mode 100644 index 00000000..17638e58 --- /dev/null +++ b/src/routes/Cart/Cart.tsx @@ -0,0 +1,95 @@ +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { FaEquals, FaPlus } from "react-icons/fa"; +import { convertPrice } from "~/utils/convert"; +import CartList from "~/components/Cart/CartList"; +import styles from "~/styles/Cart/Cart.module.scss"; + +const Cart = () => { + const navigate = useNavigate(); + + useEffect(() => { + if (!localStorage.getItem('token')) { + alert('λ‘œκ·ΈμΈμ„ ν•΄μ£Όμ„Έμš”! 🏠'); + navigate('/sweethome/login'); + } + },[]) + + + const select = useSelector((state: any) => state.selectedCart); + const cart = useSelector((state: any) => state.cart); + + const total = [...select]; + + const totalQuantity = total.reduce((acc, cur) => (acc += cur.quantity), 0); + const totalProductPrice = total.reduce((acc, cur) => (acc += cur.price), 0); + const totalPrice = totalProductPrice; + + useEffect(() => {}, [total]); + + return ( + <> + {localStorage.getItem('token') ? +
    +
    +
    +

    μž₯λ°”κ΅¬λ‹ˆ

    +
    +
    +
    +
    +
    μƒν’ˆ 이미지
    +
    μƒν’ˆλͺ…
    +
    μˆ˜λŸ‰
    +
    가격
    +
    총 κΈˆμ•‘
    +
    +
    +
    +
    + {cart.length ? ( + + ) : ( +

    μž₯λ°”κ΅¬λ‹ˆμ— λ‹΄κΈ΄ μƒν’ˆμ΄ μ—†μŠ΅λ‹ˆλ‹€.

    + )} +
    +
    +
    + {`총 ${totalQuantity}개의 μƒν’ˆ`} + β‚©{convertPrice(totalProductPrice)} +
    + +
    + 배솑비 + β‚©0 +
    + +
    + 합계 + β‚©{convertPrice(totalPrice)} +
    +
    +
    + navigate("/sweethome/buy", { state: select })} + /> + navigate("/sweethome/buy", { state: cart })} + /> +
    +
    +
    + :
    + } + + ); +}; + +export default Cart; diff --git a/src/routes/Home/Home.tsx b/src/routes/Home/Home.tsx new file mode 100644 index 00000000..c8d088f9 --- /dev/null +++ b/src/routes/Home/Home.tsx @@ -0,0 +1,16 @@ +import { Slider } from "~/utils/slider.tsx"; +import styles from "~/styles/Home/Home.module.scss"; + +const Home = () => { + return ( + <> +
    +
    + +
    +
    + + ); +}; + +export default Home; diff --git a/src/routes/Login/Login.tsx b/src/routes/Login/Login.tsx new file mode 100644 index 00000000..e792d2e2 --- /dev/null +++ b/src/routes/Login/Login.tsx @@ -0,0 +1,94 @@ +import { useState } from "react"; +import { useNavigate, Link } from "react-router-dom"; +import { useDispatch } from "react-redux"; +import { logIn } from "~/api/requests"; +import styles from "~/styles/Login/Login.module.scss"; + + +const Login = () => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const loginHandler = async (e: any) => { + e.preventDefault(); + + let body = { + email: email, + password: password + }; + + try { + const res = await logIn(body); + if (res) { + if (res.user.email === import.meta.env.VITE_ADMIN_ACCOUNT) { + navigate("/sweethome/admin"); + dispatch({ type: "RETURN", account: res }); + dispatch({ type: "LOGOUT", state: true }); + } else { + navigate("/sweethome") + dispatch({ type: "RETURN", account: res }); + dispatch({ type: "LOGOUT", state: true }); + } + } else { + dispatch({ type: "LOGOUT", state: false }); + alert("둜그인 μ‹€νŒ¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€.") + } + } catch (error: any) { + alert(error.message); + } + }; + + return ( + <> +
    +
    +
    +

    둜그인

    +
    +
    + ID + setEmail(e.target.value)} + placeholder="이메일을 μž…λ ₯ν•˜μ„Έμš”." + /> +
    +
    + PW + setPassword(e.target.value)} + /> +
    + +
    +
    +

    아이디 μ°ΎκΈ°

    +

    |

    +

    λΉ„λ°€λ²ˆν˜Έ μ°ΎκΈ°

    +
    +
    +
    +

    아직 νšŒμ›μ΄ μ•„λ‹ˆμ‹ κ°€μš”?

    +

    νšŒμ› κ°€μž…μ„ ν•˜μ‹œλ©΄ λ‹€μ–‘ν•˜κ³  νŠΉλ³„ν•œ ν˜œνƒμ„ λˆ„λ¦¬μ„Έμš”

    + + + +
    +
    +
    + + ); +}; +export default Login; diff --git a/src/routes/Mypage/MyBankAccount.tsx b/src/routes/Mypage/MyBankAccount.tsx new file mode 100644 index 00000000..2a6c4372 --- /dev/null +++ b/src/routes/Mypage/MyBankAccount.tsx @@ -0,0 +1,101 @@ +import { useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { getBankList, getAccountList } from '~/api/requests'; +import Account from '~/components/MyPage/MyBankAccount/Account'; +import AccountModal from '~/components/MyPage/MyBankAccount/AccountModal'; +import Loading from '~/components/common/Loading'; +import styles from '~/styles/Mypage/MyBankAccount.module.scss'; + +interface Bank { + id: string; + bankName: string; + bankCode: string; + accountNumber: string; + balance: number; +} + +const MyBankAccount = () => { + const [showModal, setShowModal] = useState(false) + const [bankList, setBankList] = useState([]) + const [accountList, setAccountList] = useState([]) + const [watch, setWatch] = useState(false) + const [isLoading, setIsLoading] = useState(false); + const dispatch = useDispatch(); + + useEffect(() => { + checkBankList(); + checkAccountList(); + },[watch]) + + + // 선택 κ°€λŠ₯ν•œ 은행 λͺ©λ‘ 쑰회 + const checkBankList = async () => { + setIsLoading(true); + try { + const res = await getBankList(); + setBankList(res); + } catch (error) { + alert('은행 정보λ₯Ό λΆˆλŸ¬μ˜€λŠ” 데 μ‹€νŒ¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€.'); + } + setIsLoading(false); + } + + // λ“±λ‘λœ κ³„μ’Œ 쑰회 + const checkAccountList = async () => { + setIsLoading(true); + try { + const res = await getAccountList(); + setAccountList(res.accounts); + dispatch({ type: "GET_ACCOUNT_LIST", accountList: res }); + } catch (error) { + alert('κ³„μ’Œ 정보 λΆˆλŸ¬μ˜€λŠ” 데 μ‹€νŒ¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€.') + } + setIsLoading(false); + } + + return ( + <> + {isLoading? : null} + {showModal && ( + )} +
    +
    +
    +

    κ³„μ’Œ 관리

    +
    + {accountList.length > 0 + ? (accountList.map((item, i: number)=> ( + ))) + : ( +
    +
    +

    λ“±λ‘λœ κ³„μ’Œκ°€ μ—†μŠ΅λ‹ˆλ‹€.

    +

    κ³„μ’Œ 번호λ₯Ό 등둝해 μ£Όμ„Έμš”!

    +
    +
    + )} + +
    +
    + + ) +} + +export default MyBankAccount \ No newline at end of file diff --git a/src/routes/Mypage/MyInfo.tsx b/src/routes/Mypage/MyInfo.tsx new file mode 100644 index 00000000..f30e5a45 --- /dev/null +++ b/src/routes/Mypage/MyInfo.tsx @@ -0,0 +1,131 @@ +import { useSelector } from 'react-redux'; +import { useForm } from 'react-hook-form'; +import { editInfo } from '~/api/requests'; +import styles from '~/styles/Mypage/MyInfo.module.scss'; + +interface Password { + oldPassword: string, + newPassword: string +} + +const MyInfo = () => { + const user = useSelector((state: any) => state.user) + + const { + register, + handleSubmit, + formState: { errors, isSubmitting}, + reset + } = useForm({ + mode: 'onChange', + }); + + const userPassword = { + minLength: { + value: 8, + message: "λΉ„λ°€λ²ˆν˜ΈλŠ” μ΅œμ†Œ 8μžμž…λ‹ˆλ‹€." + }, + maxLength: { + value: 16, + message: "λΉ„λ°€λ²ˆν˜ΈλŠ” μ΅œλŒ€ 16μžμž…λ‹ˆλ‹€." + } + } + + + const ChangePWHandler = async({ oldPassword, newPassword }: Password) => { + if (oldPassword.length < 8 || newPassword.length < 8) return alert('λΉ„λ°€λ²ˆν˜ΈλŠ” 8μžμ΄μƒ μž…λ ₯ν•΄μ£Όμ„Έμš”.') + const body = { + oldPassword, + newPassword + } + try { + const res = await editInfo(body) + if (res) { + alert('λΉ„λ°€λ²ˆν˜Έκ°€ λ³€κ²½λ˜μ—ˆμŠ΅λ‹ˆλ‹€.') + reset(); + } else { + alert('λΉ„λ°€λ²ˆν˜Έλ₯Ό λ‹€μ‹œ μž…λ ₯ν•΄ μ£Όμ„Έμš”.') + reset(); + } + } catch (error: any) { + alert(error.message); + } + } + + return ( +
    +
    +
    +

    개인 정보 μˆ˜μ •

    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    + ) +} + +export default MyInfo; \ No newline at end of file diff --git a/src/routes/Mypage/MyOrder.tsx b/src/routes/Mypage/MyOrder.tsx new file mode 100644 index 00000000..2e3cf820 --- /dev/null +++ b/src/routes/Mypage/MyOrder.tsx @@ -0,0 +1,210 @@ +import { useEffect, useState } from "react"; +import { + cancelTransaction, + confirmedTransaction, + getAllTransactions, + getTransaction, +} from "~/api/requests"; +import { convertPrice, convertDate, sortDate } from "~/utils/convert"; +import MyOrderDetails from "~/routes/Mypage/MyOrderDetails"; +import Loading from "~/components/common/Loading"; +import styles from "~/styles/Mypage/MyOrder.module.scss"; + +type AllTransactions = TransactionDetail[] + +interface TransactionDetail { + detailId: string + product: { + productId: string + title: string + price: number + description: string + tags: string[] + thumbnail: string + discountRate: number + } + reservation: Reservation | null + timePaid: string + isCanceled: boolean + done: boolean +} + +interface Reservation { + start: string + end: string + isCanceled: boolean + isExpired: boolean +} + + +const MyOrder = () => { + const [allList, setAllList] = useState([]); + const [showDetails, setShowDetails] = useState(false); + const [orderDetails, setOrderDetails] = useState({}); + const [isLoading, setIsLoading] = useState(false); + const [watch, setWatch] = useState(false); + + useEffect(() => { + allTransactions(); + }, [watch]); + + // 전체 거래 λ‚΄μ—­ + const allTransactions = async () => { + setIsLoading(true); + const allRes = await getAllTransactions(); + if (allRes) { + setAllList( + allRes + .filter((res: TransactionDetail) => !res.isCanceled) + .sort((a: TransactionDetail, b: TransactionDetail) => sortDate(b.timePaid) - sortDate(a.timePaid)) + ); + } else { + alert("μ£Όλ¬Έ 내역이 μ—†μŠ΅λ‹ˆλ‹€.") + } + setIsLoading(false); + }; + + // ꡬ맀 μ·¨μ†Œ + const cancelHandler = async ( + e: React.MouseEvent, + id: string + ) => { + e.preventDefault(); + setIsLoading(true); + try { + const body = { detailId: id }; + const res = await cancelTransaction(body); + if (res) { + setWatch(!watch) + alert("ꡬ맀 μ·¨μ†Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€."); + } else { + alert("ꡬ맀 ν™•μ •λœ μƒν’ˆμ„ μ·¨μ†Œν•  수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } catch (error: any) { + alert(error.message); + } finally { + setIsLoading(false); + } + }; + + // ꡬ맀 ν™•μ • + const confirmHandler = async ( + e: React.MouseEvent, + Id: string + ) => { + e.preventDefault(); + try { + const body = { detailId: Id }; + const res = await confirmedTransaction(body); + if (res) { + setWatch(!watch) + alert("ꡬ맀 ν™•μ • μ™„λ£Œ!") + } + } catch (error: any) { + alert(error.message); + } + }; + + // 단일 μƒν’ˆ 상세 + const showDetailsHandler = async ( + e: React.MouseEvent, + id: string + ) => { + e.preventDefault(); + try { + const body = { detailId: id }; + const res = await getTransaction(body); + setOrderDetails(res); + setShowDetails(true); + } catch (error: any) { + alert(error.message) + } + }; + + return ( + <> + {isLoading ? : null} + {showDetails ? ( + + ) : null} +
    +
    +
    +

    μ£Όλ¬Έ λ‚΄μ—­ 관리

    +
    + +
    +
    +
    NO.
    +
    μƒν’ˆ 이미지
    +
    μƒν’ˆλͺ…
    +
    μƒν’ˆ 가격
    +
    주문 일자
    +
    ꡬ맀 ν™•μ •
    +
    λΉ„κ³ 
    +
    +
    + +
    +
      + {allList.map((list: any, i) => ( +
    • + {i + 1} +
      + {list.product.title} +
      + {list.product.title} + {convertPrice(list.product.price)} + {convertDate(list.timePaid)} + {list.done ? "βœ…" : "❌"} +
      + {list.done + ? null + : + { + cancelHandler(e, list.detailId); + }} + /> + } + {list.done + ? null + : + confirmHandler(e, list.detailId)} + disabled={list.done ? true : false} + /> + } + { + showDetailsHandler(e, list.detailId); + }} + /> + +
      +
    • + ))} +
    +
    +
    +
    + + ); +}; + +export default MyOrder; diff --git a/src/routes/Mypage/MyOrderDetails.tsx b/src/routes/Mypage/MyOrderDetails.tsx new file mode 100644 index 00000000..110f008e --- /dev/null +++ b/src/routes/Mypage/MyOrderDetails.tsx @@ -0,0 +1,80 @@ +import { TfiClose } from "react-icons/tfi"; +import { convertDate, convertPrice } from '~/utils/convert'; +import styles from "~/styles/Mypage/MyOrderDetails.module.scss"; + +interface DetailsProps { + setShowDetails: React.Dispatch> + orderDetails: TransactionDetail +} +interface TransactionDetail { + detailId: string + product: { + productId: string + title: string + price: number + description: string + tags: string[] + thumbnail: string + discountRate: number + photo: string + } + timePaid: string + done: boolean + account: { + bankName: string + accountNumber: string + } +} + +const MyOrderDetails = ({ setShowDetails, orderDetails }: DetailsProps) => { + return ( +
    +
    +
    setShowDetails(false)} + > + +
    +
    +

    μ£Όλ¬Έ 상세 정보

    +
    +
    +
    + {orderDetails.product.title} +
    +
    +
    + 주문번호 +

    + {orderDetails.product.productId} +

    + μ£Όλ¬Έλ‚ μ§œ +

    + {convertDate(orderDetails.timePaid)} +

    +
    +
    + {orderDetails.product.title} +

    μƒν’ˆκΈˆμ•‘ β‚©{convertPrice(orderDetails.product.price)}

    +

    μƒν’ˆν™•μ • {orderDetails.done}

    +
    +
    +

    결제 상세

    +

    가상 κ³„μ’Œ 결제 β‚©{convertPrice(orderDetails.product.price)}

    +
    + {orderDetails.account.bankName} + ({orderDetails.account.accountNumber}) +
    +
    +
    +
    +
    +
    + ); +}; + +export default MyOrderDetails; diff --git a/src/routes/Mypage/Mypage.tsx b/src/routes/Mypage/Mypage.tsx new file mode 100644 index 00000000..65eeddec --- /dev/null +++ b/src/routes/Mypage/Mypage.tsx @@ -0,0 +1,94 @@ +import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; +import { logIn } from '~/api/requests'; +import SubNav from '~/components/common/SubNav'; +import MyInfo from "~/routes/Mypage/MyInfo"; +import AccountList from "~/routes/Mypage/MyBankAccount"; +import Transactions from '~/routes/Mypage/MyOrder'; +import styles from '~/styles/Mypage/Mypage.module.scss'; + + +const MyPage = () => { + const user = useSelector((state:any) => state.user) + const navigate = useNavigate(); + const [password, setPassword] = useState('') + const [passwordConfirm, setPasswordConfirm] = useState(false); + + const subNav: string[] = ["μ£Όλ¬Έ λ‚΄μ—­ 관리", "κ³„μ’Œ 정보 관리", "개인 정보 관리"]; + const [category, setCategory] = useState(''); + + useEffect(() => { + if (!localStorage.getItem('token')) { + alert('λ‘œκ·ΈμΈμ„ ν•΄μ£Όμ„Έμš”! 🏠'); + navigate('/sweethome/login'); + } + },[]) + + const onPasswordHandler = (e: React.ChangeEvent) => { + setPassword(e.target.value); + } + + const handleSubmitPasswordConfirm = async (event: React.FormEvent) => { + event.preventDefault(); + const body = { + email: user.email, + password, + }; + + try { + const res = await logIn(body); + if (res) { + setPasswordConfirm(true); + } else { + alert('λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.') + } + } catch (error: any) { + alert(error.message) + } + + }; + + return ( + <> + {localStorage.getItem('token') ? +
    + + {(category === "μ£Όλ¬Έ λ‚΄μ—­ 관리" || category === "" ) && } + {category === "κ³„μ’Œ 정보 관리" && } + {category === "개인 정보 관리" && (!passwordConfirm? +
    +
    +
    +

    개인 정보 μˆ˜μ •

    +
    +

    νšŒμ›λ‹˜μ˜ 정보λ₯Ό μ•ˆμ „ν•˜κ²Œ λ³΄ν˜Έν•˜κΈ° μœ„ν•΄ λΉ„λ°€λ²ˆν˜Έλ₯Ό λ‹€μ‹œ ν•œ 번 확인해 μ£Όμ„Έμš”!

    +
    +
    +
    +

    PW

    + +
    + +
    +
    +
    +
    + : ) + } +
    + :
    + } + + ) + +} + +export default MyPage diff --git a/src/routes/NotFound/NotFount.tsx b/src/routes/NotFound/NotFount.tsx new file mode 100644 index 00000000..8ead4899 --- /dev/null +++ b/src/routes/NotFound/NotFount.tsx @@ -0,0 +1,26 @@ +import { NavLink } from "react-router-dom"; +import { RiEmotionUnhappyLine } from "react-icons/ri" +import styles from "~/styles/NotFound/NotFound.module.scss"; + +const NotFount = () => { + return ( + <> +
    +
    +
    + + 404 +

    νŽ˜μ΄μ§€λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.

    +
    +
    + + + +
    +
    +
    + + ) +} + +export default NotFount; \ No newline at end of file diff --git a/src/routes/Shop/Shop.tsx b/src/routes/Shop/Shop.tsx new file mode 100644 index 00000000..9b5f4f07 --- /dev/null +++ b/src/routes/Shop/Shop.tsx @@ -0,0 +1,86 @@ +import { useState, useEffect } from 'react'; +import { getAllProducts } from '~/api/requests'; +import ProductItem from '~/components/Shop/ProductItem'; +import styles from '~/styles/Shop/Shop.module.scss'; + +const Shop = () => { + type AllProduct = GetProduct[] + + interface GetProduct { + id: string + title: string + price: number + description: string + tags: string + thumbnail: string | null + isSoldOut: boolean + discountRate: number + } + + const [allProducts, setAllProducts] = useState([]); + const [originalProducts, setOriginalProducts] = useState([]); + const [click, ] = useState(false); + + useEffect(() => { + spreadAllProducts(); + }, []) + + useEffect(() => { + }, [allProducts]) + + const spreadAllProducts = async () => { + try { + const res = await getAllProducts(); + setAllProducts(res); + setOriginalProducts(res); + } catch (error: any) { + alert(error.message) + } + } + + // μƒν’ˆ μΉ΄ν…Œκ³ λ¦¬ + const categorys = [ "ALL", "FURNITURE", "KITCHEN", "BEDROOM" ]; + + // μƒν’ˆ μΉ΄ν…Œκ³ λ¦¬λ³„ ν•„ν„° κΈ°λŠ₯ + const categoryHandler = (e: React.MouseEvent) => { + if ((e.target as HTMLButtonElement).value === "ALL") { + setAllProducts(originalProducts) + } else if ((e.target as HTMLButtonElement).value === "FURNITURE") { + setAllProducts(originalProducts.filter(product => product.tags === "FURNITURE")) + } else if ((e.target as HTMLButtonElement).value === "KITCHEN") { + setAllProducts(originalProducts.filter(product => product.tags === "KITCHEN")) + } else if ((e.target as HTMLButtonElement).value === "BEDROOM") { + setAllProducts(originalProducts.filter(product => product.tags === "BEDROOM")) + } + } + + return ( + <> +
    +
    +
      +
    • + {categorys.map(category => ( + + ))} +
    • +
    +
    +
    +
      + {allProducts.map((product: GetProduct)=> ( + + ))} +
    +
    +
    + + ) +} + +export default Shop \ No newline at end of file diff --git a/src/routes/Shop/ShopDetail.tsx b/src/routes/Shop/ShopDetail.tsx new file mode 100644 index 00000000..e7d53c60 --- /dev/null +++ b/src/routes/Shop/ShopDetail.tsx @@ -0,0 +1,218 @@ +import { getProduct } from "~/api/requests"; +import { priceBeforeDiscount, convertPrice } from "~/utils/convert"; +import { useState, useEffect } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { useSelector, useDispatch } from "react-redux"; +import Loading from "~/components/common/Loading"; +import styles from "~/styles/Shop/ShopDetail.module.scss"; + +const ShopDetail = () => { + type Params = { + id: string | undefined; + }; + + const { id } = useParams(); + const navigate = useNavigate(); + const dispatch = useDispatch(); + const globalCart = useSelector((state: any) => state.cart); + + const [product, setProduct] = useState({}); + const [count, setCount] = useState(1); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + getProductHandler(id); + }, []); + + // 단일 μƒν’ˆ 쑰회 + const getProductHandler = async (id: any) => { + setIsLoading(true); + try { + const res = await getProduct(id); + res["quantity"] = count; + setProduct(res); + } catch (error: any) { + alert(error.message); + } + setIsLoading(false); + }; + + // μƒν’ˆ μˆ˜λŸ‰ 계산 + const productCountHandler = (type: string) => { + if (type === "plus") { + setCount(count + 1); + } else { + if (count === 1) return; + setCount(count - 1); + } + }; + + // μž₯λ°”κ΅¬λ‹ˆ 쀑볡 처리 + const cartDuplicationHandler = (id: string, quantity: number) => { + const found = globalCart.filter((el: any) => el.id === id)[0]; + const idx = globalCart.indexOf(found); + const cartItem = { + id: product.id, + title: product.title, + price: product.price, + discountPrice: product.price, + quantity: quantity, + description: product.description, + tags: product.tags, + thumbnail: product.thumbnail, + photo: product.photo, + isSoldOut: product.isSoldOut, + reservations: product.reservations, + discountRate: product.discountRate, + isChecked: false + }; + dispatch({ + type: "RETURN_CART", + items: [ + ...globalCart.slice(0, idx), + cartItem, + ...globalCart.slice(idx + 1) + ] + }); + }; + + // μž₯λ°”κ΅¬λ‹ˆ ν•Έλ“€λŸ¬ + const cartHandler = () => { + const cartItem = { + id: product.id, + title: product.title, + price: product.price, + discountPrice: product.price, + quantity: count, + description: product.description, + tags: product.tags, + thumbnail: product.thumbnail, + photo: product.photo, + isSoldOut: product.isSoldOut, + reservations: product.reservations, + discountRate: product.discountRate, + isChecked: false + }; + + const found = globalCart.find((el: any) => el.id === cartItem.id); + + if (found) { + cartDuplicationHandler(cartItem.id, found.quantity + count); + } else { + dispatch({ type: "RETURN_CART", items: [...globalCart, cartItem] }); + } + + if (confirm("μž₯λ°”κ΅¬λ‹ˆλ₯Ό ν™•μΈν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?")) { + navigate("/sweethome/cart"); + } else return; + }; + + const buyNowHandler = () => { + if (!localStorage.getItem("token")) { + alert("둜그인이 ν•„μš”ν•©λ‹ˆλ‹€."); + navigate("/sweethome/login"); + } else { + navigate("/sweethome/buy", { state: [product] }); + } + }; + + return ( + <> + {isLoading ? : null} +
    +
    +
    + {product.title} +
    +
    +
    +

    {product.tags}

    +

    {product.title}

    +

    + {convertPrice(product.price)}원 +

    +
    + + {convertPrice( + priceBeforeDiscount(product.price, product.discountRate) + )} + 원 + + + {product.discountRate ? `${product.discountRate}%` : ""} + +
    +
    +
    +
    + productCountHandler("minus")} + /> + + productCountHandler("plus")} + /> +
    +
    +

    총 κΈˆμ•‘

    + + {product.discountRate !== 0 + ? convertPrice(product.price * count) + : convertPrice( + priceBeforeDiscount( + product.price, + product.discountRate + ) * count + )} + 원 + +
    +
    +
    + buyNowHandler()} + /> + cartHandler()} + /> +
    +
    +
    + +
    +
    + μƒν’ˆ 상세 정보 +
    +
    +

    {product.description}

    +
    +
    + +
    +
    + +
    + + ); +}; + +export default ShopDetail; diff --git a/src/routes/SignUp/SignUp.tsx b/src/routes/SignUp/SignUp.tsx new file mode 100644 index 00000000..a70e6d6c --- /dev/null +++ b/src/routes/SignUp/SignUp.tsx @@ -0,0 +1,188 @@ +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; +import { signUp } from "~/api/requests"; +import styles from "~/styles/Signup/SignUp.module.scss"; +import Loading from "~/components/common/Loading"; + +interface SignUpBody { + email: string + password: string + displayName: string + profileImgBase64?: string + pwConfirm?: string +} + +const SignUp = () => { + const [isAgree, setIsAgree] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const navigate = useNavigate(); + const { + register, + handleSubmit, + watch, + formState: { errors, isSubmitting }, + reset + } = useForm({ + mode: 'onChange' + }); + + const emailValidation = { + required: "이메일은 ν•„μˆ˜ μž…λ ₯μž…λ‹ˆλ‹€.", + pattern: { + value: /\S+@\S+\.\S+/, + message: "이메일 ν˜•μ‹μ— λ§žμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.", + } + }; + + const nameValidation = { + required: "이름은 ν•„μˆ˜ μž…λ ₯μž…λ‹ˆλ‹€.", + minLength: { + value: 3, + message: "이름은 3μžμ΄μƒμž…λ‹ˆλ‹€.", + } + }; + const passwordValidation = { + required: "λΉ„λ°€λ²ˆν˜ΈλŠ” ν•„μˆ˜ μž…λ ₯μž…λ‹ˆλ‹€.", + pattern: { + value: /^[A-za-z0-9]*$/ , + message: 'μ˜λ¬Έμ™€ 숫자만 κ°€λŠ₯ν•©λ‹ˆλ‹€.' , + } , + minLength: { + value: 8, + message: "λΉ„λ°€λ²ˆν˜ΈλŠ” 8 ~ 16μžμž…λ‹ˆλ‹€." + } + }; + + + const onAgreeHandler = (e: React.ChangeEvent) => { + if (e.target.checked) { + setIsAgree(!isAgree); + } + }; + + const onSubmitHandler = async ({email, displayName, password}: SignUpBody) => { + setIsLoading(true); + + if (!isAgree) { + return alert("μ΄μš©μ•½κ΄€ 및 κ°œμΈμ •λ³΄μˆ˜μ§‘μ— λ™μ˜ν•΄ μ£Όμ„Έμš”!"); + } + let body = { + email, + displayName, + password + }; + try { + isAgree && await signUp(body) + reset(); + if (confirm("νšŒμ›κ°€μž… 성곡!\n둜그인 νŽ˜μ΄μ§€λ‘œ μ΄λ™ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?")) { + navigate('/sweethome/login') + } else { + navigate('/sweethome') + } + } catch (error) { + console.log("SignUp Error", error); + } + setIsLoading(false); + }; + + return ( + <> + {isLoading ? : null} +
    +
    +
    +

    νšŒμ›κ°€μž…

    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +

    1. κ°œμΈμ •λ³΄μ˜ μˆ˜μ§‘ν•­λͺ© 및 μˆ˜μ§‘ 방법

    +

    + 톡계청 λ‚˜λΌν†΅κ³„ μ‚¬μ΄νŠΈμ—μ„œλŠ” 기본적인 νšŒμ› μ„œλΉ„μŠ€ μ œκ³΅μ„ + μœ„ν•œ ν•„μˆ˜μ •λ³΄λ‘œ μ‹€λͺ…인증정보와 κ°€μž…μ •λ³΄λ‘œ κ΅¬λΆ„ν•˜μ—¬ λ‹€μŒμ˜ + 정보λ₯Ό μˆ˜μ§‘ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. ν•„μˆ˜ 정보λ₯Ό μž…λ ₯ν•΄μ£Όμ…”μ•Ό νšŒμ› + μ„œλΉ„μŠ€ 이용이 κ°€λŠ₯ν•©λ‹ˆλ‹€. +

    +

    κ°€. μˆ˜μ§‘ν•˜λŠ” κ°œμΈμ •λ³΄μ˜ ν•­λͺ©

    +

    -κ°€μž…μ •λ³΄: 이메일, 이름, λΉ„λ°€λ²ˆν˜Έ

    +
    +
    +
    + +
    +
    + + ); +}; + +export default SignUp; diff --git a/src/routes/index.tsx b/src/routes/index.tsx new file mode 100644 index 00000000..1660e01c --- /dev/null +++ b/src/routes/index.tsx @@ -0,0 +1,66 @@ +import { createBrowserRouter } from "react-router-dom"; +import App from "~/App"; +import Home from "~/routes/Home/Home"; +import About from "~/routes/About/About"; +import Shop from "~/routes/Shop/Shop"; +import ShopDetail from "~/routes/Shop/ShopDetail"; +import Mypage from "~/routes/Mypage/Mypage"; +import Cart from "~/routes/Cart/Cart"; +import Login from "~/routes/Login/Login"; +import SignUp from "~/routes/SignUp/SignUp"; +import Admin from "~/routes/Admin/Admin"; +import Buy from "~/routes/Buy/Buy"; +import NotFound from "~/routes/NotFound/NotFount"; + +export default createBrowserRouter([ + { + path: "/sweethome", + element: , + children: [ + { + path: "/sweethome", + element: + }, + { + path: "/sweethome/about", + element: + }, + { + path: "/sweethome/shop", + element: + }, + { + path: "/sweethome/shop/:id", + element: + }, + { + path: "/sweethome/mypage", + element: + }, + { + path: "/sweethome/cart", + element: + }, + { + path: "/sweethome/login", + element: + }, + { + path: "/sweethome/signup", + element: + }, + { + path: "/sweethome/admin", + element: + }, + { + path: "/sweethome/buy", + element: + } + ] + }, + { + path: "*", + element: + } +]); diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 00000000..a76aa6d5 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,6 @@ +import { createStore } from "redux"; +import reducers from "~/reducers"; + +const store = createStore(reducers); + +export { store }; diff --git a/src/styles/.DS_Store b/src/styles/.DS_Store new file mode 100644 index 00000000..a45cd949 Binary files /dev/null and b/src/styles/.DS_Store differ diff --git a/src/styles/About/About.module.scss b/src/styles/About/About.module.scss new file mode 100644 index 00000000..c2bc76c2 --- /dev/null +++ b/src/styles/About/About.module.scss @@ -0,0 +1,45 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.about { + display: flex; + justify-content: center; + align-items: center; + + .container { + display: flex; + justify-content: center; + align-items: center; + max-width: 1440px; + min-height: calc(100vh - (100px + 150px)); + gap: 120px; + + .aboutImg { + min-width: 300px; + max-width: 30vw; + max-height: calc(width / 3 * 4); + overflow: hidden; + + img { + width: 100%; + height: 100%; + } + } + + .aboutText { + text-align: right; + + .title { + font-family: $font-logo; + font-size: $font-logo-size; + margin-bottom: 30px; + } + + .introduction { + font-family: "Pretendard"; + line-height: 1.6; + } + } + + } +} \ No newline at end of file diff --git a/src/styles/Admin/Admin.module.scss b/src/styles/Admin/Admin.module.scss new file mode 100644 index 00000000..93a0d5fd --- /dev/null +++ b/src/styles/Admin/Admin.module.scss @@ -0,0 +1,51 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.container { + display: flex; + justify-content: center; + + .nav { + display: flex; + justify-content: center; + width: 200px; + height: auto; + border-right: 1px solid $base-color-gray; + + .category { + position: relative; + top: 10px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 20px; + height: 30px; + text-align: center; + + li { + cursor: pointer; + } + li:nth-child(1) { + margin-top: 15px; + } + } + } + .contentsWrapper{ + width: calc(100% - 200px); + display:flex; + justify-content:center; + + .contents { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + width: 1000px; + max-width: 1240px; + min-height: 600px; + margin: 50px 0 50px; + overflow: auto; + } + } +} \ No newline at end of file diff --git a/src/styles/Admin/AdminOrder.module.scss b/src/styles/Admin/AdminOrder.module.scss new file mode 100644 index 00000000..88cdbc8b --- /dev/null +++ b/src/styles/Admin/AdminOrder.module.scss @@ -0,0 +1,88 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.adminOrder { + display: flex; + justify-content: center; + align-items: center; + margin: 50px auto; + min-height: calc(100vh - (100px + 150px)); + font-family: "Pretendard"; + + .container { + min-width: 1000px; + max-width: 1440px; + + .title { + display: flex; + justify-content: center; + margin-bottom: 40px; + + h2 { + font-size: 24px; + font-weight: 700; + } + } + + .list_nav { + margin-bottom: 10px; + + .list_nav_container { + display: grid; + justify-content: center; + align-items: center; + grid-template-columns: .7fr repeat(5, 1.6fr) repeat(3, 1fr); + width: 100%; + height: 60px; + background-color: $base-color-lightgray; + border-radius: 10px; + font-family: "Pretendard"; + font-weight: 600; + text-align: center; + } + } + + .content { + font-size: 14px; + + .allList { + .list { + display: grid; + grid-template-columns: .7fr repeat(5, 1.6fr) repeat(3, 1fr); + justify-content: center; + align-items: center; + height: 100px; + border: 1px solid $base-color-gray; + border-radius: 10px; + margin-bottom: 10px; + text-align: center; + + + .listBtn { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + font-family: "Pretendard"; + + .cancelBtn, + .confirmBtn { + width: 60px; + height: 26px; + border-radius: 10px; + border-style: none; + font-size: 14px; + cursor: pointer; + background-color: $point-color-pink; + &:hover { + opacity: .8; + } + } + } + + } + } + + } + } +} \ No newline at end of file diff --git a/src/styles/Admin/AdminProduct.module.scss b/src/styles/Admin/AdminProduct.module.scss new file mode 100644 index 00000000..a7260368 --- /dev/null +++ b/src/styles/Admin/AdminProduct.module.scss @@ -0,0 +1,170 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.adminProduct { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-family: "Pretendard"; + + .addProductContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .addProduct { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .title{ + margin-bottom: 50px; + + h2 { + font-family: $font-main; + font-size: $font-main-size; + font-weight: bold; + } + } + + form { + .productTitle, + .productPrice, + .productDescription, + .productTag, + .productDiscount { + .label { + p { + font-family: "Pretendard-Regular"; + font-size: 16px; + font-weight: 500; + margin-bottom: 4px; + } + + .input { + width: 430px; + height: 30px; + padding-left: 10px; + margin-bottom: 20px; + border: 1px solid $base-color-gray; + border-radius: 10px; + outline: none; + background: none; + color: $font-color; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + } + } + } + + .productTag { + margin-bottom: 20px; + } + + .productThumbnail, + .productPhoto { + p { + font-family: "Pretendard-Regular"; + font-size: 16px; + font-weight: 500; + margin-bottom: 4px; + } + + .input { + width: 420px; + height: 28px; + margin-bottom: 20px; + outline: none; + background: none; + color: $font-color; + } + + } + + .productIsSoldOut { + span { + margin-right: 6px; + } + + .input { + margin-bottom: 20px; + } + } + + .btn { + width: 430px; + height: 50px; + border-style: none; + border-radius: 10px; + background-color: $base-color-gray; + color: $base-color-black; + cursor: pointer; + } + } + } + + .adminProductList { + display: flex; + flex-direction: column; + align-items: center; + height: 1000px; + + .title{ + margin: 50px 0 50px; + + p { + font-family: $font-main; + font-size: $font-main-size; + font-weight: bold; + } + } + + .allProduct { + .wrapper { + table { + text-align: center; + + thead { + font-family: "Pretendard"; + + tr { + th { + padding: 10px 20px 10px; + border: 1px solid $base-color-gray; + font-weight: 500; + } + } + } + + tbody { + tr { + text-align: center; + + td { + border: 1px solid $base-color-gray; + } + + .icon { + .modifyBtn { + font-size: 24px; + margin-right: 10px; + } + + .deleteBtn { + font-size: 24px; + } + } + + } + } + } + } + } + + } + } +} \ No newline at end of file diff --git a/src/styles/Admin/AdminUser.module.scss b/src/styles/Admin/AdminUser.module.scss new file mode 100644 index 00000000..26612a5d --- /dev/null +++ b/src/styles/Admin/AdminUser.module.scss @@ -0,0 +1,57 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.adminUser { + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 50px; + + .container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .title { + margin-bottom: 50px; + + h2 { + font-family: $font-main; + font-size: $font-main-size; + font-weight: bold; + } + } + + .wrapper { + margin-bottom: 150px; + + table { + font-family: "Pretendard"; + font-size: 14px; + + thead { + tr { + th { + padding: 10px 20px 10px; + border: 1px solid $base-color-gray; + font-weight: 500; + } + } + } + + tbody { + tr { + td { + padding: 10px 20px 10px; + border: 1px solid $base-color-gray; + font-weight: 300; + text-align: center; + } + } + } + } + } + + } +} \ No newline at end of file diff --git a/src/styles/Buy/Buy.module.scss b/src/styles/Buy/Buy.module.scss new file mode 100644 index 00000000..b273e122 --- /dev/null +++ b/src/styles/Buy/Buy.module.scss @@ -0,0 +1,287 @@ +@import "../common/colors.scss"; +@import "../common/fonts.scss"; + + +.buy { + display: flex; + flex-direction: column; + align-items: center; + min-height: calc(100vh - (100px + 150px)); + margin: 50px auto; + font-family: "Pretendard"; + color: $font-color; + + h4 { + font-size: 22px; + font-weight: 600; + margin: 0 0 10px 10px; + + } + + .container { + min-width: 1000px; + max-width: 1440px; + margin: 20px 0; + + .title { + display: flex; + justify-content: center; + margin-bottom: 40px; + + h2 { + font-size: 24px; + font-weight: 700; + } + } + + .list_nav { + .list_nav_container { + display: grid; + justify-content: center; + align-items: center; + grid-template-columns: 1.3fr 1.5fr repeat(3, 1fr); + width: 100%; + height: 60px; + background-color: $base-color-lightgray; + border-radius: 10px; + font-family: "Pretendard"; + font-weight: 600; + text-align: center; + } + } + + .buyList { + .container { + width: 100%; + + .cartItem { + display: grid; + grid-template-columns: 1.3fr 1.5fr repeat(3, 1fr); + align-items: center; + min-width: 1000px; + max-width: 1440px; + padding: 20px 0 20px; + margin-bottom: 20px; + border: 1px solid $base-color-gray; + border-radius: 10px; + text-align: center; + + .checkbox { + + } + + a { + display: flex; + justify-content: center; + align-items: center; + + .itemImg { + width: 100px; + height: 100px; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + } + + .itemTitle { + span { + + } + } + + .itemQuantity { + span { + + } + } + + .itemPrice { + display: flex; + flex-direction: column; + + .originalPrice { + text-decoration: line-through; + } + } + + .totalPrice { + + } + + .deleteBtn { + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + + svg { + path { + padding: 20px; + &:hover { + color: $point-color-red; + } + } + } + } + } + } + } + + .total_price { + background-color: $base-color-lightgray; + border-radius: 10px; + height: 100px; + margin-bottom: 30px; + + .total_price_container { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + font-size: 18px; + gap: 6px; + + strong { + font-weight: 600; + } + + .plus, + .equal { + svg { + path { + color: $point-color-mint; + } + } + margin: 0 8px 0; + } + } + } + + .customer { + margin-bottom: 30px; + + .customer_container { + display: grid; + justify-content: center; + align-items: center; + grid-template-columns: repeat(2, 1fr); + width: 100%; + height: 60px; + border: 1px solid $base-color-gray; + border-radius: 10px; + font-family: "Pretendard"; + font-weight: 600; + text-align: center; + + .name { + span { + margin-left: 10px; + } + } + .email { + span { + margin-left: 10px; + } + } + } + } + + .credit { + margin-bottom: 30px; + + .credit_container { + padding: 30px 70px; + border: 1px solid $base-color-gray; + border-radius: 10px; + + .select { + display: flex; + justify-content: center; + gap: 20px; + margin-bottom: 20px; + + p { + font-weight: 600; + } + + } + + .account { + .account_info { + display: grid; + grid-template-columns: repeat(3, 1fr); + text-align: center; + align-items: center; + height: 50px; + border: 1px solid $base-color-gray; + border-radius: 10px; + margin-bottom: 10px; + box-sizing: border-box; + cursor: pointer; + &:hover { + background-color: $base-color-lightgray; + } + &:active { + border: 1.5px solid $point-color-mint; + } + } + + .noAccount { + color: $point-color-red; + text-align: center; + } + } + + .bank { + display: flex; + justify-content: center; + align-items: center; + gap: 20px; + height: 50px; + background-color: $base-color-lightgray; + border: 1px solid $base-color-gray; + border-radius: 10px; + } + } + } + + .payment_details { + .details_container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border: 1px solid $base-color-gray; + border-radius: 10px; + padding: 30px; + gap: 6px; + + span { + font-size: 24px; + font-weight: 600; + } + p { + margin: 5px 0; + } + } + } + } + + .payment_btn { + margin: 30px 0; + + .btn { + width: 500px; + height: 50px; + border-radius: 10px; + border-style: none; + color: $base-color-white; + background-color: $base-color-black; + cursor: pointer; + } + } +} \ No newline at end of file diff --git a/src/styles/Cart/Cart.module.scss b/src/styles/Cart/Cart.module.scss new file mode 100644 index 00000000..ab19c73d --- /dev/null +++ b/src/styles/Cart/Cart.module.scss @@ -0,0 +1,118 @@ +@import "../common/colors.scss"; +@import "../common/fonts.scss"; + +.checkbox { + svg { + width: 22px; + height: 22px; + } +} + +.cart { + display: flex; + justify-content: center; + min-height: calc(100vh - (100px + 150px)); + margin: 0 auto; + font-family: "Pretendard"; + color: $font-color; + + .container { + min-width: 1000px; + max-width: 1440px; + margin: 50px 0 50px; + + .title { + display: flex; + justify-content: center; + margin-bottom: 40px; + + h2 { + font-size: 24px; + font-weight: 700; + } + } + + .list_nav { + margin-bottom: 10px; + + .list_nav_container { + display: grid; + justify-content: center; + align-items: center; + grid-template-columns: 0.5fr 1fr 2fr 0.5fr repeat(2, 1fr) 0.5fr; + width: 100%; + height: 60px; + background-color: $base-color-lightgray; + border-radius: 10px; + font-family: "Pretendard"; + font-weight: 600; + text-align: center; + } + } + + .list_container { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + min-height: 30%; + border-radius: 10px; + margin-bottom: 30px; + + p { + text-align: center; + font-size: 14px; + } + } + + .total_price { + background-color: $base-color-lightgray; + border-radius: 10px; + height: 100px; + margin-bottom: 30px; + + .total_price_container { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + font-size: 18px; + gap: 6px; + + strong { + font-weight: 600; + } + + .plus, + .equal { + svg { + path { + color: $point-color-mint; + } + } + margin: 0 8px 0; + } + } + } + + .buttons { + display: flex; + justify-content: center; + gap: 10px; + + .btn { + width: 200px; + height: 50px; + border-radius: 10px; + border-style: none; + cursor: pointer; + } + .btn_selected { + background-color: $base-color-gray; + } + .btn_selectedAll { + background-color: $point-color-pink; + } + } + } +} diff --git a/src/styles/Cart/CartList.module.scss b/src/styles/Cart/CartList.module.scss new file mode 100644 index 00000000..cb128238 --- /dev/null +++ b/src/styles/Cart/CartList.module.scss @@ -0,0 +1,85 @@ +@import "../common/colors.scss"; +@import "../common/fonts.scss"; + +.cartList { + .container { + width: 100%; + font-family: "Pretendard"; + + .cartItem { + display: grid; + grid-template-columns: 0.5fr 1fr 2fr 0.5fr repeat(2, 1fr) 0.5fr; + align-items: center; + min-width: 1000px; + max-width: 1440px; + padding: 20px 0 20px; + margin-bottom: 20px; + border: 1px solid $base-color-gray; + border-radius: 10px; + text-align: center; + + a { + display: flex; + justify-content: center; + align-items: center; + + .itemImg { + width: 100px; + height: 100px; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + } + + .itemTitle { + span { + + } + } + + .itemQuantity { + span { + + } + } + + .itemPrice { + display: flex; + flex-direction: column; + + .discountPrice { + + } + + .originalPrice { + text-decoration: line-through; + } + } + + .totalPrice { + + } + + .deleteBtn { + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + + svg { + path { + padding: 20px; + &:hover { + color: $point-color-red; + } + } + } + + } + } + } +} \ No newline at end of file diff --git a/src/styles/Home/Home.module.scss b/src/styles/Home/Home.module.scss new file mode 100644 index 00000000..8b897865 --- /dev/null +++ b/src/styles/Home/Home.module.scss @@ -0,0 +1,16 @@ +@import "../common/colors.scss"; +@import "../common/fonts.scss"; + +.main { + position: relative; + width: 100vw; + height: calc(100vh - (100px + 150px)); + display:flex; + justify-content: center; + align-items: center; + + .container { + position: relative; + min-height: calc(100vh - (100px + 150px)); + } +} diff --git a/src/styles/Loading.module.scss b/src/styles/Loading.module.scss new file mode 100644 index 00000000..8f65e664 --- /dev/null +++ b/src/styles/Loading.module.scss @@ -0,0 +1,64 @@ +@import './common/colors.scss'; +@import './common/fonts.scss'; + +@keyframes walking { + 0% { + left: -200px; + } + 100% { + left: -50px; + } +} + +.loading { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto auto; + width: 100vw; + height: 100vh; + background-color: rgb(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 9; + + .container { + position: relative; + display: flex; + + .car { + position: absolute; + top: 48px; + animation: walking 2s linear infinite alternate; + animation-direction: normal; + + svg { + width: 50px; + height: 50px; + + path { + color: $point-color-mint; + } + + circle { + color: $point-color-mint; + } + } + } + + .house { + svg { + width: 100px; + height: 100px; + + path { + color: $point-color-mint; + } + } + } + + } +} \ No newline at end of file diff --git a/src/styles/Login/Login.module.scss b/src/styles/Login/Login.module.scss new file mode 100644 index 00000000..4f8002ff --- /dev/null +++ b/src/styles/Login/Login.module.scss @@ -0,0 +1,114 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +* { + margin: 0; + padding: 0; + font-style: $font-login; + font-size: $font-login-size; + font-weight: $font-login-weight; +} + +.login { + position: relative; + width: 100vw; + height: 700px; + display: flex; + justify-content: center; + + .wrapper { + position: relative; + + .form { + height: 300px; + margin: 25px; + + h2 { + position: relative; + margin: 0 auto; + font-size: 24px; + font-weight: 700; + display: block; + width: fit-content; + top: 30px; + } + + form { + display: block; + position: relative; + width: fit-content; + margin: 0 auto; + top: 100px; + + .id, + .pwd { + width: fit-content; + position: block; + margin: 0 auto; + border-bottom: solid 1px; + padding: 20px; + input { + border: none; + } + } + + .id { + display: flex; + gap: 39px; + } + + .pwd { + display: flex; + gap: 30px; + } + } + + .find { + position: relative; + width: fit-content; + margin: 0 auto; + top: 100px; + display: flex; + gap: 20px; + + p { + font-size: 12px; + color: $base-color-gray; + cursor: pointer; + } + } + } + + .signup { + position: relative; + top: 120px; + width: fit-content; + margin: 0 auto; + + p { + display: block; + width: fit-content; + margin: 0 auto; + font-size: 12px; + line-height: 1.5; + } + } + + button { + width: 430px; + height: 50px; + border-radius: 10px; + border-style: none; + cursor: pointer; + margin-bottom: 20px; + margin-top: 30px; + } + .btn__login { + background-color: $base-color-taupe; + } + .btn__signup { + background-color: $base-color-black; + color: white; + } + } +} diff --git a/src/styles/Mypage/Account.module.scss b/src/styles/Mypage/Account.module.scss new file mode 100644 index 00000000..cb23e76c --- /dev/null +++ b/src/styles/Mypage/Account.module.scss @@ -0,0 +1,31 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.account { + width: 430px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + border: 1px solid #333; + border-radius: 10px; + margin: 10px 0; + color: #333; + + .wrapper { + width: 90%; + display: flex; + justify-content: space-between; + align-items: center; + + .account_details { + display: flex; + gap: 10px; + } + + .deleteBtn { + font-size: 20px; + cursor: pointer; + } + } +} \ No newline at end of file diff --git a/src/styles/Mypage/AccountModal.module.scss b/src/styles/Mypage/AccountModal.module.scss new file mode 100644 index 00000000..faa6720d --- /dev/null +++ b/src/styles/Mypage/AccountModal.module.scss @@ -0,0 +1,123 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.title { + display: flex; + justify-content: center; + margin-bottom: 40px; + font-size: 24px; + font-weight: 700; +} + +.btn { + width: 160px; + height: 50px; + border-radius: 10px; + border-style: none; + cursor: pointer; +} + +.accountModal { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto auto; + width: 100%; + height: 100%; + background-color: rgb(0, 0, 0, 0.2); + display: flex; + justify-content: center; + align-items: center; + + form { + padding: 30px; + max-width: 700px; + max-height: 900px; + border-radius: 10px; + background-color: #fff; + display: flex; + flex-direction: column; + align-items: center; + + .paymentContainer { + display: flex; + justify-content: center; + gap: 20px; + margin: 20px 0; + + li { + display: flex; + flex-direction: column; + align-items: center; + transition: all .2s ease-in-out; + cursor: pointer; + + &:hover { + transform: scale(1.1); + } + + img { + height: 50px; + } + + p { + margin-top: 6px; + font-size: 10px; + } + } + } + + .guide { + color: $point-color-red; + font-size: 12px; + } + + .infoWrap { + margin: 30px 0 10px 50px; + width: 400px; + + label { + display: flex; + font-size: 14px; + gap: 15px; + margin-bottom: 16px; + + input { + text-align: center; + width: 60px; + border-top: none; + border-left: none; + border-right: none; + border-bottom: 1px solid $base-color-black; + outline-color: $point-color-mint; + } + } + } + + .agreementCheck { + display: flex; + align-items: center; + gap: 10px; + + label { + border: none; + } + + p{ + font-size: 14px; + } + } + + .btnWrap { + margin-top: 20px; + + .registrationBtn { + background-color: $base-color-black; + color: $base-color-white; + margin-left: 10px; + } + } + } +} \ No newline at end of file diff --git a/src/styles/Mypage/MyBankAccount.module.scss b/src/styles/Mypage/MyBankAccount.module.scss new file mode 100644 index 00000000..a3f2bcf7 --- /dev/null +++ b/src/styles/Mypage/MyBankAccount.module.scss @@ -0,0 +1,44 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.title { + display: flex; + justify-content: center; + margin-bottom: 40px; + font-size: 24px; + font-weight: 700; +} +.btn { + width: 430px; + height: 50px; + border-radius: 10px; + border-style: none; + cursor: pointer; +} + +.accountList { + margin: 50px 0; + height: 50%; + display: flex; + justify-content: center; + + .noAccount { + display: flex; + justify-content: center; + margin-bottom: 20px; + padding: 20px 0; + + .textWrap { + p { + text-align: center; + color: $point-color-red; + margin-bottom: 5px; + } + } + } + + .btn { + background-color: $base-color-black; + color: $base-color-white; + } +} \ No newline at end of file diff --git a/src/styles/Mypage/MyInfo.module.scss b/src/styles/Mypage/MyInfo.module.scss new file mode 100644 index 00000000..36da9a3d --- /dev/null +++ b/src/styles/Mypage/MyInfo.module.scss @@ -0,0 +1,65 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.title { + display: flex; + justify-content: center; + margin-bottom: 40px; + font-size: 24px; + font-weight: 700; +} + +.btn { + width: 430px; + height: 50px; + border-radius: 10px; + border-style: none; + cursor: pointer; +} + +.info { + margin: 50px 0; + height: 50%; + display: flex; + justify-content: center; + align-items: center; + + label { + display: flex; + align-items: center; + width: 420px; + height: 28px; + margin-bottom: 10px; + padding: 5px 0; + border-bottom: 1px solid $base-color-gray; + font-size: 14px; + + p { + flex-shrink: 0; + width: 100px; + } + + span { + color: $point-color-red; + font-size: 10px; + } + + .input { + width: 180px; + height: 23px; + margin: 10px; + border: none; + outline: none; + font-size: 14px; + &::placeholder { + color: $font-color; + } + + } + } + + .btn { + background-color: $base-color-gray; + color: $base-color-black; + } +} \ No newline at end of file diff --git a/src/styles/Mypage/MyOrder.module.scss b/src/styles/Mypage/MyOrder.module.scss new file mode 100644 index 00000000..047e5a09 --- /dev/null +++ b/src/styles/Mypage/MyOrder.module.scss @@ -0,0 +1,84 @@ +@import "../common/colors.scss"; +@import "../common/fonts.scss"; + +.myOrder { + display: flex; + justify-content: center; + margin: 50px auto; + min-height: calc(100vh - (100px + 150px)); + + .container { + min-width: 1000px; + max-width: 1440px; + + .title { + display: flex; + justify-content: center; + margin-bottom: 40px; + + h2 { + font-size: 24px; + font-weight: 700; + } + } + + .list_nav { + margin-bottom: 10px; + + .list_nav_container { + display: grid; + justify-content: center; + align-items: center; + grid-template-columns: .8fr 1fr 2fr repeat(4, 1fr); + width: 100%; + height: 60px; + background-color: $base-color-lightgray; + border-radius: 10px; + font-family: "Pretendard"; + font-weight: 600; + text-align: center; + } + } + + .content { + .allList { + .list { + display: grid; + grid-template-columns: .8fr 1fr 2fr repeat(4, 1fr); + justify-content: center; + align-items: center; + border: 1px solid $base-color-gray; + border-radius: 10px; + margin-bottom: 10px; + text-align: center; + + .listImg { + img { + width: 100px; + height: auto; + } + } + } + .listBtn { + display: flex; + flex-direction: column; + gap: 4px; + font-family: "Pretendard"; + + .cancelBtn, + .confirmBtn { + width: 100px; + height: 26px; + border-radius: 10px; + border-style: none; + cursor: pointer; + background-color: $point-color-pink; + &:hover { + opacity: .8; + } + } + } + } + } + } +} diff --git a/src/styles/Mypage/MyOrderDetails.module.scss b/src/styles/Mypage/MyOrderDetails.module.scss new file mode 100644 index 00000000..69bb8daa --- /dev/null +++ b/src/styles/Mypage/MyOrderDetails.module.scss @@ -0,0 +1,101 @@ +@import "../common/colors.scss"; +@import "../common/fonts.scss"; + +.title { + margin-bottom: 20px; + font-size: 24px; + font-weight: 600; +} + +.myOrderDetails { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto auto; + width: 100%; + height: 100%; + background-color: rgb(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 9; + + .container { + min-width: 800px; + min-height: 500px; + border-radius: 10px; + background-color: #fff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + + .closeBtn { + position: absolute; + top: 40px; + right: 50px; + cursor: pointer; + + svg { + width: 20px; + height: 20px; + path { + &:hover { + color: $point-color-red; + } + } + } + } + + .details { + display: flex; + gap: 30px; + margin-top: 20px; + + .photo { + height: 320px; + + img { + width: 100%; + height: 100%; + } + } + + .order_content { + display: flex; + flex-direction: column; + justify-content: space-around; + + p { + margin: 6px 0; + } + + .order_number{ + strong { + font-weight: 600; + } + } + + .item_details { + margin: 20px 0; + + strong { + font-size: 20px; + font-weight: 600; + } + } + + .payment_details { + h4 { + font-size: 18px; + font-weight: 600; + } + + } + } + } + } +} \ No newline at end of file diff --git a/src/styles/Mypage/Mypage.module.scss b/src/styles/Mypage/Mypage.module.scss new file mode 100644 index 00000000..6fd1ae37 --- /dev/null +++ b/src/styles/Mypage/Mypage.module.scss @@ -0,0 +1,71 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + + +.btn { + width: 430px; + height: 50px; + border-radius: 10px; + border-style: none; + background-color: $base-color-black; + color: $base-color-white; + cursor: pointer; +} + +.mypage { + min-height: calc(100vh - (100px + 150px)); + +} + +// 개인 정보 μˆ˜μ • - PW 확인창 +.myPageConfirm { + display: flex; + justify-content: center; + align-items: center; + margin: 50px 0; + + .wrapper { + .title { + display: flex; + justify-content: center; + margin-bottom: 40px; + + h2 { + font-size: 24px; + font-weight: 700; + } + } + + .form { + margin: 60px 0; + display: flex; + justify-content: center; + + .PWContaniner { + display: flex; + align-items: center; + gap: 10px; + width: 420px; + height: 28px; + margin-bottom: 20px; + padding: 5px 0; + border-bottom: 1px solid #000; + + p { + font-weight: 700; + } + + input { + width: 420px; + height: 23px; + border: none; + outline: none; + font-size: 14px; + text-align: center; + } + } + } + } +} + + diff --git a/src/styles/NotFound/NotFound.module.scss b/src/styles/NotFound/NotFound.module.scss new file mode 100644 index 00000000..8d768448 --- /dev/null +++ b/src/styles/NotFound/NotFound.module.scss @@ -0,0 +1,61 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.notfound { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100vw; + height: 100vh; + font-family: "Pretendard"; + + .container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .textbox { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: $point-color-mint; + margin-bottom: 40px; + + svg { + width: 100px; + height: 100px; + + path { + color: $point-color-mint; + } + } + + strong { + font-size: 80px; + color: $point-color-mint; + margin: 20px 0; + } + + p { + font-size: 60px; + text-align: center; + color: $point-color-mint;color: $point-color-mint; + } + } + + .btn { + input { + width: 200px; + height: 50px; + border-radius: 10px; + border: 1px solid $base-color-gray; + outline: none; + font-size: 20px; + background-color: $base-color-lightgray; + } + } + } +} \ No newline at end of file diff --git a/src/styles/Shop/ProductItem.module.scss b/src/styles/Shop/ProductItem.module.scss new file mode 100644 index 00000000..1ce72a26 --- /dev/null +++ b/src/styles/Shop/ProductItem.module.scss @@ -0,0 +1,63 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.productContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 285px; + height: 350px; + margin: 10px; + font-family: "Pretendard"; + + strong { + font-weight: 700; + font-size: 18px; + } + + .productPhotoBox { + width: 285px; + height: 280px; + margin-bottom: 14px; + + img { + width: 285px; + height: 280px; + } + } + + .productInfo { + text-align: center; + margin-bottom: 6px; + + .productName { + display: block; + font-size: 16px; + font-weight: 500; + } + + strong { + margin-top: 5px; + font-size: 14px; + } + } + + .productPriceBox { + display: flex; + justify-content: center; + align-items: center; + gap: 5px; + + .priceDiscount { + font-weight: 400; + color: $point-color-red; + } + + .priceThrough { + text-decoration: line-through; + font-size: 14px; + color: gray; + } + } +} \ No newline at end of file diff --git a/src/styles/Shop/Shop.module.scss b/src/styles/Shop/Shop.module.scss new file mode 100644 index 00000000..c36202a0 --- /dev/null +++ b/src/styles/Shop/Shop.module.scss @@ -0,0 +1,49 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.shop { + display:flex; + flex-direction:column; + justify-content: center; + align-items: center; + margin-bottom: 50px; + + .menuWrap { + margin: 50px; + + .menu { + display: flex; + + input { + width: 130px; + height: 48px; + border: 1px solid $base-color-taupe; + border-radius: 38px; + background-color: $base-color-lightgray; + margin-right: 10px; + font-family: "Pretendard"; + font-size: 16px; + font-weight: 500; + cursor: pointer; + + .active { + background-color: #fadad8; + } + + &:nth-child(4) { + margin-right: 0; + } + } + + + } + } + + .productListWrap{ + .productList { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-gap: 20px; + } + } +} \ No newline at end of file diff --git a/src/styles/Shop/ShopDetail.module.scss b/src/styles/Shop/ShopDetail.module.scss new file mode 100644 index 00000000..bc046ec4 --- /dev/null +++ b/src/styles/Shop/ShopDetail.module.scss @@ -0,0 +1,153 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.product { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100vw; + min-height: calc(100vh - (100px + 150px)); + margin: 50px 0 50px; + font-family: "Pretendard"; + + .infoContainer { + display: flex; + justify-content: center; + + .productImg { + margin-right: 100px; + width: 500px; + height: 500px; + + img { + width: 100%; + height: 100%; + object-fit: center; + } + } + + .productInfo { + color: $font-color; + + .productText { + .tags { + margin-bottom: 30px; + font-family: "Markazi Text", serif; + font-size: 18px; + } + + .title { + margin-bottom: 14px; + font-size: 24px; + font-weight: 600; + } + + .discountPrice { + margin-bottom: 6px; + font-size: 22px; + font-weight: 400; + } + + .price { + margin-bottom: 18px; + + .originalPrice { + margin-right: 6px; + font-size: 18px; + text-decoration: line-through; + } + + .discountRate { + font-size: 20px; + color: $point-color-red; + } + } + + .description { + margin-bottom: 20px; + } + } + + .productCount { + margin-bottom: 20px; + + .countBox { + display: flex; + margin-bottom: 30px; + + .count { + display: flex; + justify-content: center; + align-items: center; + width: 40px; + border: 1px solid $base-color-gray; + font-size: 18px; + font-weight: 600; + text-align: center; + } + + .plusBtn, + .minusBtn { + width: 40px; + height: 40px; + background-color: $base-color-lightgray; + border: 1px solid $base-color-gray; + } + } + + .countPrice { + p { + margin-bottom: 6px; + } + + span { + font-size: 24px; + } + } + } + + .buttons { + .btn { + width: 200px; + height: 50px; + border-radius: 10px; + border-style: none; + cursor: pointer; + } + + .buy { + margin-right: 8px; + background-color: $point-color-pink; + color: $base-color-black; + } + .cart { + background-color: $base-color-gray; + color: $base-color-black; + } + } + } + } + + .detailContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin: 80px 0; + + .detailTitle { + span { + font-size: 24px; + font-weight: 600; + } + } + + .detailDescription { + p { + margin: 40px 0 50px; + } + } + + } +} \ No newline at end of file diff --git a/src/styles/SignUp/SignUp.module.scss b/src/styles/SignUp/SignUp.module.scss new file mode 100644 index 00000000..e21cee99 --- /dev/null +++ b/src/styles/SignUp/SignUp.module.scss @@ -0,0 +1,82 @@ +@import '../common/colors.scss'; +@import '../common/fonts.scss'; + +.container { + display: flex; + justify-content: center; + align-items: center; + margin: 50px 0; + + .title { + display: flex; + justify-content: center; + margin-bottom: 40px; + font-size: 24px; + font-weight: 700; + } + + .label { + position: relative; + .input { + width: 430px; + height: 28px; + border: 1px solid #d5d5d5; + color: $font-color; + font-size: 14px; + outline: none; + background: none; + margin-bottom: 20px; + border-bottom: $base-color-gray 1px solid; + border-left: none; + border-right: none; + border-top: none; + &::placeholder { + color: $font-color; + } + } + span { + font-size: 12px; + color: $point-color-red; + position: absolute; + right: 0; + } + } + + + .agree { + margin: 40px 0; + width: 400px; + height: 100%; + font-size: 12px; + + .checkBox { + display: flex; + gap: 10px; + font-size: 14px; + margin-bottom: 10px; + } + + .agreeContainer { + display: flex; + justify-content: center; + width: 430px; + padding: 20px; + border-radius: 10px; + background-color: $base-color-gray; + box-sizing: border-box; + .agreeText{ + width: 90%; + } + } + } + + .btn { + width: 430px; + height: 50px; + border-radius: 10px; + border-style: none; + background-color: $base-color-black; + color: $base-color-white; + cursor: pointer; + } +} \ No newline at end of file diff --git a/src/styles/SubNav.module.scss b/src/styles/SubNav.module.scss new file mode 100644 index 00000000..f6c71d6e --- /dev/null +++ b/src/styles/SubNav.module.scss @@ -0,0 +1,25 @@ +@import "./common/colors.scss"; +@import "./common/fonts.scss"; + +.subnav { + display: flex; + justify-content: center; + align-items: center; + height: 50px; + border-bottom: 1px solid $base-color-gray; + background-color: $base-color-lightgray; + font-family: "Pretendard"; + + .container { + display: flex; + flex-direction: row; + align-items: center; + width: 75%; + max-width: 1440px; + gap: 50px; + + .category { + cursor: pointer; + } + } +} diff --git a/src/styles/TheFooter.module.scss b/src/styles/TheFooter.module.scss new file mode 100644 index 00000000..b7b0ec5d --- /dev/null +++ b/src/styles/TheFooter.module.scss @@ -0,0 +1,86 @@ +@import './common/colors.scss'; +@import './common/fonts.scss'; + +* { + margin: 0; + padding: 0; + color: $font-color; + font-family: 'Pretendard-Regular'; +} + +footer { + display: flex; + justify-content: center; + align-items: center; + // position: fixed; + // bottom: 0; + width: 100vw; + height: 150px; + background-color: $base-color-lightgray; + + .container { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + width: 90%; + max-width: 1440px; + margin-top: 20px; + + ul { + display: flex; + flex-shrink: 0; + justify-content: space-evenly; + gap: 60px; + + .logo { + width: 200px; + + h1 { + font-family: $font-logo; + font-size: $font-logo-size; + } + } + + .company, + .bank, + .customer { + div { + display: flex; + flex-direction: column; + font-size: 12px; + + h2 { + font-weight: 700; + margin-bottom: 10px; + } + + span { + line-height: 1.6em; + } + } + } + + .tel { + div { + display: flex; + flex-direction: column; + font-size: 12px; + + h2 { + font-size: 20px; + font-weight: 700; + margin-bottom: 10px; + } + + span { + line-height: 1.6em; + } + } + } + @media (min-width: 1300px) { + gap: 100px; + } + } + } +} \ No newline at end of file diff --git a/src/styles/TheHeader.module.scss b/src/styles/TheHeader.module.scss new file mode 100644 index 00000000..12351b0a --- /dev/null +++ b/src/styles/TheHeader.module.scss @@ -0,0 +1,124 @@ +@import "./common/colors.scss"; +@import "./common/fonts.scss"; + +* { + margin: 0; + padding: 0; +} + +a { + text-decoration: none; +} + +header { + display: flex; + justify-content: center; + align-items: center; + width: 100vw; + height: 100px; + border-bottom: solid 0.8px $base-color-gray; + padding-left: 40px; + + .container { + position: relative; + display: flex; + width: 90%; + max-width: 1440px; + min-width: 1250px; + + .logo { + display: flex; + align-items: center; + height: 50px; + margin-left: 70px; + margin-right: 70px; + font-family: $font-logo; + font-size: $font-logo-size; + } + + .navbar { + display: flex; + flex-direction: row; + align-items: center; + height: 50px; + right: 400px; + font-family: $font-nav; + font-size: $font-nav-size; + font-weight: $font-nav-weight; + + ul { + display: flex; + justify-content: center; + gap: 50px; + list-style: none; + + .menu { + font-family: $font-nav; + font-size: $font-nav-size; + font-weight: 100; + } + + .active { + color: $point-color-mint; + } + } + } + + .subNav { + position: absolute; + right: 0px; + height: 70px; + margin-right: 70px; + + .user { + font-family: $font-login; + font-size: $font-login-size; + font-weight: $font-login-weight; + margin-bottom: 12px; + + .userLogin { + cursor: pointer; + } + + .userLogout { + cursor: pointer; + margin-left: 40px; + } + + .userSignUp { + margin-left: 20px; + &::before { + content: "|"; + margin-right: 20px; + } + } + } + + .icons { + position: relative; + display: flex; + justify-content: center; + align-items: center; + gap: 30px; + + .mypage, + .shoppingbag { + font-size: 20px; + } + + .search { + display: flex; + justify-content: center; + align-items: center; + + .searchIcon { + cursor: pointer; + width: 20px; + height: 24px; + transform: scale(1.3); + } + } + } + } + } +} diff --git a/src/styles/TheModal.module.scss b/src/styles/TheModal.module.scss new file mode 100644 index 00000000..5e0cf32e --- /dev/null +++ b/src/styles/TheModal.module.scss @@ -0,0 +1,82 @@ +@import "./common/colors.scss"; +@import "./common/fonts.scss"; + +.modal { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.2); + display: flex; + justify-content: center; + align-items: center; + + .container { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + min-width: 500px; + min-height: 500px; + border-radius: 10px; + background-color: $base-color-white; + + .title{ + margin-bottom: 30px; + + h2 { + font-family: $font-main; + font-size: $font-main-size; + font-weight: bold; + } + } + + .contents { + gap: 10px; + + label { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + input { + width: 200px; + text-align: center; + } + } + + .editBtn { + width: 100px; + height: 26px; + border-radius: 10px; + border-style: none; + margin-top: 20px; + cursor: pointer; + background-color: $point-color-pink; + font-family: "Pretendard"; + &:hover { + opacity: .8; + } + } + } + + .closeBtn { + position: absolute; + right: 20px; + top: 20px; + + svg { + path { + &:hover { + color: $point-color-red; + } + } + } + } + } +} diff --git a/src/styles/TheSearchBar.module.scss b/src/styles/TheSearchBar.module.scss new file mode 100644 index 00000000..23fdd990 --- /dev/null +++ b/src/styles/TheSearchBar.module.scss @@ -0,0 +1,45 @@ +@import "./common/colors.scss"; +@import "./common/fonts.scss"; + +.searchContainer { + position: absolute; + top: 0; + right: 20px; + z-index: 9; + font-family: "Pretendard"; + + .search { + display: flex; + position: relative; + gap: 5px; + + .searchBar { + border: 1px solid $base-color-gray; + width: 180px; + height: 28px; + outline: none; + font-size: 0.8rem; + padding: 4px 4px 4px 6px; + box-sizing: border-box; + margin-right: 8px; + } + } + + .searchForm { + display: flex; + align-items: center; + width: 180px; + min-height: 32px; + border: 1px solid $base-color-gray; + border-top: none; + background-color: $base-color-lightgray; + padding: 4px 4px 4px 6px; + box-sizing: border-box; + margin-right: 10px; + font-size: 14px; + + .none { + display: none; + } + } +} diff --git a/src/styles/common/colors.scss b/src/styles/common/colors.scss new file mode 100644 index 00000000..814330f4 --- /dev/null +++ b/src/styles/common/colors.scss @@ -0,0 +1,19 @@ +/******************************************************************* +// μœ„μΉ˜ : src/styles/common/colors.scss +// μ„€λͺ… : 각 μš”μ†Œμ— 적용될 곡톡 색상 SCSSλ₯Ό ν•œκ³³μ— λͺ¨μ•„μ„œ κ΄€λ¦¬ν•©λ‹ˆλ‹€. +********************************************************************/ + +// 베이슀 색상 (base colors) +$base-color-white: #FCFCFC; +$base-color-lightgray: #F6F6F6; +$base-color-gray: #D9D9D9; +$base-color-taupe: #AFA8A8; +$base-color-black: #333333; + +// 포인트 색상 (point colors) +$point-color-mint: #41D3BD; +$point-color-red: #E71D36; +$point-color-pink: #FADAD8; + +// 폰트 κΈ°λ³Έ 적용 색상 (font color) +$font-color: #333333; \ No newline at end of file diff --git a/src/styles/common/fonts.scss b/src/styles/common/fonts.scss new file mode 100644 index 00000000..f0bd2a8b --- /dev/null +++ b/src/styles/common/fonts.scss @@ -0,0 +1,45 @@ +/******************************************************************* +// μœ„μΉ˜ : src/styles/common/fonts.scss +// μ„€λͺ… : 각 μš”μ†Œμ— 적용될 곡톡 폰트 SCSSλ₯Ό ν•œκ³³μ— λͺ¨μ•„μ„œ κ΄€λ¦¬ν•©λ‹ˆ. +********************************************************************/ + +/******* 폰트 리슀트 (Font List) *******/ +// Pretendard +@font-face { + font-family: "Pretendard-Regular"; + src: url("https://cdn.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff") + format("woff"); + font-weight: 400; + font-style: normal; +} + +// Poiret One (Logo) +@import url("https://fonts.googleapis.com/css2?family=Poiret+One&display=swap"); + +// Markazi Text +@import url("https://fonts.googleapis.com/css2?family=Markazi+Text:wght@400;500;600;700&display=swap"); + + +/******* λ³€μˆ˜ 리슀트 (Variables) *******/ +// Logo (λŒ€ν‘œ 둜고 κ΄€λ ¨ 폰트/μ‚¬μ΄μ¦ˆ) +$font-logo: "Poiret One"; +$font-logo-size: 30px; + +// Navbar (λ„€λΉ„κ²Œμ΄μ…˜ λ°” κ΄€λ ¨ 폰트/μ‚¬μ΄μ¦ˆ) +$font-nav: "Pretendard"; +$font-nav-size: 22px; +$font-nav-weight: 100; + +// Login, Logout, Sign-In (둜그인, λ‘œκ·Έμ•„μ›ƒ, νšŒμ›κ°€μž… κ΄€λ ¨ 폰트/μ‚¬μ΄μ¦ˆ) +// login으둜 ν†΅μΌν•˜κ² μŠ΅λ‹ˆλ‹€. +$font-login: "Markazi Text", serif; +$font-login-size: 16px; +$font-login-weight: 400; + +// Button (λ²„νŠΌ κ΄€λ ¨ 폰트/μ‚¬μ΄μ¦ˆ) +$font-btn: "Pretendard-Regular"; +$font-btn-size: 16px; + +// νŽ˜μ΄μ§€λ³„ λŒ€ν‘œ ν…μŠ€νŠΈ (e.g. νšŒμ›κ°€μž…, κ°œμΈμ •λ³΄μˆ˜μ • λ“±) +$font-main: "Pretendard-Regular"; +$font-main-size: 24px; \ No newline at end of file diff --git a/src/styles/utils/Slider.module.scss b/src/styles/utils/Slider.module.scss new file mode 100644 index 00000000..e657bfc9 --- /dev/null +++ b/src/styles/utils/Slider.module.scss @@ -0,0 +1,32 @@ +@import "../common/colors.scss"; +@import "../common/fonts.scss"; + +// width: 1288px; +// height: 578px; + +.slider { + position: relative; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 1288px; + height: calc(width / 4 * 3); + max-width: 1440px; + max-height: calc(100vh - (100px + 150px)); + + .slides { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + max-height: calc(100vh - (100px + 150px)); + + .images { + width: 100%; + height: 100%; + object-fit: contain; + } + } +} diff --git a/src/utils/convert.ts b/src/utils/convert.ts new file mode 100644 index 00000000..99027bcd --- /dev/null +++ b/src/utils/convert.ts @@ -0,0 +1,39 @@ +/******************************************************************* +// μœ„μΉ˜ : src/utils/convert.ts +// μ„€λͺ… : ν”„λ‘œμ νŠΈ μ „λ°˜μ— 걸쳐 자주 μ‚¬μš©λ˜λŠ” ν•¨μˆ˜λ₯Ό ν•œ 곳에 λͺ¨μ•„μ„œ κ΄€λ¦¬ν•©λ‹ˆλ‹€. +********************************************************************/ + +// 할인 가격 계산 +const discountPrice = (productPrice: number, productDiscount: number) => { + return productPrice * ((100 - productDiscount) / 100); +}; + +// 할인 μ „ 가격 계산 +const priceBeforeDiscount = (price:number, discountRate:number) => { + return price * 100 / (100 - discountRate) +}; + +// κΈˆμ•‘ λ‹¨μœ„ ν‘œμ‹œ +const convertPrice = (price: number) => { + return price?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); +}; + +// λ‚ μ§œ ν˜•μ‹ λ³€κ²½ +const convertDate = (dateBody: string) => { + const date = new Date(dateBody); + const year = String(date.getFullYear()).padStart(2, "0"); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const today = String(date.getDate()).padStart(2, "0"); + const hour = String(date.getHours()).padStart(2, "0"); + const min = String(date.getMinutes()).padStart(2, "0"); + return `${year}.${month}.${today} | ${hour}:${min}`; +}; + +// λ‚ μ§œ μ •λ ¬ +const sortDate = (dateBody: string) => { + const date = new Date(dateBody); + const times = date.getTime(); + return times; +} + +export { priceBeforeDiscount, discountPrice, convertPrice, convertDate, sortDate }; \ No newline at end of file diff --git a/src/utils/slider.tsx b/src/utils/slider.tsx new file mode 100644 index 00000000..9db31a53 --- /dev/null +++ b/src/utils/slider.tsx @@ -0,0 +1,51 @@ +import { Swiper, SwiperSlide } from "swiper/react"; +import { + Navigation, + Pagination, + Scrollbar, + A11y, + Autoplay,EffectFade +} from "swiper"; +import "swiper/swiper-bundle.min.css"; +import main1 from "/public/assets/Home/main1.jpeg"; +import main2 from "/public/assets/Home/main2.jpeg"; +import main3 from "/public/assets/Home/main3.jpeg"; +import styles from "~/styles/utils/Slider.module.scss"; + +export const Slider = ({}) => { + return ( + + + main1 + + + main2 + + + main3 + + + ); +}; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..bbf19996 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "~/*": ["src/*"] + }, + "target": "ES2020", + "moduleResolution": "node", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + // "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 00000000..42872c59 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 00000000..170c7971 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import path from "path"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + base: "/sweethome", + resolve: { + alias: { + "~": path.resolve(__dirname, "./src") + } + }, + +});