From 63e477f255c463106e43b3a798f2d5d813b44ac6 Mon Sep 17 00:00:00 2001 From: judahhh Date: Tue, 24 Oct 2023 00:34:22 +0900 Subject: [PATCH 01/33] =?UTF-8?q?style=20:=20=EC=A0=84=EC=97=AD=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20?= =?UTF-8?q?=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 3 ++- package-lock.json | 9 +++++++++ package.json | 1 + src/main.tsx | 14 ++++++++++---- src/styles/index.ts | 0 5 files changed, 22 insertions(+), 5 deletions(-) delete mode 100644 src/styles/index.ts diff --git a/index.html b/index.html index e4b78eae..06a4b3a8 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,10 @@ + - Vite + React + TS + CoffeeMeet
diff --git a/package-lock.json b/package-lock.json index 3742d0b9..ed92fac0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.11.0", "@tanstack/react-query": "^4.36.1", "axios": "^1.5.1", + "emotion-reset": "^3.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.47.0", @@ -3420,6 +3421,14 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/emotion-reset": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/emotion-reset/-/emotion-reset-3.0.1.tgz", + "integrity": "sha512-v6scW83qSu+wtxg7lX1s0+/2U4EAAGFxDQMkvXE10jhKtyuXCzy3/su5/MU9ZjXeNv6ZjxZH51WktrKosKUy9g==", + "peerDependencies": { + "@emotion/react": ">=11" + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", diff --git a/package.json b/package.json index 45c6a883..6a0b0a8d 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@emotion/styled": "^11.11.0", "@tanstack/react-query": "^4.36.1", "axios": "^1.5.1", + "emotion-reset": "^3.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.47.0", diff --git a/src/main.tsx b/src/main.tsx index 30a44e0d..5ad74cea 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,17 +1,23 @@ +import { Global, ThemeProvider } from '@emotion/react' import { QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import ReactDOM from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' import { queryClient } from '@/apis/queryClient' +import { globalStyle } from '@/styles/index.tsx' +import { theme } from '@/styles/index.tsx' import App from './App.tsx' ReactDOM.createRoot(document.getElementById('root')!).render( - - - - + + + + + + + , ) diff --git a/src/styles/index.ts b/src/styles/index.ts deleted file mode 100644 index e69de29b..00000000 From d8fdea9fd1a05bf1b4a3e03b9bb0839d623a8f81 Mon Sep 17 00:00:00 2001 From: judahhh Date: Tue, 24 Oct 2023 00:51:15 +0900 Subject: [PATCH 02/33] =?UTF-8?q?style=20:=20=EC=A0=84=EC=97=AD=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20?= =?UTF-8?q?=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/styles/emotion.d.ts | 9 +++ src/styles/global.ts | 125 ++++++++++++++++++++++++++++++++++++++++ src/styles/index.tsx | 2 + src/styles/palette.ts | 26 +++++++++ src/styles/theme.ts | 30 ++++++++++ src/styles/typo.ts | 45 +++++++++++++++ 6 files changed, 237 insertions(+) create mode 100644 src/styles/emotion.d.ts create mode 100644 src/styles/global.ts create mode 100644 src/styles/index.tsx create mode 100644 src/styles/palette.ts create mode 100644 src/styles/theme.ts create mode 100644 src/styles/typo.ts diff --git a/src/styles/emotion.d.ts b/src/styles/emotion.d.ts new file mode 100644 index 00000000..235c702c --- /dev/null +++ b/src/styles/emotion.d.ts @@ -0,0 +1,9 @@ +import '@emotion/react' + +import { type TypeOfPalette, type TypeOfTypo } from './theme' +declare module '@emotion/react' { + export interface Theme { + palette: TypeOfPalette + typo: TypeOfTypo + } +} diff --git a/src/styles/global.ts b/src/styles/global.ts new file mode 100644 index 00000000..cafaa3e1 --- /dev/null +++ b/src/styles/global.ts @@ -0,0 +1,125 @@ +import { css } from '@emotion/react' +import emotionReset from 'emotion-reset' + +import { media } from '@/styles/theme' +export const globalStyle = css` + ${emotionReset} + + @font-face { + font-family: 'InkLipquid'; + src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/InkLipquid.woff') + format('woff'); + font-weight: normal; + font-style: normal; + } + + @font-face { + font-family: 'Pretendard'; + src: url('https://cdn.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') + format('woff'); + font-weight: 400; + font-style: normal; + } + + body { + font-family: + 'Pretendard', + Pretendard, + -apple-system, + BlinkMacSystemFont, + system-ui, + Roboto, + 'Helvetica Neue', + 'Segoe UI', + 'Apple SD Gothic Neo', + 'Noto Sans KR', + 'Malgun Gothic', + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol', + sans-serif !important; + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + ${media.mobile} { + -ms-overflow-style: none; + } + -ms-overflow-style: none; + scrollbar-width: none; + } + ::-webkit-scrollbar { + display: none; + } + div { + box-sizing: border-box; + } + button { + background: inherit; + border: none; + box-shadow: none; + border-radius: 0; + padding: 0; + overflow: visible; + cursor: pointer; + } + button:focus { + outline: none; + } + input:focus { + outline: none; + } + textarea:focus { + outline: none; + } + .wave { + animation: complete 2s; + opacity: 0; + } + + @keyframes complete { + 0% { + opacity: 1; + position: absolute; + top: 60%; + left: 50%; + transform: translate(-50%, -50%); + } + 100% { + opacity: 0; + position: absolute; + top: 60%; + left: 50%; + transform: translate(-50%, -50%); + } + } + + .fade-out { + opacity: 0; + transition: opacity 1s ease-in-out; + } + + .is_animating { + animation: like 0.5s 1; + } + + @keyframes like { + 0% { + transform: scale(1); + } + 90% { + transform: scale(1.2); + } + 100% { + transform: scale(1.1); + } + } + + .dark-mode { + color: white; + } + .postTitle { + @media (max-width: 375px) { + font-size: 20px; + } + } +` diff --git a/src/styles/index.tsx b/src/styles/index.tsx new file mode 100644 index 00000000..4106dbe1 --- /dev/null +++ b/src/styles/index.tsx @@ -0,0 +1,2 @@ +export * from './global' +export * from './theme' diff --git a/src/styles/palette.ts b/src/styles/palette.ts new file mode 100644 index 00000000..774ef581 --- /dev/null +++ b/src/styles/palette.ts @@ -0,0 +1,26 @@ +export const palette = { + PRIMARY: '#5567F1', + SECONDARY: '#7382F8', + TERTIORY: '#90AEF6', + GRADIENT: '#ADD2F8', + DARK_PRIMARY: '#1D2026', + DARK_SECONDARY: '#494F80', + DARK_TERTIORY: '#5A76B2', + BLACK: '#000000', + WHITE: '#FFFFFF', + DARK_WHITE: '#FDFDFD', + DARK_BLUE: '#1C1F25', + RED: '#F15555', + GREEN: '#03C75A', + YELLOW: '#FEE500', + BLUE: '#004BFF', + DARK_ICON: '#4F5965', + GRAY700: '#313741', + GRAY600: '#33363B', + GRAY500: '#717580', + GRAY400: '#858892', + GRAY300: '#ADB1BA', + GRAY200: '#E5E7EC', + GRAY100: '#EFF0F2', + SKY_BLUE: '#F0F4FF', +} diff --git a/src/styles/theme.ts b/src/styles/theme.ts new file mode 100644 index 00000000..904e8110 --- /dev/null +++ b/src/styles/theme.ts @@ -0,0 +1,30 @@ +import { type Theme } from '@emotion/react' + +import { palette } from './palette' +import { typo } from './typo' + +export const theme: Theme = { + palette, + typo, +} + +export type TypeOfPalette = typeof palette +export type KeyOfPalette = keyof typeof palette + +export type KeyofTheme = keyof typeof theme + +export type TypeOfTypo = typeof typo +export type KeyOfTypo = keyof typeof typo + +export interface TextType { + typo: KeyOfTypo + color: KeyOfPalette +} + +export const customMediaQuery = (minWidth: number): string => `@media (min-width: ${minWidth}px)` + +export const media = { + custom: customMediaQuery, + pc: customMediaQuery(768), + mobile: '@media (max-width : 767px)', +} diff --git a/src/styles/typo.ts b/src/styles/typo.ts new file mode 100644 index 00000000..24c019f7 --- /dev/null +++ b/src/styles/typo.ts @@ -0,0 +1,45 @@ +import { css } from '@emotion/react' + +export const calcRem = (px: number) => `${px / 16}rem` +export const typo = { + Body_20: css` + font-family: 'Pretendard'; + font-size: ${calcRem(20)}; + font-weight: 500; + `, + Body_18: css` + font-family: 'Pretendard'; + font-size: ${calcRem(18)}; + font-weight: 500; + `, + Body_16: css` + font-family: 'Pretendard'; + font-size: ${calcRem(16)}; + font-weight: 400; + `, + Body_14: css` + font-family: 'Pretendard'; + font-size: ${calcRem(13)}; + font-weight: 400; + `, + Body_12: css` + font-family: 'Pretendard'; + font-size: ${calcRem(12)}; + font-weight: 400; + `, + Body_10: css` + font-family: 'Pretendard'; + font-size: ${calcRem(10)}; + font-weight: 400; + `, + Caption_11: css` + font-family: 'Pretendard'; + font-size: ${calcRem(11)}; + font-weight: 400; + `, + Caption_9: css` + font-family: 'Pretendard'; + font-size: ${calcRem(9)}; + font-weight: 500; + `, +} as const From 35f7d9238f3454ee0cdfb8f5c6304dac8111fac9 Mon Sep 17 00:00:00 2001 From: judahhh Date: Tue, 24 Oct 2023 00:59:48 +0900 Subject: [PATCH 03/33] =?UTF-8?q?chore=20:=20=EC=9D=B4=EC=8A=88=20?= =?UTF-8?q?=ED=85=9C=ED=94=8C=EB=A6=BF=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/bug.md | 16 ++++------------ .github/ISSUE_TEMPLATE/feature.md | 16 ++++------------ .github/ISSUE_TEMPLATE/refactor.md | 15 ++++----------- .github/ISSUE_TEMPLATE/style.md | 15 ++++----------- 4 files changed, 16 insertions(+), 46 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index d5f8095a..08a97fdd 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -1,20 +1,12 @@ --- -name: bug -about: 버그를 수정합니다 -title: '' -labels: '' -assignees: '' - ---- - ---- -name: bug issue template -about: 'about need to fix bug' -title: "🐛 [Bug] " +name: Bug Issue Template +about: '버그를 수정합니다!' +title: '🐛 [Bug] ' labels: Bug assignees: '' --- + ## 🛠️ 어떤 버그를 고치나요? ## 버그 작업 브랜치 diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md index 291c45a0..4ef54805 100644 --- a/.github/ISSUE_TEMPLATE/feature.md +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -1,20 +1,12 @@ --- -name: feature -about: 기능을 추가합니다 -title: '' -labels: '' -assignees: '' - ---- - ---- -name: feature request template -about: feature -title: "🚀 [Feature] " +name: Feature issue template +about: 새로운 기능을 추가합니다! +title: '🚀 [Feature] ' labels: Feature assignees: '' --- + ## 🚀 어떤 기능을 만드나요? ## 작업 중인 브랜치 diff --git a/.github/ISSUE_TEMPLATE/refactor.md b/.github/ISSUE_TEMPLATE/refactor.md index 44d77d17..4d6aa5f8 100644 --- a/.github/ISSUE_TEMPLATE/refactor.md +++ b/.github/ISSUE_TEMPLATE/refactor.md @@ -1,20 +1,13 @@ --- -name: refactor -about: 코드를 리팩토링합니다 -title: '' -labels: '' +name: Refactor Issue Template +about: '리팩토링이 필요한 코드를 수정합니다!' +title: '🔨 [Refactor] ' +labels: 'Refactor' assignees: '' --- ---- -name: refactor issue template -about: 'about need to refactor ' -title: " [Refactor] " -labels: Refactor -assignees: '' ---- ## 🛠️ 리팩토링이 필요한 부분 ## 리팩토링 작업 브랜치 diff --git a/.github/ISSUE_TEMPLATE/style.md b/.github/ISSUE_TEMPLATE/style.md index 62416832..27866ccf 100644 --- a/.github/ISSUE_TEMPLATE/style.md +++ b/.github/ISSUE_TEMPLATE/style.md @@ -1,20 +1,13 @@ --- -name: style -about: 애플리케이션을 디자인합니다. -title: '' -labels: '' +name: Style Issue Template +about: '새로운 디자인을 만듭니다!' +title: '💄 [Style] ' +labels: 'Style' assignees: '' --- ---- -name: style issue template -about: 'make Style Component' -title: "💄 [Style] " -labels: Style -assignees: '' ---- ## ✨ 어떤 Style 작업인가요? ## 스타일 작업 브랜치 From d50f56070f069368e02a8e1a6f9e0e755eea952b Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Tue, 24 Oct 2023 14:05:19 +0900 Subject: [PATCH 04/33] =?UTF-8?q?refactor:=20build-test.yml=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - node_modules 캐싱 기능 추가 - main, dev 브랜치 push, PR 시 빌드 테스트 돌아가도록 수정 Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- .github/workflows/build-test.yml | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index bd602610..9ae833ab 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -2,9 +2,9 @@ name: Build Test on: push: - branches: [main] + branches: [main, dev] pull_request: - branches: [main] + branches: [main, dev] workflow_dispatch: jobs: @@ -12,15 +12,31 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@v3 - - name: Set up Node.js + - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - - name: Install dependencies + - name: Cache dependencies + id: cache + uses: actions/cache@v3 + with: + # cache의 대상을 정합니다. npm에서 의존성이 설치되는 디렉터리인 node_modules를 대상으로 합니다. + path: '**/node_modules' + # cache를 무효화하를 결정하는 기준은 의존성이 변경되면 함께 변경되는 파일인 package-lock.json을 기준으로 합니다. + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + # key가 유효하지 않은 경우 runner의 운영체제 값과 node라는 suffix를 key로 복구합니다. + # 결과적으로 package-lock.json이 변경되지 않았다면 캐싱된 node_modules를 사용합니다. + # 만약 복구될 캐시가 없다면 아래에서 사용할 cache-hit는 false가 됩니다. + restore-keys: | + ${{ runner.os }}-node- + + - name: Install Dependencies + # 이전의 cache가 없다면 의존성을 설치합니다. + if: steps.cache.outputs.cache-hit != 'true' run: npm ci - name: Build React app From 7aacb40f2a4ee9b7fc107f175b2549cf9682117e Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Tue, 24 Oct 2023 14:45:05 +0900 Subject: [PATCH 05/33] =?UTF-8?q?feature:=20deploy.yml=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80=20(#15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feature: deploy.yml 파일 추가 - main 브랜치에 push할 때 S3로 파일 업로드 * fix: pre-push 코드 수정 * fix: deploy.yml 파일 수정 - build폴더가 아닌 dist 폴더로 수정 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- .github/workflows/deploy.yml | 53 ++++++++++++++++++++++++++++++++++++ .husky/pre-push | 1 + 2 files changed, 54 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..76039ceb --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,53 @@ +name: CD + +on: + push: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Cache dependencies + id: cache + uses: actions/cache@v3 + with: + # cache의 대상을 정합니다. npm에서 의존성이 설치되는 디렉터리인 node_modules를 대상으로 합니다. + path: '**/node_modules' + # cache를 무효화하를 결정하는 기준은 의존성이 변경되면 함께 변경되는 파일인 package-lock.json을 기준으로 합니다. + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + # key가 유효하지 않은 경우 runner의 운영체제 값과 node라는 suffix를 key로 복구합니다. + # 결과적으로 package-lock.json이 변경되지 않았다면 캐싱된 node_modules를 사용합니다. + # 만약 복구될 캐시가 없다면 아래에서 사용할 cache-hit는 false가 됩니다. + restore-keys: | + ${{ runner.os }}-node- + + - name: Install Dependencies + # 이전의 cache가 없다면 의존성을 설치합니다. + if: steps.cache.outputs.cache-hit != 'true' + run: npm ci + + - name: Build React app + run: npm run build + + - name: S3 Deploy + run: aws s3 sync ./dist s3://coffee-meet-frontend-s3/ --acl bucket-owner-full-control # 현재 build된 폴더에 접근 후 s3 버킷인 coffee-meet-frontend-s3에 파일 업로드 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + - name: Invalidate CloudFront Cache # 새로 리소스를 업데이트할 때 기존 캐시 무효화 + uses: chetan/invalidate-cloudfront-action@master + env: + AWS_DISTRIBUTION: ${{ secrets.AWS_DISTRIBUTION_ID }} + PATHS: '/index.html' + continue-on-error: true \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push index 936396df..e5409824 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,6 +1,7 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" +npm install npm run lint npm run build rm -rf dist From eece0719fa397bd89b16b31c79365201f3a995e3 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Tue, 24 Oct 2023 17:47:32 +0900 Subject: [PATCH 06/33] =?UTF-8?q?[Style]=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=9E=91=EC=97=85=20(#1?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style : 모바일 레이아웃 작업 * chore : 이슈, pr 템플릿 docs 수정 --- .github/ISSUE_TEMPLATE/bug.md | 2 +- .github/ISSUE_TEMPLATE/feature.md | 2 +- .github/ISSUE_TEMPLATE/refactor.md | 2 +- .github/ISSUE_TEMPLATE/style.md | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- index.html | 5 ++-- public/coffee.png | Bin 0 -> 10506 bytes public/favicon.svg | 0 public/manifest.json | 15 +++++++++++ src/App.tsx | 39 +++++++++++++++-------------- src/components/layouts/Layout.tsx | 22 ++++++++++++++++ 11 files changed, 65 insertions(+), 26 deletions(-) create mode 100644 public/coffee.png create mode 100644 public/favicon.svg create mode 100644 public/manifest.json create mode 100644 src/components/layouts/Layout.tsx diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 08a97fdd..0d953579 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -12,4 +12,4 @@ assignees: '' ## 버그 작업 브랜치 ## ☑ Bug Fix TODOS -- [x] 작업 +- [ ] 작업 diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md index 4ef54805..e4071f0d 100644 --- a/.github/ISSUE_TEMPLATE/feature.md +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -12,4 +12,4 @@ assignees: '' ## 작업 중인 브랜치 ## ☑ Implement TODO -- [x] 작업 +- [ ] 작업 diff --git a/.github/ISSUE_TEMPLATE/refactor.md b/.github/ISSUE_TEMPLATE/refactor.md index 4d6aa5f8..3ab3a04f 100644 --- a/.github/ISSUE_TEMPLATE/refactor.md +++ b/.github/ISSUE_TEMPLATE/refactor.md @@ -13,4 +13,4 @@ assignees: '' ## 리팩토링 작업 브랜치 ## ☑ Refactoring TODO -- [x] 작업 +- [ ] 작업 diff --git a/.github/ISSUE_TEMPLATE/style.md b/.github/ISSUE_TEMPLATE/style.md index 27866ccf..4a046f5d 100644 --- a/.github/ISSUE_TEMPLATE/style.md +++ b/.github/ISSUE_TEMPLATE/style.md @@ -13,4 +13,4 @@ assignees: '' ## 스타일 작업 브랜치 ## ☑ Style TODO -- [x] 작업 +- [ ] 작업 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f6e646f8..d780417b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,6 +4,6 @@ close: # ## 작업 내용 설명 -- [x] 작업 +- ## 리뷰어에게 한마디 diff --git a/index.html b/index.html index 06a4b3a8..a373b1fc 100644 --- a/index.html +++ b/index.html @@ -1,9 +1,10 @@ - + - + + CoffeeMeet diff --git a/public/coffee.png b/public/coffee.png new file mode 100644 index 0000000000000000000000000000000000000000..f699e823f82652b357030e1032d853e5f42983ad GIT binary patch literal 10506 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 { return ( - {/* }> */} - }> - } /> - } /> - } /> - } /> - }> - + }> + }> + } /> + } /> + } /> + } /> + }> + - }> - } /> - } /> - } /> - } /> - }> - + }> + } /> + } /> + } /> + } /> + }> + - }> - }> + }> + }> + }> + }> - }> - {/* */} ) } diff --git a/src/components/layouts/Layout.tsx b/src/components/layouts/Layout.tsx new file mode 100644 index 00000000..ecdc23be --- /dev/null +++ b/src/components/layouts/Layout.tsx @@ -0,0 +1,22 @@ +import styled from '@emotion/styled' +import { Outlet } from 'react-router-dom' + +import { theme } from '@/styles/theme' + +const Layout = () => { + return ( + + + + ) +} + +const MainContainer = styled.main` + position: relative; + max-width: 480px; + height: calc(var(--vh, 1vh) * 100); + margin: 0 auto; + background-color: ${theme.palette.GRAY200}; +` + +export default Layout From e0039d960e43216eabec74f29c325a449651afff Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Wed, 25 Oct 2023 18:40:20 +0900 Subject: [PATCH 07/33] =?UTF-8?q?style:=20HeroImage,=20AlertText=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=9C=EC=9E=91=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: HeroImage 추가 * style: AlertText --- src/assets/LoginImage.svg | 21 +++++++++++++++++++++ src/components/AlertText.tsx | 24 ++++++++++++++++++++++++ src/components/HeroImage.tsx | 19 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 src/assets/LoginImage.svg create mode 100644 src/components/AlertText.tsx create mode 100644 src/components/HeroImage.tsx diff --git a/src/assets/LoginImage.svg b/src/assets/LoginImage.svg new file mode 100644 index 00000000..3de58a8b --- /dev/null +++ b/src/assets/LoginImage.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/AlertText.tsx b/src/components/AlertText.tsx new file mode 100644 index 00000000..f3d3003b --- /dev/null +++ b/src/components/AlertText.tsx @@ -0,0 +1,24 @@ +import styled from '@emotion/styled' + +type AlertTextProps = { + fontSize: string + fontColor: string + children?: React.ReactNode +} + +const AlertText = ({ fontSize, fontColor, children }: AlertTextProps) => { + return ( + <> + + {children} + + + ) +} + +const StyleAlertText = styled.div` + font-size: ${(props) => props.fontSize}; + color: ${(props) => props.fontColor}; +` + +export default AlertText diff --git a/src/components/HeroImage.tsx b/src/components/HeroImage.tsx new file mode 100644 index 00000000..09c4f5a5 --- /dev/null +++ b/src/components/HeroImage.tsx @@ -0,0 +1,19 @@ +import styled from '@emotion/styled' + +import LoginImage from '../assets/LoginImage.svg' + +const HeroImage = () => { + return ( + <> + + + ) +} + +const StyleHeroImage = styled.img` + border-radius: 20px; + width: 306px; + height: 306px; +` + +export default HeroImage From f7cc442eeb01fcc8ff2854d8fe201029bd5f2280 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Wed, 25 Oct 2023 18:41:19 +0900 Subject: [PATCH 08/33] =?UTF-8?q?style:=20Input=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=20(#26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: HeroImage 추가 * style: AlertText * style: Input --- src/components/Input.tsx | 65 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/components/Input.tsx diff --git a/src/components/Input.tsx b/src/components/Input.tsx new file mode 100644 index 00000000..a3de8865 --- /dev/null +++ b/src/components/Input.tsx @@ -0,0 +1,65 @@ +import styled from '@emotion/styled' + +type InputProps = { + placeholder?: string + placeholderSize?: string + placeholderColor?: string + width?: string + height?: string + borderColor?: string + borderWidth?: string + inputTextColor?: string + inputTextSize?: string + inputBackgroundColor?: string + borderRadius?: string +} + +const Input = ({ + placeholder, + placeholderSize, + placeholderColor, + width, + height, + borderColor, + borderWidth, + borderRadius, + inputTextColor, + inputTextSize, + inputBackgroundColor, +}: InputProps) => { + return ( + <> + + + ) +} + +const StyleInput = styled.input` + ::placeholder { + font-size: ${(props) => props.placeholderSize}; + color: ${(props) => props.placeholderColor}; + } + placeholder: ${(props) => props.placeholder}; + width: ${(props) => props.width}; + height: ${(props) => props.height}; + border-color: ${(props) => props.borderColor}; + border-width: ${(props) => props.borderWidth}; + color: ${(props) => props.inputTextColor}; + background-color: ${(props) => props.inputBackgroundColor}; + border-radius: ${(props) => props.borderRadius}; + font-size: ${(props) => props.inputTextSize}; +` + +export default Input From 0dd38b2eb4bbfd68f61d2abd4f6b5c895c5041dd Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Wed, 25 Oct 2023 19:30:13 +0900 Subject: [PATCH 09/33] =?UTF-8?q?feature:=20Button=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20(#2?= =?UTF-8?q?0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feature: 네이버, 카카오 아이콘 컴포넌트 추가 * feature: NormalButton 컴포넌트 추가 * feature: palette, typo 스타일 수정 * feature: Divider, Text 공통 컴포넌트 추가 * feature: IconButtons 추가 - 관심사, 네이버, 카카오, 특정 주제, 랜덤 매칭 버튼 * feature: Button 컴포넌트 사용법 + timeStamp 유틸 함수 추가 * feature: RandomMatchingJoin 버튼 추가 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- src/assets/icons/KakaoIcon.tsx | 40 ++++ src/assets/icons/NaverIcon.tsx | 34 ++++ .../Buttons/IconButton/IconButtonStyles.ts | 104 +++++++++++ .../Buttons/IconButton/InterestButton.tsx | 73 ++++++++ .../common/Buttons/IconButton/KakaoButton.tsx | 42 +++++ .../common/Buttons/IconButton/NaverButton.tsx | 34 ++++ .../IconButton/ParticularTopicButton.tsx | 84 +++++++++ .../IconButton/RandomMatchingButton.tsx | 83 +++++++++ .../Buttons/IconButton/RandomMatchingJoin.tsx | 56 ++++++ .../common/Buttons/IconButton/index.tsx | 46 +++++ .../Buttons/NormalButton/NormalButton.tsx | 28 +++ .../NormalButton/NormalButtonStyles.ts | 176 ++++++++++++++++++ src/components/common/Buttons/index.tsx | 109 +++++++++++ src/components/common/Divider/index.tsx | 10 + src/components/common/Text/index.tsx | 25 +++ src/styles/palette.ts | 4 +- src/styles/typo.ts | 42 +++-- src/utils/getTimeStamp.ts | 40 ++++ 18 files changed, 1011 insertions(+), 19 deletions(-) create mode 100644 src/assets/icons/KakaoIcon.tsx create mode 100644 src/assets/icons/NaverIcon.tsx create mode 100644 src/components/common/Buttons/IconButton/IconButtonStyles.ts create mode 100644 src/components/common/Buttons/IconButton/InterestButton.tsx create mode 100644 src/components/common/Buttons/IconButton/KakaoButton.tsx create mode 100644 src/components/common/Buttons/IconButton/NaverButton.tsx create mode 100644 src/components/common/Buttons/IconButton/ParticularTopicButton.tsx create mode 100644 src/components/common/Buttons/IconButton/RandomMatchingButton.tsx create mode 100644 src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx create mode 100644 src/components/common/Buttons/IconButton/index.tsx create mode 100644 src/components/common/Buttons/NormalButton/NormalButton.tsx create mode 100644 src/components/common/Buttons/NormalButton/NormalButtonStyles.ts create mode 100644 src/components/common/Buttons/index.tsx create mode 100644 src/components/common/Divider/index.tsx create mode 100644 src/components/common/Text/index.tsx create mode 100644 src/utils/getTimeStamp.ts diff --git a/src/assets/icons/KakaoIcon.tsx b/src/assets/icons/KakaoIcon.tsx new file mode 100644 index 00000000..bea9b248 --- /dev/null +++ b/src/assets/icons/KakaoIcon.tsx @@ -0,0 +1,40 @@ +import { palette } from '@/styles/palette' + +export type IconProps = { + width: number + height: number + iconWidth?: number + iconHeight?: number + borderRadius: number +} + +const KakaoIcon = ({ width, height, iconWidth, iconHeight, borderRadius }: IconProps) => ( + + + + + +) + +export default KakaoIcon diff --git a/src/assets/icons/NaverIcon.tsx b/src/assets/icons/NaverIcon.tsx new file mode 100644 index 00000000..a3db7a9d --- /dev/null +++ b/src/assets/icons/NaverIcon.tsx @@ -0,0 +1,34 @@ +import { palette } from '@/styles/palette' + +import { IconProps } from './KakaoIcon' + +const NaverIcon = ({ width, height, iconWidth, iconHeight, borderRadius }: IconProps) => ( + + + + + +) + +export default NaverIcon diff --git a/src/components/common/Buttons/IconButton/IconButtonStyles.ts b/src/components/common/Buttons/IconButton/IconButtonStyles.ts new file mode 100644 index 00000000..d2421919 --- /dev/null +++ b/src/components/common/Buttons/IconButton/IconButtonStyles.ts @@ -0,0 +1,104 @@ +import { palette } from '@/styles/palette' + +import { NormalButtonStyle } from '../NormalButton/NormalButtonStyles' + +export type IconButtonType = + | 'interest' + | 'interest-dark' + | 'particular-topic' + | 'particular-topic-dark' + | 'random-matching' + | 'random-matching-dark' + | 'random-matching-join' + | 'random-matching-join-dark' + +export const iconButtonStyles: Record = { + interest: { + width: 339, + height: 70, + fontColor: palette.WHITE, + font: 'Body_18', + fontWeight: 500, + letterSpacing: -2, + borderRadius: 20, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + backgroundColor: `linear-gradient(96deg, #7382F8 49.74%, #A6BCFC 93.87%);`, + }, + 'interest-dark': { + width: 339, + height: 70, + fontColor: palette.DARK_WHITE, + font: 'Body_18', + fontWeight: 500, + letterSpacing: -2, + borderRadius: 20, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + backgroundColor: `linear-gradient(89deg, ${palette.DARK_SECONDARY} 49.41%, ${palette.DARK_TERTIARY} 92.91%)`, + }, + 'particular-topic': { + width: 344, + height: 70, + fontColor: palette.GRAY600, + font: 'Body_18', + fontWeight: 500, + letterSpacing: -2, + borderRadius: 20, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + backgroundColor: palette.WHITE, + }, + 'particular-topic-dark': { + width: 344, + height: 70, + fontColor: palette.DARK_WHITE, + font: 'Body_18', + fontWeight: 500, + letterSpacing: -2, + borderRadius: 20, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + backgroundColor: palette.GRAY700, + }, + 'random-matching': { + width: 230, + height: 70, + fontColor: palette.WHITE, + font: 'Body_18', + fontWeight: 500, + letterSpacing: -2, + borderRadius: 20, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + backgroundColor: `linear-gradient(96deg, ${palette.SECONDARY} 49.74%, #A6BCFC 93.87%);`, + }, + 'random-matching-dark': { + width: 230, + height: 70, + fontColor: palette.DARK_WHITE, + font: 'Body_18', + fontWeight: 500, + letterSpacing: -2, + borderRadius: 20, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + backgroundColor: `linear-gradient(86deg, #494F80 8.54%, #5A77B3 94.19%);`, + }, + 'random-matching-join': { + width: 230, + height: 50, + fontColor: palette.WHITE, + font: 'Body_16', + fontWeight: 500, + letterSpacing: -2, + borderRadius: 10, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + backgroundColor: `linear-gradient(96deg, #7382F8 49.74%, #A6BCFC 93.87%);`, + }, + 'random-matching-join-dark': { + width: 230, + height: 50, + fontColor: palette.WHITE, + font: 'Body_16', + fontWeight: 500, + letterSpacing: -2, + borderRadius: 10, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + backgroundColor: `linear-gradient(86deg, #494F80 8.54%, #5A77B3 94.19%);`, + }, +} diff --git a/src/components/common/Buttons/IconButton/InterestButton.tsx b/src/components/common/Buttons/IconButton/InterestButton.tsx new file mode 100644 index 00000000..c46bcbda --- /dev/null +++ b/src/components/common/Buttons/IconButton/InterestButton.tsx @@ -0,0 +1,73 @@ +import { Fragment } from 'react' +import { RiStarFill } from 'react-icons/ri' + +import { Divider } from '@/components/common/Divider' +import { Text, TextWrapper } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +import { IconButtonWrapper, IconWrapper } from '.' + +type InterestButtonProps = { + nickName: string + interests: string[] + isDarkMode?: boolean +} + +const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps) => { + const setButtonType = isDarkMode ? 'interest-dark' : 'interest' + + return ( + + + + + + + {`${nickName}의 관심사`} + + + {interests.map((interest, index) => ( + + {interest} + {index !== interests.length - 1 && } + + ))} + + + + ) +} + +export default InterestButton diff --git a/src/components/common/Buttons/IconButton/KakaoButton.tsx b/src/components/common/Buttons/IconButton/KakaoButton.tsx new file mode 100644 index 00000000..7c5068d4 --- /dev/null +++ b/src/components/common/Buttons/IconButton/KakaoButton.tsx @@ -0,0 +1,42 @@ +import styled from '@emotion/styled' + +import KakaoIcon from '@/assets/icons/KakaoIcon' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +import { IconWrapper } from '.' + +export const ButtonWrapper = styled.button<{ + buttonTheme: 'kakao' | 'naver' +}>` + width: 320px; + height: 60px; + background-color: ${(props) => (props.buttonTheme === 'naver' ? palette.GREEN : palette.YELLOW)}; + border-radius: 15px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.15); +` + +const KakaoButton = () => ( + + + + + + {'카카오톡으로 시작'} + + +) + +export default KakaoButton diff --git a/src/components/common/Buttons/IconButton/NaverButton.tsx b/src/components/common/Buttons/IconButton/NaverButton.tsx new file mode 100644 index 00000000..6c430a9c --- /dev/null +++ b/src/components/common/Buttons/IconButton/NaverButton.tsx @@ -0,0 +1,34 @@ +import NaverIcon from '@/assets/icons/NaverIcon' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +import { IconWrapper } from '.' +import { ButtonWrapper } from './KakaoButton' + +const NaverButton = () => { + return ( + + + + + + {'네이버로 시작'} + + + ) +} + +export default NaverButton diff --git a/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx b/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx new file mode 100644 index 00000000..386d65fb --- /dev/null +++ b/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx @@ -0,0 +1,84 @@ +import { BiChevronRight, BiSolidConversation } from 'react-icons/bi' + +import { Text, TextWrapper } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +import { IconButtonWrapper, IconWrapper } from '.' + +type ParticularTopicButtonProps = { + isDarkMode?: boolean +} + +const ParticularTopicButton = ({ isDarkMode }: ParticularTopicButtonProps) => { + const getButtonType = isDarkMode ? 'particular-topic-dark' : 'particular-topic' + const getIconColor = isDarkMode ? palette.DARK_WHITE : palette.GRAY600 + const getIconBackgroundColor = isDarkMode ? palette.DARK_ICON : palette.GRAY100 + const getSecondTextColor = isDarkMode ? palette.GRAY300 : palette.GRAY500 + + return ( + + + + + + + {'특정 주제로 대화하기'} + + + {'네트워크를 넓혀보세요!'} + + + + + + + ) +} + +export default ParticularTopicButton diff --git a/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx b/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx new file mode 100644 index 00000000..7ca56cc9 --- /dev/null +++ b/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx @@ -0,0 +1,83 @@ +import { BiChevronRight } from 'react-icons/bi' +import { PiTimerBold } from 'react-icons/pi' + +import { Text, TextWrapper } from '@/components/common/Text' +import { palette } from '@/styles/palette' +import { getTimeDelta } from '@/utils/getTimeStamp' + +import { IconButtonWrapper, IconWrapper } from '.' + +type RandomMatchingButtonProps = { + date: string + isDarkMode?: boolean +} + +const RandomMatchingButton = ({ date, isDarkMode }: RandomMatchingButtonProps) => { + const setButtonType = isDarkMode ? 'random-matching-dark' : 'random-matching' + + return ( + + + + + + + {'랜덤매칭 시작하기'} + + + {'마지막 채팅: '} {`${getTimeDelta(date)}`} + + + + + + + ) +} + +export default RandomMatchingButton diff --git a/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx b/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx new file mode 100644 index 00000000..5685b484 --- /dev/null +++ b/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx @@ -0,0 +1,56 @@ +import { BiChevronRight } from 'react-icons/bi' + +import { Text, TextWrapper } from '@/components/common/Text' + +import { IconButtonWrapper, IconWrapper } from '.' + +type RandomMatchingJoinButtonProps = { + isDarkMode?: boolean +} + +const RandomMatchingJoinButton = ({ isDarkMode }: RandomMatchingJoinButtonProps) => { + const setButtonType = isDarkMode ? 'random-matching-join-dark' : 'random-matching-join' + + return ( + + + + {'매칭방에 접속해주세요!'} + + + + + + + ) +} + +export default RandomMatchingJoinButton diff --git a/src/components/common/Buttons/IconButton/index.tsx b/src/components/common/Buttons/IconButton/index.tsx new file mode 100644 index 00000000..07a56af8 --- /dev/null +++ b/src/components/common/Buttons/IconButton/index.tsx @@ -0,0 +1,46 @@ +import { css } from '@emotion/react' +import styled from '@emotion/styled' + +import { typo } from '@/styles/typo' + +import { iconButtonStyles, IconButtonType } from './IconButtonStyles' +import InterestButton from './InterestButton' +import KakaoButton from './KakaoButton' +import NaverButton from './NaverButton' +import ParticularTopicButton from './ParticularTopicButton' +import RandomMatchingButton from './RandomMatchingButton' + +export const IconButtonWrapper = styled.button<{ + iconButtonType: IconButtonType +}>` + ${({ iconButtonType }) => { + const fontFunc = typo[iconButtonStyles[iconButtonType].font] + return css` + ${fontFunc( + iconButtonStyles[iconButtonType].fontWeight, + iconButtonStyles[iconButtonType].letterSpacing, + )} + width: ${iconButtonStyles[iconButtonType].width}px; + height: ${iconButtonStyles[iconButtonType].height}px; + color: ${iconButtonStyles[iconButtonType].fontColor}; + background: ${iconButtonStyles[iconButtonType].backgroundColor}; + box-shadow: ${iconButtonStyles[iconButtonType].boxShadow}; + border-radius: ${iconButtonStyles[iconButtonType].borderRadius}px; + ` + }} +` + +export const IconWrapper = styled.div<{ + borderRadius?: string + backgroundColor?: string +}>` + width: 35px; + height: 35px; + border-radius: ${(props) => props.borderRadius}; + background-color: ${(props) => props.backgroundColor}; + display: flex; + justify-content: center; + align-items: center; +` + +export { InterestButton, KakaoButton, NaverButton, ParticularTopicButton, RandomMatchingButton } diff --git a/src/components/common/Buttons/NormalButton/NormalButton.tsx b/src/components/common/Buttons/NormalButton/NormalButton.tsx new file mode 100644 index 00000000..af501a38 --- /dev/null +++ b/src/components/common/Buttons/NormalButton/NormalButton.tsx @@ -0,0 +1,28 @@ +import { css } from '@emotion/react' +import styled from '@emotion/styled' + +import { typo } from '@/styles/typo' + +import { NormalButtonStyles, NormalButtonType } from './NormalButtonStyles' + +const NormalButton = styled.button<{ + normalButtonType: NormalButtonType +}>` + ${({ normalButtonType }) => { + const fontFunc = typo[NormalButtonStyles[normalButtonType].font] + return css` + ${fontFunc( + NormalButtonStyles[normalButtonType].fontWeight, + NormalButtonStyles[normalButtonType].letterSpacing, + )} + width: ${NormalButtonStyles[normalButtonType].width}px; + height: ${NormalButtonStyles[normalButtonType].height}px; + color: ${NormalButtonStyles[normalButtonType].fontColor}; + background-color: ${NormalButtonStyles[normalButtonType].backgroundColor}; + box-shadow: ${NormalButtonStyles[normalButtonType].boxShadow}; + border-radius: ${NormalButtonStyles[normalButtonType].borderRadius}px; + ` + }} +` + +export default NormalButton diff --git a/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts b/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts new file mode 100644 index 00000000..fb028b1d --- /dev/null +++ b/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts @@ -0,0 +1,176 @@ +import { palette } from '@/styles/palette' +import { KeyOfTypo } from '@/styles/theme' + +export type NormalButtonStyle = { + width: number + height: number + fontColor: string + backgroundColor: string + font: KeyOfTypo + fontWeight: number + letterSpacing: number + boxShadow?: string + stroke?: string + borderRadius: number +} + +export type NormalButtonType = + | 'warning-accept' + | 'warning-deny' + | 'nickname-duplicate' + | 'nickname-duplicate-dark' + | 'email-certify' + | 'email-certify-dark' + | 'form-submit' + | 'admin-accept' + | 'admin-deny' + | 'modal-accept' + | 'modal-deny' + | 'matching' + | 'matching-dark' + +export const NormalButtonStyles: Record = { + 'warning-accept': { + width: 113, + height: 36, + fontColor: palette.WHITE, + backgroundColor: palette.RED, + font: 'Body_12', + fontWeight: 600, + letterSpacing: -1, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + borderRadius: 10, + }, + 'warning-deny': { + width: 113, + height: 36, + fontColor: palette.WHITE, + backgroundColor: palette.GRAY500, + font: 'Body_12', + fontWeight: 600, + letterSpacing: -1, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + borderRadius: 10, + }, + 'nickname-duplicate': { + width: 60, + height: 46, + fontColor: palette.BLACK, + backgroundColor: palette.WHITE, + font: 'Body_12', + fontWeight: 400, + letterSpacing: -0.5, + stroke: palette.GRAY200, + borderRadius: 10, + }, + 'nickname-duplicate-dark': { + width: 60, + height: 46, + fontColor: palette.DARK_WHITE, + backgroundColor: palette.GRAY600, + font: 'Body_12', + fontWeight: 400, + letterSpacing: -0.5, + stroke: palette.GRAY200, + borderRadius: 10, + }, + 'email-certify': { + width: 73, + height: 46, + fontColor: palette.BLACK, + backgroundColor: palette.WHITE, + font: 'Body_12', + fontWeight: 400, + letterSpacing: -0.5, + stroke: palette.GRAY200, + borderRadius: 10, + }, + 'email-certify-dark': { + width: 73, + height: 46, + fontColor: palette.WHITE, + backgroundColor: palette.GRAY700, + font: 'Body_12', + fontWeight: 400, + letterSpacing: -0.5, + stroke: palette.GRAY600, + borderRadius: 10, + }, + 'form-submit': { + width: 335, + height: 45, + fontColor: palette.WHITE, + backgroundColor: palette.PRIMARY, + font: 'Body_16', + fontWeight: 600, + letterSpacing: -1, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + borderRadius: 11, + }, + 'admin-accept': { + width: 110, + height: 47, + fontColor: palette.WHITE, + backgroundColor: palette.PRIMARY, + font: 'Body_16', + fontWeight: 600, + letterSpacing: -2, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + borderRadius: 50, + }, + 'admin-deny': { + width: 110, + height: 47, + fontColor: palette.GRAY400, + backgroundColor: palette.GRAY100, + font: 'Body_16', + fontWeight: 600, + letterSpacing: -2, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + borderRadius: 50, + }, + 'modal-accept': { + width: 85, + height: 40, + fontColor: palette.WHITE, + backgroundColor: palette.PRIMARY, + font: 'Body_14', + fontWeight: 600, + letterSpacing: -2, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + borderRadius: 50, + }, + 'modal-deny': { + width: 85, + height: 40, + fontColor: palette.GRAY400, + backgroundColor: palette.GRAY100, + font: 'Body_14', + fontWeight: 600, + letterSpacing: -2, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + borderRadius: 50, + }, + matching: { + width: 123, + height: 45, + fontColor: palette.WHITE, + backgroundColor: palette.TERTIARY, + font: 'Body_14', + fontWeight: 500, + letterSpacing: -1, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + borderRadius: 8, + }, + 'matching-dark': { + width: 123, + height: 45, + fontColor: palette.DARK_WHITE, + backgroundColor: palette.DARK_TERTIARY, + font: 'Body_14', + fontWeight: 500, + letterSpacing: -1, + boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + borderRadius: 8, + }, +} diff --git a/src/components/common/Buttons/index.tsx b/src/components/common/Buttons/index.tsx new file mode 100644 index 00000000..14c778d1 --- /dev/null +++ b/src/components/common/Buttons/index.tsx @@ -0,0 +1,109 @@ +import KakaoIcon from '@/assets/icons/KakaoIcon' +import NaverIcon from '@/assets/icons/NaverIcon' + +import { + InterestButton, + KakaoButton, + NaverButton, + ParticularTopicButton, + RandomMatchingButton, +} from './IconButton' +import RandomMatchingJoinButton from './IconButton/RandomMatchingJoin' +import NormalButton from './NormalButton/NormalButton' + +const Button = () => { + return ( + <> + {/* 버튼 사용법입니다! */} +
+ alert('hi!')}> + {'이메일 인증?!'} + +
+
+ alert('인증 수락')}> + {'인증 수락'} + + {'무시'} +
+
+ {'이메일 인증'} +
+
+ {'예, 나가겠습니다.'} + {'아니오, 돌아가겠습니다.'} +
+ +
+ {'수락'} + {'거절'} +
+ +
+ {'중복확인'} + {'중복확인'} +
+
+ {'이메일 인증'} + {'이메일 인증'} +
+
+ {'매칭 시작'} + {'매칭 취소'} + {'매칭 재시도'} + {'매칭 완료!!'} +
+
+ {'매칭 시작'} +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+ + ) +} + +export default Button diff --git a/src/components/common/Divider/index.tsx b/src/components/common/Divider/index.tsx new file mode 100644 index 00000000..6553babe --- /dev/null +++ b/src/components/common/Divider/index.tsx @@ -0,0 +1,10 @@ +import styled from '@emotion/styled' + +import { palette } from '@/styles/palette' + +export const Divider = styled.div` + width: 1px; + height: 10px; + margin: 0 12px; + background-color: ${palette.WHITE}; +` diff --git a/src/components/common/Text/index.tsx b/src/components/common/Text/index.tsx new file mode 100644 index 00000000..42dacb80 --- /dev/null +++ b/src/components/common/Text/index.tsx @@ -0,0 +1,25 @@ +import { css } from '@emotion/react' +import styled from '@emotion/styled' + +import { KeyOfTypo } from '@/styles/theme' +import { typo } from '@/styles/typo' + +export const TextWrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; +` + +export const Text = styled.div<{ + font: KeyOfTypo + fontWeight: number + letterSpacing: number +}>` + ${({ font, fontWeight, letterSpacing }) => { + const fontFunc = typo[font] + return css` + ${fontFunc(fontWeight, letterSpacing)} + ` + }} +` diff --git a/src/styles/palette.ts b/src/styles/palette.ts index 774ef581..e19c97dc 100644 --- a/src/styles/palette.ts +++ b/src/styles/palette.ts @@ -1,11 +1,11 @@ export const palette = { PRIMARY: '#5567F1', SECONDARY: '#7382F8', - TERTIORY: '#90AEF6', + TERTIARY: '#90AEF6', GRADIENT: '#ADD2F8', DARK_PRIMARY: '#1D2026', DARK_SECONDARY: '#494F80', - DARK_TERTIORY: '#5A76B2', + DARK_TERTIARY: '#5A76B2', BLACK: '#000000', WHITE: '#FFFFFF', DARK_WHITE: '#FDFDFD', diff --git a/src/styles/typo.ts b/src/styles/typo.ts index 24c019f7..51eed14d 100644 --- a/src/styles/typo.ts +++ b/src/styles/typo.ts @@ -2,44 +2,52 @@ import { css } from '@emotion/react' export const calcRem = (px: number) => `${px / 16}rem` export const typo = { - Body_20: css` + Body_20: (fontWeight: number = 500, letterSpacing?: number) => css` font-family: 'Pretendard'; font-size: ${calcRem(20)}; - font-weight: 500; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; `, - Body_18: css` + Body_18: (fontWeight: number = 500, letterSpacing?: number) => css` font-family: 'Pretendard'; font-size: ${calcRem(18)}; - font-weight: 500; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; `, - Body_16: css` + Body_16: (fontWeight: number = 400, letterSpacing?: number) => css` font-family: 'Pretendard'; font-size: ${calcRem(16)}; - font-weight: 400; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; `, - Body_14: css` + Body_14: (fontWeight: number = 400, letterSpacing?: number) => css` font-family: 'Pretendard'; - font-size: ${calcRem(13)}; - font-weight: 400; + font-size: ${calcRem(14)}; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; `, - Body_12: css` + Body_12: (fontWeight: number = 400, letterSpacing?: number) => css` font-family: 'Pretendard'; font-size: ${calcRem(12)}; - font-weight: 400; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; `, - Body_10: css` + Body_10: (fontWeight: number = 400, letterSpacing?: number) => css` font-family: 'Pretendard'; font-size: ${calcRem(10)}; - font-weight: 400; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; `, - Caption_11: css` + Caption_11: (fontWeight: number = 400, letterSpacing?: number) => css` font-family: 'Pretendard'; font-size: ${calcRem(11)}; - font-weight: 400; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; `, - Caption_9: css` + Caption_9: (fontWeight: number = 500, letterSpacing?: number) => css` font-family: 'Pretendard'; font-size: ${calcRem(9)}; - font-weight: 500; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; `, } as const diff --git a/src/utils/getTimeStamp.ts b/src/utils/getTimeStamp.ts new file mode 100644 index 00000000..d1ccd7aa --- /dev/null +++ b/src/utils/getTimeStamp.ts @@ -0,0 +1,40 @@ +export const getTimeDelta = (postedDate: string) => { + const dt = new Date(postedDate) + const now = new Date() + const diff = now.getTime() - dt.getTime() + + if (isNaN(diff)) { + return '알 수 없음' + } + + const seconds = Math.floor(diff / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + const days = Math.floor(hours / 24) + const months = Math.floor(days / 30) + const years = Math.floor(months / 12) + + let result = '' + + switch (true) { + case seconds < 60: + result = `${seconds}초 전` + break + case minutes < 60: + result = `${minutes}분 전` + break + case hours < 24: + result = `${hours}시간 전` + break + case days < 30: + result = `${days}일 전` + break + case months < 12: + result = `${months}달 전` + break + default: + result = `${years}년 전` + } + + return result +} From 2a7eb3612728ffdf5317c3568498265bbdf7d891 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Thu, 26 Oct 2023 13:12:04 +0900 Subject: [PATCH 10/33] =?UTF-8?q?style=20:=20flexbox=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Flexbox/index.tsx | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/components/common/Flexbox/index.tsx diff --git a/src/components/common/Flexbox/index.tsx b/src/components/common/Flexbox/index.tsx new file mode 100644 index 00000000..daa3f2b1 --- /dev/null +++ b/src/components/common/Flexbox/index.tsx @@ -0,0 +1,52 @@ +import styled from '@emotion/styled' +import { HTMLAttributes, ReactNode } from 'react' + +export interface FlexBoxProps extends HTMLAttributes { + direction?: 'row' | 'column' + justify?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' + align?: 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch' + gap?: number + fullWidth?: boolean + children: ReactNode +} + +/** + * @param direction : direction / 기본 : row + * @param jusitfy : justify-content / 기본 : center + * @param align : align-items / 기본 : center + * @param gap : gap / 기본 : 0 + * @param fullWidth: : 너비 100% 채울지 / 기본 : false + * @param children : flexbox 내부 요소 + */ + +export const FlexBox = ({ + direction = 'row', + justify = 'center', + align = 'center', + gap = 0, + fullWidth = false, + children, + ...props +}: FlexBoxProps) => { + return ( + + {children} + + ) +} + +const StyledFlexBox = styled.div` + display: flex; + flex-direction: ${(props) => props.direction}; + justify-content: ${(props) => props.justify}; + align-items: ${(props) => props.align}; + gap: ${(props) => props.gap}px; + width: ${(props) => (props.fullWidth ? '100%' : 'auto')}; +` From 230613f44fca0b17f5fe6babd816b2338abcf7a6 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Thu, 26 Oct 2023 13:12:28 +0900 Subject: [PATCH 11/33] =?UTF-8?q?style=20:=20spacing=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1=20(#33)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Spacing/index.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/components/common/Spacing/index.tsx diff --git a/src/components/common/Spacing/index.tsx b/src/components/common/Spacing/index.tsx new file mode 100644 index 00000000..75ac5d3a --- /dev/null +++ b/src/components/common/Spacing/index.tsx @@ -0,0 +1,18 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react' + +import { KeyOfPalette, theme } from '@/styles/theme' + +const Spacing = ({ size, color }: { size: number; color?: KeyOfPalette }) => { + return ( +
+ ) +} + +export default Spacing From cb42a5a2ac7bb5beb18e5371ce94527a2ea56e4f Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Thu, 26 Oct 2023 14:07:15 +0900 Subject: [PATCH 12/33] [Feature] mock service worker settings (#29) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * deploy: 초기 배포 (#23) * style : 전역 디자인 시스템 세팅 * style : 전역 디자인 시스템 세팅 * chore : 이슈 템플릿 수정 * refactor: build-test.yml 파일 수정 (#14) - node_modules 캐싱 기능 추가 - main, dev 브랜치 push, PR 시 빌드 테스트 돌아가도록 수정 Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> * feature: deploy.yml 파일 추가 (#15) * feature: deploy.yml 파일 추가 - main 브랜치에 push할 때 S3로 파일 업로드 * fix: pre-push 코드 수정 * fix: deploy.yml 파일 수정 - build폴더가 아닌 dist 폴더로 수정 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> * [Style] 모바일 레이아웃 작업 (#19) * style : 모바일 레이아웃 작업 * chore : 이슈, pr 템플릿 docs 수정 --------- Co-authored-by: judahhh Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> * chore : mock service worker 설치 및 세팅 * chore : mock service worder setting --------- Co-authored-by: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- package-lock.json | 1138 ++++++++++++++++++++++++++++++++++- package.json | 4 + public/mockServiceWorker.js | 289 +++++++++ src/main.tsx | 5 + src/mocks/handlers.ts | 8 + src/mocks/worker.ts | 5 + tsconfig.json | 3 +- 7 files changed, 1433 insertions(+), 19 deletions(-) create mode 100644 public/mockServiceWorker.js create mode 100644 src/mocks/handlers.ts create mode 100644 src/mocks/worker.ts diff --git a/package-lock.json b/package-lock.json index ed92fac0..2e02682c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "husky": "^8.0.0", "lint-staged": "^15.0.1", + "msw": "^2.0.0", "prettier": "^3.0.3", "typescript": "^5.0.2", "vite": "^4.4.5", @@ -549,6 +550,33 @@ "node": ">=6.9.0" } }, + "node_modules/@bundled-es-modules/cookie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz", + "integrity": "sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==", + "dev": true, + "dependencies": { + "cookie": "^0.5.0" + } + }, + "node_modules/@bundled-es-modules/js-levenshtein": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/js-levenshtein/-/js-levenshtein-2.0.1.tgz", + "integrity": "sha512-DERMS3yfbAljKsQc0U2wcqGKUWpdFjwqWuoMugEJlqBnKO180/n+4SR/J8MRDt1AN48X1ovgoD9KrdVXcaa3Rg==", + "dev": true, + "dependencies": { + "js-levenshtein": "^1.1.6" + } + }, + "node_modules/@bundled-es-modules/statuses": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", + "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dev": true, + "dependencies": { + "statuses": "^2.0.1" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", @@ -1202,6 +1230,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/@mswjs/cookies": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-1.0.0.tgz", + "integrity": "sha512-TdXoBdI+h/EDTsVLCX/34s4+9U0sWi92qFnIGUEikpMCSKLhBeujovyYVSoORNbYgsBH5ga7/tfxyWcEZAxiYA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.25.7.tgz", + "integrity": "sha512-U7iFYs/qU/5jfz1VDpoYz3xqX9nzhsBXw7q923dv6GiGTy+m2ZLhD33L80R/shHOW/YWjeH6k16GbIHGw+bAng==", + "dev": true, + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.2.1", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1237,6 +1291,28 @@ "node": ">= 8" } }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true + }, "node_modules/@pkgr/utils": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", @@ -2130,12 +2206,24 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", "dev": true }, + "node_modules/@types/js-levenshtein": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.2.tgz", + "integrity": "sha512-/NCbMABw2uacuyE16Iwka1EzREDD50/W2ggRBad0y1WHBvAkvR9OEINxModVY7D428gXBe0igeVX7bUc9GaslQ==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.14", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", @@ -2201,6 +2289,12 @@ "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", "dev": true }, + "node_modules/@types/statuses": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.3.tgz", + "integrity": "sha512-NwCYScf83RIwCyi5/9cXocrJB//xrqMh5PMw3mYTSFGaI3DuVjBLfO/PCk7QVAC3Da8b9NjxNmTO9Aj9T3rl/Q==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz", @@ -2715,6 +2809,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "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", @@ -2908,6 +3015,26 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", @@ -2917,6 +3044,26 @@ "node": ">=0.6" } }, + "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/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", @@ -2983,6 +3130,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/bundle-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", @@ -3068,6 +3239,51 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "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/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -3083,6 +3299,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-spinners": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-truncate": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", @@ -3099,6 +3327,84 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/clsx": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", @@ -3162,6 +3468,15 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/copy-anything": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", @@ -3318,6 +3633,18 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-data-property": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", @@ -4109,6 +4436,20 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4170,6 +4511,30 @@ "reusify": "^1.0.4" } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/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/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -4276,6 +4641,19 @@ "node": ">= 6" } }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dev": true, + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4341,6 +4719,15 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", @@ -4514,6 +4901,15 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/graphql": { + "version": "16.8.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", + "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/has": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", @@ -4591,6 +4987,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/headers-polyfill": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.2.tgz", + "integrity": "sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==", + "dev": true + }, "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", @@ -4623,28 +5025,60 @@ "url": "https://github.com/sponsors/typicode" } }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, - "engines": { - "node": ">= 4" - } - }, - "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==", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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/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/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/imurmurhash": { @@ -4672,6 +5106,157 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/inquirer/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/inquirer/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/inquirer/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -4732,6 +5317,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -4879,6 +5476,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -4900,6 +5506,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5027,6 +5639,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -5131,6 +5755,15 @@ "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5327,12 +5960,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "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/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/log-update": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", @@ -5498,6 +6153,74 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/msw": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.0.0.tgz", + "integrity": "sha512-lw9UHuzNCWoODHaThGeLLIIuzEBUQkj3fJXQnChHifMKbB2UmF2msHd4d/lnyqjAyD0XWoibdviW9wlstFPpkA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@bundled-es-modules/cookie": "^2.0.0", + "@bundled-es-modules/js-levenshtein": "^2.0.1", + "@bundled-es-modules/statuses": "^1.0.1", + "@mswjs/cookies": "^1.0.0", + "@mswjs/interceptors": "^0.25.1", + "@open-draft/until": "^2.1.0", + "@types/cookie": "^0.4.1", + "@types/js-levenshtein": "^1.1.1", + "@types/statuses": "^2.0.1", + "chalk": "^4.1.2", + "chokidar": "^3.4.2", + "formdata-node": "4.4.1", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.1", + "inquirer": "^8.2.0", + "is-node-process": "^1.2.0", + "js-levenshtein": "^1.1.6", + "node-fetch": "^2.6.7", + "outvariant": "^1.4.0", + "path-to-regexp": "^6.2.0", + "strict-event-emitter": "^0.5.0", + "type-fest": "^2.19.0", + "yargs": "^17.3.1" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.7.x <= 5.2.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "node_modules/nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -5538,12 +6261,60 @@ "tslib": "^2.0.3" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "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/npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -5749,6 +6520,99 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ora/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/outvariant": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", + "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==", + "dev": true + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5839,6 +6703,12 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -6079,6 +6949,32 @@ "react-dom": ">=16" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -6127,6 +7023,15 @@ "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==", "dev": true }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -6375,6 +7280,15 @@ "node": ">=6" } }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6398,6 +7312,15 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", @@ -6416,6 +7339,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -6430,6 +7373,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -6593,6 +7542,30 @@ "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -6834,6 +7807,12 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "node_modules/titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -6846,6 +7825,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -6866,6 +7857,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -7117,6 +8114,12 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, "node_modules/vite": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", @@ -7205,6 +8208,40 @@ } } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7358,6 +8395,15 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -7372,6 +8418,62 @@ "node": ">= 6" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 6a0b0a8d..ac87e0a9 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "husky": "^8.0.0", "lint-staged": "^15.0.1", + "msw": "^2.0.0", "prettier": "^3.0.3", "typescript": "^5.0.2", "vite": "^4.4.5", @@ -53,5 +54,8 @@ "eslint --fix", "prettier --write" ] + }, + "msw": { + "workerDirectory": "public" } } diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js new file mode 100644 index 00000000..29c33774 --- /dev/null +++ b/public/mockServiceWorker.js @@ -0,0 +1,289 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (2.0.0). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '0877fcdc026242810f5bfde0d7178db4' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + // When performing original requests, response body will + // always be a ReadableStream, even for 204 responses. + // But when creating a new Response instance on the client, + // the body for a 204 response must be null. + const responseBody = response.status === 204 ? null : responseClone.body + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseBody, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseBody], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + const headers = Object.fromEntries(requestClone.headers.entries()) + + // Remove internal MSW request header so the passthrough request + // complies with any potential CORS preflight checks on the server. + // Some servers forbid unknown request headers. + delete headers['x-msw-intention'] + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Bypass requests with the explicit bypass header. + // Such requests can be issued by "ctx.fetch()". + const mswIntention = request.headers.get('x-msw-intention') + if (['bypass', 'passthrough'].includes(mswIntention)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'MOCK_NOT_FOUND': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [channel.port2].concat(transferrables.filter(Boolean))) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} diff --git a/src/main.tsx b/src/main.tsx index 5ad74cea..3e98738f 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,11 +5,16 @@ import ReactDOM from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' import { queryClient } from '@/apis/queryClient' +import { worker } from '@/mocks/worker' import { globalStyle } from '@/styles/index.tsx' import { theme } from '@/styles/index.tsx' import App from './App.tsx' +if (process.env.NODE_ENV === 'development') { + worker.start() +} + ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts new file mode 100644 index 00000000..027f0d17 --- /dev/null +++ b/src/mocks/handlers.ts @@ -0,0 +1,8 @@ +import { http, HttpResponse } from 'msw' + +export const handlers = [ + // example + http.get('/pets', () => { + return HttpResponse.json(['Tom', 'Jerry', 'Spike']) + }), +] diff --git a/src/mocks/worker.ts b/src/mocks/worker.ts new file mode 100644 index 00000000..d3824e65 --- /dev/null +++ b/src/mocks/worker.ts @@ -0,0 +1,5 @@ +import { setupWorker } from 'msw/browser' + +import { handlers } from './handlers' + +export const worker = setupWorker(...handlers) diff --git a/tsconfig.json b/tsconfig.json index 27f4d626..51f7e0f0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,8 @@ "@/apis/*": ["src/apis/*"], "@/hooks/*": ["src/hooks/*"], "@/assets/*": ["src/assets/*"], - "@/styles/*": ["src/styles/*"] + "@/styles/*": ["src/styles/*"], + "@/mocks/*": ["src/mocks/*"] } }, "include": ["src"], From 0b837a1f2136f67666f9e7d53e32ee02a669ae3d Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:50:46 +0900 Subject: [PATCH 13/33] =?UTF-8?q?style:=20BottomSheet=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20(#34)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feature: Timer 컴포넌트 추가 + d3.js 라이브러리 설치 * feature: BottomSheet 컴포넌트 추가 * feature: RandomMatchingJoinButton props 수정 * feature: framer-motion 설치 + 애니메이션 효과 추가 * refactor: BottomSheet 폴더 변경 * refactor: isDarkMode 필수 타입으로 수정 * refactor: AlertText, HeroImage 폴더 구조 변경 * refactor: BottomSheet 컴포넌트 분리 * feature: Avatar 컴포넌트 제작 * refactor: RandomMatchingSheet 컴포넌트 이벤트 props 추가 * fix: package-lock.json 파일 오류 수정 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- package-lock.json | 731 +++++++++++++++++- package.json | 3 + .../AlertText/index.tsx} | 0 src/components/common/Avatar/index.tsx | 30 + .../common/BottomSheet/ProfileSheet.tsx | 152 ++++ .../BottomSheet/RandomMatchingSheet.tsx | 163 ++++ src/components/common/BottomSheet/Timer.tsx | 91 +++ src/components/common/BottomSheet/index.tsx | 158 ++++ .../Buttons/IconButton/InterestButton.tsx | 2 +- .../IconButton/ParticularTopicButton.tsx | 2 +- .../IconButton/RandomMatchingButton.tsx | 2 +- .../Buttons/IconButton/RandomMatchingJoin.tsx | 9 +- src/components/common/Buttons/index.tsx | 109 --- .../HeroImage/index.tsx} | 0 14 files changed, 1329 insertions(+), 123 deletions(-) rename src/components/{AlertText.tsx => common/AlertText/index.tsx} (100%) create mode 100644 src/components/common/Avatar/index.tsx create mode 100644 src/components/common/BottomSheet/ProfileSheet.tsx create mode 100644 src/components/common/BottomSheet/RandomMatchingSheet.tsx create mode 100644 src/components/common/BottomSheet/Timer.tsx create mode 100644 src/components/common/BottomSheet/index.tsx delete mode 100644 src/components/common/Buttons/index.tsx rename src/components/{HeroImage.tsx => common/HeroImage/index.tsx} (100%) diff --git a/package-lock.json b/package-lock.json index 2e02682c..cddd3e96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,9 @@ "@emotion/styled": "^11.11.0", "@tanstack/react-query": "^4.36.1", "axios": "^1.5.1", + "d3": "^7.8.5", "emotion-reset": "^3.0.1", + "framer-motion": "^10.16.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.47.0", @@ -23,6 +25,7 @@ "devDependencies": { "@rushstack/eslint-config": "^3.4.1", "@tanstack/react-query-devtools": "^4.36.1", + "@types/d3": "^7.4.2", "@types/node": "^20.8.6", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", @@ -2212,12 +2215,271 @@ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", "dev": true }, + "node_modules/@types/d3": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.2.tgz", + "integrity": "sha512-Y4g2Yb30ZJmmtqAJTqMRaqXwRawfvpdpVmyEYEcyGNhrQI/Zvkq3k7yE1tdN07aFSmNBfvmegMQ9Fe2qy9ZMhw==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.9.tgz", + "integrity": "sha512-mZowFN3p64ajCJJ4riVYlOjNlBJv3hctgAY01pjw3qTnJePD8s9DZmYDzhHKvzfCYvdjwylkU38+Vdt7Cu2FDA==", + "dev": true + }, + "node_modules/@types/d3-axis": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.5.tgz", + "integrity": "sha512-ufDAV3SQzju+uB3Jlty7SUb/jMigjpIlvDDcSGvGmmO6OT/sNO93UE0dRzwWOZeBLzrLSA0CQM4bf3iq1std3A==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.5.tgz", + "integrity": "sha512-JROQXZNq1X6QdWstESDUv1VilwZ2hBCQnWB91yal+5yZvYwGQvYsGCjrkHGfKK/8/AcX1JnERmpQzdDDuLRUsA==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.5.tgz", + "integrity": "sha512-rs26AIhJjtc+XLR4YQU8IjPTLOlDVO4PR1y+pVFYEHzKh2tE5tYz3MF4QV6iz7HboXQEaYpJQt8dH9uUkne8yA==", + "dev": true + }, + "node_modules/@types/d3-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.2.tgz", + "integrity": "sha512-At+Ski7dL8Bs58E8g8vPcFJc8tGcaC12Z4m07+p41+DRqnZQcAlp3NfYjLrhNYv+zEyQitU1CUxXNjqUyf+c0g==", + "dev": true + }, + "node_modules/@types/d3-contour": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.5.tgz", + "integrity": "sha512-wLvjwdOQVd1NL1IcW90CCt1VtpeZ3V20p/OTXlkT8uAiprrJnq2PNNnRNe1QCez4U9aMU29Z14zpJQVLW1+Lcg==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.3.tgz", + "integrity": "sha512-+Lf5NPKZ4JBC9tbudVkKceQXRxU3jJs0el9aKQvinMtdnFSOG84eVXyhCNgIFuXNQO3iIcYs7sgzN359FEOZnQ==", + "dev": true + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.5.tgz", + "integrity": "sha512-hxvq2kc+9hydVppo21JCGfcM0tLTh1DXnG3MLN0KlxsNZJH4bsdl1iXDuWtXFpWWlBrCMwSqlnoLPDxNAZU3Bg==", + "dev": true + }, + "node_modules/@types/d3-drag": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.5.tgz", + "integrity": "sha512-arHyAGvO0NEGGPCU2jTb31TlXeSxwty1bIxr5wOFOCVqVjgriXloLWXoRp39Oa0Y/qXxcAVMIonAWLrtLxUZAQ==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.5.tgz", + "integrity": "sha512-73WZR3QFOaSRVz9iOrebTbTnbo7xjcgS/i0Cq5zy0jMXPO3v/JbkTD3Zqii1eYE6v4EJ78g5VP407rm+p8fdlA==", + "dev": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-VZofjpEt8HWv3nxUAosj5o/+4JflnJ7Bbv07k17VO3T2WRuzGdZeookfaF60iVh5RdhVG49LE5w6LIshVUC6rg==", + "dev": true + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.5.tgz", + "integrity": "sha512-Rc8pb6H0RRLpAV2hEXduykUgcDUOhjSLTLmCIeo6ejzgs4SaITh/EteMb3p5Env3Hqjsqw0fCksyqopHHzMkMg==", + "dev": true, + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.7.tgz", + "integrity": "sha512-rsok4CEvPLyVWRPsFiBhanJc3up03H/EARVz4d8soPh8drv82YMuAckYy4yv8g4/81JwCng5U5/o9aj9d0T6bQ==", + "dev": true + }, + "node_modules/@types/d3-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.3.tgz", + "integrity": "sha512-kxuLXSAEJykTeL/EI3tUiEfGqru7PRdqEy099YBnqFl+fF167UVSB4+wntlZv86ZdoYf0DHjsRHnTIm8kcH7qw==", + "dev": true + }, + "node_modules/@types/d3-geo": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.6.tgz", + "integrity": "sha512-wblAES3b+C3hvp4VakwECEKtHquT/xc6K4HOna95LM1j1fd7s7WmU4V+JMQZfKhNCMkV2vWD+ZUgY2Uj6gqfuA==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.5.tgz", + "integrity": "sha512-DEcBUj1IL3WyPLDlh4m2nsNXnMLITXM5Vwcu4G85yJHtf2cVGPBjgky3L11WBnT+ayHKf06Tchk5mY1eGmd4WQ==", + "dev": true + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.3.tgz", + "integrity": "sha512-6OZ2EIB4lLj+8cUY7I/Cgn9Q+hLdA4DjJHYOQDiHL0SzqS1K9DL5xIOVBSIHgF+tiuO9MU1D36qvdIvRDRPh+Q==", + "dev": true, + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.1.tgz", + "integrity": "sha512-blRhp7ki7pVznM8k6lk5iUU9paDbVRVq+/xpf0RRgSJn5gr6SE7RcFtxooYGMBOc1RZiGyqRpVdu5AD0z0ooMA==", + "dev": true + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-nrcWPk7B9qs6xnpq60Cls44zm9eDmFAv65qi/N/emh/oftnG6uYz49aIS0mdFaGeJxVN8H3pHneMuZMV8EwFdw==", + "dev": true + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.4.tgz", + "integrity": "sha512-B725MopFDIOQ6njFbeOxIEf42HVO2Xv+FmcxQISdOKErvLbFqWz3Riu+OWujUYoogreqqyHBHcGGL/JzzXQYsw==", + "dev": true + }, + "node_modules/@types/d3-random": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.2.tgz", + "integrity": "sha512-8QhsqkKs6mymAZMrg3ZFXPxKA34rdgp3ZrtB8o6mhFsKAd1gOvR1gocWnca+kmXypQdwgnzKm9gZE2Uw8NjjKw==", + "dev": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.6.tgz", + "integrity": "sha512-lo3oMLSiqsQUovv8j15X4BNEDOsnHuGjeVg7GRbAuB2PUa1prK5BNSOu6xixgNf3nqxPl4I1BqJWrPvFGlQoGQ==", + "dev": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.1.tgz", + "integrity": "sha512-Ob7OrwiTeQXY/WBBbRHGZBOn6rH1h7y3jjpTSKYqDEeqFjktql6k2XSgNwLrLDmAsXhEn8P9NHDY4VTuo0ZY1w==", + "dev": true + }, + "node_modules/@types/d3-selection": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.8.tgz", + "integrity": "sha512-pxCZUfQyedq/DIlPXIR5wE1mIH37omOdx1yxRudL3KZ4AC+156jMjOv1z5RVlGq62f8WX2kyO0hTVgEx627QFg==", + "dev": true + }, + "node_modules/@types/d3-shape": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.4.tgz", + "integrity": "sha512-M2/xsWPsjaZc5ifMKp1EBp0gqJG0eO/zlldJNOC85Y/5DGsBQ49gDkRJ2h5GY7ZVD6KUumvZWsylSbvTaJTqKg==", + "dev": true, + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.2.tgz", + "integrity": "sha512-kbdRXTmUgNfw5OTE3KZnFQn6XdIc4QGroN5UixgdrXATmYsdlPQS6pEut9tVlIojtzuFD4txs/L+Rq41AHtLpg==", + "dev": true + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.2.tgz", + "integrity": "sha512-wr08C1Gh77qaN8JIkrn5Rz/bdt5M9bdEqFmEOcYhUSq2t2sHvLTBfb4XAtGB3D4hm0ubj50NXWWXoXyp5tPXDg==", + "dev": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-GGTvzKccVEhxmRfJEB6zhY9ieT4UhGVUIQaBzFpUO9OXy2ycAlnPCSJLzmGGgqt3KVjqN3QCQB4g1rsZnHsWhg==", + "dev": true + }, + "node_modules/@types/d3-transition": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.6.tgz", + "integrity": "sha512-K0To23B5UxNwFtKORnS5JoNYvw/DnknU5MzhHIS9czJ/lTqFFDeU6w9lArOdoTl0cZFNdNrMJSFCbRCEHccH2w==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.6.tgz", + "integrity": "sha512-dGZQaXEu7aNcCL71LPpjB58IjoQNM9oDPfQuMUJ7N/fbkcIWGX2PnmUWO1jPJ+RLbZBpRUggJUX8twKRvo2hKQ==", + "dev": true, + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", "dev": true }, + "node_modules/@types/geojson": { + "version": "7946.0.12", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.12.tgz", + "integrity": "sha512-uK2z1ZHJyC0nQRbuovXFt4mzXDwf27vQeUWNhfKGwRcWW429GOhP8HxUHlM6TLH4bzmlv/HlEjpvJh3JfmGsAA==", + "dev": true + }, "node_modules/@types/js-levenshtein": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.2.tgz", @@ -3526,6 +3788,384 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "node_modules/d3": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3688,6 +4328,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "dependencies": { + "robust-predicates": "^3.0.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -4450,6 +5098,18 @@ "node": ">=4" } }, + "node_modules/external-editor/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "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", @@ -4654,6 +5314,44 @@ "node": ">= 12.20" } }, + "node_modules/framer-motion": { + "version": "10.16.4", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.4.tgz", + "integrity": "sha512-p9V9nGomS3m6/CALXqv6nFGMuFOxbWsmaOrdmhyQimMIlLl3LC7h7l86wge/Js/8cRu5ktutS/zlzgR7eBOtFA==", + "dependencies": { + "tslib": "^2.4.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/framer-motion/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5026,12 +5724,11 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -5271,6 +5968,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -7142,6 +7847,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/rollup": { "version": "3.29.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", @@ -7312,6 +8022,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -7376,8 +8091,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/scheduler": { "version": "0.23.0", @@ -7911,8 +8625,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsutils": { "version": "3.21.0", diff --git a/package.json b/package.json index ac87e0a9..22027b5e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "@emotion/styled": "^11.11.0", "@tanstack/react-query": "^4.36.1", "axios": "^1.5.1", + "d3": "^7.8.5", "emotion-reset": "^3.0.1", + "framer-motion": "^10.16.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.47.0", @@ -26,6 +28,7 @@ "devDependencies": { "@rushstack/eslint-config": "^3.4.1", "@tanstack/react-query-devtools": "^4.36.1", + "@types/d3": "^7.4.2", "@types/node": "^20.8.6", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", diff --git a/src/components/AlertText.tsx b/src/components/common/AlertText/index.tsx similarity index 100% rename from src/components/AlertText.tsx rename to src/components/common/AlertText/index.tsx diff --git a/src/components/common/Avatar/index.tsx b/src/components/common/Avatar/index.tsx new file mode 100644 index 00000000..cf70792b --- /dev/null +++ b/src/components/common/Avatar/index.tsx @@ -0,0 +1,30 @@ +// Avatar.tsx +import styled from '@emotion/styled' + +type AvatarProps = { + width: number + height: number + imgUrl: string + margin: string + shadow?: boolean +} + +const StyledAvatar = styled.div` + width: ${(props) => (typeof props.width === 'number' ? `${props.width}px` : props.width)}; + height: ${(props) => (typeof props.height === 'number' ? `${props.height}px` : props.height)}; + background-image: url(${(props) => props.imgUrl}); + background-size: cover; + background-repeat: no-repeat; + background-position: center center; + border-radius: 50%; // 원 형태로 만들기 위함 + margin: ${(props) => props.margin}; + box-shadow: ${(props) => (props.shadow ? '0px 0px 10px rgba(0, 0, 0, 0.25)' : 'none')}; +` + +const Avatar: React.FC = ({ width, height, imgUrl, margin, shadow = false }) => { + return ( + + ) +} + +export default Avatar diff --git a/src/components/common/BottomSheet/ProfileSheet.tsx b/src/components/common/BottomSheet/ProfileSheet.tsx new file mode 100644 index 00000000..54adefca --- /dev/null +++ b/src/components/common/BottomSheet/ProfileSheet.tsx @@ -0,0 +1,152 @@ +import styled from '@emotion/styled' +import { AnimatePresence, motion } from 'framer-motion' +import { MouseEvent, useState } from 'react' +import { AiOutlineClose } from 'react-icons/ai' + +import Avatar from '@/components/common/Avatar' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +import { InterestButton } from '../Buttons/IconButton' + +const Background = styled(motion.div)` + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: flex-end; + overflow-y: hidden; +` + +const BottomContentWrapper = styled(motion.div)<{ + isDarkMode: boolean +}>` + width: 100%; + display: flex; + flex-direction: column; + height: 378px; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; +` + +const BottomContentHeader = styled.div<{ + isDarkMode: boolean +}>` + width: 100%; + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid ${({ isDarkMode }) => (isDarkMode ? palette.GRAY500 : palette.GRAY200)}; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + padding: 24px 0; +` + +const BottomContent = styled.div<{ + isDarkMode: boolean +}>` + display: flex; + flex-direction: column; + align-items: center; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; +` + +type ProfileSheetProps = { + title: string + isDarkMode: boolean +} + +const ProfileSheet = ({ title, isDarkMode }: ProfileSheetProps) => { + const [isOpen, setIsOpen] = useState(true) // ProfileSheet의 상태 + + const handleWrapperClick = (e: MouseEvent) => { + e.stopPropagation() + } + + const toggleProfileSheet = () => { + setIsOpen(false) + } + + const slideUp = { + hidden: { y: '100%', opacity: 0 }, + visible: { y: '0%', opacity: 1, transition: { type: 'spring', damping: 15, stiffness: 100 } }, + exit: { y: '100%', opacity: 0, transition: { type: 'spring', damping: 15, stiffness: 100 } }, + } + + const backgroundFade = { + hidden: { opacity: 0 }, + visible: { opacity: 1 }, + exit: { opacity: 0 }, + } + + return ( + + {isOpen && ( + + + + + + {title} + + + + + + + + + )} + + ) +} + +export default ProfileSheet diff --git a/src/components/common/BottomSheet/RandomMatchingSheet.tsx b/src/components/common/BottomSheet/RandomMatchingSheet.tsx new file mode 100644 index 00000000..0664610b --- /dev/null +++ b/src/components/common/BottomSheet/RandomMatchingSheet.tsx @@ -0,0 +1,163 @@ +import styled from '@emotion/styled' +import { AnimatePresence, motion } from 'framer-motion' +import { MouseEvent, useState } from 'react' +import { AiOutlineClose } from 'react-icons/ai' + +import RandomMatchingJoinButton from '@/components/common/Buttons/IconButton/RandomMatchingJoin' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +import Timer from './Timer' + +const Background = styled.div` + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: flex-end; + overflow-y: hidden; +` + +const BottomContentWrapper = styled(motion.div)<{ + isDarkMode: boolean +}>` + width: 100%; + display: flex; + flex-direction: column; + height: 378px; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; +` + +const BottomContentHeader = styled.div<{ + isDarkMode: boolean +}>` + width: 100%; + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid ${({ isDarkMode }) => (isDarkMode ? palette.GRAY500 : palette.GRAY200)}; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + padding: 24px 0; +` + +const BottomContent = styled.div<{ + isDarkMode: boolean +}>` + display: flex; + flex-direction: column; + align-items: center; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; +` + +type RandomMatchingSheetProps = { + title: string + isDarkMode: boolean + moveToRandomMatching: () => void + cancelRandomMatching: () => void +} + +const RandomMatchingSheet = ({ + title, + isDarkMode, + moveToRandomMatching, + cancelRandomMatching, +}: RandomMatchingSheetProps) => { + const [isOpen, setIsOpen] = useState(true) // RandomMatchingSheet의 상태 + + const handleWrapperClick = (e: MouseEvent) => { + e.stopPropagation() + } + + const toggleRandomMatchingSheet = () => { + // isOpen이 true일 때만 상태를 토글 + if (isOpen) { + cancelRandomMatching() + setIsOpen(!isOpen) + } + } + + const slideUp = { + hidden: { y: '100%', opacity: 0 }, + visible: { y: '0%', opacity: 1, transition: { type: 'spring', damping: 15, stiffness: 100 } }, + partiallyVisible: { + y: '85%', + opacity: 1, + transition: { type: 'spring', damping: 15, stiffness: 100 }, + }, + exit: { y: '100%', opacity: 0, transition: { type: 'spring', damping: 20, stiffness: 100 } }, + } + return ( + + + + + + + {title} + + + + { + console.log('타이머 종료!') + }} + /> + + + {'현재 매칭에 참가하지 않으면 다음 매칭에 불이익이 있습니다.'} + + + + + + ) +} + +export default RandomMatchingSheet diff --git a/src/components/common/BottomSheet/Timer.tsx b/src/components/common/BottomSheet/Timer.tsx new file mode 100644 index 00000000..f5254005 --- /dev/null +++ b/src/components/common/BottomSheet/Timer.tsx @@ -0,0 +1,91 @@ +import { arc, select, timer } from 'd3' +import { useEffect, useRef } from 'react' + +import { palette } from '@/styles/palette' + +type TimerProps = { + totalTime: number + isDarkMode: boolean + timeOver: () => void +} + +const Timer = ({ totalTime, isDarkMode, timeOver }: TimerProps) => { + const svgRef = useRef(null) + const currentTimer = totalTime + + useEffect(() => { + if (svgRef.current && currentTimer) { + const fontColor = isDarkMode ? palette.DARK_WHITE : palette.BLACK + const svg = select(svgRef.current).attr('width', 141).attr('height', 141) + const group = svg.append('g').attr('transform', 'translate(70, 70)') + + const arcSvg = arc().innerRadius(64).outerRadius(70).startAngle(0) + + const path = group.append('path').attr('fill', palette.TERTIARY) + + const text = group + .append('text') + .attr('text-anchor', 'middle') + .attr('dy', '0.35em') + .attr('style', 'font-family: Pretendard') + .style('fill', fontColor) + .style('font-weight', '600') + .style('font-size', '32px') + + const update = (elapsed: number) => { + if (elapsed >= currentTimer) { + timerObj.stop() + path.attr('visibility', 'hidden') + text.text('00:00') + timeOver() + return + } + + const remainingTime = currentTimer - elapsed + const minutes = Math.floor(remainingTime / 60000) // milliseconds to minutes + const seconds = Math.floor((remainingTime % 60000) / 1000) // remainder to seconds + + // 시간 형식을 00:00 으로 변환 + const displayTime = `${minutes.toString().padStart(2, '0')}:${seconds + .toString() + .padStart(2, '0')}` + text.text(displayTime) + + const angle = (elapsed / totalTime) * 2 * Math.PI + arcSvg.startAngle(angle).endAngle(2 * Math.PI) + path.attr( + 'd', + arcSvg({ + startAngle: angle, + endAngle: 2 * Math.PI, + innerRadius: 0, + outerRadius: 100, + }), + ) + } + + const timerObj = timer(update) + + return () => { + timerObj.stop() + select(svgRef.current).selectAll('*').remove() // SVG 내부의 모든 요소 제거 + } + } + }, []) + + return ( +
+ +
+ ) +} + +export default Timer diff --git a/src/components/common/BottomSheet/index.tsx b/src/components/common/BottomSheet/index.tsx new file mode 100644 index 00000000..dc8673ba --- /dev/null +++ b/src/components/common/BottomSheet/index.tsx @@ -0,0 +1,158 @@ +import styled from '@emotion/styled' +import { AnimatePresence, motion } from 'framer-motion' +import { MouseEvent, useState } from 'react' +import { AiOutlineClose } from 'react-icons/ai' + +import RandomMatchingJoinButton from '@/components/common/Buttons/IconButton/RandomMatchingJoin' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +import Timer from './Timer' + +const Background = styled.div` + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: flex-end; + overflow-y: hidden; +` + +const BottomContentWrapper = styled(motion.div)<{ + isDarkMode: boolean +}>` + width: 100%; + display: flex; + flex-direction: column; + height: 378px; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; +` + +const BottomContentHeader = styled.div<{ + isDarkMode: boolean +}>` + width: 100%; + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid ${({ isDarkMode }) => (isDarkMode ? palette.GRAY500 : palette.GRAY200)}; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + padding: 24px 0; +` + +const BottomContent = styled.div<{ + isDarkMode: boolean +}>` + display: flex; + flex-direction: column; + align-items: center; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; +` + +type BottomSheetProps = { + title: string + isDarkMode: boolean +} + +const BottomSheet = ({ title, isDarkMode }: BottomSheetProps) => { + const [isOpen, setIsOpen] = useState(true) // BottomSheet의 상태 + + const handleWrapperClick = (e: MouseEvent) => { + e.stopPropagation() + } + + const toggleBottomSheet = () => { + // isOpen이 true일 때만 상태를 토글 + if (isOpen) { + console.log('매칭 참가 취소') + setIsOpen(!isOpen) + } + } + + const slideUp = { + hidden: { y: '100%', opacity: 0 }, + visible: { y: '0%', opacity: 1, transition: { type: 'spring', damping: 15, stiffness: 100 } }, + partiallyVisible: { + y: '85%', + opacity: 1, + transition: { type: 'spring', damping: 15, stiffness: 100 }, + }, + exit: { y: '100%', opacity: 0, transition: { type: 'spring', damping: 20, stiffness: 100 } }, + } + return ( + + + + + + + {title} + + + + { + console.log('타이머 종료!') + }} + /> + { + console.log('랜덤 매칭 참가') + }} + /> + + {'현재 매칭에 참가하지 않으면 다음 매칭에 불이익이 있습니다.'} + + + + + + ) +} + +export default BottomSheet diff --git a/src/components/common/Buttons/IconButton/InterestButton.tsx b/src/components/common/Buttons/IconButton/InterestButton.tsx index c46bcbda..229c816a 100644 --- a/src/components/common/Buttons/IconButton/InterestButton.tsx +++ b/src/components/common/Buttons/IconButton/InterestButton.tsx @@ -10,7 +10,7 @@ import { IconButtonWrapper, IconWrapper } from '.' type InterestButtonProps = { nickName: string interests: string[] - isDarkMode?: boolean + isDarkMode: boolean } const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps) => { diff --git a/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx b/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx index 386d65fb..17b74b9c 100644 --- a/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx +++ b/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx @@ -6,7 +6,7 @@ import { palette } from '@/styles/palette' import { IconButtonWrapper, IconWrapper } from '.' type ParticularTopicButtonProps = { - isDarkMode?: boolean + isDarkMode: boolean } const ParticularTopicButton = ({ isDarkMode }: ParticularTopicButtonProps) => { diff --git a/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx b/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx index 7ca56cc9..1fde1eea 100644 --- a/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx +++ b/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx @@ -9,7 +9,7 @@ import { IconButtonWrapper, IconWrapper } from '.' type RandomMatchingButtonProps = { date: string - isDarkMode?: boolean + isDarkMode: boolean } const RandomMatchingButton = ({ date, isDarkMode }: RandomMatchingButtonProps) => { diff --git a/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx b/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx index 5685b484..58afee29 100644 --- a/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx +++ b/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx @@ -5,10 +5,14 @@ import { Text, TextWrapper } from '@/components/common/Text' import { IconButtonWrapper, IconWrapper } from '.' type RandomMatchingJoinButtonProps = { - isDarkMode?: boolean + isDarkMode: boolean + moveToRandomMatching: () => void } -const RandomMatchingJoinButton = ({ isDarkMode }: RandomMatchingJoinButtonProps) => { +const RandomMatchingJoinButton = ({ + isDarkMode, + moveToRandomMatching, +}: RandomMatchingJoinButtonProps) => { const setButtonType = isDarkMode ? 'random-matching-join-dark' : 'random-matching-join' return ( @@ -19,6 +23,7 @@ const RandomMatchingJoinButton = ({ isDarkMode }: RandomMatchingJoinButtonProps) justifyContent: 'space-between', alignItems: 'center', }} + onClick={moveToRandomMatching} > { - return ( - <> - {/* 버튼 사용법입니다! */} -
- alert('hi!')}> - {'이메일 인증?!'} - -
-
- alert('인증 수락')}> - {'인증 수락'} - - {'무시'} -
-
- {'이메일 인증'} -
-
- {'예, 나가겠습니다.'} - {'아니오, 돌아가겠습니다.'} -
- -
- {'수락'} - {'거절'} -
- -
- {'중복확인'} - {'중복확인'} -
-
- {'이메일 인증'} - {'이메일 인증'} -
-
- {'매칭 시작'} - {'매칭 취소'} - {'매칭 재시도'} - {'매칭 완료!!'} -
-
- {'매칭 시작'} -
-
- -
- -
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- - -
-
- -
-
- -
- - ) -} - -export default Button diff --git a/src/components/HeroImage.tsx b/src/components/common/HeroImage/index.tsx similarity index 100% rename from src/components/HeroImage.tsx rename to src/components/common/HeroImage/index.tsx From ccca7bbbc6a2bcc00c3155083ee9fedef4fb4be4 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Thu, 26 Oct 2023 15:54:52 +0900 Subject: [PATCH 14/33] =?UTF-8?q?[Style/]=20navigation=20bar=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20(#37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style : 네비게이션 바 디자인 * style : 네비게이션 바 디자인 및 라우팅 처리 * fix : 프로필 이미지를 Avatar 컴포넌트를 이용하도록 수정 * chore : 타입 에러 해결 * chore : 홈 페이지에 네비게이션 바 예시 제거 --- src/assets/images/defaultProfileImage.png | Bin 0 -> 35794 bytes src/components/common/Avatar/index.tsx | 5 +- src/components/common/NavigationBar/index.tsx | 68 ++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 src/assets/images/defaultProfileImage.png create mode 100644 src/components/common/NavigationBar/index.tsx diff --git a/src/assets/images/defaultProfileImage.png b/src/assets/images/defaultProfileImage.png new file mode 100644 index 0000000000000000000000000000000000000000..4c4675674452b1dc87b02a1a76d1ce06b009eeb8 GIT binary patch literal 35794 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 literal 0 HcmV?d00001 diff --git a/src/components/common/Avatar/index.tsx b/src/components/common/Avatar/index.tsx index cf70792b..43b56e00 100644 --- a/src/components/common/Avatar/index.tsx +++ b/src/components/common/Avatar/index.tsx @@ -1,4 +1,3 @@ -// Avatar.tsx import styled from '@emotion/styled' type AvatarProps = { @@ -17,11 +16,11 @@ const StyledAvatar = styled.div` background-repeat: no-repeat; background-position: center center; border-radius: 50%; // 원 형태로 만들기 위함 - margin: ${(props) => props.margin}; + margin: ${(props) => `${props.margin}px`}; box-shadow: ${(props) => (props.shadow ? '0px 0px 10px rgba(0, 0, 0, 0.25)' : 'none')}; ` -const Avatar: React.FC = ({ width, height, imgUrl, margin, shadow = false }) => { +const Avatar: React.FC = ({ width, height, imgUrl, margin = '0', shadow = false }) => { return ( ) diff --git a/src/components/common/NavigationBar/index.tsx b/src/components/common/NavigationBar/index.tsx new file mode 100644 index 00000000..abfc5def --- /dev/null +++ b/src/components/common/NavigationBar/index.tsx @@ -0,0 +1,68 @@ +import styled from '@emotion/styled' +import { IoChatbox } from 'react-icons/io5' +import { MdHome } from 'react-icons/md' +import { useNavigate } from 'react-router-dom' + +import defaultProfileImage from '@/assets/images/defaultProfileImage.png' +import Avatar from '@/components/common/Avatar' +import { FlexBox } from '@/components/common/Flexbox' +import { palette } from '@/styles/palette' +import { typo } from '@/styles/typo' +const NavigationBar = () => { + const navigate = useNavigate() + const moveFromNavigationBar = (path: string) => { + navigate(`/${path}`) + } + return ( + + + moveFromNavigationBar('chatlist')}> + + + {'이전대화방'} + + + moveFromNavigationBar('')}> + + + {'홈'} + + + moveFromNavigationBar('profile/edit')}> + + + + + {'프로필'} + + + + + ) +} +const StyleWrapper = styled(FlexBox)` + position: absolute; + bottom: 0px; +` +const StyleNavigationText = styled.span` + color: ${palette.GRAY600}; + font-size: ${typo.Body_10()}; +` +const StyleNavigation = styled(FlexBox)` + width: 100%; + height: 71px; + background-color: white; + box-shadow: + 0px 0px 2px 0px rgba(0, 0, 0, 0.24), + 0px 4px 4px 0px rgba(0, 0, 0, 0.14); +` +const StyleNavigationItem = styled.button` + cursor: pointer; +` +const StyleProfileImageWrapper = styled.div` + width: 100%; + height: 100%; + object-fit: cover; +` + +export default NavigationBar From b44914c80294ef6742fff5ac3f3c8ceda379a7ad Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:55:00 +0900 Subject: [PATCH 15/33] =?UTF-8?q?style:=20CountNumber=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20?= =?UTF-8?q?(#38)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: CountNumber * feat: Input Wrapper 추가. --- src/components/CountNumber.tsx | 32 ++++++++++++++++++++++++++++++++ src/components/Input.tsx | 33 ++++++++++++++++++++------------- 2 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 src/components/CountNumber.tsx diff --git a/src/components/CountNumber.tsx b/src/components/CountNumber.tsx new file mode 100644 index 00000000..018aaf35 --- /dev/null +++ b/src/components/CountNumber.tsx @@ -0,0 +1,32 @@ +import styled from '@emotion/styled' + +type CountNumberProps = { + currentLength: number + maxLength: number + color: string + right: number +} + +type StyleCountNumberProps = { + color: string + right: number +} + +const CountNumber = ({ right, currentLength, maxLength, color }: CountNumberProps): JSX.Element => { + return ( + {`${currentLength}/${maxLength}`} + ) +} + +const StyleCountNumber = styled.span` + position: relative; + right: ${(props) => props.right}px; + bottom: 3px; + font-size: 12px; + color: ${(props) => props.color}; +` + +export default CountNumber diff --git a/src/components/Input.tsx b/src/components/Input.tsx index a3de8865..28e71169 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -29,28 +29,35 @@ const Input = ({ }: InputProps) => { return ( <> - + + + ) } +const InputWrapper = styled.div` + position: relative; +` + const StyleInput = styled.input` ::placeholder { font-size: ${(props) => props.placeholderSize}; color: ${(props) => props.placeholderColor}; } + position: relative; placeholder: ${(props) => props.placeholder}; width: ${(props) => props.width}; height: ${(props) => props.height}; From 7f3ccff17fe04f20368f82bec0301fa86b30245d Mon Sep 17 00:00:00 2001 From: judahhh Date: Thu, 26 Oct 2023 16:10:01 +0900 Subject: [PATCH 16/33] =?UTF-8?q?chore=20:=20CountNumber=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/{CountNumber.tsx => common/CountNumber/index.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/{CountNumber.tsx => common/CountNumber/index.tsx} (100%) diff --git a/src/components/CountNumber.tsx b/src/components/common/CountNumber/index.tsx similarity index 100% rename from src/components/CountNumber.tsx rename to src/components/common/CountNumber/index.tsx From a93ba29512148ce589d29fcd54f7d403285f602f Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Thu, 26 Oct 2023 16:40:59 +0900 Subject: [PATCH 17/33] =?UTF-8?q?style:=20InputTimer=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20?= =?UTF-8?q?(#41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: CountNumber * feat: Input Wrapper 추가. * style: InputTimer 제작 --- src/components/CountNumber.tsx | 32 +++++++++++++++++ src/components/common/InputTimer/index.tsx | 42 ++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/components/CountNumber.tsx create mode 100644 src/components/common/InputTimer/index.tsx diff --git a/src/components/CountNumber.tsx b/src/components/CountNumber.tsx new file mode 100644 index 00000000..018aaf35 --- /dev/null +++ b/src/components/CountNumber.tsx @@ -0,0 +1,32 @@ +import styled from '@emotion/styled' + +type CountNumberProps = { + currentLength: number + maxLength: number + color: string + right: number +} + +type StyleCountNumberProps = { + color: string + right: number +} + +const CountNumber = ({ right, currentLength, maxLength, color }: CountNumberProps): JSX.Element => { + return ( + {`${currentLength}/${maxLength}`} + ) +} + +const StyleCountNumber = styled.span` + position: relative; + right: ${(props) => props.right}px; + bottom: 3px; + font-size: 12px; + color: ${(props) => props.color}; +` + +export default CountNumber diff --git a/src/components/common/InputTimer/index.tsx b/src/components/common/InputTimer/index.tsx new file mode 100644 index 00000000..01dacc46 --- /dev/null +++ b/src/components/common/InputTimer/index.tsx @@ -0,0 +1,42 @@ +import styled from '@emotion/styled' +import { useEffect, useState } from 'react' + +import { palette } from '@/styles/palette' + +type TimerProps = { + duration: number // 초 단위의 지속 시간 + fontSize?: string +} + +const Timer = ({ duration, fontSize = '1rem' }: TimerProps): JSX.Element => { + const [timeLeft, setTimeLeft] = useState(duration) + + useEffect(() => { + if (!timeLeft) return + + const intervalId = setInterval(() => { + setTimeLeft((prevTimeLeft: number) => prevTimeLeft - 1) + }, 1000) + + return () => clearInterval(intervalId) + }, [timeLeft]) + + const formatTime = (time: number) => { + const minutes = Math.floor(time / 60) + const seconds = time % 60 + return `${minutes}:${seconds.toString().padStart(2, '0')}` + } + + return ( + + {formatTime(timeLeft)} + + ) +} + +const StyledTimer = styled.span` + font-size: ${(props) => props.fontSize}; + color: ${palette.RED}; +` + +export default Timer From 386f00d1bd3cca7e8de9e9502aba5e3dc31c9383 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Fri, 27 Oct 2023 21:48:34 +0900 Subject: [PATCH 18/33] =?UTF-8?q?style=20:=20=EB=A1=9C=EB=94=A9=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20(#4?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 10 ++++++++++ package.json | 1 + src/components/common/Loading/index.tsx | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 src/components/common/Loading/index.tsx diff --git a/package-lock.json b/package-lock.json index cddd3e96..f4daf9e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "react-hook-form": "^7.47.0", "react-icons": "^4.11.0", "react-router-dom": "^6.16.0", + "react-spinners": "^0.13.8", "react-toastify": "^9.1.3" }, "devDependencies": { @@ -7642,6 +7643,15 @@ "react-dom": ">=16.8" } }, + "node_modules/react-spinners": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.8.tgz", + "integrity": "sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==", + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-toastify": { "version": "9.1.3", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", diff --git a/package.json b/package.json index 22027b5e..243bac8f 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "react-hook-form": "^7.47.0", "react-icons": "^4.11.0", "react-router-dom": "^6.16.0", + "react-spinners": "^0.13.8", "react-toastify": "^9.1.3" }, "devDependencies": { diff --git a/src/components/common/Loading/index.tsx b/src/components/common/Loading/index.tsx new file mode 100644 index 00000000..f2987d35 --- /dev/null +++ b/src/components/common/Loading/index.tsx @@ -0,0 +1,20 @@ +import styled from '@emotion/styled' +import { SyncLoader } from 'react-spinners' + +import { palette } from '@/styles/palette' +const Loading = () => { + return ( + + + + ) +} + +const LoadingWrapper = styled.div` + text-align: center; + vertical-align: middle; + position: absolute; + top: 50%; + left: 45%; +` +export default Loading From ff1ffbfe0e95b7720ce9713ef1464dced5912f19 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Fri, 27 Oct 2023 21:57:50 +0900 Subject: [PATCH 19/33] =?UTF-8?q?style:=20WhiteSelectorButton,=20DarkSelec?= =?UTF-8?q?torButton=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=20(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: whiteselectorButton * refactor: 불필요 주석 삭제 * style: darkselectorbutton * Update src/components/common/DarkSelectorButton/index.tsx * Update src/components/common/WhiteSelectorButton/index.tsx * fix: font-weight 오타 수정 --------- Co-authored-by: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- .../common/DarkSelectorButton/index.tsx | 77 +++++++++++++++++++ .../common/WhiteSelectorButton/index.tsx | 63 +++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 src/components/common/DarkSelectorButton/index.tsx create mode 100644 src/components/common/WhiteSelectorButton/index.tsx diff --git a/src/components/common/DarkSelectorButton/index.tsx b/src/components/common/DarkSelectorButton/index.tsx new file mode 100644 index 00000000..df56355e --- /dev/null +++ b/src/components/common/DarkSelectorButton/index.tsx @@ -0,0 +1,77 @@ +import styled from '@emotion/styled' +import { useState } from 'react' + +import { palette } from '@/styles/palette' + +type ToggleButtonProps = { + buttonName: string + selectedButtonColor: string + defaultButtonColor?: string + selectedTextColor: string + onClick?: () => void +} + +type StyledButtonProps = { + backgroundColor: string + textColor: string +} + +const DarkSelectorButton = ({ + buttonName, + selectedButtonColor, + defaultButtonColor = palette.WHITE, + selectedTextColor = palette.SECONDARY, + onClick, +}: ToggleButtonProps) => { + const [backgroundColor, setBackgroundColor] = useState(defaultButtonColor) + const [textColor, setTextColor] = useState(palette.SECONDARY) // 텍스트 색상에 대한 state + + const handleButtonClick = () => { + setBackgroundColor((prevColor) => { + if (prevColor === defaultButtonColor) { + setTextColor(selectedTextColor) + return selectedButtonColor + } else { + setTextColor(palette.SECONDARY) + return defaultButtonColor + } + }) + if (onClick) onClick() + } + + return ( + + {buttonName} + + ) +} + +const StyledButton = styled.button` + margin: 0 4px; + height: 36px; + padding: 10px 15px 10px 15px; + font-size: 12px; + cursor: pointer; + border: none; + border-radius: 10px; + background-color: ${(props) => props.backgroundColor}; + transition: background-color 0.3s; + &:hover { + opacity: 0.9; + } + &:focus { + outline: none; + } + color: ${(props) => props.textColor}; + display: inline-block; + vertical-align: middle; + line-height: 1; + font-weight: 600; + letter-spacing: -1px; +` + +export default DarkSelectorButton diff --git a/src/components/common/WhiteSelectorButton/index.tsx b/src/components/common/WhiteSelectorButton/index.tsx new file mode 100644 index 00000000..4d02fd04 --- /dev/null +++ b/src/components/common/WhiteSelectorButton/index.tsx @@ -0,0 +1,63 @@ +import styled from '@emotion/styled' +import { useState } from 'react' + +import { palette } from '@/styles/palette' + +type ToggleButtonProps = { + buttonName: string + selectedButtonColor: string + defaultButtonColor?: string + onClick?: () => void +} + +type StyledButtonProps = { + backgroundColor: string +} + +const WhiteSelectorButton = ({ + buttonName, + selectedButtonColor, + defaultButtonColor = palette.TERTIARY, + onClick, +}: ToggleButtonProps) => { + const [backgroundColor, setBackgroundColor] = useState(defaultButtonColor) + + const handleButtonClick = () => { + setBackgroundColor((prevColor) => + prevColor === defaultButtonColor ? selectedButtonColor : defaultButtonColor, + ) + if (onClick) onClick() + } + + return ( + + {buttonName} + + ) +} + +const StyledButton = styled.button` + margin: 0 4px; + height: 36px; + padding: 10px 15px 10px 15px; + font-size: 12px; + cursor: pointer; + border: none; + border-radius: 10px; + background-color: ${(props) => props.backgroundColor}; + transition: background-color 0.3s; + &:hover { + opacity: 0.9; + } + &:focus { + outline: none; + } + color: ${palette.WHITE}; + display: inline-block; + vertical-align: middle; + line-height: 1; + letter-spacing: -1px; + font-weight: 600; +` + +export default WhiteSelectorButton From f24a9b6b5d61a5033c877c611c7442d0b455ca3d Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Mon, 30 Oct 2023 18:25:25 +0900 Subject: [PATCH 20/33] =?UTF-8?q?style:=20ListRow=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20(#43)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Style prefix 붙이도록 수정 * refactor: Divider 컴포넌트 수정 + 웹 폰트 수정 - Divider 컴포넌트가 확장성 있도록 수정 - 웹 폰트 오타 수정 * feature: AdminListRow 컴포넌트 추가 - 관리자 페이지에 사용되는 ListRow 컴포넌트 * feature: ProfileListRow 컴포넌트 추가 - 프로필 페이지에 사용되는 ListRow --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- .../Buttons/IconButton/InterestButton.tsx | 14 +-- .../common/Buttons/IconButton/KakaoButton.tsx | 6 +- .../common/Buttons/IconButton/NaverButton.tsx | 6 +- .../IconButton/ParticularTopicButton.tsx | 14 +-- .../IconButton/RandomMatchingButton.tsx | 14 +-- .../Buttons/IconButton/RandomMatchingJoin.tsx | 10 +- .../common/Buttons/IconButton/index.tsx | 4 +- src/components/common/Divider/index.tsx | 17 +++- .../common/ListRow/AdminListRow.tsx | 90 ++++++++++++++++++ .../common/ListRow/ProfileListRow.tsx | 94 +++++++++++++++++++ src/styles/global.ts | 11 +-- 11 files changed, 233 insertions(+), 47 deletions(-) create mode 100644 src/components/common/ListRow/AdminListRow.tsx create mode 100644 src/components/common/ListRow/ProfileListRow.tsx diff --git a/src/components/common/Buttons/IconButton/InterestButton.tsx b/src/components/common/Buttons/IconButton/InterestButton.tsx index 229c816a..942e060e 100644 --- a/src/components/common/Buttons/IconButton/InterestButton.tsx +++ b/src/components/common/Buttons/IconButton/InterestButton.tsx @@ -5,7 +5,7 @@ import { Divider } from '@/components/common/Divider' import { Text, TextWrapper } from '@/components/common/Text' import { palette } from '@/styles/palette' -import { IconButtonWrapper, IconWrapper } from '.' +import { StyleIconButtonWrapper, StyleIconWrapper } from '.' type InterestButtonProps = { nickName: string @@ -17,7 +17,7 @@ const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps const setButtonType = isDarkMode ? 'interest-dark' : 'interest' return ( - - - + {`${nickName}의 관심사`} @@ -61,12 +61,14 @@ const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps {interests.map((interest, index) => ( {interest} - {index !== interests.length - 1 && } + {index !== interests.length - 1 && ( + + )} ))} - + ) } diff --git a/src/components/common/Buttons/IconButton/KakaoButton.tsx b/src/components/common/Buttons/IconButton/KakaoButton.tsx index 7c5068d4..37b7776c 100644 --- a/src/components/common/Buttons/IconButton/KakaoButton.tsx +++ b/src/components/common/Buttons/IconButton/KakaoButton.tsx @@ -4,7 +4,7 @@ import KakaoIcon from '@/assets/icons/KakaoIcon' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -import { IconWrapper } from '.' +import { StyleIconWrapper } from '.' export const ButtonWrapper = styled.button<{ buttonTheme: 'kakao' | 'naver' @@ -21,13 +21,13 @@ export const ButtonWrapper = styled.button<{ const KakaoButton = () => ( - - + { return ( - - + { const getSecondTextColor = isDarkMode ? palette.GRAY300 : palette.GRAY500 return ( - { alignItems: 'center', }} > - { height: 20, }} /> - + { {'네트워크를 넓혀보세요!'}
- { height: 30, }} /> - - + + ) } diff --git a/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx b/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx index 1fde1eea..f705d881 100644 --- a/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx +++ b/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx @@ -5,7 +5,7 @@ import { Text, TextWrapper } from '@/components/common/Text' import { palette } from '@/styles/palette' import { getTimeDelta } from '@/utils/getTimeStamp' -import { IconButtonWrapper, IconWrapper } from '.' +import { StyleIconButtonWrapper, StyleIconWrapper } from '.' type RandomMatchingButtonProps = { date: string @@ -16,7 +16,7 @@ const RandomMatchingButton = ({ date, isDarkMode }: RandomMatchingButtonProps) = const setButtonType = isDarkMode ? 'random-matching-dark' : 'random-matching' return ( - - - + - - - + + ) } diff --git a/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx b/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx index 58afee29..12174a8b 100644 --- a/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx +++ b/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx @@ -2,7 +2,7 @@ import { BiChevronRight } from 'react-icons/bi' import { Text, TextWrapper } from '@/components/common/Text' -import { IconButtonWrapper, IconWrapper } from '.' +import { StyleIconButtonWrapper, StyleIconWrapper } from '.' type RandomMatchingJoinButtonProps = { isDarkMode: boolean @@ -16,7 +16,7 @@ const RandomMatchingJoinButton = ({ const setButtonType = isDarkMode ? 'random-matching-join-dark' : 'random-matching-join' return ( - - - - + + ) } diff --git a/src/components/common/Buttons/IconButton/index.tsx b/src/components/common/Buttons/IconButton/index.tsx index 07a56af8..95519bd2 100644 --- a/src/components/common/Buttons/IconButton/index.tsx +++ b/src/components/common/Buttons/IconButton/index.tsx @@ -10,7 +10,7 @@ import NaverButton from './NaverButton' import ParticularTopicButton from './ParticularTopicButton' import RandomMatchingButton from './RandomMatchingButton' -export const IconButtonWrapper = styled.button<{ +export const StyleIconButtonWrapper = styled.button<{ iconButtonType: IconButtonType }>` ${({ iconButtonType }) => { @@ -30,7 +30,7 @@ export const IconButtonWrapper = styled.button<{ }} ` -export const IconWrapper = styled.div<{ +export const StyleIconWrapper = styled.div<{ borderRadius?: string backgroundColor?: string }>` diff --git a/src/components/common/Divider/index.tsx b/src/components/common/Divider/index.tsx index 6553babe..af4efe1d 100644 --- a/src/components/common/Divider/index.tsx +++ b/src/components/common/Divider/index.tsx @@ -2,9 +2,16 @@ import styled from '@emotion/styled' import { palette } from '@/styles/palette' -export const Divider = styled.div` - width: 1px; - height: 10px; - margin: 0 12px; - background-color: ${palette.WHITE}; +type DividerProps = { + width: number + height: number + margin?: string + isDarkMode: boolean +} + +export const Divider = styled.div` + width: ${({ width }) => width}px; + height: ${({ height }) => height}px; + margin: ${({ margin }) => margin}; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY500 : palette.GRAY100)}; ` diff --git a/src/components/common/ListRow/AdminListRow.tsx b/src/components/common/ListRow/AdminListRow.tsx new file mode 100644 index 00000000..7976bf03 --- /dev/null +++ b/src/components/common/ListRow/AdminListRow.tsx @@ -0,0 +1,90 @@ +import { FlexBox } from '@/components/common/Flexbox' +import { StyleList } from '@/components/common/ListRow/ProfileListRow' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +type ProfileListRowProps = { + height: number + nickname: string + infoMessage: string | number + isDarkMode: boolean +} +const AdminListRow = ({ height, nickname, infoMessage, isDarkMode }: ProfileListRowProps) => { + const renderInfoMessage = () => { + if (typeof infoMessage === 'number') { + return ( + + + {'누적 '} + + + {infoMessage} + + + {' 회'} + + + ) + } + return ( + + {infoMessage} + + ) + } + + return ( + + + {nickname} + + {renderInfoMessage()} + + ) +} + +export default AdminListRow diff --git a/src/components/common/ListRow/ProfileListRow.tsx b/src/components/common/ListRow/ProfileListRow.tsx new file mode 100644 index 00000000..d687e016 --- /dev/null +++ b/src/components/common/ListRow/ProfileListRow.tsx @@ -0,0 +1,94 @@ +import styled from '@emotion/styled' +import { ReactNode } from 'react' + +import { FlexBox } from '@/components/common/Flexbox' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +export const StyleList = styled(FlexBox)<{ + width: number + height: number +}>` + width: ${({ width }) => width}px; + height: ${({ height }) => height}px; + display: flex; + justify-content: space-between; +` + +const StyleIconWrapper = styled.div<{ + width: number + height: number + borderRadius?: string + backgroundColor: string +}>` + width: ${({ width }) => width}px; + height: ${({ height }) => height}px; + display: flex; + justify-content: center; + align-items: center; + border-radius: ${({ borderRadius }) => borderRadius}; + background-color: ${({ backgroundColor }) => backgroundColor}; +` + +type ProfileListRowProps = { + firstIcon: ReactNode + title: string + additionalContent?: ReactNode | string + isDarkMode?: boolean +} +const ProfileListRow = ({ + firstIcon, + title, + additionalContent, + isDarkMode, +}: ProfileListRowProps) => { + const isAdditionalContentString = typeof additionalContent === 'string' + const additionalContentColor = isAdditionalContentString ? palette.GRAY300 : undefined + + return ( + + + {firstIcon} + + + {title} + + + {additionalContent} + + + ) +} + +export default ProfileListRow diff --git a/src/styles/global.ts b/src/styles/global.ts index cafaa3e1..2230a98c 100644 --- a/src/styles/global.ts +++ b/src/styles/global.ts @@ -6,19 +6,12 @@ export const globalStyle = css` ${emotionReset} @font-face { - font-family: 'InkLipquid'; - src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/InkLipquid.woff') - format('woff'); - font-weight: normal; - font-style: normal; - } - - @font-face { - font-family: 'Pretendard'; + 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; + font-display: swap; } body { From cc07575218d9b98479790b9aa6ac4c8e6d5d1735 Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Mon, 30 Oct 2023 18:41:36 +0900 Subject: [PATCH 21/33] =?UTF-8?q?style:=20=08useToast=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=ED=9B=85=20=EC=A0=9C=EC=9E=91=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feature: useToast 커스텀 훅 추가 * refactor: RandomMatchingJoinButton으로 이름 변경 * feature: Layout 컴포넌트에 ToastContainer 설정 * refactor: NormalButton 컴포넌트 수정 - isDarkMode props 받도록 수정 * fix: RandomMatchingJoin.tsx 파일 오류 수정 * fix: 파일명 오류 수정 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- .../BottomSheet/RandomMatchingSheet.tsx | 2 +- src/components/common/BottomSheet/index.tsx | 158 ------------------ .../Buttons/IconButton/RandomMatchingJoin.tsx | 61 ------- .../IconButton/RandomMatchingJoinButton.tsx | 61 +++++++ .../Buttons/NormalButton/NormalButton.tsx | 28 ++-- src/components/layouts/Layout.tsx | 4 + src/hooks/useToast.tsx | 25 +++ 7 files changed, 109 insertions(+), 230 deletions(-) delete mode 100644 src/components/common/BottomSheet/index.tsx create mode 100644 src/components/common/Buttons/IconButton/RandomMatchingJoinButton.tsx create mode 100644 src/hooks/useToast.tsx diff --git a/src/components/common/BottomSheet/RandomMatchingSheet.tsx b/src/components/common/BottomSheet/RandomMatchingSheet.tsx index 0664610b..8b6a68be 100644 --- a/src/components/common/BottomSheet/RandomMatchingSheet.tsx +++ b/src/components/common/BottomSheet/RandomMatchingSheet.tsx @@ -3,7 +3,7 @@ import { AnimatePresence, motion } from 'framer-motion' import { MouseEvent, useState } from 'react' import { AiOutlineClose } from 'react-icons/ai' -import RandomMatchingJoinButton from '@/components/common/Buttons/IconButton/RandomMatchingJoin' +import RandomMatchingJoinButton from '@/components/common/Buttons/IconButton/RandomMatchingJoinButton' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' diff --git a/src/components/common/BottomSheet/index.tsx b/src/components/common/BottomSheet/index.tsx deleted file mode 100644 index dc8673ba..00000000 --- a/src/components/common/BottomSheet/index.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import styled from '@emotion/styled' -import { AnimatePresence, motion } from 'framer-motion' -import { MouseEvent, useState } from 'react' -import { AiOutlineClose } from 'react-icons/ai' - -import RandomMatchingJoinButton from '@/components/common/Buttons/IconButton/RandomMatchingJoin' -import { Text } from '@/components/common/Text' -import { palette } from '@/styles/palette' - -import Timer from './Timer' - -const Background = styled.div` - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: flex-end; - overflow-y: hidden; -` - -const BottomContentWrapper = styled(motion.div)<{ - isDarkMode: boolean -}>` - width: 100%; - display: flex; - flex-direction: column; - height: 378px; - border-top-left-radius: 20px; - border-top-right-radius: 20px; - background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; -` - -const BottomContentHeader = styled.div<{ - isDarkMode: boolean -}>` - width: 100%; - position: relative; - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid ${({ isDarkMode }) => (isDarkMode ? palette.GRAY500 : palette.GRAY200)}; - background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; - border-top-left-radius: 20px; - border-top-right-radius: 20px; - padding: 24px 0; -` - -const BottomContent = styled.div<{ - isDarkMode: boolean -}>` - display: flex; - flex-direction: column; - align-items: center; - border-top-left-radius: 20px; - border-top-right-radius: 20px; - background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; -` - -type BottomSheetProps = { - title: string - isDarkMode: boolean -} - -const BottomSheet = ({ title, isDarkMode }: BottomSheetProps) => { - const [isOpen, setIsOpen] = useState(true) // BottomSheet의 상태 - - const handleWrapperClick = (e: MouseEvent) => { - e.stopPropagation() - } - - const toggleBottomSheet = () => { - // isOpen이 true일 때만 상태를 토글 - if (isOpen) { - console.log('매칭 참가 취소') - setIsOpen(!isOpen) - } - } - - const slideUp = { - hidden: { y: '100%', opacity: 0 }, - visible: { y: '0%', opacity: 1, transition: { type: 'spring', damping: 15, stiffness: 100 } }, - partiallyVisible: { - y: '85%', - opacity: 1, - transition: { type: 'spring', damping: 15, stiffness: 100 }, - }, - exit: { y: '100%', opacity: 0, transition: { type: 'spring', damping: 20, stiffness: 100 } }, - } - return ( - - - - - - - {title} - - - - { - console.log('타이머 종료!') - }} - /> - { - console.log('랜덤 매칭 참가') - }} - /> - - {'현재 매칭에 참가하지 않으면 다음 매칭에 불이익이 있습니다.'} - - - - - - ) -} - -export default BottomSheet diff --git a/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx b/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx index 12174a8b..e69de29b 100644 --- a/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx +++ b/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx @@ -1,61 +0,0 @@ -import { BiChevronRight } from 'react-icons/bi' - -import { Text, TextWrapper } from '@/components/common/Text' - -import { StyleIconButtonWrapper, StyleIconWrapper } from '.' - -type RandomMatchingJoinButtonProps = { - isDarkMode: boolean - moveToRandomMatching: () => void -} - -const RandomMatchingJoinButton = ({ - isDarkMode, - moveToRandomMatching, -}: RandomMatchingJoinButtonProps) => { - const setButtonType = isDarkMode ? 'random-matching-join-dark' : 'random-matching-join' - - return ( - - - - {'매칭방에 접속해주세요!'} - - - - - - - ) -} - -export default RandomMatchingJoinButton diff --git a/src/components/common/Buttons/IconButton/RandomMatchingJoinButton.tsx b/src/components/common/Buttons/IconButton/RandomMatchingJoinButton.tsx new file mode 100644 index 00000000..12174a8b --- /dev/null +++ b/src/components/common/Buttons/IconButton/RandomMatchingJoinButton.tsx @@ -0,0 +1,61 @@ +import { BiChevronRight } from 'react-icons/bi' + +import { Text, TextWrapper } from '@/components/common/Text' + +import { StyleIconButtonWrapper, StyleIconWrapper } from '.' + +type RandomMatchingJoinButtonProps = { + isDarkMode: boolean + moveToRandomMatching: () => void +} + +const RandomMatchingJoinButton = ({ + isDarkMode, + moveToRandomMatching, +}: RandomMatchingJoinButtonProps) => { + const setButtonType = isDarkMode ? 'random-matching-join-dark' : 'random-matching-join' + + return ( + + + + {'매칭방에 접속해주세요!'} + + + + + + + ) +} + +export default RandomMatchingJoinButton diff --git a/src/components/common/Buttons/NormalButton/NormalButton.tsx b/src/components/common/Buttons/NormalButton/NormalButton.tsx index af501a38..060bbd38 100644 --- a/src/components/common/Buttons/NormalButton/NormalButton.tsx +++ b/src/components/common/Buttons/NormalButton/NormalButton.tsx @@ -7,20 +7,28 @@ import { NormalButtonStyles, NormalButtonType } from './NormalButtonStyles' const NormalButton = styled.button<{ normalButtonType: NormalButtonType + isDarkMode: boolean }>` - ${({ normalButtonType }) => { - const fontFunc = typo[NormalButtonStyles[normalButtonType].font] + ${({ normalButtonType, isDarkMode }) => { + const processedTypeKey = isDarkMode ? `${normalButtonType}-dark` : normalButtonType + const processedType = ( + NormalButtonStyles[processedTypeKey as NormalButtonType] ? processedTypeKey : normalButtonType + ) as NormalButtonType + + console.log(processedType) + + const fontFunc = typo[NormalButtonStyles[processedType].font] return css` ${fontFunc( - NormalButtonStyles[normalButtonType].fontWeight, - NormalButtonStyles[normalButtonType].letterSpacing, + NormalButtonStyles[processedType].fontWeight, + NormalButtonStyles[processedType].letterSpacing, )} - width: ${NormalButtonStyles[normalButtonType].width}px; - height: ${NormalButtonStyles[normalButtonType].height}px; - color: ${NormalButtonStyles[normalButtonType].fontColor}; - background-color: ${NormalButtonStyles[normalButtonType].backgroundColor}; - box-shadow: ${NormalButtonStyles[normalButtonType].boxShadow}; - border-radius: ${NormalButtonStyles[normalButtonType].borderRadius}px; + width: ${NormalButtonStyles[processedType].width}px; + height: ${NormalButtonStyles[processedType].height}px; + color: ${NormalButtonStyles[processedType].fontColor}; + background-color: ${NormalButtonStyles[processedType].backgroundColor}; + box-shadow: ${NormalButtonStyles[processedType].boxShadow}; + border-radius: ${NormalButtonStyles[processedType].borderRadius}px; ` }} ` diff --git a/src/components/layouts/Layout.tsx b/src/components/layouts/Layout.tsx index ecdc23be..8fe3fb31 100644 --- a/src/components/layouts/Layout.tsx +++ b/src/components/layouts/Layout.tsx @@ -1,5 +1,8 @@ +import 'react-toastify/dist/ReactToastify.css' + import styled from '@emotion/styled' import { Outlet } from 'react-router-dom' +import { ToastContainer } from 'react-toastify' import { theme } from '@/styles/theme' @@ -7,6 +10,7 @@ const Layout = () => { return ( + ) } diff --git a/src/hooks/useToast.tsx b/src/hooks/useToast.tsx new file mode 100644 index 00000000..cf7581cb --- /dev/null +++ b/src/hooks/useToast.tsx @@ -0,0 +1,25 @@ +import { ReactNode } from 'react' +import { toast } from 'react-toastify' + +type ToastType = 'success' | 'error' | 'info' | 'warning' + +type ToastProps = { + message: string | ReactNode + type: ToastType + isDarkMode: boolean +} + +export const useToast = () => { + const showToast = ({ message, type, isDarkMode }: ToastProps) => { + toast(message, { + position: 'top-center', + draggable: true, + theme: isDarkMode ? 'dark' : 'light', + type, + }) + } + + return { showToast } +} + +export default useToast From 1560475ddc506978407782c7eee6b92b57c49318 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Mon, 30 Oct 2023 18:46:08 +0900 Subject: [PATCH 22/33] =?UTF-8?q?[Style]=20Modal=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20(#52)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore : zustand 설치 및 store 폴더 생성 * feat : 모달창 전역 상태 추가 * style : 모달창 컴포넌트 제작 * fix: package.json 쉼표 오류 * fix: package-lock.json 쉼표 오류 * fix : 모달창 type 받아 분기 처리 * fix : type 에러 수정 * chore : 주석 제거 * fix: isDarkMode 옵셔널로 수정 --------- Co-authored-by: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- package-lock.json | 46 ++++++- package.json | 6 +- src/assets/icons/Exclamation.svg | 5 + src/assets/icons/Warning.svg | 3 + .../Buttons/NormalButton/NormalButton.tsx | 4 +- .../NormalButton/NormalButtonStyles.ts | 2 +- src/components/common/Modal/index.tsx | 124 ++++++++++++++++++ src/components/layouts/Layout.tsx | 2 + src/hooks/useModal.tsx | 22 ++++ src/store/ModalStore.tsx | 28 ++++ tsconfig.json | 3 +- 11 files changed, 235 insertions(+), 10 deletions(-) create mode 100644 src/assets/icons/Exclamation.svg create mode 100644 src/assets/icons/Warning.svg create mode 100644 src/components/common/Modal/index.tsx create mode 100644 src/hooks/useModal.tsx create mode 100644 src/store/ModalStore.tsx diff --git a/package-lock.json b/package-lock.json index f4daf9e9..60875e21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,9 @@ "react-icons": "^4.11.0", "react-router-dom": "^6.16.0", "react-spinners": "^0.13.8", - "react-toastify": "^9.1.3" + "react-toastify": "^9.1.3", + "zustand": "^4.4.4", + "zustand-persist": "^0.4.0" }, "devDependencies": { "@rushstack/eslint-config": "^3.4.1", @@ -2518,13 +2520,13 @@ "version": "15.7.9", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.2.30", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.30.tgz", "integrity": "sha512-OfqdJnDsSo4UNw0bqAjFCuBpLYQM7wvZidz0hVxHRjrEkzRlvZL1pJVyOSY55HMiKvRNEo9DUBRuEl7FNlJ/Vg==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2544,7 +2546,7 @@ "version": "0.16.5", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==", - "dev": true + "devOptional": true }, "node_modules/@types/semver": { "version": "7.5.4", @@ -9208,6 +9210,42 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.4.tgz", + "integrity": "sha512-5UTUIAiHMNf5+mFp7/AnzJXS7+XxktULFN0+D1sCiZWyX7ZG+AQpqs2qpYrynRij4QvoDdCD+U+bmg/cG3Ucxw==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/zustand-persist": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/zustand-persist/-/zustand-persist-0.4.0.tgz", + "integrity": "sha512-u6bBIc4yZRpSKBKuTNhoqvoIb09gGHk2NkiPg4K7MPIWTYZg70PlpBn48QEDnKZwfNurnf58TaW5BuMGIMf5hw==", + "peerDependencies": { + "react": ">=16.8.0", + "zustand": ">=3.6.3" + } } } } diff --git a/package.json b/package.json index 243bac8f..5c48947f 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,10 @@ "react-hook-form": "^7.47.0", "react-icons": "^4.11.0", "react-router-dom": "^6.16.0", - "react-spinners": "^0.13.8", - "react-toastify": "^9.1.3" + "react-toastify": "^9.1.3", + "zustand": "^4.4.4", + "zustand-persist": "^0.4.0", + "react-spinners": "^0.13.8" }, "devDependencies": { "@rushstack/eslint-config": "^3.4.1", diff --git a/src/assets/icons/Exclamation.svg b/src/assets/icons/Exclamation.svg new file mode 100644 index 00000000..c6348371 --- /dev/null +++ b/src/assets/icons/Exclamation.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/Warning.svg b/src/assets/icons/Warning.svg new file mode 100644 index 00000000..8a1b6177 --- /dev/null +++ b/src/assets/icons/Warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/common/Buttons/NormalButton/NormalButton.tsx b/src/components/common/Buttons/NormalButton/NormalButton.tsx index 060bbd38..db596958 100644 --- a/src/components/common/Buttons/NormalButton/NormalButton.tsx +++ b/src/components/common/Buttons/NormalButton/NormalButton.tsx @@ -7,9 +7,9 @@ import { NormalButtonStyles, NormalButtonType } from './NormalButtonStyles' const NormalButton = styled.button<{ normalButtonType: NormalButtonType - isDarkMode: boolean + isDarkMode?: boolean }>` - ${({ normalButtonType, isDarkMode }) => { + ${({ normalButtonType, isDarkMode = false }) => { const processedTypeKey = isDarkMode ? `${normalButtonType}-dark` : normalButtonType const processedType = ( NormalButtonStyles[processedTypeKey as NormalButtonType] ? processedTypeKey : normalButtonType diff --git a/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts b/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts index fb028b1d..2f97c3b7 100644 --- a/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts +++ b/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts @@ -144,7 +144,7 @@ export const NormalButtonStyles: Record = { width: 85, height: 40, fontColor: palette.GRAY400, - backgroundColor: palette.GRAY100, + backgroundColor: palette.GRAY200, font: 'Body_14', fontWeight: 600, letterSpacing: -2, diff --git a/src/components/common/Modal/index.tsx b/src/components/common/Modal/index.tsx new file mode 100644 index 00000000..75b43e9a --- /dev/null +++ b/src/components/common/Modal/index.tsx @@ -0,0 +1,124 @@ +import styled from '@emotion/styled' + +import ExclamationIcon from '@/assets/icons/Exclamation.svg' +import WarningIcon from '@/assets/icons/Warning.svg' +import NormalButton from '@/components/common/Buttons/NormalButton/NormalButton' +import useModalStore from '@/store/ModalStore' +import { palette } from '@/styles/palette' +import { typo } from '@/styles/typo' + +const Modal = () => { + const { modalState, setModalState, okFunc, mainText, subText, type } = useModalStore() + const OkAndClose = () => { + okFunc() + closeModal() + } + const closeModal = () => { + setModalState(false) + } + return ( + <> + {modalState ? ( + + + {type == 'confirm' ? ( + + ) : ( + + )} + + {mainText} + {subText} + {type === 'confirm' ? ( + + + {'확인'} + + + {'취소'} + + + ) : ( + + + {'예, 나가겠습니다.'} + + + {'아니오, 돌아가겠습니다.'} + + + )} + + + ) : ( + '' + )} + + ) +} + +const StyleModalWrapper = styled.div` + z-index: 999; + display: flex; + position: absolute; + justify-content: center; + align-items: center; + width: 100%; + background-color: rgba(0, 0, 0, 0.4); + border-radius: 10px; + top: 0; + left: 0; + right: 0; + bottom: 0; +` +const StyleModal = styled.div<{ type: string }>` + width: 344px; + height: ${({ type }) => (type == 'warn' ? '195.6px' : '246px')}; + z-index: 1; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: white; + border-radius: 10px; + box-shadow: 3px 3px 3px ${palette.GRAY400}; + text-align: center; +` + +const StyleButtonWrapper = styled.span` + justify-content: center; + margin: 10px; + display: flex; +` +const StyleMainText = styled.div<{ subTrue: boolean }>` + color: ${palette.BLACK}; + text-align: center; + font-size: ${typo.Body_20()}; + margin-top: ${({ subTrue }) => (subTrue ? '' : '10px')}; + margin-bottom: ${({ subTrue }) => (subTrue ? '20px' : '30px')}; +` +const StyleSubText = styled.span<{ type: string }>` + color: ${palette.GRAY500}; + text-align: center; + font-size: ${typo.Body_14()}; +` +const StyleIcon = styled.img` + margin: 22px; +` +export default Modal diff --git a/src/components/layouts/Layout.tsx b/src/components/layouts/Layout.tsx index 8fe3fb31..e6e41eb6 100644 --- a/src/components/layouts/Layout.tsx +++ b/src/components/layouts/Layout.tsx @@ -4,11 +4,13 @@ import styled from '@emotion/styled' import { Outlet } from 'react-router-dom' import { ToastContainer } from 'react-toastify' +import Modal from '@/components/common/Modal' import { theme } from '@/styles/theme' const Layout = () => { return ( + diff --git a/src/hooks/useModal.tsx b/src/hooks/useModal.tsx new file mode 100644 index 00000000..de18310e --- /dev/null +++ b/src/hooks/useModal.tsx @@ -0,0 +1,22 @@ +import Modal from '@/components/common/Modal' +import useModalStore from '@/store/ModalStore' + +type ModalConfirmPropsType = { + type: 'warn' | 'confirm' + okFunc: () => void + mainText: string + subText?: string +} +export const useModal = () => { + const { setModalState, setOkFunc, setMainText, setSubText, setType } = useModalStore() + + const openModal = ({ mainText, subText, okFunc, type }: ModalConfirmPropsType) => { + setModalState(true) + setType(type) + setMainText(mainText) + setSubText(subText) + setOkFunc(okFunc) + } + + return { openModal, Modal } +} diff --git a/src/store/ModalStore.tsx b/src/store/ModalStore.tsx new file mode 100644 index 00000000..c87e8751 --- /dev/null +++ b/src/store/ModalStore.tsx @@ -0,0 +1,28 @@ +import { create } from 'zustand' + +type ModalState = { + modalState: boolean + type: 'confirm' | 'warn' + okFunc: () => void + mainText: string + subText?: string | undefined + setType: (type: 'confirm' | 'warn') => void + setSubText: (text: string | undefined) => void + setModalState: (state: boolean) => void + setMainText: (text: string) => void + setOkFunc: (func: () => void) => void +} + +const useModalStore = create((set) => ({ + modalState: false, + okFunc: () => {}, + mainText: '', + subText: '', + type: 'confirm', + setType: (type) => set({ type: type }), + setSubText: (text) => set({ subText: text }), + setModalState: (state) => set({ modalState: state }), + setMainText: (text) => set({ mainText: text }), + setOkFunc: (func) => set({ okFunc: func }), +})) +export default useModalStore diff --git a/tsconfig.json b/tsconfig.json index 51f7e0f0..027d4a68 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,8 @@ "@/hooks/*": ["src/hooks/*"], "@/assets/*": ["src/assets/*"], "@/styles/*": ["src/styles/*"], - "@/mocks/*": ["src/mocks/*"] + "@/mocks/*": ["src/mocks/*"], + "@/store/*": ["src/store/*"] } }, "include": ["src"], From 2099b0034bdc4890a84e76c736d8bef8b774c41a Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Tue, 31 Oct 2023 18:32:47 +0900 Subject: [PATCH 23/33] =?UTF-8?q?style:=20selector=20button=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=20(#55)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: white,dark selectorbutton erase * style: selectorbutton * style: selectorbutton re --- .../common/DarkSelectorButton/index.tsx | 77 ------------------ .../common/SelectorButton/index.tsx | 81 +++++++++++++++++++ .../common/WhiteSelectorButton/index.tsx | 63 --------------- 3 files changed, 81 insertions(+), 140 deletions(-) delete mode 100644 src/components/common/DarkSelectorButton/index.tsx create mode 100644 src/components/common/SelectorButton/index.tsx delete mode 100644 src/components/common/WhiteSelectorButton/index.tsx diff --git a/src/components/common/DarkSelectorButton/index.tsx b/src/components/common/DarkSelectorButton/index.tsx deleted file mode 100644 index df56355e..00000000 --- a/src/components/common/DarkSelectorButton/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import styled from '@emotion/styled' -import { useState } from 'react' - -import { palette } from '@/styles/palette' - -type ToggleButtonProps = { - buttonName: string - selectedButtonColor: string - defaultButtonColor?: string - selectedTextColor: string - onClick?: () => void -} - -type StyledButtonProps = { - backgroundColor: string - textColor: string -} - -const DarkSelectorButton = ({ - buttonName, - selectedButtonColor, - defaultButtonColor = palette.WHITE, - selectedTextColor = palette.SECONDARY, - onClick, -}: ToggleButtonProps) => { - const [backgroundColor, setBackgroundColor] = useState(defaultButtonColor) - const [textColor, setTextColor] = useState(palette.SECONDARY) // 텍스트 색상에 대한 state - - const handleButtonClick = () => { - setBackgroundColor((prevColor) => { - if (prevColor === defaultButtonColor) { - setTextColor(selectedTextColor) - return selectedButtonColor - } else { - setTextColor(palette.SECONDARY) - return defaultButtonColor - } - }) - if (onClick) onClick() - } - - return ( - - {buttonName} - - ) -} - -const StyledButton = styled.button` - margin: 0 4px; - height: 36px; - padding: 10px 15px 10px 15px; - font-size: 12px; - cursor: pointer; - border: none; - border-radius: 10px; - background-color: ${(props) => props.backgroundColor}; - transition: background-color 0.3s; - &:hover { - opacity: 0.9; - } - &:focus { - outline: none; - } - color: ${(props) => props.textColor}; - display: inline-block; - vertical-align: middle; - line-height: 1; - font-weight: 600; - letter-spacing: -1px; -` - -export default DarkSelectorButton diff --git a/src/components/common/SelectorButton/index.tsx b/src/components/common/SelectorButton/index.tsx new file mode 100644 index 00000000..3cbcbbed --- /dev/null +++ b/src/components/common/SelectorButton/index.tsx @@ -0,0 +1,81 @@ +import styled from '@emotion/styled' +import { useState } from 'react' + +import { palette } from '@/styles/palette' + +type SelectorButtonProps = { + isDarkMode: boolean + buttonName: string + onClick?: (selected: boolean) => void + isButtonselected?: boolean +} + +const SelectorButton = ({ + isDarkMode, + buttonName, + onClick, + isButtonselected = false, +}: SelectorButtonProps) => { + const defaultSettings = isDarkMode + ? { + selectedButtonColor: palette.SECONDARY, + defaultButtonColor: palette.WHITE, + textColor: palette.SECONDARY, + } + : { + selectedButtonColor: palette.BLUE, + defaultButtonColor: palette.TERTIARY, + textColor: palette.WHITE, + } + + const initialBackgroundColor = isButtonselected + ? defaultSettings.selectedButtonColor + : defaultSettings.defaultButtonColor + const [backgroundColor, setBackgroundColor] = useState(initialBackgroundColor) + const [currentTextColor, setCurrentTextColor] = useState(defaultSettings.textColor) + + const handleButtonClick = () => { + const isSelected = backgroundColor !== defaultSettings.selectedButtonColor + setBackgroundColor( + isSelected ? defaultSettings.selectedButtonColor : defaultSettings.defaultButtonColor, + ) + if (defaultSettings.textColor !== palette.WHITE) { + setCurrentTextColor(isSelected ? palette.WHITE : defaultSettings.textColor) + } + if (onClick) onClick(isSelected) + } + + return ( + + {buttonName} + + ) +} + +const StyledButton = styled.button<{ backgroundColor: string; textColor: string }>` + margin: 8px; + height: 36px; + padding: 10px 15px 10px 15px; + font-size: 12px; + cursor: pointer; + border: none; + border-radius: 10px; + background-color: ${(props) => props.backgroundColor}; + transition: background-color 0.3s; + &:hover { + opacity: 0.9; + } + &:focus { + outline: none; + } + color: ${(props) => props.textColor}; + display: inline-block; + vertical-align: middle; + line-height: 1; +` + +export default SelectorButton diff --git a/src/components/common/WhiteSelectorButton/index.tsx b/src/components/common/WhiteSelectorButton/index.tsx deleted file mode 100644 index 4d02fd04..00000000 --- a/src/components/common/WhiteSelectorButton/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import styled from '@emotion/styled' -import { useState } from 'react' - -import { palette } from '@/styles/palette' - -type ToggleButtonProps = { - buttonName: string - selectedButtonColor: string - defaultButtonColor?: string - onClick?: () => void -} - -type StyledButtonProps = { - backgroundColor: string -} - -const WhiteSelectorButton = ({ - buttonName, - selectedButtonColor, - defaultButtonColor = palette.TERTIARY, - onClick, -}: ToggleButtonProps) => { - const [backgroundColor, setBackgroundColor] = useState(defaultButtonColor) - - const handleButtonClick = () => { - setBackgroundColor((prevColor) => - prevColor === defaultButtonColor ? selectedButtonColor : defaultButtonColor, - ) - if (onClick) onClick() - } - - return ( - - {buttonName} - - ) -} - -const StyledButton = styled.button` - margin: 0 4px; - height: 36px; - padding: 10px 15px 10px 15px; - font-size: 12px; - cursor: pointer; - border: none; - border-radius: 10px; - background-color: ${(props) => props.backgroundColor}; - transition: background-color 0.3s; - &:hover { - opacity: 0.9; - } - &:focus { - outline: none; - } - color: ${palette.WHITE}; - display: inline-block; - vertical-align: middle; - line-height: 1; - letter-spacing: -1px; - font-weight: 600; -` - -export default WhiteSelectorButton From c219120bbf29893070ed10e37f1a3302f5a61fe5 Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Tue, 31 Oct 2023 18:42:23 +0900 Subject: [PATCH 24/33] =?UTF-8?q?style:=20Appbar,=20Card,=20GradationBackg?= =?UTF-8?q?round,=20PageContainer=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20+=20Home=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#57)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: CountNumber, Input 위치 변경 * refactor: Avatar 컴포넌트 props 타입 수정 + tsdoc 추가 * feature: Tip 컴포넌트 추가 * feature: AppHeader 컴포넌트 추가 * refactor: IconButtonType 수정 + 클릭 효과 속성 추가 * feature: Profile 페이지 라우팅 추가 * refactor: AppHeader 높이값 받도록 수정 * style: typo, palette, Layout, global 파일 수정 * refactor: RandomMatchingSheet 동적으로 변하도록 수정 * refactor: NormalButton 폴더 변경 + 클릭 시 배경 변경 타입 추가 * refactor: tsdoc 추가 * feature: GradationBackground 컴포넌트 추가 * refactor: NavigationBar 라우팅 + position: sticky로 수정 * feature: PageContainer 컴포넌트 추가 * feature: AvatarGroup 컴포넌트 추가 * feature: Card 컴포넌트 추가 * feature: Home 페이지 레이아웃 추가 * refactor: tsdoc 추가 * fix: merge conflict 해결용 파일 추가 * fix: NormalButton 파일 에러 수정 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- src/components/CountNumber.tsx | 32 --- src/components/common/AppHeader/index.tsx | 100 ++++++++ src/components/common/Avatar/index.tsx | 39 +++- .../BottomSheet/RandomMatchingSheet.tsx | 8 +- .../Buttons/IconButton/IconButtonStyles.ts | 17 +- .../IconButton/ParticularTopicButton.tsx | 3 + .../IconButton/RandomMatchingButton.tsx | 13 +- .../common/Buttons/IconButton/index.tsx | 7 +- .../Buttons/NormalButton/NormalButton.tsx | 36 --- .../NormalButton/NormalButtonStyles.ts | 3 +- .../common/Buttons/NormalButton/index.tsx | 28 +++ .../common/GradationBackground/index.tsx | 21 ++ .../{Input.tsx => common/Input/index.tsx} | 0 src/components/common/Modal/index.tsx | 2 +- src/components/common/NavigationBar/index.tsx | 10 +- src/components/common/PageContainer/index.tsx | 33 +++ src/components/home/AvatarGroup.tsx | 46 ++++ src/components/home/Card.tsx | 217 ++++++++++++++++++ src/components/home/Tip.tsx | 48 ++++ src/components/layouts/Layout.tsx | 2 +- src/pages/home/Home.tsx | 53 ++++- src/pages/profile/ProfileDefault.tsx | 5 + src/pages/profile/index.tsx | 3 + src/styles/global.ts | 7 + src/styles/palette.ts | 1 + src/styles/typo.ts | 35 ++- 26 files changed, 665 insertions(+), 104 deletions(-) delete mode 100644 src/components/CountNumber.tsx create mode 100644 src/components/common/AppHeader/index.tsx create mode 100644 src/components/common/Buttons/NormalButton/index.tsx create mode 100644 src/components/common/GradationBackground/index.tsx rename src/components/{Input.tsx => common/Input/index.tsx} (100%) create mode 100644 src/components/common/PageContainer/index.tsx create mode 100644 src/components/home/AvatarGroup.tsx create mode 100644 src/components/home/Card.tsx create mode 100644 src/components/home/Tip.tsx create mode 100644 src/pages/profile/ProfileDefault.tsx diff --git a/src/components/CountNumber.tsx b/src/components/CountNumber.tsx deleted file mode 100644 index 018aaf35..00000000 --- a/src/components/CountNumber.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import styled from '@emotion/styled' - -type CountNumberProps = { - currentLength: number - maxLength: number - color: string - right: number -} - -type StyleCountNumberProps = { - color: string - right: number -} - -const CountNumber = ({ right, currentLength, maxLength, color }: CountNumberProps): JSX.Element => { - return ( - {`${currentLength}/${maxLength}`} - ) -} - -const StyleCountNumber = styled.span` - position: relative; - right: ${(props) => props.right}px; - bottom: 3px; - font-size: 12px; - color: ${(props) => props.color}; -` - -export default CountNumber diff --git a/src/components/common/AppHeader/index.tsx b/src/components/common/AppHeader/index.tsx new file mode 100644 index 00000000..56681a61 --- /dev/null +++ b/src/components/common/AppHeader/index.tsx @@ -0,0 +1,100 @@ +import styled from '@emotion/styled' +import { BiSolidMoon } from 'react-icons/bi' +import { RiSunFill } from 'react-icons/ri' +import { useNavigate } from 'react-router-dom' + +import Avatar from '@/components/common/Avatar' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +import { FlexBox } from '../Flexbox' + +const StyleAppHeader = styled.div<{ height?: string }>` + width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + height: ${({ height }) => height}; + text-align: center; + padding: 6.5% 5% 7%; +` + +type AppHeaderProps = { + nickname: string + isDarkMode: boolean + height?: string +} + +/** + * @param nickname - 유저 닉네임 + * @param isDarkMode - 다크모드 여부 + * @param height - 컴포넌트 높이 + */ + +const AppHeader = ({ nickname, isDarkMode, height }: AppHeaderProps) => { + const navigate = useNavigate() + const moveFromAppHeader = (path: string) => { + navigate(`/${path}`) + } + + return ( + + + { + moveFromAppHeader('profile') + }} + /> + {isDarkMode ? ( + + ) : ( + + )} + + + + {nickname} + + + {'님, 안녕하세요! 오늘도 즐거운 커피밋! ☕️'} + + + + ) +} + +export default AppHeader diff --git a/src/components/common/Avatar/index.tsx b/src/components/common/Avatar/index.tsx index 43b56e00..16b5cf22 100644 --- a/src/components/common/Avatar/index.tsx +++ b/src/components/common/Avatar/index.tsx @@ -1,10 +1,14 @@ import styled from '@emotion/styled' +import defaultProfileImage from '@/assets/images/defaultProfileImage.png' // 이미지 import + type AvatarProps = { - width: number - height: number + width: number | string + height: number | string imgUrl: string margin: string + onClick?: () => void + border?: string shadow?: boolean } @@ -17,12 +21,39 @@ const StyledAvatar = styled.div` background-position: center center; border-radius: 50%; // 원 형태로 만들기 위함 margin: ${(props) => `${props.margin}px`}; + border: ${(props) => (props.border ? props.border : 'none')}; box-shadow: ${(props) => (props.shadow ? '0px 0px 10px rgba(0, 0, 0, 0.25)' : 'none')}; ` -const Avatar: React.FC = ({ width, height, imgUrl, margin = '0', shadow = false }) => { +/** + * `Avatar` component for displaying profile images. + * @param width - 아바타의 너비 (픽셀 또는 유효한 CSS 단위). + * @param height - 아바타의 높이 (픽셀 또는 유효한 CSS 단위). + * @param imgUrl - 아바타의 이미지 URL. 기본 이미지는 `defaultProfileImage`이다. + * @param margin - 아바타의 마진 (픽셀 또는 유효한 CSS 단위). + * @param onClick - (Optional) 클릭 이벤트. + * @param border - (Optional) 아바타의 테두리. 기본 값은 `none`이다. + * @param shadow - (Optional) 아바타의 그림자. 기본 값은 `false`이다. + */ +const Avatar = ({ + width, + height, + imgUrl, + margin = '0', + onClick, + border, + shadow = false, +}: AvatarProps) => { return ( - + ) } diff --git a/src/components/common/BottomSheet/RandomMatchingSheet.tsx b/src/components/common/BottomSheet/RandomMatchingSheet.tsx index 8b6a68be..d88d0683 100644 --- a/src/components/common/BottomSheet/RandomMatchingSheet.tsx +++ b/src/components/common/BottomSheet/RandomMatchingSheet.tsx @@ -16,7 +16,7 @@ const Background = styled.div` display: flex; justify-content: center; align-items: flex-end; - overflow-y: hidden; + position: absolute; ` const BottomContentWrapper = styled(motion.div)<{ @@ -25,7 +25,7 @@ const BottomContentWrapper = styled(motion.div)<{ width: 100%; display: flex; flex-direction: column; - height: 378px; + height: 400px; border-top-left-radius: 20px; border-top-right-radius: 20px; background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; @@ -86,13 +86,13 @@ const RandomMatchingSheet = ({ const slideUp = { hidden: { y: '100%', opacity: 0 }, - visible: { y: '0%', opacity: 1, transition: { type: 'spring', damping: 15, stiffness: 100 } }, + visible: { y: '22px', opacity: 1, transition: { type: 'spring', damping: 15, stiffness: 100 } }, partiallyVisible: { y: '85%', opacity: 1, transition: { type: 'spring', damping: 15, stiffness: 100 }, }, - exit: { y: '100%', opacity: 0, transition: { type: 'spring', damping: 20, stiffness: 100 } }, + exit: { y: '100%', opacity: 0, transition: { type: 'spring', damping: 15, stiffness: 100 } }, } return ( diff --git a/src/components/common/Buttons/IconButton/IconButtonStyles.ts b/src/components/common/Buttons/IconButton/IconButtonStyles.ts index d2421919..3ed54696 100644 --- a/src/components/common/Buttons/IconButton/IconButtonStyles.ts +++ b/src/components/common/Buttons/IconButton/IconButtonStyles.ts @@ -14,7 +14,7 @@ export type IconButtonType = export const iconButtonStyles: Record = { interest: { - width: 339, + width: '339px', height: 70, fontColor: palette.WHITE, font: 'Body_18', @@ -25,7 +25,7 @@ export const iconButtonStyles: Record = { backgroundColor: `linear-gradient(96deg, #7382F8 49.74%, #A6BCFC 93.87%);`, }, 'interest-dark': { - width: 339, + width: '339px', height: 70, fontColor: palette.DARK_WHITE, font: 'Body_18', @@ -36,7 +36,7 @@ export const iconButtonStyles: Record = { backgroundColor: `linear-gradient(89deg, ${palette.DARK_SECONDARY} 49.41%, ${palette.DARK_TERTIARY} 92.91%)`, }, 'particular-topic': { - width: 344, + width: '100%', height: 70, fontColor: palette.GRAY600, font: 'Body_18', @@ -47,7 +47,7 @@ export const iconButtonStyles: Record = { backgroundColor: palette.WHITE, }, 'particular-topic-dark': { - width: 344, + width: '100%', height: 70, fontColor: palette.DARK_WHITE, font: 'Body_18', @@ -58,7 +58,7 @@ export const iconButtonStyles: Record = { backgroundColor: palette.GRAY700, }, 'random-matching': { - width: 230, + width: '230px', height: 70, fontColor: palette.WHITE, font: 'Body_18', @@ -67,9 +67,10 @@ export const iconButtonStyles: Record = { borderRadius: 20, boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', backgroundColor: `linear-gradient(96deg, ${palette.SECONDARY} 49.74%, #A6BCFC 93.87%);`, + activeBackgroundColor: `linear-gradient(89deg, ${palette.DARK_SECONDARY} 49.41%, ${palette.DARK_TERTIARY} 92.91%)`, }, 'random-matching-dark': { - width: 230, + width: '230px', height: 70, fontColor: palette.DARK_WHITE, font: 'Body_18', @@ -80,7 +81,7 @@ export const iconButtonStyles: Record = { backgroundColor: `linear-gradient(86deg, #494F80 8.54%, #5A77B3 94.19%);`, }, 'random-matching-join': { - width: 230, + width: '230px', height: 50, fontColor: palette.WHITE, font: 'Body_16', @@ -91,7 +92,7 @@ export const iconButtonStyles: Record = { backgroundColor: `linear-gradient(96deg, #7382F8 49.74%, #A6BCFC 93.87%);`, }, 'random-matching-join-dark': { - width: 230, + width: '230px', height: 50, fontColor: palette.WHITE, font: 'Body_16', diff --git a/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx b/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx index e268f1c2..4f308f2f 100644 --- a/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx +++ b/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx @@ -9,6 +9,9 @@ type ParticularTopicButtonProps = { isDarkMode: boolean } +/** + * @param isDarkMode - 다크모드 여부 + */ const ParticularTopicButton = ({ isDarkMode }: ParticularTopicButtonProps) => { const getButtonType = isDarkMode ? 'particular-topic-dark' : 'particular-topic' const getIconColor = isDarkMode ? palette.DARK_WHITE : palette.GRAY600 diff --git a/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx b/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx index f705d881..43f61c3d 100644 --- a/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx +++ b/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx @@ -10,9 +10,17 @@ import { StyleIconButtonWrapper, StyleIconWrapper } from '.' type RandomMatchingButtonProps = { date: string isDarkMode: boolean + onClick: () => void } -const RandomMatchingButton = ({ date, isDarkMode }: RandomMatchingButtonProps) => { +/** + * + * @param date - 마지막 채팅 시간 + * @param isDarkMode - 다크모드 여부 + * @param onClick - 랜덤매칭 버튼 클릭 이벤트 + */ + +const RandomMatchingButton = ({ date, isDarkMode, onClick }: RandomMatchingButtonProps) => { const setButtonType = isDarkMode ? 'random-matching-dark' : 'random-matching' return ( @@ -23,6 +31,7 @@ const RandomMatchingButton = ({ date, isDarkMode }: RandomMatchingButtonProps) = justifyContent: 'space-between', alignItems: 'center', }} + onClick={onClick} > ` - ${({ normalButtonType, isDarkMode = false }) => { - const processedTypeKey = isDarkMode ? `${normalButtonType}-dark` : normalButtonType - const processedType = ( - NormalButtonStyles[processedTypeKey as NormalButtonType] ? processedTypeKey : normalButtonType - ) as NormalButtonType - - console.log(processedType) - - const fontFunc = typo[NormalButtonStyles[processedType].font] - return css` - ${fontFunc( - NormalButtonStyles[processedType].fontWeight, - NormalButtonStyles[processedType].letterSpacing, - )} - width: ${NormalButtonStyles[processedType].width}px; - height: ${NormalButtonStyles[processedType].height}px; - color: ${NormalButtonStyles[processedType].fontColor}; - background-color: ${NormalButtonStyles[processedType].backgroundColor}; - box-shadow: ${NormalButtonStyles[processedType].boxShadow}; - border-radius: ${NormalButtonStyles[processedType].borderRadius}px; - ` - }} -` - -export default NormalButton diff --git a/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts b/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts index 2f97c3b7..811b0a4f 100644 --- a/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts +++ b/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts @@ -2,7 +2,7 @@ import { palette } from '@/styles/palette' import { KeyOfTypo } from '@/styles/theme' export type NormalButtonStyle = { - width: number + width: number | string height: number fontColor: string backgroundColor: string @@ -12,6 +12,7 @@ export type NormalButtonStyle = { boxShadow?: string stroke?: string borderRadius: number + activeBackgroundColor?: string } export type NormalButtonType = diff --git a/src/components/common/Buttons/NormalButton/index.tsx b/src/components/common/Buttons/NormalButton/index.tsx new file mode 100644 index 00000000..af501a38 --- /dev/null +++ b/src/components/common/Buttons/NormalButton/index.tsx @@ -0,0 +1,28 @@ +import { css } from '@emotion/react' +import styled from '@emotion/styled' + +import { typo } from '@/styles/typo' + +import { NormalButtonStyles, NormalButtonType } from './NormalButtonStyles' + +const NormalButton = styled.button<{ + normalButtonType: NormalButtonType +}>` + ${({ normalButtonType }) => { + const fontFunc = typo[NormalButtonStyles[normalButtonType].font] + return css` + ${fontFunc( + NormalButtonStyles[normalButtonType].fontWeight, + NormalButtonStyles[normalButtonType].letterSpacing, + )} + width: ${NormalButtonStyles[normalButtonType].width}px; + height: ${NormalButtonStyles[normalButtonType].height}px; + color: ${NormalButtonStyles[normalButtonType].fontColor}; + background-color: ${NormalButtonStyles[normalButtonType].backgroundColor}; + box-shadow: ${NormalButtonStyles[normalButtonType].boxShadow}; + border-radius: ${NormalButtonStyles[normalButtonType].borderRadius}px; + ` + }} +` + +export default NormalButton diff --git a/src/components/common/GradationBackground/index.tsx b/src/components/common/GradationBackground/index.tsx new file mode 100644 index 00000000..8a673377 --- /dev/null +++ b/src/components/common/GradationBackground/index.tsx @@ -0,0 +1,21 @@ +import styled from '@emotion/styled' + +import { palette } from '@/styles/palette' + +const StyledGradationBackground = styled.div` + width: 100%; + height: 100vh; + background: linear-gradient(139deg, ${palette.PRIMARY} 23.32%, ${palette.GRADIENT} 61.26%); + display: flex; + flex-direction: column; +` + +/** + * + * @param children - 자식 컴포넌트 + */ +const GradationBackground = ({ children }: { children: React.ReactNode }) => { + return {children} +} + +export default GradationBackground diff --git a/src/components/Input.tsx b/src/components/common/Input/index.tsx similarity index 100% rename from src/components/Input.tsx rename to src/components/common/Input/index.tsx diff --git a/src/components/common/Modal/index.tsx b/src/components/common/Modal/index.tsx index 75b43e9a..59cf7b8a 100644 --- a/src/components/common/Modal/index.tsx +++ b/src/components/common/Modal/index.tsx @@ -2,7 +2,7 @@ import styled from '@emotion/styled' import ExclamationIcon from '@/assets/icons/Exclamation.svg' import WarningIcon from '@/assets/icons/Warning.svg' -import NormalButton from '@/components/common/Buttons/NormalButton/NormalButton' +import NormalButton from '@/components/common/Buttons/NormalButton' import useModalStore from '@/store/ModalStore' import { palette } from '@/styles/palette' import { typo } from '@/styles/typo' diff --git a/src/components/common/NavigationBar/index.tsx b/src/components/common/NavigationBar/index.tsx index abfc5def..22316e93 100644 --- a/src/components/common/NavigationBar/index.tsx +++ b/src/components/common/NavigationBar/index.tsx @@ -16,7 +16,7 @@ const NavigationBar = () => { return ( - moveFromNavigationBar('chatlist')}> + moveFromNavigationBar('chat-list')}> {'이전대화방'} @@ -28,7 +28,7 @@ const NavigationBar = () => { {'홈'} - moveFromNavigationBar('profile/edit')}> + moveFromNavigationBar('profile')}> @@ -41,7 +41,7 @@ const NavigationBar = () => { ) } const StyleWrapper = styled(FlexBox)` - position: absolute; + position: sticky; bottom: 0px; ` const StyleNavigationText = styled.span` @@ -53,8 +53,8 @@ const StyleNavigation = styled(FlexBox)` height: 71px; background-color: white; box-shadow: - 0px 0px 2px 0px rgba(0, 0, 0, 0.24), - 0px 4px 4px 0px rgba(0, 0, 0, 0.14); + 0px 0px 10px 0px rgba(0, 0, 0, 0.24), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14); ` const StyleNavigationItem = styled.button` cursor: pointer; diff --git a/src/components/common/PageContainer/index.tsx b/src/components/common/PageContainer/index.tsx new file mode 100644 index 00000000..9455dc6a --- /dev/null +++ b/src/components/common/PageContainer/index.tsx @@ -0,0 +1,33 @@ +import styled from '@emotion/styled' +import { ReactNode } from 'react' + +import { palette } from '@/styles/palette' + +const StyledPageContainer = styled.div<{ height: string }>` + width: 100%; + height: ${({ height }) => height}; + padding: 5% 5% 5%; + background-color: ${palette.GRAY100}; + border-top-right-radius: 20px; + border-top-left-radius: 20px; + margin-top: auto; + flex: 1; + overflow-y: scroll; + box-shadow: inset 0 5px 10px rgba(0, 0, 0, 0.2); +` + +type PageContainerProps = { + children: ReactNode + height?: string +} + +/** + * + * @param children - 자식 컴포넌트 + * @param height - 높이 + */ +const PageContainer = ({ children, height = '77%' }: PageContainerProps) => { + return {children} +} + +export default PageContainer diff --git a/src/components/home/AvatarGroup.tsx b/src/components/home/AvatarGroup.tsx new file mode 100644 index 00000000..9c438643 --- /dev/null +++ b/src/components/home/AvatarGroup.tsx @@ -0,0 +1,46 @@ +import styled from '@emotion/styled' + +import Avatar from '@/components/common/Avatar' + +const StyleAvatarGroup = styled.div` + display: flex; + position: relative; + justify-content: flex-end; + align-items: center; + width: 100%; + position: relative; +` + +const StyleAvatarWrapper = styled.div` + position: absolute; + right: 0; + display: flex; + justify-content: flex-end; + align-items: center; + width: 100%; +` + +type AvatarGroupProps = { + avatarList: string[] +} + +const AvatarGroup = ({ avatarList }: AvatarGroupProps) => { + return ( + + {avatarList.map((avatar, index) => { + return ( + + + + ) + })} + + ) +} + +export default AvatarGroup diff --git a/src/components/home/Card.tsx b/src/components/home/Card.tsx new file mode 100644 index 00000000..af6ddd78 --- /dev/null +++ b/src/components/home/Card.tsx @@ -0,0 +1,217 @@ +import styled from '@emotion/styled' +import { timer } from 'd3' +import { AnimatePresence, motion } from 'framer-motion' +import { useEffect, useRef, useState } from 'react' +import { PulseLoader } from 'react-spinners' + +import { RandomMatchingButton } from '@/components/common/Buttons/IconButton' +import NormalButton from '@/components/common/Buttons/NormalButton' +import Spacing from '@/components/common/Spacing' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +import AvatarGroup from './AvatarGroup' +import Tip from './Tip' + +const StyleCard = styled(motion.div)` + width: 100%; + height: 348px; + border-radius: 20px; + background-color: ${palette.WHITE}; + box-shadow: ${palette.SHADOW}; + display: flex; + flex-direction: column; + margin: 0 auto; + justify-content: center; + align-items: center; + padding: 19px 7px 15px; +` + +const StyleWatingWrapper = styled(motion.div)` + width: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1; +` + +const StyleWatingTopWrapper = styled.div` + width: 100%; + height: 38px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 20px; +` + +const StyleWatingTopTextWrapper = styled.div` + display: flex; + height: inherit; + justify-content: center; + align-items: flex-end; +` + +const StyleWatingMidWrapper = styled.div` + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +` + +const StyleWatingBottomWrapper = styled.div`` + +type TimerRefType = ReturnType | null + +type CardProps = { + isMatching: boolean + isDarkMode: boolean + onClick: () => void +} + +/** + * @param isMatching - 현재 매칭 여부 + * @param isDarkMode - 다크모드 여부 + * @param onClick - 매칭 버튼 클릭 이벤트 + */ + +const Card = ({ isMatching, isDarkMode, onClick }: CardProps) => { + const [time, setTime] = useState(0) + const timerRef = useRef(null) + + const handleCancelClick = () => { + setTime(0) + if (timerRef.current) { + timerRef.current.stop() + } + onClick() + } + + const formatTime = (time: number) => { + const minutes = Math.floor(time / 60000) + .toString() + .padStart(2, '0') + const seconds = Math.floor((time % 60000) / 1000) + .toString() + .padStart(2, '0') + return `${minutes}:${seconds}` + } + + const watingCounter = { + hidden: { opacity: 0 }, + visible: { opacity: 1, transition: { type: 'spring', damping: 12, duration: 0.5 } }, + exit: { opacity: 0, transition: { duration: 1 } }, + } + + useEffect(() => { + if (isMatching) { + const startTime = Date.now() + const updateTimer = () => { + const elapsedTime = Date.now() - startTime + setTime(elapsedTime) + } + timerRef.current = timer(updateTimer, 1000) + } else { + if (timerRef.current) { + timerRef.current.stop() + } + } + + return () => { + if (timerRef.current) { + timerRef.current.stop() + } + } + }, [isMatching]) + + return ( + + + {!isMatching ? ( + + + + ) : ( + + + + + {'3'} + + + {'/5'} + + + + + + + + {formatTime(time)} + + + + {'매칭 취소'} + + + + {'매칭 중'}    + + + + + + + + + )} + + + ) +} + +export default Card diff --git a/src/components/home/Tip.tsx b/src/components/home/Tip.tsx new file mode 100644 index 00000000..4196d6a5 --- /dev/null +++ b/src/components/home/Tip.tsx @@ -0,0 +1,48 @@ +import styled from '@emotion/styled' + +import { palette } from '@/styles/palette' + +import { Text } from '../common/Text' + +const StyleTipHeader = styled.div` + flex: 1; + display: flex; + flex-direction: column; + height: 63.525px; + padding: 11px 18px 0px; + border-top: 1px solid ${palette.GRAY200}; +` + +/** + * 홈페이지에서 사용되는 `Tip` 컴포넌트. + */ +const Tip = () => { + return ( + + + {'💡 Tip!'} + + + {'커피밋 채팅방은 3일이 지나면 사라집니다!'} + + + ) +} + +export default Tip diff --git a/src/components/layouts/Layout.tsx b/src/components/layouts/Layout.tsx index e6e41eb6..86890eb5 100644 --- a/src/components/layouts/Layout.tsx +++ b/src/components/layouts/Layout.tsx @@ -19,7 +19,7 @@ const Layout = () => { const MainContainer = styled.main` position: relative; - max-width: 480px; + max-width: 414px; height: calc(var(--vh, 1vh) * 100); margin: 0 auto; background-color: ${theme.palette.GRAY200}; diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 8a89bb95..0377915c 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -1,5 +1,56 @@ +import { useState } from 'react' + +import AppHeader from '@/components/common/AppHeader' +import { ParticularTopicButton } from '@/components/common/Buttons/IconButton' +import GradationBackground from '@/components/common/GradationBackground' +import NavigationBar from '@/components/common/NavigationBar' +import PageContainer from '@/components/common/PageContainer' +import { Text } from '@/components/common/Text' +import Card from '@/components/home/Card' + const Home = () => { - return
{'Home'}
+ const nickname = '우땅' + const isDarkMode = false + const [isMatching, setIsMatching] = useState(false) + + return ( + <> + + + + + {'진행중인 매칭'} + + { + setIsMatching((prev) => !prev) + }} + isDarkMode={isDarkMode} + /> + + {'커피밋의 추천기능'} + + + + + + + ) } export default Home diff --git a/src/pages/profile/ProfileDefault.tsx b/src/pages/profile/ProfileDefault.tsx new file mode 100644 index 00000000..b3ec47ba --- /dev/null +++ b/src/pages/profile/ProfileDefault.tsx @@ -0,0 +1,5 @@ +const ProfileDefault = () => { + return
{'ProfileDefault'}
+} + +export default ProfileDefault diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index 042db445..9985622a 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -1,5 +1,6 @@ import { Route, Routes } from 'react-router-dom' +import ProfileDefault from '@/pages/profile/ProfileDefault' import ProfileEdit from '@/pages/profile/ProfileEdit' import ProfileHelpDesk from '@/pages/profile/ProfileHelpDesk' import ProfilePrivacy from '@/pages/profile/ProfilePrivacy' @@ -7,6 +8,8 @@ import ProfilePrivacy from '@/pages/profile/ProfilePrivacy' const ProfilePage = () => { return ( + } /> + }> }> }> diff --git a/src/styles/global.ts b/src/styles/global.ts index 2230a98c..0b907b7c 100644 --- a/src/styles/global.ts +++ b/src/styles/global.ts @@ -43,6 +43,13 @@ export const globalStyle = css` ::-webkit-scrollbar { display: none; } + + body, + html { + overflow: hidden; + background-color: #000; + } + div { box-sizing: border-box; } diff --git a/src/styles/palette.ts b/src/styles/palette.ts index e19c97dc..78c9ae54 100644 --- a/src/styles/palette.ts +++ b/src/styles/palette.ts @@ -23,4 +23,5 @@ export const palette = { GRAY200: '#E5E7EC', GRAY100: '#EFF0F2', SKY_BLUE: '#F0F4FF', + SHADOW: '0px 4px 20px rgba(0, 0, 0, 0.15)', } diff --git a/src/styles/typo.ts b/src/styles/typo.ts index 51eed14d..9dabead3 100644 --- a/src/styles/typo.ts +++ b/src/styles/typo.ts @@ -2,50 +2,69 @@ import { css } from '@emotion/react' export const calcRem = (px: number) => `${px / 16}rem` export const typo = { + Body_32: (fontWeight: number = 600, letterSpacing?: number) => css` + font-family: 'Pretendard-Regular'; + font-size: ${calcRem(32)}; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; + `, + Body_24: (fontWeight: number = 500, letterSpacing?: number) => css` + font-family: 'Pretendard-Regular'; + font-size: ${calcRem(24)}; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; + `, + Body_22: (fontWeight: number = 500, letterSpacing?: number) => css` + font-family: 'Pretendard-Regular'; + font-size: ${calcRem(22)}; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; + `, + Body_20: (fontWeight: number = 500, letterSpacing?: number) => css` - font-family: 'Pretendard'; + font-family: 'Pretendard-Regular'; font-size: ${calcRem(20)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, Body_18: (fontWeight: number = 500, letterSpacing?: number) => css` - font-family: 'Pretendard'; + font-family: 'Pretendard-Regular'; font-size: ${calcRem(18)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, Body_16: (fontWeight: number = 400, letterSpacing?: number) => css` - font-family: 'Pretendard'; + font-family: 'Pretendard-Regular'; font-size: ${calcRem(16)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, Body_14: (fontWeight: number = 400, letterSpacing?: number) => css` - font-family: 'Pretendard'; + font-family: 'Pretendard-Regular'; font-size: ${calcRem(14)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, Body_12: (fontWeight: number = 400, letterSpacing?: number) => css` - font-family: 'Pretendard'; + font-family: 'Pretendard-Regular'; font-size: ${calcRem(12)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, Body_10: (fontWeight: number = 400, letterSpacing?: number) => css` - font-family: 'Pretendard'; + font-family: 'Pretendard-Regular'; font-size: ${calcRem(10)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, Caption_11: (fontWeight: number = 400, letterSpacing?: number) => css` - font-family: 'Pretendard'; + font-family: 'Pretendard-Regular'; font-size: ${calcRem(11)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, Caption_9: (fontWeight: number = 500, letterSpacing?: number) => css` - font-family: 'Pretendard'; + font-family: 'Pretendard-Regular'; font-size: ${calcRem(9)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; From f29d741fad3da9fd5b774e6bc0e6d2cceddbc1e2 Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Tue, 31 Oct 2023 18:44:33 +0900 Subject: [PATCH 25/33] =?UTF-8?q?style:=20PageHeader=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20(#59)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feature: ExitIcon 컴포넌트 추가 * feature: BackChevron 컴포넌트 추가 * feature: PageHeader 컴포넌트 추가 * refactor: 사용하지 않는 코드 제거 * refactor: tsdoc 추가 * refactor: tsdoc 추가 + exitClick으로 props명 변경 * refactor: PageHeader border-bottom 수정 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- src/assets/icons/ExitIcon.tsx | 100 ++++++++++++++++++++ src/components/common/BackChevron/index.tsx | 62 ++++++++++++ src/components/common/PageHeader/index.tsx | 87 +++++++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 src/assets/icons/ExitIcon.tsx create mode 100644 src/components/common/BackChevron/index.tsx create mode 100644 src/components/common/PageHeader/index.tsx diff --git a/src/assets/icons/ExitIcon.tsx b/src/assets/icons/ExitIcon.tsx new file mode 100644 index 00000000..2979315a --- /dev/null +++ b/src/assets/icons/ExitIcon.tsx @@ -0,0 +1,100 @@ +export type IconProps = { + isDarkMode?: boolean + exitClick?: () => void +} + +/** + * @param isDarkMode - (Optional) 다크모드 여부 + * @param exitClick - (Optional) 나가기 클릭 이벤트 + */ + +const ExitIcon = ({ isDarkMode, exitClick }: IconProps) => ( + + {isDarkMode ? ( + + + + + + + ) : ( + + + + + + + )} + +) + +export default ExitIcon diff --git a/src/components/common/BackChevron/index.tsx b/src/components/common/BackChevron/index.tsx new file mode 100644 index 00000000..0e9f69cc --- /dev/null +++ b/src/components/common/BackChevron/index.tsx @@ -0,0 +1,62 @@ +import styled from '@emotion/styled' +import { BsChevronLeft } from 'react-icons/bs' + +import { palette } from '@/styles/palette' + +const StyleBackChevron = styled.div` + width: 38px; + height: 38px; + border-radius: 10px; + display: flex; + justify-content: center; + align-items: center; + background-color: ${({ hasBackground, isDarkMode }) => + isDarkMode + ? hasBackground + ? `${palette.GRAY600}` + : 'transparent' + : hasBackground + ? `${palette.WHITE}` + : 'transparent'}; + border: ${({ hasBackground, isDarkMode }) => + isDarkMode + ? hasBackground + ? `1px solid ${palette.GRAY500}` + : `1px solid ${palette.DARK_TERTIARY}` + : hasBackground + ? `1px solid ${palette.GRAY300}` + : `1px solid ${palette.TERTIARY}`}; +` + +type BackChevronProps = { + hasBackground?: boolean + isDarkMode?: boolean + prevClick?: () => void +} + +/** + * @param hasBackground - (Optional) 배경색 여부 + * @param isDarkMode - (Optional) 다크모드 여부 + * @param prevClick - (Optional) 뒤로가기 클릭 이벤트 + */ + +const BackChevron = ({ hasBackground, isDarkMode, prevClick }: BackChevronProps) => { + return ( + + + + ) +} + +export default BackChevron diff --git a/src/components/common/PageHeader/index.tsx b/src/components/common/PageHeader/index.tsx new file mode 100644 index 00000000..dd0fece2 --- /dev/null +++ b/src/components/common/PageHeader/index.tsx @@ -0,0 +1,87 @@ +import styled from '@emotion/styled' + +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +const StylePageHeader = styled.div<{ + isDarkMode?: boolean + hasBackground?: boolean +}>` + width: 100%; + height: 72px; + padding: 0 18px; + display: flex; + justify-content: space-between; + align-items: center; + border-radius: 20px 20px 0 0; + border-bottom: 1px solid + ${({ isDarkMode, hasBackground }) => + isDarkMode + ? hasBackground + ? palette.GRAY500 + : 'none' + : hasBackground + ? palette.GRAY300 + : 'none'}; + background-color: ${({ isDarkMode, hasBackground }) => + isDarkMode + ? hasBackground + ? `${palette.DARK_BLUE}` + : 'transparent' + : hasBackground + ? `${palette.GRAY100}` + : 'transparent'}; +` + +const StyleIcon = styled.div` + width: 38px; + height: 38px; + display: flex; + justify-content: center; + align-items: center; +` + +type PageHeaderProps = { + title: string + leftIcon?: React.ReactNode + rightIcon?: React.ReactNode + isDarkMode?: boolean + hasBackground?: boolean + onClick?: () => void +} + +/** + * @param title - 타이틀 + * @param leftIcon - (Optional) 왼쪽 아이콘 + * @param rightIcon - (Optional) 오른쪽 아이콘 + * @param isDarkMode - (Optional) 다크모드 여부 + * @param hasBackground - (Optional) 배경색 여부 + * @param onClick - (Optional) 클릭 이벤트 + */ + +const PageHeader = ({ leftIcon, rightIcon, title, isDarkMode, hasBackground }: PageHeaderProps) => { + return ( + + {leftIcon} + + {title} + + {rightIcon} + + ) +} + +export default PageHeader From 78c0f0d68a73f57f88c30a2b698eb5c6f48c95dd Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Wed, 1 Nov 2023 17:34:53 +0900 Subject: [PATCH 26/33] =?UTF-8?q?[Style]=20chatting=20bubble=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20=EB=B0=8F=20?= =?UTF-8?q?=EC=86=8C=EC=BC=93=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC?= =?UTF-8?q?=EB=A6=AC=20=EC=84=A4=EC=B9=98=20(#65)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style : chatting bubble 컴포넌트 publishing * chore : 소켓 라이브러리 stomp 설치 및 세팅 --- package-lock.json | 93 ++++++++++++++++--- package.json | 7 +- setupProxy.ts | 10 ++ src/components/common/ChattingText/index.tsx | 42 +++++++++ .../common/chattingBubble/index.tsx | 83 +++++++++++++++++ tsconfig.json | 1 + vite.config.ts | 9 ++ 7 files changed, 232 insertions(+), 13 deletions(-) create mode 100644 setupProxy.ts create mode 100644 src/components/common/ChattingText/index.tsx create mode 100644 src/components/common/chattingBubble/index.tsx diff --git a/package-lock.json b/package-lock.json index 60875e21..5a0cde2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,14 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@stomp/stompjs": "^7.0.0", "@tanstack/react-query": "^4.36.1", + "@types/stompjs": "^2.3.7", "axios": "^1.5.1", "d3": "^7.8.5", "emotion-reset": "^3.0.1", "framer-motion": "^10.16.4", + "http-proxy-middleware": "^2.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.47.0", @@ -1692,6 +1695,11 @@ "integrity": "sha512-2yn4qTkXZTByQffL3ymS6viYuyZk3YnJT49bopGBlm9Thtyfa7iuFUV6tt+09YIRO1sjmSWILf4dPj6+Dr5YVA==", "dev": true }, + "node_modules/@stomp/stompjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-7.0.0.tgz", + "integrity": "sha512-fGdq4wPDnSV/KyOsjq4P+zLc8MFWC3lMmP5FBgLWKPJTYcuCbAIrnRGjB7q2jHZdYCOD5vxLuFoKIYLy5/u8Pw==" + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -2483,6 +2491,14 @@ "integrity": "sha512-uK2z1ZHJyC0nQRbuovXFt4mzXDwf27vQeUWNhfKGwRcWW429GOhP8HxUHlM6TLH4bzmlv/HlEjpvJh3JfmGsAA==", "dev": true }, + "node_modules/@types/http-proxy": { + "version": "1.17.13", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.13.tgz", + "integrity": "sha512-GkhdWcMNiR5QSQRYnJ+/oXzu0+7JJEPC8vkWXK351BkhjraZF+1W13CUYARUvX9+NqIU2n6YHA4iwywsc/M6Sw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/js-levenshtein": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.2.tgz", @@ -2506,7 +2522,6 @@ "version": "20.8.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz", "integrity": "sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==", - "dev": true, "dependencies": { "undici-types": "~5.25.1" } @@ -2560,6 +2575,14 @@ "integrity": "sha512-NwCYScf83RIwCyi5/9cXocrJB//xrqMh5PMw3mYTSFGaI3DuVjBLfO/PCk7QVAC3Da8b9NjxNmTO9Aj9T3rl/Q==", "dev": true }, + "node_modules/@types/stompjs": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@types/stompjs/-/stompjs-2.3.7.tgz", + "integrity": "sha512-E9Gw+P2ifAIdo6DFXVvR8sDqmCadVVJc5JyEh3hAGIBz24dMidxfrrMahfQT+zDbDYnvi4rDpvrpSI+3UkiSPw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz", @@ -3355,7 +3378,6 @@ "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" }, @@ -5214,7 +5236,6 @@ "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" }, @@ -5702,6 +5723,47 @@ "react-is": "^16.7.0" } }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", @@ -6110,7 +6172,6 @@ "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" } @@ -6158,7 +6219,6 @@ "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" }, @@ -6224,7 +6284,6 @@ "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" } @@ -6253,6 +6312,17 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -6793,7 +6863,6 @@ "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" @@ -7435,7 +7504,6 @@ "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" }, @@ -7749,6 +7817,11 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -8575,7 +8648,6 @@ "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" }, @@ -8780,8 +8852,7 @@ "node_modules/undici-types": { "version": "5.25.3", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", - "dev": true + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" }, "node_modules/untildify": { "version": "4.0.0", diff --git a/package.json b/package.json index 5c48947f..2b425564 100644 --- a/package.json +++ b/package.json @@ -13,20 +13,23 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@stomp/stompjs": "^7.0.0", "@tanstack/react-query": "^4.36.1", + "@types/stompjs": "^2.3.7", "axios": "^1.5.1", "d3": "^7.8.5", "emotion-reset": "^3.0.1", "framer-motion": "^10.16.4", + "http-proxy-middleware": "^2.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.47.0", "react-icons": "^4.11.0", "react-router-dom": "^6.16.0", + "react-spinners": "^0.13.8", "react-toastify": "^9.1.3", "zustand": "^4.4.4", - "zustand-persist": "^0.4.0", - "react-spinners": "^0.13.8" + "zustand-persist": "^0.4.0" }, "devDependencies": { "@rushstack/eslint-config": "^3.4.1", diff --git a/setupProxy.ts b/setupProxy.ts new file mode 100644 index 00000000..b7c3139b --- /dev/null +++ b/setupProxy.ts @@ -0,0 +1,10 @@ +import { createProxyMiddleware } from 'http-proxy-middleware' + +module.exports = (app) => { + app.use( + createProxyMiddleware('/api/chat-stomp', { + target: process.env.VITE_BASE_URL, + ws: true, + }), + ) +} diff --git a/src/components/common/ChattingText/index.tsx b/src/components/common/ChattingText/index.tsx new file mode 100644 index 00000000..24032bd9 --- /dev/null +++ b/src/components/common/ChattingText/index.tsx @@ -0,0 +1,42 @@ +import styled from '@emotion/styled' +import { type HTMLAttributes, type ReactNode } from 'react' + +import { type TextType, theme } from '@/styles/theme' +/** + * @param as Text 컴포넌트의 종류 : 'span' | 'p' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'div'; + * @param typo Typo theme 선택 + * @param color Palette theme 선택 + */ + +export interface TextProps extends HTMLAttributes { + as?: 'span' | 'p' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'div' + typo: TextType['typo'] + color?: TextType['color'] + children: ReactNode +} + +export type TextPropsKey = 'typo' | 'color' + +export const ChattingText = ({ + typo = 'Body_16', + as = 'h1', + color, + children, + ...props +}: TextProps) => { + return ( + + {children} + + ) +} + +const StyledText = styled.span<{ + typoKey: TextType['typo'] + colorKey?: TextType['color'] +}>` + white-space: pre-wrap; + color: ${({ colorKey }) => { + return colorKey && theme.palette[colorKey] + }}; +` diff --git a/src/components/common/chattingBubble/index.tsx b/src/components/common/chattingBubble/index.tsx new file mode 100644 index 00000000..a5b4fcd2 --- /dev/null +++ b/src/components/common/chattingBubble/index.tsx @@ -0,0 +1,83 @@ +import styled from '@emotion/styled' +import { ComponentProps } from 'react' + +import { ChattingText } from '@/components/common/ChattingText' +import { FlexBox } from '@/components/common/Flexbox' +import { palette } from '@/styles/palette' +import { type KeyOfPalette, type KeyOfTypo } from '@/styles/theme' + +interface ChattingBubbleProps extends ComponentProps<'div'> { + isMyChat?: boolean + message: string + time: string + messageTypo?: KeyOfTypo + messageColor?: KeyOfPalette + timeTypo?: KeyOfTypo + timeColor?: KeyOfPalette + color?: KeyOfPalette +} + +/** + * @param isMychat : 내 채팅인지, 상대방의 채팅인지 여부 / 기본 : false + * @param message : 메시지 내용 + * @param time : 메시지 작성 시간 + * @param messageType : message에 적용할 typo + * @param messageColor: message에 적용시킬 color + * @param timeType : time에 적용할 typo + * @param timeColor : time에 적용할 color + */ + +const ChattingBubble = ({ + isMyChat = false, + message, + time, + messageTypo = 'Body_12', + messageColor = 'BLACK', + timeTypo = 'Caption_11', + timeColor = 'GRAY500', + ...props +}: ChattingBubbleProps) => { + return ( + + + + {message} + + + + {time} + + + ) +} + +const BubbleContainer = styled(FlexBox)<{ isMyChat: boolean }>` + justify-content: ${(props) => props.isMyChat && 'flex-end'}; +` + +const StyledText = styled.div<{ + isMyChat: boolean +}>` + border-radius: 10px; + background-color: ${palette.WHITE}; + padding: 7px 12px; + word-wrap: break-word; + order: ${(props) => (props.isMyChat ? '2' : '1')}; +` + +const TimeText = styled(ChattingText)` + order: 1; + line-height: 150%; +` + +const MessageText = styled(ChattingText)` + line-height: 150%; +` + +export default ChattingBubble diff --git a/tsconfig.json b/tsconfig.json index 027d4a68..0f1ca04b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ // "typeRoots":["./node_modules/@types","./types"], "module": "ESNext", "skipLibCheck": true, + "types": ["node","vite/client"], /* Bundler mode */ "moduleResolution": "bundler", diff --git a/vite.config.ts b/vite.config.ts index 8b2b1405..649ef791 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,4 +8,13 @@ export default defineConfig({ resolve: { alias: { '@/': `${process.cwd()}/src/` }, }, + server: { + port: 3000, + https: true, + hmr: { + host: process.env.VITE_BASE_URL, + port: 3001, + protocol: 'wss', + }, + }, }) From de5f268c4f5e50a22a181d7e2b1219daed07261b Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Thu, 2 Nov 2023 02:11:19 +0900 Subject: [PATCH 27/33] =?UTF-8?q?style:=20BusinessCardContainer=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20(?= =?UTF-8?q?=EC=9E=ACPR)=20(#62)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: businesscardcontainer * style: Text 컴포넌트 textColor 속성 추가 --- .../common/BusinessCardContainer/index.tsx | 112 ++++++++++++++++++ src/components/common/Text/index.tsx | 4 +- 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/components/common/BusinessCardContainer/index.tsx diff --git a/src/components/common/BusinessCardContainer/index.tsx b/src/components/common/BusinessCardContainer/index.tsx new file mode 100644 index 00000000..8708f84c --- /dev/null +++ b/src/components/common/BusinessCardContainer/index.tsx @@ -0,0 +1,112 @@ +import styled from '@emotion/styled' +import { useState } from 'react' +import { TiDelete } from 'react-icons/ti' + +import camera from '@/assets/images/camera.svg' +import { palette } from '@/styles/palette' + +import Spacing from '../Spacing' +import { Text } from '../Text' + +type BusinessCardContainerProps = { + isDarkMode: boolean +} + +const BusinessCardContainer = ({ isDarkMode }: BusinessCardContainerProps) => { + const [uploadedImage, setUploadedImage] = useState(null) + + const handleImageUpload = (event: React.ChangeEvent) => { + if (!uploadedImage) { + const file = event.target.files?.[0] + const reader = new FileReader() + + reader.onloadend = () => { + setUploadedImage(reader.result as string) + } + + if (file) { + reader.readAsDataURL(file) + } + } + } + + const handleRemoveImage = () => { + setUploadedImage(null) + } + + return ( + + + {'명함사진 업로드'} + + + + {uploadedImage ? ( + <> + + + + ) : ( + + )} + + + ) +} + +const Wrapper = styled.div` + position: relative; + width: 300px; +` + +const Placeholder = styled.div` + width: 88px; + height: 88px; + background-color: ${palette.WHITE}; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +` + +const ImageContainer = styled.div` + width: 88px; + position: relative; +` + +const StyledImage = styled.img` + width: 88px; + height: 88px; + object-fit: cover; + position: relative; + border-radius: 10px; +` + +const CameraIcon = styled.img` + width: 38px; + height: 38px; +` + +export default BusinessCardContainer diff --git a/src/components/common/Text/index.tsx b/src/components/common/Text/index.tsx index 42dacb80..10aa093c 100644 --- a/src/components/common/Text/index.tsx +++ b/src/components/common/Text/index.tsx @@ -15,11 +15,13 @@ export const Text = styled.div<{ font: KeyOfTypo fontWeight: number letterSpacing: number + textColor?: string }>` - ${({ font, fontWeight, letterSpacing }) => { + ${({ font, fontWeight, letterSpacing, textColor }) => { const fontFunc = typo[font] return css` ${fontFunc(fontWeight, letterSpacing)} + color: ${textColor}; ` }} ` From fb88c7d76e063fddd36aba34128d7fa43bb66346 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:24:16 +0900 Subject: [PATCH 28/33] =?UTF-8?q?test:=20API=20Test=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20(#76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: businesscardcontainer * style: Text 컴포넌트 textColor 속성 추가 * style: alerttext, countnumber 수정 * style: selectorbutton 수정 * style: customselectorbutton * refactor: 필수 prop 설정 * style: selectorbuttoncontainer * refactor: 함수컴포넌트 구조 변경 * refactor: react 삭제 * test: APITest 코드 --- src/apis/test.ts | 13 ++ src/components/common/AlertText/index.tsx | 13 +- src/components/common/CountNumber/index.tsx | 26 +++- .../common/CustomSelectorButton/index.tsx | 121 ++++++++++++++++++ .../common/SelectorButton/index.tsx | 17 ++- .../common/SelectorButtonContainer/index.tsx | 114 +++++++++++++++++ src/pages/profile/ProfileEdit.tsx | 21 ++- 7 files changed, 312 insertions(+), 13 deletions(-) create mode 100644 src/apis/test.ts create mode 100644 src/components/common/CustomSelectorButton/index.tsx create mode 100644 src/components/common/SelectorButtonContainer/index.tsx diff --git a/src/apis/test.ts b/src/apis/test.ts new file mode 100644 index 00000000..f733ed88 --- /dev/null +++ b/src/apis/test.ts @@ -0,0 +1,13 @@ +import axios from 'axios' + +export const testWithBtn = async (nickname: string) => { + return axios + .create({ + baseURL: import.meta.env.VITE_TEST_URL, + }) + .get('/api/v1/users/duplicate', { + params: { + nickname: nickname, + }, + }) +} diff --git a/src/components/common/AlertText/index.tsx b/src/components/common/AlertText/index.tsx index f3d3003b..6e3e4741 100644 --- a/src/components/common/AlertText/index.tsx +++ b/src/components/common/AlertText/index.tsx @@ -4,12 +4,19 @@ type AlertTextProps = { fontSize: string fontColor: string children?: React.ReactNode + padding?: string + textAlign?: string } -const AlertText = ({ fontSize, fontColor, children }: AlertTextProps) => { +const AlertText = ({ padding, textAlign, fontSize, fontColor, children }: AlertTextProps) => { return ( <> - + {children} @@ -19,6 +26,8 @@ const AlertText = ({ fontSize, fontColor, children }: AlertTextProps) => { const StyleAlertText = styled.div` font-size: ${(props) => props.fontSize}; color: ${(props) => props.fontColor}; + padding: ${(props) => props.padding}; + text-align: ${(props) => props.textAlign}; ` export default AlertText diff --git a/src/components/common/CountNumber/index.tsx b/src/components/common/CountNumber/index.tsx index 018aaf35..fd1dfa1a 100644 --- a/src/components/common/CountNumber/index.tsx +++ b/src/components/common/CountNumber/index.tsx @@ -4,26 +4,38 @@ type CountNumberProps = { currentLength: number maxLength: number color: string - right: number + right?: number + top?: number } type StyleCountNumberProps = { color: string - right: number + right?: number + top?: number } -const CountNumber = ({ right, currentLength, maxLength, color }: CountNumberProps): JSX.Element => { +const CountNumber = ({ + top, + right, + currentLength, + maxLength, + color, +}: CountNumberProps): JSX.Element => { return ( - {`${currentLength}/${maxLength}`} +
+ {`${currentLength}/${maxLength}`} +
) } const StyleCountNumber = styled.span` position: relative; right: ${(props) => props.right}px; + top: ${(props) => props.top}px; bottom: 3px; font-size: 12px; color: ${(props) => props.color}; diff --git a/src/components/common/CustomSelectorButton/index.tsx b/src/components/common/CustomSelectorButton/index.tsx new file mode 100644 index 00000000..2c11bcff --- /dev/null +++ b/src/components/common/CustomSelectorButton/index.tsx @@ -0,0 +1,121 @@ +import styled from '@emotion/styled' +import { useEffect, useState } from 'react' +import { TiDelete } from 'react-icons/ti' + +import { palette } from '@/styles/palette' + +type CustomSelectorButtonProps = { + isDarkMode: boolean + buttonName: string + onClick: (selected: boolean) => void + onRemove: () => void + isButtonselected: boolean + maxLengthReached: boolean +} + +const CustomSelectorButton = ({ + isDarkMode, + buttonName, + onClick, + onRemove, + isButtonselected: propIsButtonSelected, + maxLengthReached, +}: CustomSelectorButtonProps) => { + const defaultSettings = isDarkMode + ? { + selectedButtonColor: palette.SECONDARY, + defaultButtonColor: palette.WHITE, + textColor: palette.SECONDARY, + } + : { + selectedButtonColor: palette.BLUE, + defaultButtonColor: palette.TERTIARY, + textColor: palette.WHITE, + } + + const [isButtonSelected, setIsButtonSelected] = useState(propIsButtonSelected) + + useEffect(() => { + setIsButtonSelected(propIsButtonSelected) + }, [propIsButtonSelected]) + + const handleButtonClick = () => { + if (maxLengthReached && !isButtonSelected) { + onClick && onClick(true) + return + } + setIsButtonSelected((prevState) => !prevState) + if (onClick) onClick(!isButtonSelected) + } + + return ( + + + {buttonName} + + + + + + ) +} + +const StyledButtonWrapper = styled.div` + display: flex; + align-items: center; +` + +const RemoveButton = styled.button` + margin-left: 8px; + background-color: transparent; + border: none; + cursor: pointer; + &:focus { + outline: none; + } +` + +const StyledButton = styled.button<{ + backgroundColor: string + textColor: string +}>` + margin: 8px; + height: 36px; + padding: 10px 15px 10px 15px; + font-size: 12px; + cursor: pointer; + border: none; + border-radius: 10px; + background-color: ${(props) => props.backgroundColor}; + transition: background-color 0.3s; + &:hover { + opacity: 0.9; + } + &:focus { + outline: none; + } + color: ${(props) => props.textColor}; + display: inline-block; + vertical-align: middle; + line-height: 1; +` + +export default CustomSelectorButton diff --git a/src/components/common/SelectorButton/index.tsx b/src/components/common/SelectorButton/index.tsx index 3cbcbbed..a28fac92 100644 --- a/src/components/common/SelectorButton/index.tsx +++ b/src/components/common/SelectorButton/index.tsx @@ -8,13 +8,15 @@ type SelectorButtonProps = { buttonName: string onClick?: (selected: boolean) => void isButtonselected?: boolean + maxLengthReached: boolean } const SelectorButton = ({ isDarkMode, buttonName, onClick, - isButtonselected = false, + isButtonselected: propIsButtonSelected = false, + maxLengthReached = false, }: SelectorButtonProps) => { const defaultSettings = isDarkMode ? { @@ -27,7 +29,7 @@ const SelectorButton = ({ defaultButtonColor: palette.TERTIARY, textColor: palette.WHITE, } - + const [isButtonselected, setIsButtonselected] = useState(propIsButtonSelected) const initialBackgroundColor = isButtonselected ? defaultSettings.selectedButtonColor : defaultSettings.defaultButtonColor @@ -36,6 +38,12 @@ const SelectorButton = ({ const handleButtonClick = () => { const isSelected = backgroundColor !== defaultSettings.selectedButtonColor + + if (maxLengthReached && !isButtonselected) { + onClick && onClick(true) + return + } + setIsButtonselected(!isButtonselected) setBackgroundColor( isSelected ? defaultSettings.selectedButtonColor : defaultSettings.defaultButtonColor, ) @@ -56,7 +64,10 @@ const SelectorButton = ({ ) } -const StyledButton = styled.button<{ backgroundColor: string; textColor: string }>` +const StyledButton = styled.button<{ + backgroundColor: string + textColor: string +}>` margin: 8px; height: 36px; padding: 10px 15px 10px 15px; diff --git a/src/components/common/SelectorButtonContainer/index.tsx b/src/components/common/SelectorButtonContainer/index.tsx new file mode 100644 index 00000000..26d7c2b5 --- /dev/null +++ b/src/components/common/SelectorButtonContainer/index.tsx @@ -0,0 +1,114 @@ +import styled from '@emotion/styled' +import { useState } from 'react' + +import { palette } from '@/styles/palette' + +import AlertText from '../AlertText' +import CountNumber from '../CountNumber' +import CustomSelectorButton from '../CustomSelectorButton' +import SelectorButton from '../SelectorButton' + +type SelectorButtonContainerProps = { + isDarkMode: boolean + buttonNames: string[] + maxLength: number +} + +const SelectorButtonContainer = ({ + isDarkMode, + buttonNames, + maxLength, +}: SelectorButtonContainerProps) => { + const [selectedCount, setSelectedCount] = useState(0) + const [showAlert, setShowAlert] = useState(false) + const [customButtons, setCustomButtons] = useState([]) + + const handleButtonSelection = (isSelected: boolean) => { + if (isSelected) { + if (selectedCount + 1 > maxLength) { + setShowAlert(true) + return + } else { + setSelectedCount(selectedCount + 1) + } + } else { + setSelectedCount(Math.max(selectedCount - 1, 0)) + } + + setShowAlert(false) + } + const handleCustomButtonClick = (isSelected: boolean) => { + handleButtonSelection(isSelected) + } + + const handleCustomButtonRemove = (buttonName: string) => { + const updatedCustomButtons = customButtons.filter((name) => name !== buttonName) + setCustomButtons(updatedCustomButtons) + setSelectedCount(Math.max(selectedCount - 1, 0)) + } + + return ( + + + {buttonNames.map((name, index) => ( + = maxLength} + isButtonselected={false} + /> + ))} + {customButtons.map((name, index) => ( + handleCustomButtonClick(isSelected)} + onRemove={() => handleCustomButtonRemove(name)} + maxLengthReached={selectedCount >= maxLength} + isButtonselected={false} + /> + ))} + {showAlert && ( + + {`최대 ${maxLength}개까지만 설정할 수 있습니다!`} + + )} + + + + ) +} + +const OuterWrapper = styled.div` + position: relative; +` + +const Container = styled.div<{ isDarkMode: boolean }>` + width: 348px; + min-height: 235px; + max-height: 400px; + overflow-y: auto; + padding: 10px; + border: 1px solid gray; + position: relative; + background-color: ${(props) => (props.isDarkMode ? palette.GRAY600 : palette.WHITE)}; + border-radius: 10px; + border-color: ${(props) => (props.isDarkMode ? 'none' : palette.GRAY200)}; + border-width: ${(props) => (props.isDarkMode ? 'none' : '1px')}; +` + +export default SelectorButtonContainer diff --git a/src/pages/profile/ProfileEdit.tsx b/src/pages/profile/ProfileEdit.tsx index 9b847cca..60da403f 100644 --- a/src/pages/profile/ProfileEdit.tsx +++ b/src/pages/profile/ProfileEdit.tsx @@ -1,5 +1,24 @@ +import { testWithBtn } from '@/apis/test' +import NormalButton from '@/components/common/Buttons/NormalButton' + const ProfileEdit = () => { - return
{'ProfileEdit'}
+ const testClick = async () => { + try { + const response = await testWithBtn('길동이') + console.log(response.data) + // 로그인 성공 시 필요한 로직을 추가합니다. + } catch (error) { + console.error('카카오 로그인에 실패했습니다.', error) + } + } + return ( +
+ {'ProfileEdit'} + + {'닉네임 중복 테스트'} + +
+ ) } export default ProfileEdit From 7d6c994fa8e72cafb1bb035b7a6ffe01e4a0e438 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:50:59 +0900 Subject: [PATCH 29/33] test: retest (#78) --- src/apis/test.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/apis/test.ts b/src/apis/test.ts index f733ed88..f16d0110 100644 --- a/src/apis/test.ts +++ b/src/apis/test.ts @@ -1,13 +1,9 @@ import axios from 'axios' export const testWithBtn = async (nickname: string) => { - return axios - .create({ - baseURL: import.meta.env.VITE_TEST_URL, - }) - .get('/api/v1/users/duplicate', { - params: { - nickname: nickname, - }, - }) + return axios.get('http://13.125.194.230/api/v1/users/duplicate', { + params: { + nickname: nickname, + }, + }) } From 03a429c284ed45fff4390944ef70c88ed9527007 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Thu, 2 Nov 2023 17:16:36 +0900 Subject: [PATCH 30/33] =?UTF-8?q?fix:=20vite=20=EC=84=A4=EC=A0=95=20(cors)?= =?UTF-8?q?=20(#80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: retest * fix: vite 설정 (cors) --- vite.config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vite.config.ts b/vite.config.ts index 649ef791..dddba90b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,6 +9,12 @@ export default defineConfig({ alias: { '@/': `${process.cwd()}/src/` }, }, server: { + proxy: { + '/api': { + target: 'http://13.125.194.230', + changeOrigin: true, + }, + }, port: 3000, https: true, hmr: { From 543ddb65695bffd353d5c4d7d9d7621854439008 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Thu, 2 Nov 2023 17:37:23 +0900 Subject: [PATCH 31/33] =?UTF-8?q?fix:=20cors=20=EC=9E=AC=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20(#82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: retest * fix: vite 설정 (cors) * fix: cors2 --- src/apis/test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/apis/test.ts b/src/apis/test.ts index f16d0110..95c713f5 100644 --- a/src/apis/test.ts +++ b/src/apis/test.ts @@ -2,6 +2,9 @@ import axios from 'axios' export const testWithBtn = async (nickname: string) => { return axios.get('http://13.125.194.230/api/v1/users/duplicate', { + headers: { + 'Referrer-Policy': 'no-referrer', // 또는 필요에 따라 다른 값 + }, params: { nickname: nickname, }, From 8b394e01cf771cdbc8acf5627774e11a4947be8a Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Thu, 2 Nov 2023 18:02:15 +0900 Subject: [PATCH 32/33] =?UTF-8?q?fix:=20vite=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- src/apis/test.ts | 4 ++-- vite.config.ts | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/apis/test.ts b/src/apis/test.ts index 95c713f5..61eef3ba 100644 --- a/src/apis/test.ts +++ b/src/apis/test.ts @@ -1,12 +1,12 @@ import axios from 'axios' export const testWithBtn = async (nickname: string) => { - return axios.get('http://13.125.194.230/api/v1/users/duplicate', { + return axios.get(`http://13.125.194.230/api/v1/users/duplicate`, { headers: { 'Referrer-Policy': 'no-referrer', // 또는 필요에 따라 다른 값 }, params: { - nickname: nickname, + nickname, }, }) } diff --git a/vite.config.ts b/vite.config.ts index dddba90b..33a6dcae 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,12 +15,12 @@ export default defineConfig({ changeOrigin: true, }, }, - port: 3000, - https: true, - hmr: { - host: process.env.VITE_BASE_URL, - port: 3001, - protocol: 'wss', - }, + // port: 3000, + // https: true, + // hmr: { + // host: process.env.VITE_BASE_URL, + // port: 3001, + // protocol: 'wss', + // }, }, }) From 3bb369487a6c1b0dbec4c49c1d851afddfef908e Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Thu, 2 Nov 2023 18:20:09 +0900 Subject: [PATCH 33/33] =?UTF-8?q?fix:=20axios=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#86)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- src/apis/test.ts | 9 +-------- src/pages/profile/ProfileDefault.tsx | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/apis/test.ts b/src/apis/test.ts index 61eef3ba..fdfdae2f 100644 --- a/src/apis/test.ts +++ b/src/apis/test.ts @@ -1,12 +1,5 @@ import axios from 'axios' export const testWithBtn = async (nickname: string) => { - return axios.get(`http://13.125.194.230/api/v1/users/duplicate`, { - headers: { - 'Referrer-Policy': 'no-referrer', // 또는 필요에 따라 다른 값 - }, - params: { - nickname, - }, - }) + return axios.get(`http://13.125.194.230/api/v1/users/duplicate?nickname=${nickname}`) } diff --git a/src/pages/profile/ProfileDefault.tsx b/src/pages/profile/ProfileDefault.tsx index b3ec47ba..df7e8164 100644 --- a/src/pages/profile/ProfileDefault.tsx +++ b/src/pages/profile/ProfileDefault.tsx @@ -1,5 +1,19 @@ +import { useNavigate } from 'react-router-dom' + const ProfileDefault = () => { - return
{'ProfileDefault'}
+ const navigate = useNavigate() + + return ( +
+ +
+ ) } export default ProfileDefault