From 63e477f255c463106e43b3a798f2d5d813b44ac6 Mon Sep 17 00:00:00 2001 From: judahhh Date: Tue, 24 Oct 2023 00:34:22 +0900 Subject: [PATCH 001/180] =?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 002/180] =?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 003/180] =?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 004/180] =?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 005/180] =?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 006/180] =?UTF-8?q?[Style]=20=EB=AA=A8=EB=B0=94=EC=9D=BC?= =?UTF-8?q?=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?(#19)?= 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 007/180] =?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 008/180] =?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 009/180] =?UTF-8?q?feature:=20Button=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?(#20)?= 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 010/180] =?UTF-8?q?style=20:=20flexbox=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=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 011/180] =?UTF-8?q?style=20:=20spacing=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=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 012/180] [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 013/180] =?UTF-8?q?style:=20BottomSheet=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=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 014/180] =?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 015/180] =?UTF-8?q?style:=20CountNumber=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91=20(#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 016/180] =?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 017/180] =?UTF-8?q?style:=20InputTimer=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91=20(#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 018/180] =?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 019/180] =?UTF-8?q?style:=20WhiteSelectorButton,=20DarkSel?= =?UTF-8?q?ectorButton=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 020/180] =?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 021/180] =?UTF-8?q?style:=20=08useToast=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=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 022/180] =?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 023/180] =?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 024/180] =?UTF-8?q?style:=20Appbar,=20Card,=20GradationBac?= =?UTF-8?q?kground,=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 025/180] =?UTF-8?q?style:=20PageHeader=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=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 026/180] =?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 027/180] =?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 028/180] =?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 029/180] 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 030/180] =?UTF-8?q?fix:=20vite=20=EC=84=A4=EC=A0=95=20(cor?= =?UTF-8?q?s)=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 031/180] =?UTF-8?q?fix:=20cors=20=EC=9E=AC=EC=84=A4?= =?UTF-8?q?=EC=A0=95=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 032/180] =?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 033/180] =?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 From f5db34ea87fa651af3899577c5c1209f9a7403b0 Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Sat, 4 Nov 2023 17:05:10 +0900 Subject: [PATCH 034/180] =?UTF-8?q?fix:=20REST=20API=20=EC=A3=BC=EC=86=8C?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#92)?= 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apis/test.ts b/src/apis/test.ts index fdfdae2f..5510969c 100644 --- a/src/apis/test.ts +++ b/src/apis/test.ts @@ -1,5 +1,5 @@ import axios from 'axios' export const testWithBtn = async (nickname: string) => { - return axios.get(`http://13.125.194.230/api/v1/users/duplicate?nickname=${nickname}`) + return axios.get(`https://api.coffee-meet.site/api/v1/users/duplicate?nickname=${nickname}`) } From eddaa7daea3cd74f04245ac4f50e82edb788c222 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:23:25 +0900 Subject: [PATCH 035/180] =?UTF-8?q?refactor:=20AlertText,=20CountNumber=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=88=98=EC=A0=95=20(#68)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: businesscardcontainer * style: Text 컴포넌트 textColor 속성 추가 * style: alerttext, countnumber 수정 * refactor: pr 수정사항 반영 * refactor: styled emotion 코드 통일성 적용 --- .../common/BusinessCardContainer/index.tsx | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/components/common/BusinessCardContainer/index.tsx b/src/components/common/BusinessCardContainer/index.tsx index 8708f84c..ee5cd766 100644 --- a/src/components/common/BusinessCardContainer/index.tsx +++ b/src/components/common/BusinessCardContainer/index.tsx @@ -17,15 +17,15 @@ const BusinessCardContainer = ({ isDarkMode }: BusinessCardContainerProps) => { const handleImageUpload = (event: React.ChangeEvent) => { if (!uploadedImage) { - const file = event.target.files?.[0] - const reader = new FileReader() + const uploadedFile = event.target.files?.[0] + const uploadedFileReader = new FileReader() - reader.onloadend = () => { - setUploadedImage(reader.result as string) + uploadedFileReader.onloadend = () => { + setUploadedImage(uploadedFileReader.result as string) } - if (file) { - reader.readAsDataURL(file) + if (uploadedFile) { + uploadedFileReader.readAsDataURL(uploadedFile) } } } @@ -35,7 +35,7 @@ const BusinessCardContainer = ({ isDarkMode }: BusinessCardContainerProps) => { } return ( - + { /> ) : ( - + + )} - + ) } -const Wrapper = styled.div` +const BusinessCardContainerWrapper = styled.div` position: relative; width: 300px; ` -const Placeholder = styled.div` +const CameraIconWrapper = styled.div` width: 88px; height: 88px; background-color: ${palette.WHITE}; @@ -108,5 +108,11 @@ const CameraIcon = styled.img` width: 38px; height: 38px; ` +const UploadLabel = styled.label` + cursor: pointer; +` +const HiddenInput = styled.input` + display: none; +` export default BusinessCardContainer From da20cce42d84ba1b7ff367d3a7730074d0e5de97 Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:10:10 +0900 Subject: [PATCH 036/180] =?UTF-8?q?feature:=20HomePage=20=EB=8B=A4?= =?UTF-8?q?=ED=81=AC=EB=AA=A8=EB=93=9C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20+=20Card=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20+=20timerWorker=20=EC=B6=94=EA=B0=80=20(#9?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: DARK_GRADIENT 색 추가 * feature: 다크모드/화이트모드 설정을 위한 ThemeStore 추가 * feature: ParticularTopicButton에 이동하는 props 추가 * feature: AppHeader 컴포넌트 다크모드 설정 기능 추가 * feature: PageContainer 컴포넌트 다크모드 props 추가 * refactor: Card 컴포넌트 코드 분리 * refactor: Tip 컴포넌트 주석 제거 + import문 수정 * feature: timerWorker 추가 + TimerStore 추가 - 백그라운드에서 동작하는 웹 워커로 타이머 동작 - 새로고침을 해도 계속 동작하지만 어플리케이션을 종료하면 동작 종료 - 현재 동기적으로 코드가 작동하고 있어서, indexedDB를 사용해서 비동기 동작을 지원하도록 수정할 계획 * feature: 다크모드 추가 - GradationBackground, NavigationBar, Home 페이지 * refactor: 사용하지 않는 코드 제거 * refactor: Card 컴포넌트 수정 -undefined값을 가지는 time props 수정 - navigationType 에러 수정 * refactor: AppHeader 컴포넌트 수정 - cursor: pointer 스타일 추가 * refactor: Styled prefix 사용하도록 PR 관련 코드 수정 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- public/timerWorker.js | 28 +++ src/components/common/AppHeader/index.tsx | 12 +- .../Buttons/IconButton/InterestButton.tsx | 10 +- .../common/Buttons/IconButton/KakaoButton.tsx | 6 +- .../common/Buttons/IconButton/NaverButton.tsx | 6 +- .../IconButton/ParticularTopicButton.tsx | 21 +- .../IconButton/RandomMatchingButton.tsx | 14 +- .../IconButton/RandomMatchingJoinButton.tsx | 10 +- .../common/Buttons/IconButton/index.tsx | 4 +- .../common/GradationBackground/index.tsx | 20 +- src/components/common/NavigationBar/index.tsx | 69 +++--- src/components/common/PageContainer/index.tsx | 14 +- src/components/home/Card.tsx | 196 ++++-------------- src/components/home/CardBottom.tsx | 54 +++++ src/components/home/CardHeader.tsx | 60 ++++++ src/components/home/CardMiddle.tsx | 81 ++++++++ src/components/home/Tip.tsx | 12 +- src/pages/home/Home.tsx | 40 ++-- src/store/ThemeStore.tsx | 23 ++ src/store/TimerStore.tsx | 52 +++++ src/styles/palette.ts | 1 + 21 files changed, 489 insertions(+), 244 deletions(-) create mode 100644 public/timerWorker.js create mode 100644 src/components/home/CardBottom.tsx create mode 100644 src/components/home/CardHeader.tsx create mode 100644 src/components/home/CardMiddle.tsx create mode 100644 src/store/ThemeStore.tsx create mode 100644 src/store/TimerStore.tsx diff --git a/public/timerWorker.js b/public/timerWorker.js new file mode 100644 index 00000000..a82f3788 --- /dev/null +++ b/public/timerWorker.js @@ -0,0 +1,28 @@ +const timerState = { + intervalId: null, + startTime: null, +} + +const sendTick = () => { + const elapsedTime = Date.now() - timerState.startTime + postMessage({ type: 'TICK', time: elapsedTime }) +} + +onmessage = (event) => { + const { type, startTime: receivedStartTime } = event.data + switch (type) { + case 'START': + if (timerState.intervalId === null) { + timerState.startTime = receivedStartTime + timerState.intervalId = setInterval(sendTick, 1000) + } + break + case 'RESET': + clearInterval(timerState.intervalId) + timerState.intervalId = null + timerState.startTime = null + break + default: + console.error('Unknown message type:', type) + } +} diff --git a/src/components/common/AppHeader/index.tsx b/src/components/common/AppHeader/index.tsx index 56681a61..d69edb38 100644 --- a/src/components/common/AppHeader/index.tsx +++ b/src/components/common/AppHeader/index.tsx @@ -23,15 +23,17 @@ type AppHeaderProps = { nickname: string isDarkMode: boolean height?: string + toggleDarkMode: () => void } /** * @param nickname - 유저 닉네임 * @param isDarkMode - 다크모드 여부 * @param height - 컴포넌트 높이 + * @param toggleDarkMode - 다크모드 토글 함수 */ -const AppHeader = ({ nickname, isDarkMode, height }: AppHeaderProps) => { +const AppHeader = ({ nickname, isDarkMode, height, toggleDarkMode }: AppHeaderProps) => { const navigate = useNavigate() const moveFromAppHeader = (path: string) => { navigate(`/${path}`) @@ -59,14 +61,18 @@ const AppHeader = ({ nickname, isDarkMode, height }: AppHeaderProps) => { size={'20px'} style={{ color: palette.WHITE, + cursor: 'pointer', }} + onClick={toggleDarkMode} /> ) : ( )}
@@ -76,7 +82,7 @@ const AppHeader = ({ nickname, isDarkMode, height }: AppHeaderProps) => { fontWeight={600} letterSpacing={-0.5} style={{ - color: isDarkMode ? palette.WHITE : palette.DARK_WHITE, + color: isDarkMode ? palette.DARK_WHITE : palette.WHITE, marginRight: 5, }} > @@ -87,7 +93,7 @@ const AppHeader = ({ nickname, isDarkMode, height }: AppHeaderProps) => { fontWeight={600} letterSpacing={-0.5} style={{ - color: isDarkMode ? palette.WHITE : palette.DARK_WHITE, + color: isDarkMode ? palette.DARK_WHITE : palette.WHITE, }} > {'님, 안녕하세요! 오늘도 즐거운 커피밋! ☕️'} diff --git a/src/components/common/Buttons/IconButton/InterestButton.tsx b/src/components/common/Buttons/IconButton/InterestButton.tsx index 942e060e..bb4c4a5a 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 { StyleIconButtonWrapper, StyleIconWrapper } from '.' +import { StyledIconButtonWrapper, StyledIconWrapper } from '.' type InterestButtonProps = { nickName: string @@ -17,7 +17,7 @@ const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps const setButtonType = isDarkMode ? 'interest-dark' : 'interest' return ( - - - + {`${nickName}의 관심사`} @@ -68,7 +68,7 @@ const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps ))} - + ) } diff --git a/src/components/common/Buttons/IconButton/KakaoButton.tsx b/src/components/common/Buttons/IconButton/KakaoButton.tsx index 37b7776c..3501a646 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 { StyleIconWrapper } from '.' +import { StyledIconWrapper } from '.' export const ButtonWrapper = styled.button<{ buttonTheme: 'kakao' | 'naver' @@ -21,13 +21,13 @@ export const ButtonWrapper = styled.button<{ const KakaoButton = () => ( - - + { return ( - - + void } /** * @param isDarkMode - 다크모드 여부 */ -const ParticularTopicButton = ({ isDarkMode }: ParticularTopicButtonProps) => { +const ParticularTopicButton = ({ + isDarkMode, + moveToParticularTopic, +}: 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 ( - - { height: 20, }} /> - + { {'네트워크를 넓혀보세요!'} - { height: 30, }} /> - - + + ) } diff --git a/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx b/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx index 43f61c3d..4979c955 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 { StyleIconButtonWrapper, StyleIconWrapper } from '.' +import { StyledIconButtonWrapper, StyledIconWrapper } from '.' type RandomMatchingButtonProps = { date: string @@ -24,7 +24,7 @@ const RandomMatchingButton = ({ date, isDarkMode, onClick }: RandomMatchingButto const setButtonType = isDarkMode ? 'random-matching-dark' : 'random-matching' return ( - - - + - - - + + ) } diff --git a/src/components/common/Buttons/IconButton/RandomMatchingJoinButton.tsx b/src/components/common/Buttons/IconButton/RandomMatchingJoinButton.tsx index 12174a8b..e3bbec4c 100644 --- a/src/components/common/Buttons/IconButton/RandomMatchingJoinButton.tsx +++ b/src/components/common/Buttons/IconButton/RandomMatchingJoinButton.tsx @@ -2,7 +2,7 @@ import { BiChevronRight } from 'react-icons/bi' import { Text, TextWrapper } from '@/components/common/Text' -import { StyleIconButtonWrapper, StyleIconWrapper } from '.' +import { StyledIconButtonWrapper, StyledIconWrapper } 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 04c0ba11..1505d460 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 StyleIconButtonWrapper = styled.button<{ +export const StyledIconButtonWrapper = styled.button<{ iconButtonType: IconButtonType }>` ${({ iconButtonType }) => { @@ -35,7 +35,7 @@ export const StyleIconButtonWrapper = styled.button<{ }} ` -export const StyleIconWrapper = styled.div<{ +export const StyledIconWrapper = styled.div<{ borderRadius?: string backgroundColor?: string }>` diff --git a/src/components/common/GradationBackground/index.tsx b/src/components/common/GradationBackground/index.tsx index 8a673377..5b4069ec 100644 --- a/src/components/common/GradationBackground/index.tsx +++ b/src/components/common/GradationBackground/index.tsx @@ -2,20 +2,30 @@ import styled from '@emotion/styled' import { palette } from '@/styles/palette' -const StyledGradationBackground = styled.div` +const StyledGradationBackground = styled.div<{ + isDarkMode: boolean +}>` width: 100%; height: 100vh; - background: linear-gradient(139deg, ${palette.PRIMARY} 23.32%, ${palette.GRADIENT} 61.26%); + background: ${({ isDarkMode }) => + isDarkMode + ? `linear-gradient(157deg, ${palette.BLACK} 16.84%, ${palette.DARK_GRADIENT} 40%)` + : `linear-gradient(139deg, ${palette.PRIMARY} 23.32%, ${palette.GRADIENT} 61.26%)`}; display: flex; flex-direction: column; ` +type GradationBackgroundProps = { + children: React.ReactNode + isDarkMode: boolean +} + /** - * * @param children - 자식 컴포넌트 + * @param isDarkMode - 다크모드 여부 */ -const GradationBackground = ({ children }: { children: React.ReactNode }) => { - return {children} +const GradationBackground = ({ children, isDarkMode }: GradationBackgroundProps) => { + return {children} } export default GradationBackground diff --git a/src/components/common/NavigationBar/index.tsx b/src/components/common/NavigationBar/index.tsx index 22316e93..1d94eac5 100644 --- a/src/components/common/NavigationBar/index.tsx +++ b/src/components/common/NavigationBar/index.tsx @@ -8,58 +8,77 @@ import Avatar from '@/components/common/Avatar' import { FlexBox } from '@/components/common/Flexbox' import { palette } from '@/styles/palette' import { typo } from '@/styles/typo' -const NavigationBar = () => { + +type NavigationBarProps = { + isDarkMode: boolean +} + +const NavigationBar = ({ isDarkMode }: NavigationBarProps) => { const navigate = useNavigate() const moveFromNavigationBar = (path: string) => { navigate(`/${path}`) } return ( - - - moveFromNavigationBar('chat-list')}> + + + moveFromNavigationBar('chat-list')}> - - {'이전대화방'} + + {'이전대화방'} - - moveFromNavigationBar('')}> + + moveFromNavigationBar('')}> - - {'홈'} + + {'홈'} - - moveFromNavigationBar('profile')}> + + moveFromNavigationBar('profile')}> - + - - {'프로필'} + + {'프로필'} - - - + + + ) } -const StyleWrapper = styled(FlexBox)` +const StyledWrapper = styled(FlexBox)` position: sticky; bottom: 0px; ` -const StyleNavigationText = styled.span` - color: ${palette.GRAY600}; +const StyledNavigationText = styled.span<{ + isDarkMode: boolean +}>` + color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.DARK_BLUE)}; font-size: ${typo.Body_10()}; ` -const StyleNavigation = styled(FlexBox)` +const StyledNavigation = styled(FlexBox)<{ + isDarkMode: boolean +}>` width: 100%; height: 71px; - background-color: white; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.GRAY100)}; box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.24), 0px 4px 5px 0px rgba(0, 0, 0, 0.14); ` -const StyleNavigationItem = styled.button` +const StyledNavigationItem = styled.button` cursor: pointer; ` -const StyleProfileImageWrapper = styled.div` +const StyledProfileImageWrapper = styled.div` width: 100%; height: 100%; object-fit: cover; diff --git a/src/components/common/PageContainer/index.tsx b/src/components/common/PageContainer/index.tsx index 9455dc6a..d55a87bc 100644 --- a/src/components/common/PageContainer/index.tsx +++ b/src/components/common/PageContainer/index.tsx @@ -3,11 +3,11 @@ import { ReactNode } from 'react' import { palette } from '@/styles/palette' -const StyledPageContainer = styled.div<{ height: string }>` +const StyledPageContainer = styled.div<{ height: string; isDarkMode: boolean }>` width: 100%; height: ${({ height }) => height}; padding: 5% 5% 5%; - background-color: ${palette.GRAY100}; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_BLUE : palette.GRAY100)}; border-top-right-radius: 20px; border-top-left-radius: 20px; margin-top: auto; @@ -19,15 +19,21 @@ const StyledPageContainer = styled.div<{ height: string }>` type PageContainerProps = { children: ReactNode height?: string + isDarkMode: boolean } /** * * @param children - 자식 컴포넌트 * @param height - 높이 + * @param isDarkMode - 다크모드 여부 */ -const PageContainer = ({ children, height = '77%' }: PageContainerProps) => { - return {children} +const PageContainer = ({ children, height = '77%', isDarkMode }: PageContainerProps) => { + return ( + + {children} + + ) } export default PageContainer diff --git a/src/components/home/Card.tsx b/src/components/home/Card.tsx index af6ddd78..40d3e007 100644 --- a/src/components/home/Card.tsx +++ b/src/components/home/Card.tsx @@ -1,23 +1,22 @@ 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 { useEffect } from 'react' 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 useTimerStore from '@/store/TimerStore' import { palette } from '@/styles/palette' -import AvatarGroup from './AvatarGroup' -import Tip from './Tip' +import CardBottom from './CardBottom' +import CardHeader from './CardHeader' +import CardMiddle from './CardMiddle' -const StyleCard = styled(motion.div)` +const StyledCard = styled(motion.div)<{ + isDarkMode: boolean +}>` width: 100%; height: 348px; border-radius: 20px; - background-color: ${palette.WHITE}; box-shadow: ${palette.SHADOW}; display: flex; flex-direction: column; @@ -25,9 +24,10 @@ const StyleCard = styled(motion.div)` justify-content: center; align-items: center; padding: 19px 7px 15px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; ` -const StyleWatingWrapper = styled(motion.div)` +const StyledWatingWrapper = styled(motion.div)` width: 100%; display: flex; flex-direction: column; @@ -35,99 +35,44 @@ const StyleWatingWrapper = styled(motion.div)` 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 +const watingCounter = { + hidden: { opacity: 0 }, + visible: { opacity: 1, transition: { type: 'spring', damping: 12, duration: 0.5 } }, + exit: { opacity: 0, transition: { duration: 1 } }, +} 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 Card = ({ isDarkMode }: CardProps) => { + const { time, isRunning, startTimer, resetTimer } = useTimerStore() - const handleCancelClick = () => { - setTime(0) - if (timerRef.current) { - timerRef.current.stop() + window.onload = () => { + const navigationType = ( + performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming + ).type + if (navigationType !== 'reload') { + resetTimer() } - 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() - } + if (isRunning) { + startTimer() + } else if (!isRunning) { + resetTimer() } - }, [isMatching]) + }, [isRunning, startTimer, resetTimer]) return ( - - {!isMatching ? ( + + {!isRunning ? ( { exit={'exit'} variants={watingCounter} > - + ) : ( - - - - - {'3'} - - - {'/5'} - - - - + - - - {formatTime(time)} - - - - {'매칭 취소'} - - - - {'매칭 중'}    - - - - - - - - + + + + )} - + ) } diff --git a/src/components/home/CardBottom.tsx b/src/components/home/CardBottom.tsx new file mode 100644 index 00000000..c3943de5 --- /dev/null +++ b/src/components/home/CardBottom.tsx @@ -0,0 +1,54 @@ +import styled from '@emotion/styled' + +import { palette } from '@/styles/palette' + +import { Text } from '../common/Text' + +const StyledBottomHeader = styled.div<{ + isDarkMode: boolean +}>` + display: flex; + flex-direction: column; + height: 63.525px; + padding: 11px 18px 0px; + border-top: 1px solid ${({ isDarkMode }) => (isDarkMode ? palette.GRAY500 : palette.GRAY100)}; +` + +type CardBottomProps = { + isDarkMode: boolean +} + +const CardBottom = ({ isDarkMode }: CardBottomProps) => { + return ( + + + {'💡 Tip!'} + + + {'커피밋 채팅방은 3일이 지나면 사라집니다!'} + + + ) +} + +export default CardBottom diff --git a/src/components/home/CardHeader.tsx b/src/components/home/CardHeader.tsx new file mode 100644 index 00000000..16904ac0 --- /dev/null +++ b/src/components/home/CardHeader.tsx @@ -0,0 +1,60 @@ +import styled from '@emotion/styled' + +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +import AvatarGroup from './AvatarGroup' + +const StyledWatingTopWrapper = styled.div` + width: 100%; + height: 38px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 20px; +` + +const StyledWatingTopTextWrapper = styled.div` + display: flex; + height: inherit; + justify-content: center; + align-items: flex-end; +` + +type CardHeaderProps = { + totalMemberLen: number + currentMemberLen: number + isDarkMode: boolean +} + +const CardHeader = ({ totalMemberLen, currentMemberLen, isDarkMode }: CardHeaderProps) => { + return ( + + + + {currentMemberLen} + + + {`/${totalMemberLen}`} + + + + + ) +} + +export default CardHeader diff --git a/src/components/home/CardMiddle.tsx b/src/components/home/CardMiddle.tsx new file mode 100644 index 00000000..b8ff1a74 --- /dev/null +++ b/src/components/home/CardMiddle.tsx @@ -0,0 +1,81 @@ +import styled from '@emotion/styled' +import { PulseLoader } from 'react-spinners' + +import { palette } from '@/styles/palette' + +import NormalButton from '../common/Buttons/NormalButton' +import Spacing from '../common/Spacing' +import { Text } from '../common/Text' + +const StyledWatingMidWrapper = styled.div` + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +` + +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}` +} + +type CardMiddleProps = { + time: number + handleResetClick: () => void + isDarkMode: boolean +} + +const CardMiddle = ({ time, handleResetClick, isDarkMode }: CardMiddleProps) => { + return ( + + + {formatTime(time)} + + + + {'매칭 취소'} + + + + {'매칭 중'}    + + + + ) +} + +export default CardMiddle diff --git a/src/components/home/Tip.tsx b/src/components/home/Tip.tsx index 4196d6a5..2230fe4a 100644 --- a/src/components/home/Tip.tsx +++ b/src/components/home/Tip.tsx @@ -1,10 +1,9 @@ import styled from '@emotion/styled' +import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -import { Text } from '../common/Text' - -const StyleTipHeader = styled.div` +const StyledTipHeader = styled.div` flex: 1; display: flex; flex-direction: column; @@ -13,12 +12,9 @@ const StyleTipHeader = styled.div` border-top: 1px solid ${palette.GRAY200}; ` -/** - * 홈페이지에서 사용되는 `Tip` 컴포넌트. - */ const Tip = () => { return ( - + { > {'커피밋 채팅방은 3일이 지나면 사라집니다!'} - + ) } diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 0377915c..238d5ebd 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -1,5 +1,3 @@ -import { useState } from 'react' - import AppHeader from '@/components/common/AppHeader' import { ParticularTopicButton } from '@/components/common/Buttons/IconButton' import GradationBackground from '@/components/common/GradationBackground' @@ -7,47 +5,57 @@ import NavigationBar from '@/components/common/NavigationBar' import PageContainer from '@/components/common/PageContainer' import { Text } from '@/components/common/Text' import Card from '@/components/home/Card' +import useToast from '@/hooks/useToast' +import useThemeStore from '@/store/ThemeStore' // Import the store +import { palette } from '@/styles/palette' const Home = () => { const nickname = '우땅' - const isDarkMode = false - const [isMatching, setIsMatching] = useState(false) + const isDarkMode = useThemeStore((state) => state.isDarkMode) + const toggleDarkMode = useThemeStore((state) => state.toggleDarkMode) + + const { showToast } = useToast() return ( <> - - - + + + {'진행중인 매칭'} - { - setIsMatching((prev) => !prev) - }} - isDarkMode={isDarkMode} - /> + {'커피밋의 추천기능'} - + { + showToast({ + message: '아직 준비중인 기능입니다!', + type: 'info', + isDarkMode, + }) + }} + /> - + ) diff --git a/src/store/ThemeStore.tsx b/src/store/ThemeStore.tsx new file mode 100644 index 00000000..b60a61d6 --- /dev/null +++ b/src/store/ThemeStore.tsx @@ -0,0 +1,23 @@ +import { create } from 'zustand' +import { createJSONStorage, persist } from 'zustand/middleware' + +type ThemeState = { + isDarkMode: boolean + storage?: Storage + toggleDarkMode: () => void +} + +const useThemeStore = create( + persist( + (set) => ({ + isDarkMode: false, + toggleDarkMode: () => set((state) => ({ isDarkMode: !state.isDarkMode })), + }), + { + name: 'theme-store', + storage: createJSONStorage(() => localStorage), + }, + ), +) + +export default useThemeStore diff --git a/src/store/TimerStore.tsx b/src/store/TimerStore.tsx new file mode 100644 index 00000000..b506b234 --- /dev/null +++ b/src/store/TimerStore.tsx @@ -0,0 +1,52 @@ +import { create } from 'zustand' +import { createJSONStorage, persist } from 'zustand/middleware' + +const timerWorker = new Worker(`./timerWorker.js`) + +type TimerState = { + time?: number + isRunning?: boolean + startTime?: number | null + matchingEndTime?: number | null + startTimer: () => void + stopTimer: () => void + resetTimer: () => void + setStartTime: (startTime: number | null) => void + setMatchingEndTime: (matchingEndTime: number | null) => void +} + +const useTimerStore = create( + persist( + (set, get) => ({ + startTimer: async () => { + const startTime = get().startTime || Date.now() + timerWorker.postMessage({ type: 'START', startTime }) + set({ isRunning: true, startTime }) + }, + stopTimer: () => { + set({ isRunning: false, startTime: null }) + timerWorker.postMessage({ type: 'STOP' }) + }, + resetTimer: () => { + const matchingEndTime = Date.now() + set({ time: 0, isRunning: false, startTime: null, matchingEndTime }) + timerWorker.postMessage({ type: 'RESET' }) + }, + setStartTime: (startTime) => set({ startTime }), + setMatchingEndTime: (matchingEndTime) => set({ matchingEndTime }), + }), + { + name: 'timer-store', + storage: createJSONStorage(() => localStorage), + }, + ), +) + +timerWorker.onmessage = (event) => { + const { type, time } = event.data + if (type === 'TICK') { + useTimerStore.setState({ time }) + } +} + +export default useTimerStore diff --git a/src/styles/palette.ts b/src/styles/palette.ts index 78c9ae54..936ca04d 100644 --- a/src/styles/palette.ts +++ b/src/styles/palette.ts @@ -3,6 +3,7 @@ export const palette = { SECONDARY: '#7382F8', TERTIARY: '#90AEF6', GRADIENT: '#ADD2F8', + DARK_GRADIENT: '#45599F', DARK_PRIMARY: '#1D2026', DARK_SECONDARY: '#494F80', DARK_TERTIARY: '#5A76B2', From a20bc133948426a6ec0283fa07e53782d1f8866f Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:27:51 +0900 Subject: [PATCH 037/180] =?UTF-8?q?style:=20LoginPage=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=9E=91=EC=84=B1=20(#96)?= 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 삭제 * style: LoginPage 레이아웃 작성 * refactor: 이미지 경로 변경 * refactor: pr 수정사항 반영 * refactor: LoginImage 경로 변경 * style: naver, kakao Button 텍스트 위치 수정 * refactor: frament 제거 * style: 배경색 skyblue 적용. 불필요 fragment 제거. --- .../common/Buttons/IconButton/KakaoButton.tsx | 2 +- .../common/Buttons/IconButton/NaverButton.tsx | 1 + src/components/common/HeroImage/index.tsx | 10 +++- src/pages/login/Login.tsx | 56 ++++++++++++++++++- 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/components/common/Buttons/IconButton/KakaoButton.tsx b/src/components/common/Buttons/IconButton/KakaoButton.tsx index 3501a646..ba92fd63 100644 --- a/src/components/common/Buttons/IconButton/KakaoButton.tsx +++ b/src/components/common/Buttons/IconButton/KakaoButton.tsx @@ -32,7 +32,7 @@ const KakaoButton = () => ( font={'Body_18'} fontWeight={600} letterSpacing={-1} - style={{ flex: 1, textAlign: 'left' }} + style={{ flex: 1, textAlign: 'left', marginLeft: 20 }} > {'카카오톡으로 시작'} diff --git a/src/components/common/Buttons/IconButton/NaverButton.tsx b/src/components/common/Buttons/IconButton/NaverButton.tsx index ea96a724..e23106fc 100644 --- a/src/components/common/Buttons/IconButton/NaverButton.tsx +++ b/src/components/common/Buttons/IconButton/NaverButton.tsx @@ -23,6 +23,7 @@ const NaverButton = () => { flex: 1, textAlign: 'left', color: palette.WHITE, + marginLeft: 20, }} > {'네이버로 시작'} diff --git a/src/components/common/HeroImage/index.tsx b/src/components/common/HeroImage/index.tsx index 09c4f5a5..b33f1c31 100644 --- a/src/components/common/HeroImage/index.tsx +++ b/src/components/common/HeroImage/index.tsx @@ -1,15 +1,19 @@ import styled from '@emotion/styled' -import LoginImage from '../assets/LoginImage.svg' +import LoginImage from '@/assets/LoginImage.svg' const HeroImage = () => { return ( - <> + - + ) } +const ImageContainer = styled.div` + display: flex; + justify-content: center; +` const StyleHeroImage = styled.img` border-radius: 20px; width: 306px; diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index 573cdb54..dd70f0d8 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -1,5 +1,59 @@ +import styled from '@emotion/styled' + +import { KakaoButton, NaverButton } from '@/components/common/Buttons/IconButton' +import HeroImage from '@/components/common/HeroImage' +import Spacing from '@/components/common/Spacing' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + const Login = () => { - return
{'Login'}
+ return ( + + + + + + + {'☕️커피밋'} + + + + + {'회사의 경계를 넘어, '} + + + + + {' 새로운 대화의 세계를 탐험하세요!'} + + + + + + + + + + + ) } +const LoginOuterWrapper = styled.div` + background-color: ${palette.SKY_BLUE}; +` + +const OauthWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +` + +const StyledContainer = styled.div` + width: 100%; + height: 441px; + background-color: ${palette.WHITE}; + border-radius: 20px; + text-align: center; +` export default Login From 462d656c92b451e63b414c8de57705b387410a61 Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:20:00 +0900 Subject: [PATCH 038/180] =?UTF-8?q?feature:=20NotFoundPage=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91=20(#102)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: font-family를 동적으로 변경하도록 수정 - 600 미만이면 'Pretendard-Regular' - 600 이상이면 'Pretendard' * feature: NotFound 페이지 제작 - NotFoundIcon 추가 * fix: GradationBackground 에 isDarkMode props 추가 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- src/assets/icons/NotFoundIcon.tsx | 256 ++++++++++++++++++ .../common/GradationBackground/index.tsx | 26 +- src/pages/notFound/NotFound.tsx | 97 ++++++- src/styles/typo.ts | 50 ++-- 4 files changed, 393 insertions(+), 36 deletions(-) create mode 100644 src/assets/icons/NotFoundIcon.tsx diff --git a/src/assets/icons/NotFoundIcon.tsx b/src/assets/icons/NotFoundIcon.tsx new file mode 100644 index 00000000..e421b0a6 --- /dev/null +++ b/src/assets/icons/NotFoundIcon.tsx @@ -0,0 +1,256 @@ +import { IconProps } from './ExitIcon' + +/** + * @param isDarkMode - 다크모드 여부 + */ + +const NotFoundIcon = ({ isDarkMode }: Pick) => { + return ( + + {!isDarkMode ? ( + + + + + + + + + + + + + + + + + + + + + ) : ( + + + + + + + + + + + + + + + + + + + + + )} + + ) +} + +export default NotFoundIcon diff --git a/src/components/common/GradationBackground/index.tsx b/src/components/common/GradationBackground/index.tsx index 5b4069ec..97820692 100644 --- a/src/components/common/GradationBackground/index.tsx +++ b/src/components/common/GradationBackground/index.tsx @@ -2,6 +2,19 @@ import styled from '@emotion/styled' import { palette } from '@/styles/palette' +type GradationBackgroundProps = { + children: React.ReactNode + isDarkMode: boolean +} + +/** + * @param children - 렌더링 할 하위 컴포넌트 + * @param isDarkMode - 다크모드 여부 + */ +const GradationBackground = ({ children, isDarkMode }: GradationBackgroundProps) => { + return {children} +} + const StyledGradationBackground = styled.div<{ isDarkMode: boolean }>` @@ -15,17 +28,4 @@ const StyledGradationBackground = styled.div<{ flex-direction: column; ` -type GradationBackgroundProps = { - children: React.ReactNode - isDarkMode: boolean -} - -/** - * @param children - 자식 컴포넌트 - * @param isDarkMode - 다크모드 여부 - */ -const GradationBackground = ({ children, isDarkMode }: GradationBackgroundProps) => { - return {children} -} - export default GradationBackground diff --git a/src/pages/notFound/NotFound.tsx b/src/pages/notFound/NotFound.tsx index 5ad5d5ce..46518bd1 100644 --- a/src/pages/notFound/NotFound.tsx +++ b/src/pages/notFound/NotFound.tsx @@ -1,5 +1,100 @@ +import styled from '@emotion/styled' + +import NotFoundIcon from '@/assets/icons/NotFoundIcon' +import NormalButton from '@/components/common/Buttons/NormalButton' +import GradationBackground from '@/components/common/GradationBackground' +import Spacing from '@/components/common/Spacing' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + const NotFoundPage = () => { - return
{'NotFoundPage'}
+ // TODO: zustand로 darkMode 상태 받기 + const isDarkMode = true + + return ( + + + + + + + {'404'} + + {'앗, 현재 페이지를 찾을 수 없어요! 😭'} + + + + + + {'홈으로 돌아가기'} + + + + + + + ) } +const StyledNotFoundPage = styled.div` + display: flex; + flex-direction: column; +` + +const StyledNotFoundPageBackground = styled.div<{ + isDarkMode: boolean +}>` + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; + height: 100%; + border-top-right-radius: 20px; + border-top-left-radius: 20px; + display: flex; + flex-direction: column; + justify-content: space-between; +` + +const StyledNotFoundPageHeaderContent = styled(Text)<{ isDarkMode: boolean }>` + text-align: center; + color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.BLACK)}; + + @media (max-width: 280px) { + font-size: 4rem; + } +` + +const StyledNotFoundPageMainContent = styled.div` + text-align: center; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + @media (max-width: 280px) { + font-size: 0.875rem; + } +` + +const StyledNotFoundIconWrapper = styled.div` + display: flex; + flex: 1; + flex-direction: column; + justify-content: center; + align-items: center; +` + export default NotFoundPage diff --git a/src/styles/typo.ts b/src/styles/typo.ts index 9dabead3..64a06477 100644 --- a/src/styles/typo.ts +++ b/src/styles/typo.ts @@ -2,69 +2,75 @@ 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'; + Body_72: (fontWeight = 600, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : 'Pretendard-Regular'}; + font-size: ${calcRem(72)}; + font-weight: ${fontWeight}; + letter-spacing: ${letterSpacing}px; + `, + Body_32: (fontWeight = 600, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : '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'; + Body_24: (fontWeight = 500, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : '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'; + Body_22: (fontWeight = 500, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : 'Pretendard-Regular'}; font-size: ${calcRem(22)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, - Body_20: (fontWeight: number = 500, letterSpacing?: number) => css` - font-family: 'Pretendard-Regular'; + Body_20: (fontWeight = 500, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : 'Pretendard-Regular'}; font-size: ${calcRem(20)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, - Body_18: (fontWeight: number = 500, letterSpacing?: number) => css` - font-family: 'Pretendard-Regular'; + Body_18: (fontWeight = 500, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : 'Pretendard-Regular'}; font-size: ${calcRem(18)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, - Body_16: (fontWeight: number = 400, letterSpacing?: number) => css` - font-family: 'Pretendard-Regular'; + Body_16: (fontWeight = 400, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : 'Pretendard-Regular'}; font-size: ${calcRem(16)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, - Body_14: (fontWeight: number = 400, letterSpacing?: number) => css` - font-family: 'Pretendard-Regular'; + Body_14: (fontWeight = 400, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : 'Pretendard-Regular'}; font-size: ${calcRem(14)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, - Body_12: (fontWeight: number = 400, letterSpacing?: number) => css` - font-family: 'Pretendard-Regular'; + Body_12: (fontWeight = 400, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : 'Pretendard-Regular'}; font-size: ${calcRem(12)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, - Body_10: (fontWeight: number = 400, letterSpacing?: number) => css` - font-family: 'Pretendard-Regular'; + Body_10: (fontWeight = 400, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : 'Pretendard-Regular'}; font-size: ${calcRem(10)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, - Caption_11: (fontWeight: number = 400, letterSpacing?: number) => css` - font-family: 'Pretendard-Regular'; + Caption_11: (fontWeight = 400, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : 'Pretendard-Regular'}; font-size: ${calcRem(11)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; `, - Caption_9: (fontWeight: number = 500, letterSpacing?: number) => css` - font-family: 'Pretendard-Regular'; + Caption_9: (fontWeight = 500, letterSpacing?: number) => css` + font-family: ${fontWeight >= 600 ? 'Pretendard' : 'Pretendard-Regular'}; font-size: ${calcRem(9)}; font-weight: ${fontWeight}; letter-spacing: ${letterSpacing}px; From 6b0a18a71a8f32802d7a214b041efd3b120617cc Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:47:24 +0900 Subject: [PATCH 039/180] =?UTF-8?q?refactor:=20SelectorButton=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#70)?= 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 수정 * refactor: pr 수정사항 반영 * refactor: prop 명 변경 * refactor: 변수명 수정 * refactor: pr 재수정 --- .../common/SelectorButton/index.tsx | 25 ++++++++++--------- .../common/SelectorButtonContainer/index.tsx | 6 ++--- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/components/common/SelectorButton/index.tsx b/src/components/common/SelectorButton/index.tsx index a28fac92..035cd792 100644 --- a/src/components/common/SelectorButton/index.tsx +++ b/src/components/common/SelectorButton/index.tsx @@ -6,17 +6,17 @@ import { palette } from '@/styles/palette' type SelectorButtonProps = { isDarkMode: boolean buttonName: string - onClick?: (selected: boolean) => void - isButtonselected?: boolean - maxLengthReached: boolean + isButtonClicked?: (selected: boolean) => void + isButtonSelected?: boolean + isMaxLengthReached: boolean } const SelectorButton = ({ isDarkMode, buttonName, - onClick, - isButtonselected: propIsButtonSelected = false, - maxLengthReached = false, + isButtonClicked, + isButtonSelected: propIsButtonSelected = false, + isMaxLengthReached = false, }: SelectorButtonProps) => { const defaultSettings = isDarkMode ? { @@ -29,8 +29,9 @@ const SelectorButton = ({ defaultButtonColor: palette.TERTIARY, textColor: palette.WHITE, } - const [isButtonselected, setIsButtonselected] = useState(propIsButtonSelected) - const initialBackgroundColor = isButtonselected + + const [isButtonSelected, setIsButtonSelected] = useState(propIsButtonSelected) + const initialBackgroundColor = isButtonSelected ? defaultSettings.selectedButtonColor : defaultSettings.defaultButtonColor const [backgroundColor, setBackgroundColor] = useState(initialBackgroundColor) @@ -39,18 +40,18 @@ const SelectorButton = ({ const handleButtonClick = () => { const isSelected = backgroundColor !== defaultSettings.selectedButtonColor - if (maxLengthReached && !isButtonselected) { - onClick && onClick(true) + if (isMaxLengthReached && !isButtonSelected) { + isButtonClicked && isButtonClicked(true) return } - setIsButtonselected(!isButtonselected) + setIsButtonSelected(isSelected) setBackgroundColor( isSelected ? defaultSettings.selectedButtonColor : defaultSettings.defaultButtonColor, ) if (defaultSettings.textColor !== palette.WHITE) { setCurrentTextColor(isSelected ? palette.WHITE : defaultSettings.textColor) } - if (onClick) onClick(isSelected) + if (isButtonClicked) isButtonClicked(isSelected) } return ( diff --git a/src/components/common/SelectorButtonContainer/index.tsx b/src/components/common/SelectorButtonContainer/index.tsx index 26d7c2b5..9d6a2a15 100644 --- a/src/components/common/SelectorButtonContainer/index.tsx +++ b/src/components/common/SelectorButtonContainer/index.tsx @@ -55,9 +55,9 @@ const SelectorButtonContainer = ({ key={index} isDarkMode={isDarkMode} buttonName={name} - onClick={handleButtonSelection} - maxLengthReached={selectedCount >= maxLength} - isButtonselected={false} + isButtonClicked={handleButtonSelection} + isMaxLengthReached={selectedCount >= maxLength} + isButtonSelected={false} /> ))} {customButtons.map((name, index) => ( From 470ae0516f8d4733a3d929bb87c2d257b95f1355 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:47:49 +0900 Subject: [PATCH 040/180] =?UTF-8?q?style:=20CustomSelectorButton=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20(#7?= =?UTF-8?q?1)?= 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 설정 * refactor: pr 수정사항 반영 * refactor: 속성명 수정 --- .../common/BusinessCardContainer/index.tsx | 1 + .../common/CustomSelectorButton/index.tsx | 24 +++++++++---------- .../common/SelectorButtonContainer/index.tsx | 6 ++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/components/common/BusinessCardContainer/index.tsx b/src/components/common/BusinessCardContainer/index.tsx index ee5cd766..104b1e40 100644 --- a/src/components/common/BusinessCardContainer/index.tsx +++ b/src/components/common/BusinessCardContainer/index.tsx @@ -108,6 +108,7 @@ const CameraIcon = styled.img` width: 38px; height: 38px; ` + const UploadLabel = styled.label` cursor: pointer; ` diff --git a/src/components/common/CustomSelectorButton/index.tsx b/src/components/common/CustomSelectorButton/index.tsx index 2c11bcff..d2e5cab7 100644 --- a/src/components/common/CustomSelectorButton/index.tsx +++ b/src/components/common/CustomSelectorButton/index.tsx @@ -7,19 +7,19 @@ import { palette } from '@/styles/palette' type CustomSelectorButtonProps = { isDarkMode: boolean buttonName: string - onClick: (selected: boolean) => void + isButtonClicked: (selected: boolean) => void onRemove: () => void - isButtonselected: boolean - maxLengthReached: boolean + isButtonSelected: boolean + isMaxLengthReached: boolean } const CustomSelectorButton = ({ isDarkMode, buttonName, - onClick, + isButtonClicked, onRemove, - isButtonselected: propIsButtonSelected, - maxLengthReached, + isButtonSelected: propIsButtonSelected, + isMaxLengthReached, }: CustomSelectorButtonProps) => { const defaultSettings = isDarkMode ? { @@ -40,12 +40,12 @@ const CustomSelectorButton = ({ }, [propIsButtonSelected]) const handleButtonClick = () => { - if (maxLengthReached && !isButtonSelected) { - onClick && onClick(true) + if (isMaxLengthReached && !isButtonSelected) { + isButtonClicked && isButtonClicked(true) return } setIsButtonSelected((prevState) => !prevState) - if (onClick) onClick(!isButtonSelected) + if (isButtonClicked) isButtonClicked(!isButtonSelected) } return ( @@ -61,7 +61,7 @@ const CustomSelectorButton = ({ > {buttonName} - + - + ) } @@ -83,7 +83,7 @@ const StyledButtonWrapper = styled.div` align-items: center; ` -const RemoveButton = styled.button` +const StyledRemoveButton = styled.button` margin-left: 8px; background-color: transparent; border: none; diff --git a/src/components/common/SelectorButtonContainer/index.tsx b/src/components/common/SelectorButtonContainer/index.tsx index 9d6a2a15..397585bb 100644 --- a/src/components/common/SelectorButtonContainer/index.tsx +++ b/src/components/common/SelectorButtonContainer/index.tsx @@ -65,10 +65,10 @@ const SelectorButtonContainer = ({ key={index} isDarkMode={isDarkMode} buttonName={name} - onClick={(isSelected) => handleCustomButtonClick(isSelected)} + isButtonClicked={(isSelected) => handleCustomButtonClick(isSelected)} onRemove={() => handleCustomButtonRemove(name)} - maxLengthReached={selectedCount >= maxLength} - isButtonselected={false} + isMaxLengthReached={selectedCount >= maxLength} + isButtonSelected={false} /> ))} {showAlert && ( From 4f9db624087d131224cb19a8c80731bd26a9f3a6 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:48:23 +0900 Subject: [PATCH 041/180] =?UTF-8?q?style:=20SelectorButtonContainer=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=20?= =?UTF-8?q?(#72)?= 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 삭제 * refactor: pr 수정사항 반영 --- .../common/BusinessCardContainer/index.tsx | 2 +- .../common/SelectorButtonContainer/index.tsx | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/common/BusinessCardContainer/index.tsx b/src/components/common/BusinessCardContainer/index.tsx index 104b1e40..f918c80a 100644 --- a/src/components/common/BusinessCardContainer/index.tsx +++ b/src/components/common/BusinessCardContainer/index.tsx @@ -17,6 +17,7 @@ const BusinessCardContainer = ({ isDarkMode }: BusinessCardContainerProps) => { const handleImageUpload = (event: React.ChangeEvent) => { if (!uploadedImage) { + const uploadedFile = event.target.files?.[0] const uploadedFileReader = new FileReader() @@ -79,7 +80,6 @@ const BusinessCardContainerWrapper = styled.div` position: relative; width: 300px; ` - const CameraIconWrapper = styled.div` width: 88px; height: 88px; diff --git a/src/components/common/SelectorButtonContainer/index.tsx b/src/components/common/SelectorButtonContainer/index.tsx index 397585bb..5d4abafa 100644 --- a/src/components/common/SelectorButtonContainer/index.tsx +++ b/src/components/common/SelectorButtonContainer/index.tsx @@ -48,8 +48,8 @@ const SelectorButtonContainer = ({ } return ( - - + + {buttonNames.map((name, index) => ( )} - + - + ) } -const OuterWrapper = styled.div` +const ButtonsContainerOuterWrapper = styled.div` position: relative; ` -const Container = styled.div<{ isDarkMode: boolean }>` +const ButtonsContainer = styled.div<{ isDarkMode: boolean }>` width: 348px; min-height: 235px; max-height: 400px; From 2ea0a9ad8770dfd7d9beac8b300e4395eaa51094 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:48:57 +0900 Subject: [PATCH 042/180] =?UTF-8?q?refactor:=20Input=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20ref,=20onchange=20=EC=B6=94=EA=B0=80=20(#9?= =?UTF-8?q?0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Input 컴포넌트 ref, onchange 추가 * refactor: pr 수정사항 반영 --- src/components/common/Input/index.tsx | 60 +++++++++++++++------------ 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/src/components/common/Input/index.tsx b/src/components/common/Input/index.tsx index 28e71169..13382ae9 100644 --- a/src/components/common/Input/index.tsx +++ b/src/components/common/Input/index.tsx @@ -1,4 +1,5 @@ import styled from '@emotion/styled' +import React, { ForwardedRef } from 'react' type InputProps = { placeholder?: string @@ -12,25 +13,32 @@ type InputProps = { inputTextSize?: string inputBackgroundColor?: string borderRadius?: string + onChange?: (e: React.ChangeEvent) => void } -const Input = ({ - placeholder, - placeholderSize, - placeholderColor, - width, - height, - borderColor, - borderWidth, - borderRadius, - inputTextColor, - inputTextSize, - inputBackgroundColor, -}: InputProps) => { - return ( - <> - - , + ) => { + return ( + + - - - ) -} + /> + + ) + }, +) +Input.displayName = 'Input' -const InputWrapper = styled.div` +const StyledInputWrapper = styled.div` position: relative; ` -const StyleInput = styled.input` +const StyledInput = 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}; border-color: ${(props) => props.borderColor}; @@ -67,6 +74,7 @@ const StyleInput = styled.input` background-color: ${(props) => props.inputBackgroundColor}; border-radius: ${(props) => props.borderRadius}; font-size: ${(props) => props.inputTextSize}; + position: relative; ` export default Input From 6c1f4fb4e18336f9141cf65e624039dfd41000cf Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Sat, 11 Nov 2023 12:52:33 +0900 Subject: [PATCH 043/180] =?UTF-8?q?feature:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20OAuth=20=EC=97=B0=EA=B2=B0=20+?= =?UTF-8?q?=20=EB=B0=98=EC=9D=91=ED=98=95=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EA=B5=AC=EC=84=B1=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feature: LoginPendingPage 컴포넌트 추가 - App.tsx에 라우팅 추가 * refactor: 네이버, 카카오 버튼 props 수정 - moveToOAuthProvider props 받도록 수정 * refactor: HeroImage 크기 수정 * feature: LoginPending 페이지 추가 - zustand-persist로 현재 OAuth provider를 저장하도록 설정 - Status Code가 400이면 회원가입 페이지로 가도록 라우팅 * fix: 회원가입 리다이렉트 상태코드 404로 변경 * refactor: 로그인 페이지 반응형 구현 - Spacing 컴포넌트 css props 받도록 리팩토링 - max-width: 280px인 갤럭시 폴드를 중심으로 리팩토링 * refactor: 로그인 페이지 폰트 크기 반응형으로 수정 * feature: 로그인 리다이렉팅 시 authCode를 state에 담도록 수정 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- src/App.tsx | 4 +- .../common/Buttons/IconButton/KakaoButton.tsx | 48 ++++--- .../common/Buttons/IconButton/NaverButton.tsx | 26 ++-- .../common/Buttons/IconButton/index.tsx | 23 ++- src/components/common/HeroImage/index.tsx | 18 ++- src/components/common/Spacing/index.tsx | 25 ++-- src/pages/login/Login.tsx | 131 ++++++++++++++---- src/pages/loginPending/LoginPending.tsx | 57 ++++++++ src/pages/loginPending/index.tsx | 14 ++ src/pages/register/RegisterUser.tsx | 6 + src/store/AuthStore.tsx | 35 +++++ 11 files changed, 305 insertions(+), 82 deletions(-) create mode 100644 src/pages/loginPending/LoginPending.tsx create mode 100644 src/pages/loginPending/index.tsx create mode 100644 src/store/AuthStore.tsx diff --git a/src/App.tsx b/src/App.tsx index 6bbb4a37..fc7fbbb1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import ChattingPage from '@/pages/chatting' import HomePage from '@/pages/home' import LandingPage from '@/pages/landing' import LoginPage from '@/pages/login' +import LoginPendingPage from '@/pages/loginPending' import NotFoundPage from '@/pages/notFound/NotFound' import ProfilePage from '@/pages/profile' import PrivateRoute from '@/pages/redirect/PrivateRoute' @@ -25,9 +26,10 @@ const App = () => { }> - }> + }> } /> } /> + } /> } /> } /> }> diff --git a/src/components/common/Buttons/IconButton/KakaoButton.tsx b/src/components/common/Buttons/IconButton/KakaoButton.tsx index ba92fd63..827b98f7 100644 --- a/src/components/common/Buttons/IconButton/KakaoButton.tsx +++ b/src/components/common/Buttons/IconButton/KakaoButton.tsx @@ -1,13 +1,29 @@ import styled from '@emotion/styled' import KakaoIcon from '@/assets/icons/KakaoIcon' +import { OAuthButtonProps, StyledIconWrapper } from '@/components/common/Buttons/IconButton' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -import { StyledIconWrapper } from '.' +const KakaoButton = ({ moveToOAuthProvider }: OAuthButtonProps) => ( + + + + + + {'카카오톡으로 시작'} + + +) -export const ButtonWrapper = styled.button<{ +export const StyledButtonWrapper = styled.button<{ buttonTheme: 'kakao' | 'naver' + onClick: () => void }>` width: 320px; height: 60px; @@ -17,26 +33,16 @@ export const ButtonWrapper = styled.button<{ justify-content: space-between; align-items: center; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.15); + + @media (max-width: 280px) { + width: 250px; + } ` -const KakaoButton = () => ( - - - - - - {'카카오톡으로 시작'} - - -) +export const StyledButtonText = styled(Text)` + @media (max-width: 280px) { + font-size: 16px; + } +` export default KakaoButton diff --git a/src/components/common/Buttons/IconButton/NaverButton.tsx b/src/components/common/Buttons/IconButton/NaverButton.tsx index e23106fc..1ec3a3c0 100644 --- a/src/components/common/Buttons/IconButton/NaverButton.tsx +++ b/src/components/common/Buttons/IconButton/NaverButton.tsx @@ -1,21 +1,17 @@ import NaverIcon from '@/assets/icons/NaverIcon' -import { Text } from '@/components/common/Text' +import { OAuthButtonProps, StyledIconWrapper } from '@/components/common/Buttons/IconButton' +import { + StyledButtonText, + StyledButtonWrapper, +} from '@/components/common/Buttons/IconButton/KakaoButton' import { palette } from '@/styles/palette' - -import { StyledIconWrapper } from '.' -import { ButtonWrapper } from './KakaoButton' - -const NaverButton = () => { +const NaverButton = ({ moveToOAuthProvider }: OAuthButtonProps) => { return ( - - + + - { }} > {'네이버로 시작'} - - + + ) } diff --git a/src/components/common/Buttons/IconButton/index.tsx b/src/components/common/Buttons/IconButton/index.tsx index 1505d460..a27158be 100644 --- a/src/components/common/Buttons/IconButton/index.tsx +++ b/src/components/common/Buttons/IconButton/index.tsx @@ -1,14 +1,20 @@ import { css } from '@emotion/react' import styled from '@emotion/styled' +import { + iconButtonStyles, + IconButtonType, +} from '@/components/common/Buttons/IconButton/IconButtonStyles' +import InterestButton from '@/components/common/Buttons/IconButton/InterestButton' +import KakaoButton from '@/components/common/Buttons/IconButton/KakaoButton' +import NaverButton from '@/components/common/Buttons/IconButton/NaverButton' +import ParticularTopicButton from '@/components/common/Buttons/IconButton/ParticularTopicButton' +import RandomMatchingButton from '@/components/common/Buttons/IconButton/RandomMatchingButton' 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 type OAuthButtonProps = { + moveToOAuthProvider: () => void +} export const StyledIconButtonWrapper = styled.button<{ iconButtonType: IconButtonType @@ -41,11 +47,16 @@ export const StyledIconWrapper = styled.div<{ }>` width: 35px; height: 35px; + margin: 4px 38px 4px 20px; border-radius: ${(props) => props.borderRadius}; background-color: ${(props) => props.backgroundColor}; display: flex; justify-content: center; align-items: center; + + @media (max-width: 280px) { + margin: 2px 10px 2px 10px; + } ` export { InterestButton, KakaoButton, NaverButton, ParticularTopicButton, RandomMatchingButton } diff --git a/src/components/common/HeroImage/index.tsx b/src/components/common/HeroImage/index.tsx index b33f1c31..1d437d04 100644 --- a/src/components/common/HeroImage/index.tsx +++ b/src/components/common/HeroImage/index.tsx @@ -2,22 +2,28 @@ import styled from '@emotion/styled' import LoginImage from '@/assets/LoginImage.svg' -const HeroImage = () => { +const HeroImage = ({ ...props }) => { return ( - + - + ) } -const ImageContainer = styled.div` +const StyledHeroImageContainer = styled.div` display: flex; justify-content: center; ` + const StyleHeroImage = styled.img` border-radius: 20px; - width: 306px; - height: 306px; + width: 350px; + height: 350px; + + @media (max-width: 375px) { + width: 250px; + height: 250px; + } ` export default HeroImage diff --git a/src/components/common/Spacing/index.tsx b/src/components/common/Spacing/index.tsx index 75ac5d3a..b32d4253 100644 --- a/src/components/common/Spacing/index.tsx +++ b/src/components/common/Spacing/index.tsx @@ -1,17 +1,26 @@ /** @jsxImportSource @emotion/react */ -import { css } from '@emotion/react' +import { css, SerializedStyles } from '@emotion/react' import { KeyOfPalette, theme } from '@/styles/theme' -const Spacing = ({ size, color }: { size: number; color?: KeyOfPalette }) => { +type SpacingProps = { + size: number + color?: KeyOfPalette + css?: SerializedStyles +} + +const Spacing = ({ size, color, css: customCss }: SpacingProps) => { return (
+ css={[ + css` + height: ${size}px; + width: 100%; + background-color: ${color ? `${theme.palette[color]}` : 'transparent'}; + `, + customCss, // 사용자 정의 스타일을 적용 + ]} + /> ) } diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index dd70f0d8..df5b3806 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -1,59 +1,140 @@ +import { css } from '@emotion/react' import styled from '@emotion/styled' import { KakaoButton, NaverButton } from '@/components/common/Buttons/IconButton' import HeroImage from '@/components/common/HeroImage' import Spacing from '@/components/common/Spacing' import { Text } from '@/components/common/Text' +import useAuthStore from '@/store/AuthStore' import { palette } from '@/styles/palette' +export type Provider = 'naver' | 'kakao' + +const BASE_URL = import.meta.env.VITE_BASE_URL + const Login = () => { - return ( - - + const setProvider = useAuthStore((state) => state.setProvider) + + const handleMoveToAuthProvider = async (provider: Provider) => { + window.location.assign(`${BASE_URL}/v1/oauth2.0/${provider}`) + setProvider(provider) + } - - - + return ( + + + + + {'☕️커피밋'} - - + + - + {'회사의 경계를 넘어, '} - - + + - + {' 새로운 대화의 세계를 탐험하세요!'} - - - - - - - - - - + + + + + { + handleMoveToAuthProvider('naver') + }} + /> + + { + handleMoveToAuthProvider('kakao') + }} + /> + + + ) } -const LoginOuterWrapper = styled.div` +const StyledLoginOuterWrapper = styled.div` background-color: ${palette.SKY_BLUE}; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; ` -const OauthWrapper = styled.div` +const StyledOauthWrapper = styled.div` display: flex; justify-content: center; align-items: center; flex-direction: column; ` -const StyledContainer = styled.div` +const StyledLoginContainer = styled.div` + flex: 1; width: 100%; height: 441px; background-color: ${palette.WHITE}; border-radius: 20px; text-align: center; + padding-bottom: 10%; +` + +const StyledHeaderText = styled(Text)` + @media (max-width: 280px) { + font-size: 24px; + } +` + +const StyledSubText = styled(Text)` + @media (max-width: 280px) { + font-size: 14px; + } ` export default Login diff --git a/src/pages/loginPending/LoginPending.tsx b/src/pages/loginPending/LoginPending.tsx new file mode 100644 index 00000000..e8a21700 --- /dev/null +++ b/src/pages/loginPending/LoginPending.tsx @@ -0,0 +1,57 @@ +import styled from '@emotion/styled' +import { useNavigate, useSearchParams } from 'react-router-dom' +import { PulseLoader } from 'react-spinners' + +import { axiosAPI } from '@/apis/axios' +import useAuthStore from '@/store/AuthStore' +import { palette } from '@/styles/palette' + +const LoginPending = () => { + const navigate = useNavigate() + const [searchParams] = useSearchParams() + const authCode = searchParams.get('code') + const setToken = useAuthStore((state) => state.setAuthTokens) + const provider = useAuthStore((state) => state.provider) + + const routeAuthInfo = async () => { + await axiosAPI + .get(`${import.meta.env.VITE_BASE_URL}/v1/users/login/${provider}?code=${authCode}`) + .then((res) => { + setToken({ + accessToken: res.data.accessToken, + refreshToken: res.data.refreshToken, + }) + }) + .catch((err) => { + if (err.response.status === 404) { + navigate('/register/user', { state: { authCode } }) + } + }) + } + + routeAuthInfo() + + return ( + + + + ) +} + +const StyledLoginPending = styled.div` + background-color: ${palette.SKY_BLUE}; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100vh; +` + +export default LoginPending diff --git a/src/pages/loginPending/index.tsx b/src/pages/loginPending/index.tsx new file mode 100644 index 00000000..34955703 --- /dev/null +++ b/src/pages/loginPending/index.tsx @@ -0,0 +1,14 @@ +import { Route, Routes } from 'react-router-dom' + +import LoginPending from '@/pages/loginPending/LoginPending' +const LoginPendingPage = () => { + return ( + + }> + {' '} + + + ) +} + +export default LoginPendingPage diff --git a/src/pages/register/RegisterUser.tsx b/src/pages/register/RegisterUser.tsx index e843c5ea..ed2687e7 100644 --- a/src/pages/register/RegisterUser.tsx +++ b/src/pages/register/RegisterUser.tsx @@ -1,4 +1,10 @@ +import { useLocation } from 'react-router-dom' + const Register = () => { + const location = useLocation() + const { authCode } = location.state || {} + + console.log(authCode) return
{'Register'}
} diff --git a/src/store/AuthStore.tsx b/src/store/AuthStore.tsx new file mode 100644 index 00000000..87ea1140 --- /dev/null +++ b/src/store/AuthStore.tsx @@ -0,0 +1,35 @@ +import { create } from 'zustand' +import { createJSONStorage, persist } from 'zustand/middleware' + +import { Provider } from '@/pages/login/Login' + +type Tokens = { + accessToken: string + refreshToken: string +} + +type AuthState = { + provider: Provider + authTokens?: Tokens + setProvider: (provider: Provider) => void + setAuthTokens: (authTokens: Tokens) => void +} + +const useAuthStore = create( + persist( + (set) => ({ + provider: 'kakao', + setProvider: (provider: Provider) => set(() => ({ provider })), + setAuthTokens: (authTokens: Tokens) => + set(() => ({ + authTokens, + })), + }), + { + name: 'auth-store', + storage: createJSONStorage(() => localStorage), + }, + ), +) + +export default useAuthStore From c93926b0220c7f6223f24aa0fd23b4ba22f16e1b Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Sat, 11 Nov 2023 13:40:07 +0900 Subject: [PATCH 044/180] =?UTF-8?q?hotfix:=20VITE=5FBASE=5FURL=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EB=B3=80=EC=88=98=20=EC=84=A4=EC=A0=95=20(#113)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: VITE_BASE_URL 환경변수 설정 * fix: 띄어쓰기 오류 수정 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- .env.production | 1 + src/components/common/BusinessCardContainer/index.tsx | 1 - src/components/common/GradationBackground/index.tsx | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 .env.production diff --git a/.env.production b/.env.production new file mode 100644 index 00000000..04849b83 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +VITE_BASE_URL='https://api.coffee-meet.site/api' \ No newline at end of file diff --git a/src/components/common/BusinessCardContainer/index.tsx b/src/components/common/BusinessCardContainer/index.tsx index f918c80a..34a87af0 100644 --- a/src/components/common/BusinessCardContainer/index.tsx +++ b/src/components/common/BusinessCardContainer/index.tsx @@ -17,7 +17,6 @@ const BusinessCardContainer = ({ isDarkMode }: BusinessCardContainerProps) => { const handleImageUpload = (event: React.ChangeEvent) => { if (!uploadedImage) { - const uploadedFile = event.target.files?.[0] const uploadedFileReader = new FileReader() diff --git a/src/components/common/GradationBackground/index.tsx b/src/components/common/GradationBackground/index.tsx index 97820692..2eb2968f 100644 --- a/src/components/common/GradationBackground/index.tsx +++ b/src/components/common/GradationBackground/index.tsx @@ -14,7 +14,7 @@ type GradationBackgroundProps = { const GradationBackground = ({ children, isDarkMode }: GradationBackgroundProps) => { return {children} } - + const StyledGradationBackground = styled.div<{ isDarkMode: boolean }>` From 91d9fcbdb803db0b95fd67ee0b338e90eb95bea6 Mon Sep 17 00:00:00 2001 From: wukddang <43228743+funkyblues@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:02:52 +0900 Subject: [PATCH 045/180] =?UTF-8?q?fix:=20OAuth=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=98=A4=EB=A5=98=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/loginPending/LoginPending.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/loginPending/LoginPending.tsx b/src/pages/loginPending/LoginPending.tsx index e8a21700..db05e1c8 100644 --- a/src/pages/loginPending/LoginPending.tsx +++ b/src/pages/loginPending/LoginPending.tsx @@ -15,7 +15,7 @@ const LoginPending = () => { const routeAuthInfo = async () => { await axiosAPI - .get(`${import.meta.env.VITE_BASE_URL}/v1/users/login/${provider}?code=${authCode}`) + .get(`${import.meta.env.VITE_BASE_URL}/v1/users/login/${provider}?authCode=${authCode}`) .then((res) => { setToken({ accessToken: res.data.accessToken, From 70a2ae9ce1488e130a82b3e1872fc4f83292cb24 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Sun, 12 Nov 2023 20:12:23 +0900 Subject: [PATCH 046/180] =?UTF-8?q?[Feature]=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=EB=8B=89=EB=84=A4=EC=9E=84,=20=EA=B4=80=EC=8B=AC?= =?UTF-8?q?=EC=82=AC=20=EC=A0=95=EB=B3=B4=20=EB=93=B1=EB=A1=9D=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84=20(#119)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style : RegisterInput 공통 컴포넌트 제작 * feat : 유저 정보 등록 페이지 기능 구현 * fix : Oauth 로그인 후 authCode와 token을 받아와 넘겨주도록 수정 * chore : 사용되지 않는 import 문 삭제 * chore : msw 버전에 따른 import 수정 * chore : 사용되지 않은 변수 에러 해결 * chore : 주석 제거 * chore : 닉네임 중복 요청 react-query로 수정 * chore : 사용되지 않은 import 문 제거 --- package-lock.json | 280 +++++++++--------- package.json | 2 +- src/apis/axios.ts | 5 +- src/components/common/PageHeader/index.tsx | 1 + src/components/common/RegisterInput/index.tsx | 51 ++++ .../common/SelectorButton/index.tsx | 5 +- .../common/SelectorButtonContainer/index.tsx | 10 +- src/mocks/handlers.ts | 16 +- src/mocks/worker.ts | 2 +- src/pages/register/RegisterUser.tsx | 203 ++++++++++++- src/store/InterestStore.tsx | 13 + 11 files changed, 431 insertions(+), 157 deletions(-) create mode 100644 src/components/common/RegisterInput/index.tsx create mode 100644 src/store/InterestStore.tsx diff --git a/package-lock.json b/package-lock.json index 5a0cde2d..3a0967a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "husky": "^8.0.0", "lint-staged": "^15.0.1", - "msw": "^2.0.0", + "msw": "^1.3.2", "prettier": "^3.0.3", "typescript": "^5.0.2", "vite": "^4.4.5", @@ -559,33 +559,6 @@ "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", @@ -1240,29 +1213,44 @@ } }, "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==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.2.tgz", + "integrity": "sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==", "dev": true, + "dependencies": { + "@types/set-cookie-parser": "^2.4.0", + "set-cookie-parser": "^2.4.6" + }, "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==", + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.17.10.tgz", + "integrity": "sha512-N8x7eSLGcmUFNWZRxT1vsHvypzIRgQYdG0rJey/rZCy6zT/30qDt8Joj7FxzGNLSwXbeZqJOMqDurp7ra4hgbw==", "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", + "@open-draft/until": "^1.0.3", + "@types/debug": "^4.1.7", + "@xmldom/xmldom": "^0.8.3", + "debug": "^4.3.3", + "headers-polyfill": "3.2.5", "outvariant": "^1.2.1", - "strict-event-emitter": "^0.5.1" + "strict-event-emitter": "^0.2.4", + "web-encoding": "^1.1.5" }, "engines": { - "node": ">=18" + "node": ">=14" + } + }, + "node_modules/@mswjs/interceptors/node_modules/strict-event-emitter": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz", + "integrity": "sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==", + "dev": true, + "dependencies": { + "events": "^3.3.0" } }, "node_modules/@nodelib/fs.scandir": { @@ -1300,26 +1288,10 @@ "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==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", + "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==", "dev": true }, "node_modules/@pkgr/utils": { @@ -2479,6 +2451,15 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", @@ -2518,6 +2499,12 @@ "dev": true, "peer": true }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "dev": true + }, "node_modules/@types/node": { "version": "20.8.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz", @@ -2569,11 +2556,14 @@ "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/@types/set-cookie-parser": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.6.tgz", + "integrity": "sha512-tjIRMxGztGfIbW2/d20MdJmAPZbabtdW051cKfU+nvZXUnKKifHbY2CyL/C0EGabUB8ahIRjanYzTqJUQR8TAQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/stompjs": { "version": "2.3.7", @@ -3009,6 +2999,22 @@ "vite": "^4" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "dev": true, + "optional": true + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -3756,9 +3762,9 @@ "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==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "dev": true, "engines": { "node": ">= 0.6" @@ -5086,6 +5092,15 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "dev": true }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -5325,19 +5340,6 @@ "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/framer-motion": { "version": "10.16.4", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.4.tgz", @@ -5710,9 +5712,9 @@ } }, "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==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.2.5.tgz", + "integrity": "sha512-tUCGvt191vNSQgttSyJoibR+VO+I6+iCHIUdhzEMJKE+EAL8BwCN7fUOZlY4ofOelNHsK+gEjxB/B+9N3EWtdA==", "dev": true }, "node_modules/hoist-non-react-statics": { @@ -6041,6 +6043,22 @@ "node": ">=12" } }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -6931,33 +6949,29 @@ "dev": true }, "node_modules/msw": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.0.0.tgz", - "integrity": "sha512-lw9UHuzNCWoODHaThGeLLIIuzEBUQkj3fJXQnChHifMKbB2UmF2msHd4d/lnyqjAyD0XWoibdviW9wlstFPpkA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/msw/-/msw-1.3.2.tgz", + "integrity": "sha512-wKLhFPR+NitYTkQl5047pia0reNGgf0P6a1eTnA5aNlripmiz0sabMvvHcicE8kQ3/gZcI0YiPFWmYfowfm3lA==", "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", + "@mswjs/cookies": "^0.2.2", + "@mswjs/interceptors": "^0.17.10", + "@open-draft/until": "^1.0.3", "@types/cookie": "^0.4.1", "@types/js-levenshtein": "^1.1.1", - "@types/statuses": "^2.0.1", - "chalk": "^4.1.2", + "chalk": "^4.1.1", "chokidar": "^3.4.2", - "formdata-node": "4.4.1", + "cookie": "^0.4.2", "graphql": "^16.8.1", - "headers-polyfill": "^4.0.1", + "headers-polyfill": "3.2.5", "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", + "strict-event-emitter": "^0.4.3", "type-fest": "^2.19.0", "yargs": "^17.3.1" }, @@ -6965,14 +6979,14 @@ "msw": "cli/index.js" }, "engines": { - "node": ">=18" + "node": ">=14" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mswjs" }, "peerDependencies": { - "typescript": ">= 4.7.x <= 5.2.x" + "typescript": ">= 4.4.x <= 5.2.x" }, "peerDependenciesMeta": { "typescript": { @@ -7038,25 +7052,6 @@ "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", @@ -8201,6 +8196,12 @@ "node": ">=10" } }, + "node_modules/set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", + "dev": true + }, "node_modules/set-function-length": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", @@ -8341,19 +8342,10 @@ "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==", + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", + "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", "dev": true }, "node_modules/string_decoder": { @@ -8910,6 +8902,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9013,13 +9018,16 @@ "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==", + "node_modules/web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", "dev": true, - "engines": { - "node": ">= 14" + "dependencies": { + "util": "^0.12.3" + }, + "optionalDependencies": { + "@zxing/text-encoding": "0.9.0" } }, "node_modules/webidl-conversions": { diff --git a/package.json b/package.json index 2b425564..d5f0d6b6 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "husky": "^8.0.0", "lint-staged": "^15.0.1", - "msw": "^2.0.0", + "msw": "^1.3.2", "prettier": "^3.0.3", "typescript": "^5.0.2", "vite": "^4.4.5", diff --git a/src/apis/axios.ts b/src/apis/axios.ts index 29b461b0..5c964b6b 100644 --- a/src/apis/axios.ts +++ b/src/apis/axios.ts @@ -1,5 +1,7 @@ import axios from 'axios' +import useAuthStore from '@/store/AuthStore' + export const axiosAPI = axios.create({ baseURL: import.meta.env.VITE_BASE_URL, }) @@ -9,7 +11,8 @@ axiosAPI.interceptors.request.use( function (config) { // 요청 바로 직전 // axios 설정값에 대해 작성합니다. - // config.headers['Authorization'] = `Bearer ${localStorage.getItem('token')}` + const { authTokens } = useAuthStore() + config.headers['Authorization'] = `Bearer ${authTokens}` return config }, diff --git a/src/components/common/PageHeader/index.tsx b/src/components/common/PageHeader/index.tsx index dd0fece2..573e24a9 100644 --- a/src/components/common/PageHeader/index.tsx +++ b/src/components/common/PageHeader/index.tsx @@ -39,6 +39,7 @@ const StyleIcon = styled.div` display: flex; justify-content: center; align-items: center; + cursor: pointer; ` type PageHeaderProps = { diff --git a/src/components/common/RegisterInput/index.tsx b/src/components/common/RegisterInput/index.tsx new file mode 100644 index 00000000..7ec3b146 --- /dev/null +++ b/src/components/common/RegisterInput/index.tsx @@ -0,0 +1,51 @@ +import styled from '@emotion/styled' +import { ComponentProps, forwardRef } from 'react' + +import { palette } from '@/styles/palette' +import { typo } from '@/styles/typo' + +interface InputProps extends ComponentProps<'input'> { + width?: number + height?: number + placeholder: string + type?: string +} + +const RegisterInput = forwardRef(function Input( + { width, height = 46, type = 'text', placeholder, ...props }: InputProps, + inputRef, +) { + return ( + + + + ) +}) + +const StyleInputWrapper = styled.span` + display: flex; + justify-content: center; + align-items: center; + /* width: 100%; */ +` +const StyleInput = styled.input<{ widthProps?: number; heightProps?: number }>` + background-color: ${palette.WHITE}; + height: ${({ heightProps }) => (heightProps ? `${heightProps}px` : '46px')}; + border: 1px solid ${palette.GRAY200}; + border-radius: 10px; + width: ${({ widthProps }) => (widthProps ? `${widthProps}px` : '100%')}; + font-size: ${typo.Body_14()}; + /* box-shadow: 3px 3px 1px ${palette.GRAY200}; */ + padding-left: 18px; + padding-right: 18px; + color: ${palette.GRAY400}; +` + +export default RegisterInput diff --git a/src/components/common/SelectorButton/index.tsx b/src/components/common/SelectorButton/index.tsx index 035cd792..42f78790 100644 --- a/src/components/common/SelectorButton/index.tsx +++ b/src/components/common/SelectorButton/index.tsx @@ -1,8 +1,8 @@ import styled from '@emotion/styled' import { useState } from 'react' +import useInterestStore from '@/store/InterestStore' import { palette } from '@/styles/palette' - type SelectorButtonProps = { isDarkMode: boolean buttonName: string @@ -36,6 +36,7 @@ const SelectorButton = ({ : defaultSettings.defaultButtonColor const [backgroundColor, setBackgroundColor] = useState(initialBackgroundColor) const [currentTextColor, setCurrentTextColor] = useState(defaultSettings.textColor) + const { interestList, setInterestList } = useInterestStore() const handleButtonClick = () => { const isSelected = backgroundColor !== defaultSettings.selectedButtonColor @@ -48,6 +49,8 @@ const SelectorButton = ({ setBackgroundColor( isSelected ? defaultSettings.selectedButtonColor : defaultSettings.defaultButtonColor, ) + setInterestList([...interestList, buttonName]) + console.log(interestList) if (defaultSettings.textColor !== palette.WHITE) { setCurrentTextColor(isSelected ? palette.WHITE : defaultSettings.textColor) } diff --git a/src/components/common/SelectorButtonContainer/index.tsx b/src/components/common/SelectorButtonContainer/index.tsx index 5d4abafa..cdc94cae 100644 --- a/src/components/common/SelectorButtonContainer/index.tsx +++ b/src/components/common/SelectorButtonContainer/index.tsx @@ -1,13 +1,11 @@ import styled from '@emotion/styled' import { useState } from 'react' +import AlertText from '@/components/common/AlertText' +import CountNumber from '@/components/common/CountNumber' +import CustomSelectorButton from '@/components/common/CustomSelectorButton' +import SelectorButton from '@/components/common/SelectorButton' 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[] diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 027f0d17..baea0fb1 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,8 +1,16 @@ -import { http, HttpResponse } from 'msw' +import { rest } from 'msw' +const nickname = '주다다' export const handlers = [ - // example - http.get('/pets', () => { - return HttpResponse.json(['Tom', 'Jerry', 'Spike']) + rest.get(`/v1/users/duplicate?nickname=${nickname}`, async (req, res, ctx) => { + await sleep(200) + console.log(req) + return res(ctx.status(200), ctx.json(res)) }), ] + +async function sleep(timeout: number) { + return new Promise((resolve) => { + setTimeout(resolve, timeout) + }) +} diff --git a/src/mocks/worker.ts b/src/mocks/worker.ts index d3824e65..9fee342d 100644 --- a/src/mocks/worker.ts +++ b/src/mocks/worker.ts @@ -1,4 +1,4 @@ -import { setupWorker } from 'msw/browser' +import { setupWorker } from 'msw' import { handlers } from './handlers' diff --git a/src/pages/register/RegisterUser.tsx b/src/pages/register/RegisterUser.tsx index ed2687e7..a9ca6ac7 100644 --- a/src/pages/register/RegisterUser.tsx +++ b/src/pages/register/RegisterUser.tsx @@ -1,11 +1,200 @@ +import styled from '@emotion/styled' +import { useMutation } from '@tanstack/react-query' +import { useRef, useState } from 'react' +import { MdWbSunny } from 'react-icons/md' +import { useNavigate } from 'react-router-dom' import { useLocation } from 'react-router-dom' -const Register = () => { - const location = useLocation() - const { authCode } = location.state || {} +import { axiosAPI } from '@/apis/axios' +import AlertText from '@/components/common/AlertText' +import NormalButton from '@/components/common/Buttons/NormalButton' +import { FlexBox } from '@/components/common/Flexbox' +import RegisterInput from '@/components/common/RegisterInput' +import SelectorButtonContainer from '@/components/common/SelectorButtonContainer' +import Spacing from '@/components/common/Spacing' +import useInterestStore from '@/store/InterestStore' +import { palette } from '@/styles/palette' +import { typo } from '@/styles/typo' - console.log(authCode) - return
{'Register'}
-} +const RegisterUser = () => { + const InterestList = [ + '요리', + '맛집', + '음악', + '연애', + '패션', + '여행', + '운동', + '게임', + '재테크', + '자격증', + '외국어', + '전자기기', + '반려동물', + ] + const navigate = useNavigate() + const { authCode } = useLocation().state || {} + const inputRef = useRef(null) + const [doubleChecked, setDoubleChecked] = useState(false) + const [nicknameDuplicated, setNicknameDuplicated] = useState(null) + let nickname = '' + const { interestList } = useInterestStore() + + const handleClickDoubleCheck = async (nickname: string) => { + return await axiosAPI.get(`/v1/users/duplicate?nickname=${nickname}`) + } + const doubleCheckMutation = useMutation((nickname: string) => handleClickDoubleCheck(nickname), { + onSuccess: (response) => { + if (response.status == 200) { + //사용가능한 닉네임일 경우 + setDoubleChecked(true) + setNicknameDuplicated(false) + } else { + //이미 사용 중인 닉네임일 경우 + setDoubleChecked(true) + setNicknameDuplicated(true) + } + }, + onError: () => {}, + }) + const doubleCheckNickName = async () => { + if (inputRef.current && inputRef.current.value.length == 0) { + setDoubleChecked(null) + return + } + if (inputRef.current == null) return -export default Register + nickname = inputRef.current.value + doubleCheckMutation.mutate(nickname) + } + const formValidation = () => { + if (nickname.length === 0) return false + else if (doubleChecked) return false + else if (nicknameDuplicated) return false + else return true + } + const submitUserProfileData = () => { + if (!formValidation()) { + console.log(nickname, interestList) + if (doubleChecked && inputRef.current !== null && interestList.length > 0) { + const body = { + authCode: authCode, + nickname: nickname, + keywords: interestList, + oAuthProvider: 'KAKAO', + } + console.log(body) + registerMutation.mutate(body) + } + } + } + const registerPost = async (body: object) => { + return await axiosAPI.post('/v1/users/sign-up', body) + } + const registerMutation = useMutation((body: object) => registerPost(body), { + onSuccess: (response) => { + console.log(response) + navigate('/register/company') + }, + onError: (err) => { + console.log(err) + }, + }) + return ( + + + + + + {'프로필 등록'} + + + + + + + + + + + doubleCheckNickName()}> + {'중복확인'} + + + {nicknameDuplicated === null && doubleChecked === null && ( + + {'닉네임 중복검사를 해주세요!'} + + )} + {nicknameDuplicated === false && doubleChecked && ( + + {'사용 가능한 닉네임입니다.'} + + )} + {nicknameDuplicated === true && doubleChecked && ( + + {'이미 사용 중인 닉네임입니다.'} + + )} + + {'관심사'} + + + + + + + {'다음'} + + + + ) +} +const StyleRegisterWrapper = styled.div` + background-color: ${palette.GRAY100}; + height: 100%; +` +const StyleRegisterHeader = styled.div`` +const StyleHeaderText = styled.span` + font-size: ${typo.Body_24()}; +` +const StyleDivider = styled.hr` + height: 1px; + background-color: ${palette.GRAY200}; + border: 0; +` +const StyleInterestText = styled.div` + padding: 10px; + margin-left: 25px; + font-size: ${typo.Body_18()}; +` +const StyleSubmitButtonWrapper = styled.div` + display: flex; + width: 100%; + justify-content: center; + position: absolute; + bottom: 22px; +` +const StyleIcon = styled.button` + cursor: pointer; +` +export default RegisterUser diff --git a/src/store/InterestStore.tsx b/src/store/InterestStore.tsx new file mode 100644 index 00000000..20a4f89b --- /dev/null +++ b/src/store/InterestStore.tsx @@ -0,0 +1,13 @@ +import { create } from 'zustand' + +type InterestList = { + interestList: string[] + setInterestList: (list: string[]) => void +} + +const useInterestStore = create((set) => ({ + interestList: [], + setInterestList: (list) => set({ interestList: list }), +})) + +export default useInterestStore From 067e9b93a840495717930fec9dd3e2698c32f2eb Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Sun, 12 Nov 2023 20:23:36 +0900 Subject: [PATCH 047/180] =?UTF-8?q?feature:=20ChatList=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=A0=9C=EC=9E=91=20+=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Home 레이아웃 수정 - PageContainer 스타일을 동적으로 받도록 수정 * refactor: PageContainer 컴포넌트 수정 - style props 받을 수 있도록 수정 - 다크모드 받을 수 있도록 수정 * refactor: 반응형 디자인 - AppHeader: max-width: 280px일 때 텍스트 폰트 줄이도록 수정 - Avatar: max-width: 280px일 때 아바타 크기 줄이도록 수정 * refactor: PageHeader 컴포넌트 수정 - max-width: 414px로 가지도록 수정 - cursor: pointer 가지도록 수정 * refactor: AvatarGroup 수정 - style props 받도록 수정 - AvatarGroup이 높이를 가지도록 수정 * refactor: Card 컴포넌트 수정 - PageContainer 수정에 따른 리팩토링 * feature: ChatList 페이지 구현 - chatRoomList 더미데이터 추가 - ChatRoomBubbles: chatRoomList를 받아서 ChatRoomBubble 렌더링 - ChatRoomBubble: 채팅방 생성 시점 기준 1일 이후를 D-Day로 설정한 타이머 추가 - max-width: 280px일 때 column이 하나로만 나오도록 반응형 디자인 * refactor: ChatRoomBubble 빌드 오류 수정 * fix: merge conflict 해결 * refactor: Card 컴포넌트의 Styled 태그 코드들 하단으로 이동 * hotfix: VITE_BASE_URL 환경변수 설정 (#113) * fix: VITE_BASE_URL 환경변수 설정 * fix: 띄어쓰기 오류 수정 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> * feature: 이전대화방 MSW 설정 + framer-motion 효과 추가 - /api/v1/histories로 이전 대화방 목록 요청하도록 세팅 (+useQuery) - 로컬에서도 MSW 동작할 수 있도록 설정 --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> --- src/apis/axios.ts | 5 +- src/apis/chatList/ChatListApi.ts | 13 + src/components/chatList/ChatRoomBubble.tsx | 179 +++++++++++++ src/components/chatList/ChatRoomBubbles.tsx | 51 ++++ src/components/common/AppHeader/index.tsx | 33 ++- src/components/common/Avatar/index.tsx | 9 + .../IconButton/ParticularTopicButton.tsx | 1 + src/components/common/PageContainer/index.tsx | 16 +- src/components/common/PageHeader/index.tsx | 35 ++- src/components/home/AvatarGroup.tsx | 45 ++-- src/components/home/Card.tsx | 235 +++++++++++++----- src/mocks/handlers.ts | 29 +++ src/pages/chatList/ChatList.tsx | 62 ++++- src/pages/home/Home.tsx | 97 ++++---- vite.config.ts | 2 +- 15 files changed, 672 insertions(+), 140 deletions(-) create mode 100644 src/apis/chatList/ChatListApi.ts create mode 100644 src/components/chatList/ChatRoomBubble.tsx create mode 100644 src/components/chatList/ChatRoomBubbles.tsx diff --git a/src/apis/axios.ts b/src/apis/axios.ts index 5c964b6b..172182fb 100644 --- a/src/apis/axios.ts +++ b/src/apis/axios.ts @@ -3,7 +3,10 @@ import axios from 'axios' import useAuthStore from '@/store/AuthStore' export const axiosAPI = axios.create({ - baseURL: import.meta.env.VITE_BASE_URL, + baseURL: + process.env.NODE_ENV === 'development' + ? 'http://localhost:5173' + : import.meta.env.VITE_BASE_URL, }) // Add a request interceptor diff --git a/src/apis/chatList/ChatListApi.ts b/src/apis/chatList/ChatListApi.ts new file mode 100644 index 00000000..fa588a31 --- /dev/null +++ b/src/apis/chatList/ChatListApi.ts @@ -0,0 +1,13 @@ +import { axiosAPI } from '@/apis/axios' + +const ChatListApi = { + // TODO: zustand로 AccessToken 받아서 요청하기 (백엔드 개발 완료 후) + GET_CHAT_LIST: async () => { + const response = await axiosAPI.get(`/v1/histories`) + return { + data: response.data, + } + }, +} + +export default ChatListApi diff --git a/src/components/chatList/ChatRoomBubble.tsx b/src/components/chatList/ChatRoomBubble.tsx new file mode 100644 index 00000000..ad928411 --- /dev/null +++ b/src/components/chatList/ChatRoomBubble.tsx @@ -0,0 +1,179 @@ +import styled from '@emotion/styled' +import { timer } from 'd3' +import { useEffect, useState } from 'react' + +import Spacing from '@/components/common/Spacing' +import { Text, TextWrapper } from '@/components/common/Text' +import AvatarGroup from '@/components/home/AvatarGroup' +import { palette } from '@/styles/palette' + +type ChatRoomBubbleProps = { + title: string + participants: string[] + createdAt: string + isDarkMode: boolean +} + +const ChatRoomBubble = ({ title, participants, createdAt, isDarkMode }: ChatRoomBubbleProps) => { + const [timeRemaining, setTimeRemaining] = useState({ + hours: '00', + minutes: '00', + seconds: '00', + }) + + useEffect(() => { + const createdTime = new Date(new Date(createdAt).getTime() + 24 * 60 * 60 * 1000) + + const updateTimer = () => { + const currentTime = new Date() + const timeDifference = createdTime.getTime() - currentTime.getTime() + + if (timeDifference > 0) { + const leftHours = String(Math.floor(timeDifference / (1000 * 60 * 60))).padStart(2, '0') + const leftMinutes = String( + Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60)), + ).padStart(2, '0') + const leftSeconds = String(Math.floor((timeDifference % (1000 * 60)) / 1000)).padStart( + 2, + '0', + ) + setTimeRemaining({ hours: leftHours, minutes: leftMinutes, seconds: leftSeconds }) + } else { + timerInstance.stop() + setTimeRemaining({ hours: '00', minutes: '00', seconds: '00' }) + } + } + + const timerInstance = timer(updateTimer) + + return () => timerInstance.stop() + }, []) + + return ( + + + {title} + + + + + + + {'참여자'} + + + {participants.map((participant, index) => { + return ( + + {participant} + {index !== participants.length - 1 && ', '} + + ) + })} + + + + + + {'남은시간'} + + {`${timeRemaining.hours}시간 ${timeRemaining.minutes}분 ${timeRemaining.seconds}초`} + + + + ) +} + +const StyledChatRoomBubble = styled.div<{ isDarkMode: boolean }>` + display: flex; + flex-direction: column; + width: 100%; + max-width: 161px; + height: 161px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; + border-radius: 20px; + box-shadow: 0px 4px 20px 0px rgba(0, 0, 0, 0.05); + position: relative; + cursor: pointer; +` + +const StyledAvatarGroupWrapper = styled.div` + position: relative; + width: 100%; + flex: 1; + display: flex; + flex-direction: column; + justify-content: flex-end; + padding-bottom: 10px; +` + +const StyledChatRoomBubbleTextWrapper = styled(TextWrapper)` + flex-direction: row; + justify-content: space-between; + padding: 0 10px; +` + +const StyledChatRoomBubbleParticipantsWrapper = styled(TextWrapper)` + flex-direction: row; + justify-content: flex-end; + max-width: 80px; + flex-wrap: wrap; + row-gap: 3px; +` + +export default ChatRoomBubble diff --git a/src/components/chatList/ChatRoomBubbles.tsx b/src/components/chatList/ChatRoomBubbles.tsx new file mode 100644 index 00000000..3d50c91a --- /dev/null +++ b/src/components/chatList/ChatRoomBubbles.tsx @@ -0,0 +1,51 @@ +import styled from '@emotion/styled' + +import ChatRoomBubble from './ChatRoomBubble' + +const StyledChatRoomBubbleWrapper = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr; + justify-items: center; + align-items: center; + width: 100%; + padding: 25% 5% 5%; + gap: 30px; + row-gap: 50px; + + @media (max-width: 280px) { + grid-template-columns: 1fr; + padding: 30% 5% 5%; + } +` + +type ChatRoom = { + title: string + participants: string[] + createdAt: string +} + +type ChatRoomBubblesProps = { + chatRoomList: ChatRoom[] + isDarkMode: boolean +} + +const ChatRoomBubbles = ({ chatRoomList, isDarkMode }: ChatRoomBubblesProps) => { + return ( + + {chatRoomList.map((chatRoom, idx) => { + return ( + + ) + })} + + ) +} + +export default ChatRoomBubbles diff --git a/src/components/common/AppHeader/index.tsx b/src/components/common/AppHeader/index.tsx index d69edb38..f0b32e6e 100644 --- a/src/components/common/AppHeader/index.tsx +++ b/src/components/common/AppHeader/index.tsx @@ -19,6 +19,29 @@ const StyleAppHeader = styled.div<{ height?: string }>` padding: 6.5% 5% 7%; ` +const StyledAppHeaderLargeText = styled(Text)>` + font: Body_24; + font-weight: 600; + letter-spacing: -0.5; + color: ${({ isDarkMode }) => (isDarkMode ? palette.WHITE : palette.DARK_WHITE)}; + margin-right: 5px; + + @media (max-width: 280px) { + font-size: 1.25rem; + } +` + +const StyledAppHeaderSmallText = styled(Text)>` + font: Body_18; + font-weight: 600; + letter-spacing: -0.5; + color: ${({ isDarkMode }) => (isDarkMode ? palette.WHITE : palette.DARK_WHITE)}; + + @media (max-width: 280px) { + font-size: 0.85rem; + } +` + type AppHeaderProps = { nickname: string isDarkMode: boolean @@ -77,7 +100,8 @@ const AppHeader = ({ nickname, isDarkMode, height, toggleDarkMode }: AppHeaderPr )} - {nickname} - - + {'님, 안녕하세요! 오늘도 즐거운 커피밋! ☕️'} - + ) diff --git a/src/components/common/Avatar/index.tsx b/src/components/common/Avatar/index.tsx index 16b5cf22..0182e0b3 100644 --- a/src/components/common/Avatar/index.tsx +++ b/src/components/common/Avatar/index.tsx @@ -23,6 +23,15 @@ const StyledAvatar = styled.div` 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')}; + + @media (max-width: 280px) { + width: ${(props) => + typeof props.width === 'number' ? `${props.width * 0.95}px` : `calc(${props.width} * 0.95)`}; + height: ${(props) => + typeof props.height === 'number' + ? `${props.height * 0.95}px` + : `calc(${props.height} * 0.95)`}; + } ` /** diff --git a/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx b/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx index 2d3a0abc..39d92e99 100644 --- a/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx +++ b/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx @@ -12,6 +12,7 @@ type ParticularTopicButtonProps = { /** * @param isDarkMode - 다크모드 여부 + * @param moveToParticularTopic? - 특정 주제로 대화하기 버튼 클릭 시 이동할 페이지 */ const ParticularTopicButton = ({ isDarkMode, diff --git a/src/components/common/PageContainer/index.tsx b/src/components/common/PageContainer/index.tsx index d55a87bc..61c43496 100644 --- a/src/components/common/PageContainer/index.tsx +++ b/src/components/common/PageContainer/index.tsx @@ -6,7 +6,6 @@ import { palette } from '@/styles/palette' const StyledPageContainer = styled.div<{ height: string; isDarkMode: boolean }>` width: 100%; height: ${({ height }) => height}; - padding: 5% 5% 5%; background-color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_BLUE : palette.GRAY100)}; border-top-right-radius: 20px; border-top-left-radius: 20px; @@ -18,19 +17,26 @@ const StyledPageContainer = styled.div<{ height: string; isDarkMode: boolean }>` type PageContainerProps = { children: ReactNode - height?: string isDarkMode: boolean + height?: string + style?: React.CSSProperties } /** * * @param children - 자식 컴포넌트 + * @param isDarkMode - 다크 모드 여부 * @param height - 높이 - * @param isDarkMode - 다크모드 여부 + * @param style - (Optional) React.CSSProperties */ -const PageContainer = ({ children, height = '77%', isDarkMode }: PageContainerProps) => { +const PageContainer = ({ + children, + height = '77%', + isDarkMode = false, + ...props +}: PageContainerProps) => { return ( - + {children} ) diff --git a/src/components/common/PageHeader/index.tsx b/src/components/common/PageHeader/index.tsx index 573e24a9..ca5cd0d1 100644 --- a/src/components/common/PageHeader/index.tsx +++ b/src/components/common/PageHeader/index.tsx @@ -3,10 +3,11 @@ import styled from '@emotion/styled' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -const StylePageHeader = styled.div<{ +const StyledPageHeader = styled.div<{ isDarkMode?: boolean hasBackground?: boolean }>` + max-width: 414px; width: 100%; height: 72px; padding: 0 18px; @@ -33,7 +34,7 @@ const StylePageHeader = styled.div<{ : 'transparent'}; ` -const StyleIcon = styled.div` +const StyledIcon = styled.div` width: 38px; height: 38px; display: flex; @@ -49,6 +50,7 @@ type PageHeaderProps = { isDarkMode?: boolean hasBackground?: boolean onClick?: () => void + style?: React.CSSProperties } /** @@ -60,10 +62,23 @@ type PageHeaderProps = { * @param onClick - (Optional) 클릭 이벤트 */ -const PageHeader = ({ leftIcon, rightIcon, title, isDarkMode, hasBackground }: PageHeaderProps) => { +const PageHeader = ({ + leftIcon, + rightIcon, + title, + isDarkMode, + hasBackground, + ...props +}: PageHeaderProps) => { return ( - - {leftIcon} + + + {leftIcon} + {title} - {rightIcon} - + + {rightIcon} + + ) } diff --git a/src/components/home/AvatarGroup.tsx b/src/components/home/AvatarGroup.tsx index 9c438643..4ffbbfdb 100644 --- a/src/components/home/AvatarGroup.tsx +++ b/src/components/home/AvatarGroup.tsx @@ -2,44 +2,61 @@ import styled from '@emotion/styled' import Avatar from '@/components/common/Avatar' -const StyleAvatarGroup = styled.div` +const StyledAvatarGroup = styled.div<{ avatarHeight: number }>` display: flex; - position: relative; - justify-content: flex-end; align-items: center; width: 100%; - position: relative; + height: ${({ avatarHeight }) => `${avatarHeight}px`}; ` -const StyleAvatarWrapper = styled.div` +const StyledAvatarWrapper = styled.div` position: absolute; - right: 0; display: flex; justify-content: flex-end; align-items: center; - width: 100%; ` type AvatarGroupProps = { avatarList: string[] + avatarWidth?: number + avatarHeight?: number + style?: React.CSSProperties } -const AvatarGroup = ({ avatarList }: AvatarGroupProps) => { +/** + * @param avatarList: string[] - 아바타 이미지 리스트 + * @param avatarWidth?: number - 아바타 이미지의 width 값 + * @param avatarHeight?: number - 아바타 이미지의 height 값 + * @param style?: React.CSSProperties - 컴포넌트 스타일 + */ + +const AvatarGroup = ({ + avatarList, + avatarWidth = 34, + avatarHeight = 34, + ...props +}: AvatarGroupProps) => { return ( - + {avatarList.map((avatar, index) => { return ( - - - + + ) })} - + ) } diff --git a/src/components/home/Card.tsx b/src/components/home/Card.tsx index 40d3e007..1087ed60 100644 --- a/src/components/home/Card.tsx +++ b/src/components/home/Card.tsx @@ -1,78 +1,84 @@ import styled from '@emotion/styled' +import { timer } from 'd3' import { AnimatePresence, motion } from 'framer-motion' -import { useEffect } from 'react' +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 useTimerStore from '@/store/TimerStore' +import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -import CardBottom from './CardBottom' -import CardHeader from './CardHeader' -import CardMiddle from './CardMiddle' +import Tip from './Tip' -const StyledCard = styled(motion.div)<{ - isDarkMode: boolean -}>` - width: 100%; - height: 348px; - border-radius: 20px; - box-shadow: ${palette.SHADOW}; - display: flex; - flex-direction: column; - margin: 0 auto; - justify-content: center; - align-items: center; - padding: 19px 7px 15px; - background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; -` - -const StyledWatingWrapper = styled(motion.div)` - width: 100%; - display: flex; - flex-direction: column; - justify-content: space-between; - flex: 1; -` - -const watingCounter = { - hidden: { opacity: 0 }, - visible: { opacity: 1, transition: { type: 'spring', damping: 12, duration: 0.5 } }, - exit: { opacity: 0, transition: { duration: 1 } }, -} +type TimerRefType = ReturnType | null type CardProps = { + isMatching: boolean isDarkMode: boolean + onClick: () => void } /** + * @param isMatching - 현재 매칭 여부 * @param isDarkMode - 다크모드 여부 + * @param onClick - 매칭 버튼 클릭 이벤트 */ -const Card = ({ isDarkMode }: CardProps) => { - const { time, isRunning, startTimer, resetTimer } = useTimerStore() +const Card = ({ isMatching, isDarkMode, onClick }: CardProps) => { + const [time, setTime] = useState(0) + const timerRef = useRef(null) - window.onload = () => { - const navigationType = ( - performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming - ).type - if (navigationType !== 'reload') { - resetTimer() + 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 (isRunning) { - startTimer() - } else if (!isRunning) { - resetTimer() + 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() + } } - }, [isRunning, startTimer, resetTimer]) + + return () => { + if (timerRef.current) { + timerRef.current.stop() + } + } + }, [isMatching]) return ( - - {!isRunning ? ( + + {!isMatching ? ( { exit={'exit'} variants={watingCounter} > - + ) : ( - - + + + + {'3'} + + + {'/5'} + + + - - - - + + + {formatTime(time)} + + + + {'매칭 취소'} + + + + {'매칭 중'}    + + + + + + + + )} - + ) } +const StyleCard = styled(motion.div)<{ + isDarkMode: boolean +}>` + width: 100%; + height: 348px; + border-radius: 20px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; + box-shadow: ${palette.SHADOW}; + display: flex; + flex-direction: column; + margin: 0 auto; + justify-content: center; + align-items: center; + padding: 5% 1% 5%; +` + +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 5%; + position: relative; +` + +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`` + export default Card diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index baea0fb1..754b9948 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -7,6 +7,35 @@ export const handlers = [ console.log(req) return res(ctx.status(200), ctx.json(res)) }), + http.get('/v1/histories', () => { + return HttpResponse.json([ + { + title: '🥤️ 차가운 아메리카노-6', + participants: ['우땅', '빅맘', '롤로노아 조로'], + createdAt: '2023-11-05T22:00:00', + }, + { + title: '🧃 미지근한 사과주스-23', + participants: ['우땅', '빅맘', '루피'], + createdAt: '2023-11-05T22:30:00', + }, + { + title: '☕️ 따뜻한 아메리카노-10', + participants: ['우땅', '빅맘', '나미'], + createdAt: '2023-11-05T23:00:00', + }, + { + title: '🍰️ 차가운 케이크-8', + participants: ['우땅', '빅맘', '상디'], + createdAt: '2023-11-05T24:00:00', + }, + { + title: '🍦 고소한 아이스크림-2', + participants: ['우땅', '빅맘', '우솝'], + createdAt: '2023-11-06T00:10:50', + }, + ]) + }), ] async function sleep(timeout: number) { diff --git a/src/pages/chatList/ChatList.tsx b/src/pages/chatList/ChatList.tsx index d73e7bc2..595943d7 100644 --- a/src/pages/chatList/ChatList.tsx +++ b/src/pages/chatList/ChatList.tsx @@ -1,5 +1,65 @@ +import { useQuery } from '@tanstack/react-query' +import { motion } from 'framer-motion' +import { useNavigate } from 'react-router-dom' + +import ChatListApi from '@/apis/chatList/ChatListApi' +import ChatRoomBubbles from '@/components/chatList/ChatRoomBubbles' +import BackChevron from '@/components/common/BackChevron' +import GradationBackground from '@/components/common/GradationBackground' +import NavigationBar from '@/components/common/NavigationBar' +import PageContainer from '@/components/common/PageContainer' +import PageHeader from '@/components/common/PageHeader' +import Spacing from '@/components/common/Spacing' +import useThemeStore from '@/store/ThemeStore' + const ChatList = () => { - return
{'ChatList'}
+ const isDarkMode = useThemeStore((state) => state.isDarkMode) + const navigate = useNavigate() + + // TODO: TanStack의 useQuery를 사용하여 채팅방 목록 가져오기 + // 일단 MSW로 mock data를 만들어서 사용 + const { data, isSuccess } = useQuery(['chatRoomList'], ChatListApi.GET_CHAT_LIST) + + const containerVariants = { + initial: { opacity: 0 }, + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { delay: 0, duration: 0.5 }, + }, + } + + return ( + + + + { + navigate(-1) + }} + /> + } + isDarkMode={isDarkMode} + hasBackground={true} + style={{ + position: 'fixed', + zIndex: 1, + }} + /> + {isSuccess && ( + + + + )} + + + + ) } export default ChatList diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 238d5ebd..97813355 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -1,3 +1,5 @@ +import { useState } from 'react' + import AppHeader from '@/components/common/AppHeader' import { ParticularTopicButton } from '@/components/common/Buttons/IconButton' import GradationBackground from '@/components/common/GradationBackground' @@ -6,58 +8,69 @@ import PageContainer from '@/components/common/PageContainer' import { Text } from '@/components/common/Text' import Card from '@/components/home/Card' import useToast from '@/hooks/useToast' -import useThemeStore from '@/store/ThemeStore' // Import the store +import useThemeStore from '@/store/ThemeStore' import { palette } from '@/styles/palette' const Home = () => { const nickname = '우땅' const isDarkMode = useThemeStore((state) => state.isDarkMode) const toggleDarkMode = useThemeStore((state) => state.toggleDarkMode) + const [isMatching, setIsMatching] = useState(false) const { showToast } = useToast() return ( - <> - - - - - {'진행중인 매칭'} - - - - {'커피밋의 추천기능'} - - { - showToast({ - message: '아직 준비중인 기능입니다!', - type: 'info', - isDarkMode, - }) - }} - /> - - - - + + + + + {'진행중인 매칭'} + + { + setIsMatching((prev) => !prev) + }} + isDarkMode={isDarkMode} + /> + + {'커피밋의 추천기능'} + + { + showToast({ + message: '아직 준비중인 기능입니다!', + type: 'info', + isDarkMode, + }) + }} + /> + + + ) } diff --git a/vite.config.ts b/vite.config.ts index 33a6dcae..2553920d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ server: { proxy: { '/api': { - target: 'http://13.125.194.230', + target: process.env.SERVER_EC2_URL, changeOrigin: true, }, }, From 527dffe04346b7e4168ae961dc1734714f25719b Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Mon, 13 Nov 2023 08:22:32 +0900 Subject: [PATCH 048/180] =?UTF-8?q?[Fix]=20=EA=B4=80=EC=8B=AC=EC=82=AC=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=ED=95=B4=EC=A0=9C=20=EC=8B=9C=20=EC=A0=84?= =?UTF-8?q?=EC=97=AD=20store=EC=97=90=20=EB=B0=98=EC=98=81=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20(#121)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix : axios 요청이 가지 않는 에러 해결 * chore : 주석 제거 * fix : 관심사 버튼 해제 시 전역 store에 반영되지 않는 문제 해결 --- package-lock.json | 246 +++++++----------- package.json | 2 +- src/apis/axios.ts | 6 +- .../common/SelectorButton/index.tsx | 7 +- src/mocks/handlers.ts | 21 +- src/mocks/worker.ts | 2 +- src/pages/register/RegisterUser.tsx | 67 +++-- 7 files changed, 168 insertions(+), 183 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3a0967a7..14692153 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "husky": "^8.0.0", "lint-staged": "^15.0.1", - "msw": "^1.3.2", + "msw": "^2.0.5", "prettier": "^3.0.3", "typescript": "^5.0.2", "vite": "^4.4.5", @@ -559,6 +559,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", @@ -1213,44 +1240,29 @@ } }, "node_modules/@mswjs/cookies": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.2.tgz", - "integrity": "sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-1.1.0.tgz", + "integrity": "sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==", "dev": true, - "dependencies": { - "@types/set-cookie-parser": "^2.4.0", - "set-cookie-parser": "^2.4.6" - }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/@mswjs/interceptors": { - "version": "0.17.10", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.17.10.tgz", - "integrity": "sha512-N8x7eSLGcmUFNWZRxT1vsHvypzIRgQYdG0rJey/rZCy6zT/30qDt8Joj7FxzGNLSwXbeZqJOMqDurp7ra4hgbw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.25.11.tgz", + "integrity": "sha512-27aonWAjdeoZN4j4j6QvePOSOacQUucFRUESAU8FUXsmmagDjmyOi4/NHizxzY/NHSk/HAyqF/IMhl+9puhqNw==", "dev": true, "dependencies": { - "@open-draft/until": "^1.0.3", - "@types/debug": "^4.1.7", - "@xmldom/xmldom": "^0.8.3", - "debug": "^4.3.3", - "headers-polyfill": "3.2.5", + "@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.2.4", - "web-encoding": "^1.1.5" + "strict-event-emitter": "^0.5.1" }, "engines": { - "node": ">=14" - } - }, - "node_modules/@mswjs/interceptors/node_modules/strict-event-emitter": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz", - "integrity": "sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==", - "dev": true, - "dependencies": { - "events": "^3.3.0" + "node": ">=18" } }, "node_modules/@nodelib/fs.scandir": { @@ -1288,10 +1300,26 @@ "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": "1.0.3", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", - "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==", + "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": { @@ -2451,15 +2479,6 @@ "@types/d3-selection": "*" } }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "dev": true, - "dependencies": { - "@types/ms": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", @@ -2499,12 +2518,6 @@ "dev": true, "peer": true }, - "node_modules/@types/ms": { - "version": "0.7.34", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", - "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", - "dev": true - }, "node_modules/@types/node": { "version": "20.8.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz", @@ -2556,14 +2569,11 @@ "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", "dev": true }, - "node_modules/@types/set-cookie-parser": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.6.tgz", - "integrity": "sha512-tjIRMxGztGfIbW2/d20MdJmAPZbabtdW051cKfU+nvZXUnKKifHbY2CyL/C0EGabUB8ahIRjanYzTqJUQR8TAQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } + "node_modules/@types/statuses": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.4.tgz", + "integrity": "sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==", + "dev": true }, "node_modules/@types/stompjs": { "version": "2.3.7", @@ -2999,22 +3009,6 @@ "vite": "^4" } }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", - "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@zxing/text-encoding": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", - "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", - "dev": true, - "optional": true - }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -3762,9 +3756,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "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" @@ -5092,15 +5086,6 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "dev": true }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -5712,9 +5697,9 @@ } }, "node_modules/headers-polyfill": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.2.5.tgz", - "integrity": "sha512-tUCGvt191vNSQgttSyJoibR+VO+I6+iCHIUdhzEMJKE+EAL8BwCN7fUOZlY4ofOelNHsK+gEjxB/B+9N3EWtdA==", + "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": { @@ -6043,22 +6028,6 @@ "node": ">=12" } }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -6949,29 +6918,32 @@ "dev": true }, "node_modules/msw": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/msw/-/msw-1.3.2.tgz", - "integrity": "sha512-wKLhFPR+NitYTkQl5047pia0reNGgf0P6a1eTnA5aNlripmiz0sabMvvHcicE8kQ3/gZcI0YiPFWmYfowfm3lA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.0.5.tgz", + "integrity": "sha512-vjI7rEtv879CvbTGUfcI6JjKVE/XX/MU8uggiCkAzfl930U8LSp0h81RnfNfVOllELlbLTCvGkpV3e7TuVBGdA==", "dev": true, "hasInstallScript": true, "dependencies": { - "@mswjs/cookies": "^0.2.2", - "@mswjs/interceptors": "^0.17.10", - "@open-draft/until": "^1.0.3", + "@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.1.0", + "@mswjs/interceptors": "^0.25.11", + "@open-draft/until": "^2.1.0", "@types/cookie": "^0.4.1", "@types/js-levenshtein": "^1.1.1", - "chalk": "^4.1.1", + "@types/statuses": "^2.0.1", + "chalk": "^4.1.2", "chokidar": "^3.4.2", - "cookie": "^0.4.2", "graphql": "^16.8.1", - "headers-polyfill": "3.2.5", + "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.4.3", + "strict-event-emitter": "^0.5.0", "type-fest": "^2.19.0", "yargs": "^17.3.1" }, @@ -6979,14 +6951,14 @@ "msw": "cli/index.js" }, "engines": { - "node": ">=14" + "node": ">=18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mswjs" }, "peerDependencies": { - "typescript": ">= 4.4.x <= 5.2.x" + "typescript": ">= 4.7.x <= 5.2.x" }, "peerDependenciesMeta": { "typescript": { @@ -8196,12 +8168,6 @@ "node": ">=10" } }, - "node_modules/set-cookie-parser": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", - "dev": true - }, "node_modules/set-function-length": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", @@ -8342,10 +8308,19 @@ "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.4.6", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", - "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", + "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": { @@ -8902,19 +8877,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9018,18 +8980,6 @@ "defaults": "^1.0.3" } }, - "node_modules/web-encoding": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", - "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", - "dev": true, - "dependencies": { - "util": "^0.12.3" - }, - "optionalDependencies": { - "@zxing/text-encoding": "0.9.0" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index d5f0d6b6..a821a0af 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "husky": "^8.0.0", "lint-staged": "^15.0.1", - "msw": "^1.3.2", + "msw": "^2.0.5", "prettier": "^3.0.3", "typescript": "^5.0.2", "vite": "^4.4.5", diff --git a/src/apis/axios.ts b/src/apis/axios.ts index 172182fb..d1c72937 100644 --- a/src/apis/axios.ts +++ b/src/apis/axios.ts @@ -1,6 +1,6 @@ import axios from 'axios' -import useAuthStore from '@/store/AuthStore' +// import useAuthStore from '@/store/AuthStore' export const axiosAPI = axios.create({ baseURL: @@ -14,8 +14,8 @@ axiosAPI.interceptors.request.use( function (config) { // 요청 바로 직전 // axios 설정값에 대해 작성합니다. - const { authTokens } = useAuthStore() - config.headers['Authorization'] = `Bearer ${authTokens}` + // const { authTokens } = useAuthStore() + // config.headers['Authorization'] = `Bearer ${authTokens}` return config }, diff --git a/src/components/common/SelectorButton/index.tsx b/src/components/common/SelectorButton/index.tsx index 42f78790..4f058f45 100644 --- a/src/components/common/SelectorButton/index.tsx +++ b/src/components/common/SelectorButton/index.tsx @@ -46,15 +46,18 @@ const SelectorButton = ({ return } setIsButtonSelected(isSelected) + + setInterestList([...interestList, buttonName]) setBackgroundColor( isSelected ? defaultSettings.selectedButtonColor : defaultSettings.defaultButtonColor, ) - setInterestList([...interestList, buttonName]) - console.log(interestList) + + console.log(interestList.filter((v) => v != buttonName)) if (defaultSettings.textColor !== palette.WHITE) { setCurrentTextColor(isSelected ? palette.WHITE : defaultSettings.textColor) } if (isButtonClicked) isButtonClicked(isSelected) + if (!isSelected) setInterestList(interestList.filter((v) => v != buttonName)) } return ( diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 754b9948..60ebacce 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,12 +1,6 @@ -import { rest } from 'msw' +import { http, HttpResponse } from 'msw' const nickname = '주다다' - export const handlers = [ - rest.get(`/v1/users/duplicate?nickname=${nickname}`, async (req, res, ctx) => { - await sleep(200) - console.log(req) - return res(ctx.status(200), ctx.json(res)) - }), http.get('/v1/histories', () => { return HttpResponse.json([ { @@ -36,10 +30,11 @@ export const handlers = [ }, ]) }), -] -async function sleep(timeout: number) { - return new Promise((resolve) => { - setTimeout(resolve, timeout) - }) -} + http.get(`/v1/users/duplicate?nickname=${nickname}`, () => { + return new HttpResponse(null, { + status: 200, + statusText: 'Out Of Apples', + }) + }), +] diff --git a/src/mocks/worker.ts b/src/mocks/worker.ts index 9fee342d..d3824e65 100644 --- a/src/mocks/worker.ts +++ b/src/mocks/worker.ts @@ -1,4 +1,4 @@ -import { setupWorker } from 'msw' +import { setupWorker } from 'msw/browser' import { handlers } from './handlers' diff --git a/src/pages/register/RegisterUser.tsx b/src/pages/register/RegisterUser.tsx index a9ca6ac7..238f0c02 100644 --- a/src/pages/register/RegisterUser.tsx +++ b/src/pages/register/RegisterUser.tsx @@ -12,7 +12,10 @@ import { FlexBox } from '@/components/common/Flexbox' import RegisterInput from '@/components/common/RegisterInput' import SelectorButtonContainer from '@/components/common/SelectorButtonContainer' import Spacing from '@/components/common/Spacing' +import useToast from '@/hooks/useToast' +import useAuthStore from '@/store/AuthStore' import useInterestStore from '@/store/InterestStore' +import useThemeStore from '@/store/ThemeStore' import { palette } from '@/styles/palette' import { typo } from '@/styles/typo' @@ -39,14 +42,18 @@ const RegisterUser = () => { const [nicknameDuplicated, setNicknameDuplicated] = useState(null) let nickname = '' const { interestList } = useInterestStore() + const { provider } = useAuthStore() + const { showToast } = useToast() + const isDarkMode = useThemeStore((state) => state.isDarkMode) - const handleClickDoubleCheck = async (nickname: string) => { + const getNicknameValid = async (nickname: string) => { return await axiosAPI.get(`/v1/users/duplicate?nickname=${nickname}`) } - const doubleCheckMutation = useMutation((nickname: string) => handleClickDoubleCheck(nickname), { + const doubleCheckMutation = useMutation((nickname: string) => getNicknameValid(nickname), { onSuccess: (response) => { if (response.status == 200) { //사용가능한 닉네임일 경우 + console.log(response) setDoubleChecked(true) setNicknameDuplicated(false) } else { @@ -58,30 +65,53 @@ const RegisterUser = () => { onError: () => {}, }) const doubleCheckNickName = async () => { - if (inputRef.current && inputRef.current.value.length == 0) { + if (inputRef.current !== null && inputRef.current.value.length == 0) { setDoubleChecked(null) return } - if (inputRef.current == null) return - - nickname = inputRef.current.value - doubleCheckMutation.mutate(nickname) + if (inputRef.current !== null) { + nickname = inputRef.current.value + doubleCheckMutation.mutate(nickname) + // const response = getNicknameValid(nickname) + // console.log(response) + } } const formValidation = () => { - if (nickname.length === 0) return false - else if (doubleChecked) return false - else if (nicknameDuplicated) return false - else return true + console.log(doubleChecked) + if (inputRef.current !== null && inputRef.current.value.length === 0) { + showToast({ + message: '닉네임을 입력하세요!', + type: 'warning', + isDarkMode, + }) + return false + } else if (!doubleChecked) { + showToast({ + message: '중복검사를 해주세요!', + type: 'warning', + isDarkMode, + }) + return false + } else if (nicknameDuplicated) { + showToast({ + message: '사용할 수 없는 닉네임입니다.', + type: 'warning', + isDarkMode, + }) + return false + } else { + return true + } } const submitUserProfileData = () => { - if (!formValidation()) { + if (formValidation()) { console.log(nickname, interestList) if (doubleChecked && inputRef.current !== null && interestList.length > 0) { const body = { authCode: authCode, - nickname: nickname, + nickname: inputRef.current.value, keywords: interestList, - oAuthProvider: 'KAKAO', + oAuthProvider: provider, } console.log(body) registerMutation.mutate(body) @@ -91,9 +121,16 @@ const RegisterUser = () => { const registerPost = async (body: object) => { return await axiosAPI.post('/v1/users/sign-up', body) } + const registerMutation = useMutation((body: object) => registerPost(body), { onSuccess: (response) => { console.log(response) + showToast({ + message: '닉네임, 관심사 정보 등록을 완료했습니다!', + type: 'success', + isDarkMode, + }) + navigate('/register/company') }, onError: (err) => { @@ -117,7 +154,7 @@ const RegisterUser = () => { - doubleCheckNickName()}> + {'중복확인'} From 2b437131d574c28e3504b6f08655ca43863f1160 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:36:01 +0900 Subject: [PATCH 049/180] =?UTF-8?q?feature:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20'=EC=8A=B9=EC=9D=B8=20?= =?UTF-8?q?=EB=8C=80=EA=B8=B0=20=EB=AA=A9=EB=A1=9D',=20'=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=8B=A0=EA=B3=A0=20=EB=82=B4=EC=97=AD'=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=EC=9E=91=EC=97=85,=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91=20(me?= =?UTF-8?q?rgeX,=20draft=20=EB=AF=B8=EC=A0=81=EC=9A=A9)=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: AdminAppHeader 컴포넌트 추가 * style: AdminNavigationBar 컴포넌트 생성 * style: AdminNavigationBar 색 변경 * feat: AdminTabs 컴포넌트 추가 * refactor: 컴포넌트 변수명 변경 * refactor: 컴포넌트 변수명 변경 * feat: AdminApprovalList 컴포넌트 추가 * feat: outerwrapper 제거 * feat: AdminReportListRow 컴포넌트 예시 추가 * feat: AdminReportList 컴포넌트 예시 추가 * feat: AdminTabs 컴포넌트로 다른 컴포넌트들 연결 및 디자인 수정, 변수명 수정 * refactor: admin 컴포넌트 이전 * refactor: eslint 에러 수정 * refactor: import 경로 변경 --------- Co-authored-by: Changuk Woo <43228743+wukdddang@users.noreply.github.com> --- .../common/AppHeader/AdminAppHeader.tsx | 75 +++++++++++++++ .../common/ListRow/AdminApprovalList.tsx | 89 +++++++++++++++++ .../common/ListRow/AdminApprovalListRow.tsx | 95 +++++++++++++++++++ .../common/ListRow/AdminReportList.tsx | 89 +++++++++++++++++ ...dminListRow.tsx => AdminReportListRow.tsx} | 4 +- .../NavigationBar/AdminNavigationBar.tsx | 64 +++++++++++++ src/pages/admin/components/AdminTabs.tsx | 79 +++++++++++++++ 7 files changed, 493 insertions(+), 2 deletions(-) create mode 100644 src/components/common/AppHeader/AdminAppHeader.tsx create mode 100644 src/components/common/ListRow/AdminApprovalList.tsx create mode 100644 src/components/common/ListRow/AdminApprovalListRow.tsx create mode 100644 src/components/common/ListRow/AdminReportList.tsx rename src/components/common/ListRow/{AdminListRow.tsx => AdminReportListRow.tsx} (93%) create mode 100644 src/components/common/NavigationBar/AdminNavigationBar.tsx create mode 100644 src/pages/admin/components/AdminTabs.tsx diff --git a/src/components/common/AppHeader/AdminAppHeader.tsx b/src/components/common/AppHeader/AdminAppHeader.tsx new file mode 100644 index 00000000..7ea328d6 --- /dev/null +++ b/src/components/common/AppHeader/AdminAppHeader.tsx @@ -0,0 +1,75 @@ +import styled from '@emotion/styled' +import { IoIosNotifications } from 'react-icons/io' + +import { FlexBox } from '@/components/common/Flexbox' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +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 AdminAppHeader = ({ nickname, isDarkMode, height }: AppHeaderProps) => { + return ( + + + {}} + /> + + + + {nickname} + + + {'님, 안녕하세요! 오늘도 즐거운 커피밋! ☕️'} + + + + ) +} + +export default AdminAppHeader diff --git a/src/components/common/ListRow/AdminApprovalList.tsx b/src/components/common/ListRow/AdminApprovalList.tsx new file mode 100644 index 00000000..6d789230 --- /dev/null +++ b/src/components/common/ListRow/AdminApprovalList.tsx @@ -0,0 +1,89 @@ +import styled from '@emotion/styled' + +import AdminApprovalListRow from '@/components/common/ListRow/AdminApprovalListRow' +import { palette } from '@/styles/palette' + +const AdminApprovalList = () => { + return ( + + + + + + + + + + + + + + + ) +} +const AdminApprovalListContainer = styled.div` + background-color: ${palette.WHITE}; + overflow: scroll; + height: 500px; +` +export default AdminApprovalList diff --git a/src/components/common/ListRow/AdminApprovalListRow.tsx b/src/components/common/ListRow/AdminApprovalListRow.tsx new file mode 100644 index 00000000..6d244afa --- /dev/null +++ b/src/components/common/ListRow/AdminApprovalListRow.tsx @@ -0,0 +1,95 @@ +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 AdminApprovalListRow = ({ + height, + nickname, + infoMessage, + isDarkMode, +}: ProfileListRowProps) => { + const renderInfoMessage = () => { + if (typeof infoMessage === 'number') { + return ( + + + {'누적 '} + + + {infoMessage} + + + {' 회'} + + + ) + } + return ( + + {infoMessage} + + ) + } + + return ( + + + {nickname} + + {renderInfoMessage()} + + ) +} + +export default AdminApprovalListRow diff --git a/src/components/common/ListRow/AdminReportList.tsx b/src/components/common/ListRow/AdminReportList.tsx new file mode 100644 index 00000000..fa30b240 --- /dev/null +++ b/src/components/common/ListRow/AdminReportList.tsx @@ -0,0 +1,89 @@ +import styled from '@emotion/styled' + +import AdminReportListRow from '@/components/common/ListRow/AdminReportListRow' +import { palette } from '@/styles/palette' + +const AdminReportList = () => { + return ( + + + + + + + + + + + + + + + ) +} +const AdminReportListContainer = styled.div` + background-color: ${palette.WHITE}; + overflow: scroll; + height: 500px; +` +export default AdminReportList diff --git a/src/components/common/ListRow/AdminListRow.tsx b/src/components/common/ListRow/AdminReportListRow.tsx similarity index 93% rename from src/components/common/ListRow/AdminListRow.tsx rename to src/components/common/ListRow/AdminReportListRow.tsx index 7976bf03..312e9721 100644 --- a/src/components/common/ListRow/AdminListRow.tsx +++ b/src/components/common/ListRow/AdminReportListRow.tsx @@ -9,7 +9,7 @@ type ProfileListRowProps = { infoMessage: string | number isDarkMode: boolean } -const AdminListRow = ({ height, nickname, infoMessage, isDarkMode }: ProfileListRowProps) => { +const AdminReportListRow = ({ height, nickname, infoMessage, isDarkMode }: ProfileListRowProps) => { const renderInfoMessage = () => { if (typeof infoMessage === 'number') { return ( @@ -87,4 +87,4 @@ const AdminListRow = ({ height, nickname, infoMessage, isDarkMode }: ProfileList ) } -export default AdminListRow +export default AdminReportListRow diff --git a/src/components/common/NavigationBar/AdminNavigationBar.tsx b/src/components/common/NavigationBar/AdminNavigationBar.tsx new file mode 100644 index 00000000..7758444f --- /dev/null +++ b/src/components/common/NavigationBar/AdminNavigationBar.tsx @@ -0,0 +1,64 @@ +import styled from '@emotion/styled' +import { MdHome } from 'react-icons/md' +import { useNavigate } from 'react-router-dom' + +import { FlexBox } from '@/components/common/Flexbox' +import { palette } from '@/styles/palette' +import { typo } from '@/styles/typo' + +type NavigationBarProps = { + isDarkMode: boolean +} + +const AdminNavigationBar = ({ isDarkMode }: NavigationBarProps) => { + const navigate = useNavigate() + const moveFromNavigationBar = (path: string) => { + navigate(`/${path}`) + } + return ( + + + moveFromNavigationBar('')}> + + + {'홈'} + + + + + ) +} + +const StyledWrapper = styled(FlexBox)` + position: sticky; + bottom: 0px; +` + +const StyledNavigationText = styled.span<{ + isDarkMode: boolean +}>` + color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.DARK_BLUE)}; + font-size: ${typo.Body_10()}; +` + +const StyledNavigation = styled(FlexBox)<{ + isDarkMode: boolean +}>` + width: 100%; + height: 71px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; + box-shadow: + 0px 0px 10px 0px rgba(0, 0, 0, 0.24), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14); +` + +const StyledNavigationItem = styled.button` + cursor: pointer; +` + +export default AdminNavigationBar diff --git a/src/pages/admin/components/AdminTabs.tsx b/src/pages/admin/components/AdminTabs.tsx new file mode 100644 index 00000000..651d4bab --- /dev/null +++ b/src/pages/admin/components/AdminTabs.tsx @@ -0,0 +1,79 @@ +import styled from '@emotion/styled' +import { useState } from 'react' + +import AdminApprovalList from '@/components/common/ListRow/AdminApprovalList' +import AdminReportList from '@/components/common/ListRow/AdminReportList' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +const ApprovalList = () => +const ReportList = () => +// 탭에서 보여줄 컴포넌트 리스트 + +interface TabProps { + isActive: boolean +} + +const AdminTabs = () => { + const [activeTab, setActiveTab] = useState('approval') + + return ( + <> + + setActiveTab('approval')}> + + {'승인 대기 목록'} + + + setActiveTab('report')}> + + {'사용자 신고 내역'} + + + + + + {activeTab === 'approval' && } + {activeTab === 'report' && } + + + ) +} + +const StyledTabsContainer = styled.div` + background-color: ${palette.PRIMARY}; + display: flex; + width: 100%; +` + +const StyledListContainer = styled.div` + width: 100%; +` +const StyledLeftTab = styled.button` + flex: 1; + padding: 30px 20px; + cursor: pointer; + background: ${(props) => (props.isActive ? palette.GRAY100 : palette.WHITE)}; + border: none; + border-radius: 30px 0 0 0; +` +const StyledRightTab = styled.button` + flex: 1; + padding: 30px 20px; + cursor: pointer; + background: ${(props) => (props.isActive ? palette.GRAY100 : palette.WHITE)}; + border: none; + border-radius: 0 30px 0 0; +` + +export default AdminTabs From 039465697df6030004d5eff71bc69e9e9da8a7fb Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:44:11 +0900 Subject: [PATCH 050/180] =?UTF-8?q?feature:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83,=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(mergeX,?= =?UTF-8?q?=20draft)=20(#116)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: AdminAppHeader 컴포넌트 추가 * style: AdminNavigationBar 컴포넌트 생성 * style: AdminNavigationBar 색 변경 * feat: AdminTabs 컴포넌트 추가 * refactor: 컴포넌트 변수명 변경 * refactor: 컴포넌트 변수명 변경 * feat: AdminApprovalList 컴포넌트 추가 * feat: outerwrapper 제거 * feat: AdminReportListRow 컴포넌트 예시 추가 * feat: AdminReportList 컴포넌트 예시 추가 * feat: AdminTabs 컴포넌트로 다른 컴포넌트들 연결 및 디자인 수정, 변수명 수정 * refactor: admin 컴포넌트 이전 * refactor: eslint 에러 수정 * refactor: import 경로 변경 * feat: AdminApprovalInfo 컴포넌트 1 * style: 예시 이미지 추가 * feat: AdminApprovalInfo 컴포넌트 추가 1 * feat: input text fontweight 속성 추가 * style: Input 속성 추가 * style: AdminApprovalInfo 컴포넌트 디자인 2 * style: AdminReportInfo 컴포넌트 1 * refactor: props명 수정 * style: AdminReportInfo 컴포넌트 1 * style: AdminReportInfoListRow 컴포넌트 * feat: useModal 컴포넌트 적용, 기타 스타일 수정 * feat: AdminReportInfo 컴포넌트 userModal 적용 * refactor: 불필요 컴포넌트 삭제 * refactor: 이름 수정 * feat: AdminReportListRow 속성 추가 * feat: 관리자페이지 신고자 목록 클릭 시, 정보 출력 * feat: AdminReportList 특정 목록 클릭 시, 해당 AdminReportInfo 출력 * feat: AdminReportList mockdata 추가, 함수 연결 * refactor: 이름 예시 변경 * refactor: 변수명 수정 (adminapprovalinfo와 구분) * feat: AdminTabs 승인 탭 추가 * refactor: 변수명 수정(구별필요) * feat: 승인 페이지 이름 적용, 불필요 컴포넌트 제거 * refactor: mock data 적용. 변수명 수정. * feat: 승인페이지 mock data 적용. 목록 클릭 함수 추가. interface 추가. * feat: adminapprovallistrow onclick 함수 속성 추가 * style: ListRow의 ListContainer 가운데 정렬 * style: 중복 HomeNavigationBar 제거 * feat: AdminInfo 컴포넌트 AdminPageHeader컴포넌트 적용 * feat: AdminPageHeader 컴포넌트 추가 * style: Admin 컴포넌트 스타일 수정(폰트,cursor,가운데정렬등) * style: Input component padding 속성추가 * feat: adminlogin 컴포넌트 추가 * feat: adminapprovalinfo input 텍스트로 변경 (+스타일 적용) * style: 관리자 페이지 스타일 피그마 세부 적용 --- src/assets/images/businessCardExample.jpg | Bin 0 -> 41936 bytes src/components/common/Input/index.tsx | 10 ++ .../common/ListRow/AdminApprovalList.tsx | 122 +++++++----------- .../common/ListRow/AdminApprovalListRow.tsx | 18 ++- .../common/ListRow/AdminReportInfoListRow.tsx | 95 ++++++++++++++ .../common/ListRow/AdminReportList.tsx | 122 +++++++----------- .../common/ListRow/AdminReportListRow.tsx | 22 +++- src/pages/admin/AdminLogin.tsx | 26 +++- .../admin/components/AdminApprovalInfo.tsx | 106 +++++++++++++++ .../admin/components/AdminPageHeader.tsx | 60 +++++++++ .../admin/components/AdminReportInfo.tsx | 107 +++++++++++++++ src/pages/admin/components/AdminTabs.tsx | 73 +++++++++-- 12 files changed, 590 insertions(+), 171 deletions(-) create mode 100644 src/assets/images/businessCardExample.jpg create mode 100644 src/components/common/ListRow/AdminReportInfoListRow.tsx create mode 100644 src/pages/admin/components/AdminApprovalInfo.tsx create mode 100644 src/pages/admin/components/AdminPageHeader.tsx create mode 100644 src/pages/admin/components/AdminReportInfo.tsx diff --git a/src/assets/images/businessCardExample.jpg b/src/assets/images/businessCardExample.jpg new file mode 100644 index 0000000000000000000000000000000000000000..14aceeaa2fb0ef2a9201361b3bd96561a0ff4fba GIT binary patch literal 41936 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

T$I^MzB|jWp3K5$ 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 literal 0 HcmV?d00001 diff --git a/src/components/common/Input/index.tsx b/src/components/common/Input/index.tsx index 13382ae9..af24ec47 100644 --- a/src/components/common/Input/index.tsx +++ b/src/components/common/Input/index.tsx @@ -5,6 +5,7 @@ type InputProps = { placeholder?: string placeholderSize?: string placeholderColor?: string + padding?: string width?: string height?: string borderColor?: string @@ -12,6 +13,8 @@ type InputProps = { inputTextColor?: string inputTextSize?: string inputBackgroundColor?: string + inputTextFontWeight?: number + borderRadius?: string onChange?: (e: React.ChangeEvent) => void } @@ -22,6 +25,7 @@ const Input = React.forwardRef( placeholder, placeholderSize, placeholderColor, + padding, width, height, borderColor, @@ -31,6 +35,7 @@ const Input = React.forwardRef( inputTextSize, inputBackgroundColor, onChange, + inputTextFontWeight, }: InputProps, ref: ForwardedRef, ) => { @@ -42,6 +47,7 @@ const Input = React.forwardRef( placeholder={placeholder} placeholderSize={placeholderSize} placeholderColor={placeholderColor} + padding={padding} width={width} height={height} borderColor={borderColor} @@ -50,6 +56,7 @@ const Input = React.forwardRef( inputTextSize={inputTextSize} inputBackgroundColor={inputBackgroundColor} borderRadius={borderRadius} + inputTextFontWeight={inputTextFontWeight} /> ) @@ -74,7 +81,10 @@ const StyledInput = styled.input` background-color: ${(props) => props.inputBackgroundColor}; border-radius: ${(props) => props.borderRadius}; font-size: ${(props) => props.inputTextSize}; + font-weight: ${(props) => props.inputTextFontWeight}; + padding: ${(props) => props.padding}; position: relative; + box-sizing: border-box; ` export default Input diff --git a/src/components/common/ListRow/AdminApprovalList.tsx b/src/components/common/ListRow/AdminApprovalList.tsx index 6d789230..c20fd627 100644 --- a/src/components/common/ListRow/AdminApprovalList.tsx +++ b/src/components/common/ListRow/AdminApprovalList.tsx @@ -3,87 +3,57 @@ import styled from '@emotion/styled' import AdminApprovalListRow from '@/components/common/ListRow/AdminApprovalListRow' import { palette } from '@/styles/palette' -const AdminApprovalList = () => { + +interface AdminApprovalListProps { + onApproveSelect: (nickname: string) => void +} + +const AdminApprovalList = ({ onApproveSelect }: AdminApprovalListProps) => { + const handlePersonApproval = (nickname: string) => { + onApproveSelect(nickname) + } + const AdminApprovalListData = [ + { nickname: '유명한', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '박상민', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '박은지', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '주다현', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '남궁호수', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '우창욱', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, + ] + return ( - - - - - - - - - - - - - - + + + {AdminApprovalListData.map((data, index) => ( + handlePersonApproval(data.nickname)} + /> + ))} + + ) } +const AdminApprovalListContainerOuterWrapper = styled.div` + background-color: ${palette.WHITE}; + width: 100%; +` const AdminApprovalListContainer = styled.div` background-color: ${palette.WHITE}; overflow: scroll; - height: 500px; + height: 662px; + width: 80%; + margin: auto; + cursor: pointer; ` + export default AdminApprovalList diff --git a/src/components/common/ListRow/AdminApprovalListRow.tsx b/src/components/common/ListRow/AdminApprovalListRow.tsx index 6d244afa..87a07afa 100644 --- a/src/components/common/ListRow/AdminApprovalListRow.tsx +++ b/src/components/common/ListRow/AdminApprovalListRow.tsx @@ -3,18 +3,25 @@ import { StyleList } from '@/components/common/ListRow/ProfileListRow' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -type ProfileListRowProps = { +type AdminApprovalListRowProps = { + height: number nickname: string infoMessage: string | number isDarkMode: boolean + + onClick?: () => void + } const AdminApprovalListRow = ({ height, nickname, infoMessage, isDarkMode, -}: ProfileListRowProps) => { + + onClick, +}: AdminApprovalListRowProps) => { + const renderInfoMessage = () => { if (typeof infoMessage === 'number') { return ( @@ -70,12 +77,17 @@ const AdminApprovalListRow = ({ return ( { + const renderInfoMessage = () => { + if (typeof infoMessage === 'number') { + return ( + + + {'누적 '} + + + {infoMessage} + + + {' 회'} + + + ) + } + return ( + + {infoMessage} + + ) + } + + return ( + + + {nickname} + + {renderInfoMessage()} + + ) +} + +export default AdminReportInfoListRow diff --git a/src/components/common/ListRow/AdminReportList.tsx b/src/components/common/ListRow/AdminReportList.tsx index fa30b240..095b4d02 100644 --- a/src/components/common/ListRow/AdminReportList.tsx +++ b/src/components/common/ListRow/AdminReportList.tsx @@ -3,87 +3,55 @@ import styled from '@emotion/styled' import AdminReportListRow from '@/components/common/ListRow/AdminReportListRow' import { palette } from '@/styles/palette' -const AdminReportList = () => { +interface AdminReportListProps { + onReportSelect: (nickname: string) => void +} +const AdminReportList = ({ onReportSelect }: AdminReportListProps) => { + const handlePersonReported = (nickname: string) => { + onReportSelect(nickname) + } + const AdminReportListData = [ + { nickname: '유명한', height: 71, infoMessage: '누적 1회', isDarkMode: false }, + { nickname: '박상민', height: 71, infoMessage: '누적 2회', isDarkMode: false }, + { nickname: '박은지', height: 71, infoMessage: '누적 2회', isDarkMode: false }, + { nickname: '주다현', height: 71, infoMessage: '누적 1회', isDarkMode: false }, + { nickname: '남궁호수', height: 71, infoMessage: '누적 1회', isDarkMode: false }, + { nickname: '우창욱', height: 71, infoMessage: '누적 2회', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, + { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, + ] + return ( - - - - - - - - - - - - - - + + + {AdminReportListData.map((data, index) => ( + handlePersonReported(data.nickname)} + /> + ))} + + ) } -const AdminReportListContainer = styled.div` +const StyledAdminReportListContainer = styled.div` background-color: ${palette.WHITE}; overflow: scroll; - height: 500px; + height: 662px; + width: 80%; + margin: auto; + cursor: pointer; +` +const StyledAdminReportListContainerOuterWrapper = styled.div` + background-color: ${palette.WHITE}; + width: 100%; + ` export default AdminReportList diff --git a/src/components/common/ListRow/AdminReportListRow.tsx b/src/components/common/ListRow/AdminReportListRow.tsx index 312e9721..83f3bfcb 100644 --- a/src/components/common/ListRow/AdminReportListRow.tsx +++ b/src/components/common/ListRow/AdminReportListRow.tsx @@ -3,13 +3,24 @@ import { StyleList } from '@/components/common/ListRow/ProfileListRow' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -type ProfileListRowProps = { + +type AdminReportListRowProps = { + height: number nickname: string infoMessage: string | number isDarkMode: boolean + + onClick?: () => void } -const AdminReportListRow = ({ height, nickname, infoMessage, isDarkMode }: ProfileListRowProps) => { +const AdminReportListRow = ({ + height, + nickname, + infoMessage, + isDarkMode, + onClick, +}: AdminReportListRowProps) => { + const renderInfoMessage = () => { if (typeof infoMessage === 'number') { return ( @@ -65,12 +76,17 @@ const AdminReportListRow = ({ height, nickname, infoMessage, isDarkMode }: Profi return ( { - return

{'AdminLogin'}
+ //msw 테스트 코드 + fetch('/admin/reports/2') + .then((response) => { + if (!response.ok) { + throw new Error('네트워크 상태가 불안정합니다.') + } + return response.json() + }) + .then((data) => console.log(data)) + .catch((error) => console.error('fetch 오류 발생.', error)) + return ( + + + + + ) } +const StyledAdminLoginOuterWrapper = styled.div` + background-color: ${palette.PRIMARY}; +` export default AdminLogin diff --git a/src/pages/admin/components/AdminApprovalInfo.tsx b/src/pages/admin/components/AdminApprovalInfo.tsx new file mode 100644 index 00000000..1a4faa2e --- /dev/null +++ b/src/pages/admin/components/AdminApprovalInfo.tsx @@ -0,0 +1,106 @@ +import styled from '@emotion/styled' + +import businessCardExample from '@/assets/images/businessCardExample.jpg' +import NormalButton from '@/components/common/Buttons/NormalButton' +import Spacing from '@/components/common/Spacing' +import { Text } from '@/components/common/Text' +import { useModal } from '@/hooks/useModal' +import { palette } from '@/styles/palette' + +import AdminPageHeader from './AdminPageHeader' + +interface AdminApprovalInfoProps { + selectedApprovalNickname: string +} + +const AdminApprovalInfo = ({ selectedApprovalNickname }: AdminApprovalInfoProps) => { + const { openModal } = useModal() + const handleAcceptCertificationBtn = () => { + openModal({ + type: 'confirm', + mainText: '인증을 수락하시겠습니까?', + okFunc: () => console.log('okFunc'), + }) + } + + return ( + + + + + + + {'이메일'} + + + + {`myeonghan@naver.com`} + {/* {`${selectedApprovalNickname}의 이메일, userId를 받을 수 있음 (API 요청)`} */} + + + + + + {'명함 이미지'} + + + + + + + {'인증 수락'} + + {'거절'} + + + + + ) +} + +const StyledAdminLoginOuterWrapper = styled.div` + background-color: ${palette.PRIMARY}; + overflow: scroll; + height: 662px; +` + +const StyledAdminApproveFormContainer = styled.div` + background-color: ${palette.WHITE}; + overflow: scroll; +` +const StyledEmailTextWrapper = styled.div` + padding: 16px 34px 16px 34px; + background-color: ${palette.SKY_BLUE}; + width: 246px; + border-radius: 20px; + text-align: center; +` +const StyledEmailContainer = styled.div` + display: flex; + justify-content: space-around; + align-items: center; + padding: 40px 0; +` +const StyledImage = styled.img` + width: 100%; + max-width: 100%; + height: auto; + margin: 20px 0 20px 0; +` +const StyledButtonsWrapper = styled.div` + display: flex; + justify-content: space-evenly; + padding: 25px 0 25px 0; +` +export default AdminApprovalInfo diff --git a/src/pages/admin/components/AdminPageHeader.tsx b/src/pages/admin/components/AdminPageHeader.tsx new file mode 100644 index 00000000..92bc62ac --- /dev/null +++ b/src/pages/admin/components/AdminPageHeader.tsx @@ -0,0 +1,60 @@ +import styled from '@emotion/styled' +import { BiLeftArrowAlt } from 'react-icons/bi' + +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +interface AdminPageHeaderProps { + username: string +} + +const AdminPageHeader = ({ username }: AdminPageHeaderProps) => { + return ( + + + + + + + {username} + + + + + + ) +} +const StyledAdminPageHeaderContainerOuterWrapper = styled.div` + background-color: ${palette.WHITE}; +` +const StyledAdminPageHeaderContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + width: 90%; + margin: 0 auto; + height: 65px; + background-color: ${palette.WHITE}; + border-bottom: 1px solid ${palette.GRAY300}; + padding-top: 10px; +` + +const StyledBackIcon = styled.span` + cursor: pointer; +` + +const StyledUsernameWrapper = styled.span` + margin: 0; +` + +const StyledBackIconWidthRightSpace = styled.div` + width: 40px; +` + +export default AdminPageHeader diff --git a/src/pages/admin/components/AdminReportInfo.tsx b/src/pages/admin/components/AdminReportInfo.tsx new file mode 100644 index 00000000..bb014ab9 --- /dev/null +++ b/src/pages/admin/components/AdminReportInfo.tsx @@ -0,0 +1,107 @@ +import styled from '@emotion/styled' + +import NormalButton from '@/components/common/Buttons/NormalButton' +import AdminReportInfoListRow from '@/components/common/ListRow/AdminReportInfoListRow' +import Spacing from '@/components/common/Spacing' +import { useModal } from '@/hooks/useModal' +import { palette } from '@/styles/palette' + +import AdminPageHeader from './AdminPageHeader' + +interface AdminReportInfoProps { + selectedReportNickname: string +} + +const AdminReportInfo = ({ selectedReportNickname }: AdminReportInfoProps) => { + const { openModal } = useModal() + const handleAccumulationAddBtn = () => { + openModal({ + type: 'confirm', + mainText: '신고를 누적하시겠습니까?', + okFunc: () => console.log('okFunc'), + }) + } + return ( + + + + + + + + + + + + + + + + + {'누적 추가'} + + {'무시'} + + + + + + ) +} + +const StyledAdminReportInfoOuterWrapper = styled.div` + background-color: ${palette.PRIMARY}; + overflow: scroll; + height: 662px; +` + +const StyledAdminReportInfoContainer = styled.div` + background-color: ${palette.WHITE}; +` + +const StyledButtonsWrapper = styled.div` + display: flex; + justify-content: space-evenly; + padding: 60px 0 25px 0; +` +const StyledReportInfoListWrapper = styled.div` + background-color: ${palette.WHITE}; + width: 100%; + margin: 0 auto; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +` +const StyledReportInfoListOuterWrapper = styled.div` + background-color: ${palette.WHITE}; +` +const StyledBelowWhiteSpace = styled.div` + background-color: ${palette.WHITE}; +` +export default AdminReportInfo diff --git a/src/pages/admin/components/AdminTabs.tsx b/src/pages/admin/components/AdminTabs.tsx index 651d4bab..763b68b7 100644 --- a/src/pages/admin/components/AdminTabs.tsx +++ b/src/pages/admin/components/AdminTabs.tsx @@ -3,19 +3,48 @@ import { useState } from 'react' import AdminApprovalList from '@/components/common/ListRow/AdminApprovalList' import AdminReportList from '@/components/common/ListRow/AdminReportList' + +import HomeNavigationBar from '@/components/common/NavigationBar/AdminNavigationBar' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -const ApprovalList = () => -const ReportList = () => -// 탭에서 보여줄 컴포넌트 리스트 +import AdminApprovalInfo from './AdminApprovalInfo' +import AdminReportInfo from './AdminReportInfo' + interface TabProps { isActive: boolean } +interface ReportListProps { + onPersonReportedSelected: (reportNickname: string) => void +} +interface ApprovalListProps { + onPersonApprovalSelected: (approvalNickname: string) => void +} + const AdminTabs = () => { const [activeTab, setActiveTab] = useState('approval') + const [selectedReportNickname, setSelectedReportNickname] = useState(null) + const [selectedApprovalNickname, setSelectedApprovalNickname] = useState(null) + + const handleReportSelectNickname = (reportNickname: string) => { + setSelectedReportNickname(reportNickname) + setActiveTab('reportInfo') + } + const handleApprovalSelectNickname = (approvalNickname: string) => { + setSelectedApprovalNickname(approvalNickname) + setActiveTab('approvalInfo') + } + + const ApprovalList = ({ onPersonApprovalSelected }: ApprovalListProps) => ( + + ) + const ReportList = ({ onPersonReportedSelected }: ReportListProps) => ( + + ) + // 탭에서 보여줄 컴포넌트들 + return ( <> @@ -24,7 +53,9 @@ const AdminTabs = () => { {'승인 대기 목록'} @@ -34,7 +65,9 @@ const AdminTabs = () => { {'사용자 신고 내역'} @@ -43,9 +76,22 @@ const AdminTabs = () => { - {activeTab === 'approval' && } - {activeTab === 'report' && } + + {activeTab === 'approval' && ( + + )} + {activeTab === 'report' && ( + + )} + {activeTab === 'reportInfo' && selectedReportNickname && ( + + )} + {activeTab === 'approvalInfo' && selectedApprovalNickname && ( + + )} + + ) } @@ -61,18 +107,23 @@ const StyledListContainer = styled.div` ` const StyledLeftTab = styled.button` flex: 1; - padding: 30px 20px; + + padding: 27px 25px 23px 25px; cursor: pointer; background: ${(props) => (props.isActive ? palette.GRAY100 : palette.WHITE)}; - border: none; + border: 1.5px solid ${palette.GRAY100}; + box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); border-radius: 30px 0 0 0; ` const StyledRightTab = styled.button` + padding: 27px 25px 23px 25px; + flex: 1; - padding: 30px 20px; cursor: pointer; background: ${(props) => (props.isActive ? palette.GRAY100 : palette.WHITE)}; - border: none; + border: 1.5px solid ${palette.GRAY100}; + box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); + border-radius: 0 30px 0 0; ` From 306009f384b76bd3b9fa962460b13358aeacbee8 Mon Sep 17 00:00:00 2001 From: judahhh Date: Tue, 14 Nov 2023 16:59:02 +0900 Subject: [PATCH 051/180] =?UTF-8?q?chore=20:=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=EC=97=90=EC=84=9C=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=EC=9D=84=20=EB=B3=BC=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/loginPending/LoginPending.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/loginPending/LoginPending.tsx b/src/pages/loginPending/LoginPending.tsx index db05e1c8..58d21472 100644 --- a/src/pages/loginPending/LoginPending.tsx +++ b/src/pages/loginPending/LoginPending.tsx @@ -1,4 +1,5 @@ import styled from '@emotion/styled' +import { local } from 'd3' import { useNavigate, useSearchParams } from 'react-router-dom' import { PulseLoader } from 'react-spinners' @@ -17,6 +18,8 @@ const LoginPending = () => { await axiosAPI .get(`${import.meta.env.VITE_BASE_URL}/v1/users/login/${provider}?authCode=${authCode}`) .then((res) => { + console.log(res.data.accessToken) + localStorage.setItem('jwt', res.data.accessToken) setToken({ accessToken: res.data.accessToken, refreshToken: res.data.refreshToken, From ca173db032d8e6567041f2b37655a8085db87a1e Mon Sep 17 00:00:00 2001 From: judahhh Date: Tue, 14 Nov 2023 16:59:50 +0900 Subject: [PATCH 052/180] =?UTF-8?q?chore=20:=20=EC=9E=98=EB=AA=BB=20import?= =?UTF-8?q?=20=EB=90=9C=20import=20=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/loginPending/LoginPending.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/loginPending/LoginPending.tsx b/src/pages/loginPending/LoginPending.tsx index 58d21472..c9ef545e 100644 --- a/src/pages/loginPending/LoginPending.tsx +++ b/src/pages/loginPending/LoginPending.tsx @@ -1,5 +1,4 @@ import styled from '@emotion/styled' -import { local } from 'd3' import { useNavigate, useSearchParams } from 'react-router-dom' import { PulseLoader } from 'react-spinners' From 5c39374948d72e8c4fd4e6e696013b31fcfb2599 Mon Sep 17 00:00:00 2001 From: judahhh Date: Tue, 14 Nov 2023 17:05:37 +0900 Subject: [PATCH 053/180] =?UTF-8?q?chore=20:=20lint=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/ListRow/AdminApprovalList.tsx | 1 - src/components/common/ListRow/AdminApprovalListRow.tsx | 7 ------- src/components/common/ListRow/AdminReportList.tsx | 1 - src/components/common/ListRow/AdminReportListRow.tsx | 7 ------- src/pages/admin/components/AdminTabs.tsx | 9 --------- 5 files changed, 25 deletions(-) diff --git a/src/components/common/ListRow/AdminApprovalList.tsx b/src/components/common/ListRow/AdminApprovalList.tsx index c20fd627..f0059201 100644 --- a/src/components/common/ListRow/AdminApprovalList.tsx +++ b/src/components/common/ListRow/AdminApprovalList.tsx @@ -3,7 +3,6 @@ import styled from '@emotion/styled' import AdminApprovalListRow from '@/components/common/ListRow/AdminApprovalListRow' import { palette } from '@/styles/palette' - interface AdminApprovalListProps { onApproveSelect: (nickname: string) => void } diff --git a/src/components/common/ListRow/AdminApprovalListRow.tsx b/src/components/common/ListRow/AdminApprovalListRow.tsx index 87a07afa..31149d17 100644 --- a/src/components/common/ListRow/AdminApprovalListRow.tsx +++ b/src/components/common/ListRow/AdminApprovalListRow.tsx @@ -4,14 +4,12 @@ import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' type AdminApprovalListRowProps = { - height: number nickname: string infoMessage: string | number isDarkMode: boolean onClick?: () => void - } const AdminApprovalListRow = ({ height, @@ -21,7 +19,6 @@ const AdminApprovalListRow = ({ onClick, }: AdminApprovalListRowProps) => { - const renderInfoMessage = () => { if (typeof infoMessage === 'number') { return ( @@ -77,17 +74,13 @@ const AdminApprovalListRow = ({ return ( { - const renderInfoMessage = () => { if (typeof infoMessage === 'number') { return ( @@ -76,17 +73,13 @@ const AdminReportListRow = ({ return ( { ) // 탭에서 보여줄 컴포넌트들 - return ( <> @@ -53,9 +50,7 @@ const AdminTabs = () => { {'승인 대기 목록'} @@ -65,9 +60,7 @@ const AdminTabs = () => { {'사용자 신고 내역'} @@ -76,7 +69,6 @@ const AdminTabs = () => { - {activeTab === 'approval' && ( )} @@ -91,7 +83,6 @@ const AdminTabs = () => { )} - ) } From e1a507cf35fc03537902c50e68eefa3781e617c5 Mon Sep 17 00:00:00 2001 From: Changuk Woo <43228743+wukdddang@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:44:40 +0900 Subject: [PATCH 054/180] =?UTF-8?q?feature:=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A0=9C=EC=9E=91=20(#109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Modal 컴포넌트 관련 코드 리팩토링 - useModal: acceptText, cancelText 받도록 수정 - Modal: framer-motion으로 애니메이션 효과 추가 * refactor: tsdoc 추가 + CSS 스타일 수정 - tsdoc: NaverIcon, KakaoIcon - CSS 스타일 수정: AppHeader,Avatar, BackChevron * refactor: CSS 스타일 수정 - Divider: width, height props string으로 타입 수정 - InterestButton: width props string으로 수정 - NavigationBar: Text 컴포넌트 사용하도록 수정 * refactor: tsdoc 추가 + emotion 코드 위치 변경 - emotion 코드 위치 변경: ProfileListRow, PageContainer, PageHeader - tsdoc 추가: Text 컴포넌트 * refactor: Home 페이지 fragment 제거 * feature: ProfileDefault 페이지 제작 - useModal: 로그아웃, 계정 삭제 시 모달 사용 - useNavigate: 뒤로가기, 프로필 수정, 회사 정보 변경, 로그아웃, 계정 삭제에 라우팅 기능 추가 - 페이지 레이아웃 작업 - ProfileEdit: 기존 테스트 코드 제거 * refactor: BusinessCardContainer 공백 제거 * refactor: StyleList 관련 conflict 해결 * feature: 프로필 페이지 반응형 디자인 제작 - 갤럭시 Fold에서 적절하게 보이도록 미디어 쿼리 사용 * refactor: 사용하지 않는 파일 제거 + 반응형 디자인 - 모달 버튼의 폰트 크기 수정 * feature: 프로필 페이지 다크모드 코드 추가 * feature: Modal 컴포넌트 다크모드 추가 * feature: 404 페이지에서 홈으로 라우팅하게 수정 * fix: msw 2 버전으로 수정 * fix: authTokens이 없어서 생기는 오류 수정 * feature: 레이아웃 깨지는 부분 수정 * fix: useAuthTokens 주석 처리 * chore: msw 2.0.5 버전으로 수정 * Update handlers.ts --------- Co-authored-by: wukddang <43228743+funkyblues@users.noreply.github.com> Co-authored-by: from1to2 <124763142+from1to2@users.noreply.github.com> Co-authored-by: DaHyeonJu --- public/mockServiceWorker.js | 2 +- src/assets/icons/KakaoIcon.tsx | 8 + src/assets/icons/NaverIcon.tsx | 8 + src/components/common/AppHeader/index.tsx | 5 +- src/components/common/Avatar/index.tsx | 52 ++-- src/components/common/BackChevron/index.tsx | 1 + .../Buttons/IconButton/IconButtonStyles.ts | 4 +- .../Buttons/IconButton/InterestButton.tsx | 9 +- .../common/Buttons/IconButton/index.tsx | 3 +- src/components/common/Divider/index.tsx | 15 +- .../common/GradationBackground/index.tsx | 2 +- .../common/ListRow/ProfileListRow.tsx | 64 +++-- src/components/common/Modal/index.tsx | 187 +++++++++--- src/components/common/NavigationBar/index.tsx | 32 ++- src/components/common/PageHeader/index.tsx | 79 +++-- src/components/common/Text/index.tsx | 13 +- src/components/home/Card.tsx | 235 +++++---------- src/hooks/useModal.tsx | 27 +- src/mocks/handlers.ts | 7 + src/pages/chatList/ChatList.tsx | 11 +- src/pages/home/Home.tsx | 15 +- src/pages/notFound/NotFound.tsx | 15 +- src/pages/profile/ProfileDefault.tsx | 269 +++++++++++++++++- src/pages/profile/ProfileEdit.tsx | 17 +- src/store/ModalStore.tsx | 12 + 25 files changed, 722 insertions(+), 370 deletions(-) diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index 29c33774..33127c83 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -2,7 +2,7 @@ /* tslint:disable */ /** - * Mock Service Worker (2.0.0). + * Mock Service Worker (2.0.5). * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. diff --git a/src/assets/icons/KakaoIcon.tsx b/src/assets/icons/KakaoIcon.tsx index bea9b248..6c42296b 100644 --- a/src/assets/icons/KakaoIcon.tsx +++ b/src/assets/icons/KakaoIcon.tsx @@ -8,6 +8,14 @@ export type IconProps = { borderRadius: number } +/** + * @param width - 너비 (string) + * @param height - 높이 (string) + * @param iconWidth - (Optional) 아이콘 너비 (string) + * @param iconHeight - (Optional) 아이콘 높이 (string) + * @param borderRadius - (Optional) 아이콘 테두리 반지름 (string) + */ + const KakaoIcon = ({ width, height, iconWidth, iconHeight, borderRadius }: IconProps) => ( ( ` justify-content: flex-end; height: ${({ height }) => height}; text-align: center; - padding: 6.5% 5% 7%; + padding: 15% 5% 7%; ` const StyledAppHeaderLargeText = styled(Text)>` @@ -75,6 +75,9 @@ const AppHeader = ({ nickname, isDarkMode, height, toggleDarkMode }: AppHeaderPr height={49} imgUrl={''} margin={'0'} + style={{ + cursor: 'pointer', + }} onClick={() => { moveFromAppHeader('profile') }} diff --git a/src/components/common/Avatar/index.tsx b/src/components/common/Avatar/index.tsx index 0182e0b3..f1b5786b 100644 --- a/src/components/common/Avatar/index.tsx +++ b/src/components/common/Avatar/index.tsx @@ -6,43 +6,23 @@ type AvatarProps = { width: number | string height: number | string imgUrl: string - margin: string + margin?: string onClick?: () => void border?: string shadow?: boolean + style?: React.CSSProperties } -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}px`}; - border: ${(props) => (props.border ? props.border : 'none')}; - box-shadow: ${(props) => (props.shadow ? '0px 0px 10px rgba(0, 0, 0, 0.25)' : 'none')}; - - @media (max-width: 280px) { - width: ${(props) => - typeof props.width === 'number' ? `${props.width * 0.95}px` : `calc(${props.width} * 0.95)`}; - height: ${(props) => - typeof props.height === 'number' - ? `${props.height * 0.95}px` - : `calc(${props.height} * 0.95)`}; - } -` - /** * `Avatar` component for displaying profile images. * @param width - 아바타의 너비 (픽셀 또는 유효한 CSS 단위). * @param height - 아바타의 높이 (픽셀 또는 유효한 CSS 단위). * @param imgUrl - 아바타의 이미지 URL. 기본 이미지는 `defaultProfileImage`이다. - * @param margin - 아바타의 마진 (픽셀 또는 유효한 CSS 단위). + * @param margin - (Optional) 아바타의 마진 (픽셀 또는 유효한 CSS 단위). * @param onClick - (Optional) 클릭 이벤트. * @param border - (Optional) 아바타의 테두리. 기본 값은 `none`이다. * @param shadow - (Optional) 아바타의 그림자. 기본 값은 `false`이다. + * @param props - (Optional) 추가적인 CSS 속성. */ const Avatar = ({ width, @@ -52,6 +32,7 @@ const Avatar = ({ onClick, border, shadow = false, + ...props }: AvatarProps) => { return ( ) } +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}px`}; + border: ${(props) => (props.border ? props.border : 'none')}; + box-shadow: ${(props) => (props.shadow ? '0px 0px 10px rgba(0, 0, 0, 0.25)' : 'none')}; + + @media (max-width: 280px) { + width: ${(props) => + typeof props.width === 'number' ? `${props.width * 0.95}px` : `calc(${props.width} * 0.95)`}; + height: ${(props) => + typeof props.height === 'number' + ? `${props.height * 0.95}px` + : `calc(${props.height} * 0.95)`}; + } +` + export default Avatar diff --git a/src/components/common/BackChevron/index.tsx b/src/components/common/BackChevron/index.tsx index 0e9f69cc..02575d1f 100644 --- a/src/components/common/BackChevron/index.tsx +++ b/src/components/common/BackChevron/index.tsx @@ -10,6 +10,7 @@ const StyleBackChevron = styled.div` display: flex; justify-content: center; align-items: center; + cursor: pointer; background-color: ${({ hasBackground, isDarkMode }) => isDarkMode ? hasBackground diff --git a/src/components/common/Buttons/IconButton/IconButtonStyles.ts b/src/components/common/Buttons/IconButton/IconButtonStyles.ts index 3ed54696..227464a3 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: '339px', + width: '100%', 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: '339px', + width: '100%', height: 70, fontColor: palette.DARK_WHITE, font: 'Body_18', diff --git a/src/components/common/Buttons/IconButton/InterestButton.tsx b/src/components/common/Buttons/IconButton/InterestButton.tsx index bb4c4a5a..44040444 100644 --- a/src/components/common/Buttons/IconButton/InterestButton.tsx +++ b/src/components/common/Buttons/IconButton/InterestButton.tsx @@ -13,6 +13,12 @@ type InterestButtonProps = { isDarkMode: boolean } +/** + * @param nickName: 닉네임, string + * @param interests: 관심사 리스트 (최대 3개), string[] + * @param isDarkMode: 다크모드 여부, boolean + */ + const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps) => { const setButtonType = isDarkMode ? 'interest-dark' : 'interest' @@ -32,7 +38,6 @@ const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps display: 'flex', justifyContent: 'center', alignItems: 'center', - margin: '18px 33px 18px 14px', }} > {interest} {index !== interests.length - 1 && ( - + )} ))} diff --git a/src/components/common/Buttons/IconButton/index.tsx b/src/components/common/Buttons/IconButton/index.tsx index a27158be..6e4332d6 100644 --- a/src/components/common/Buttons/IconButton/index.tsx +++ b/src/components/common/Buttons/IconButton/index.tsx @@ -53,9 +53,10 @@ export const StyledIconWrapper = styled.div<{ display: flex; justify-content: center; align-items: center; + margin: 18px 33px 18px 14px; @media (max-width: 280px) { - margin: 2px 10px 2px 10px; + margin: 18px 14px 18px 14px; } ` diff --git a/src/components/common/Divider/index.tsx b/src/components/common/Divider/index.tsx index af4efe1d..ebe61325 100644 --- a/src/components/common/Divider/index.tsx +++ b/src/components/common/Divider/index.tsx @@ -3,15 +3,22 @@ import styled from '@emotion/styled' import { palette } from '@/styles/palette' type DividerProps = { - width: number - height: number + width: string + height: string margin?: string isDarkMode: boolean } +/** + * @param width - 너비 (string) + * @param height - 높이 (string) + * @param margin - (Optional) 마진 + * @param isDarkMode - 다크모드 여부 + */ + export const Divider = styled.div` - width: ${({ width }) => width}px; - height: ${({ height }) => height}px; + width: ${({ width }) => width}; + height: ${({ height }) => height}; margin: ${({ margin }) => margin}; background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY500 : palette.GRAY100)}; ` diff --git a/src/components/common/GradationBackground/index.tsx b/src/components/common/GradationBackground/index.tsx index 2eb2968f..df42f66c 100644 --- a/src/components/common/GradationBackground/index.tsx +++ b/src/components/common/GradationBackground/index.tsx @@ -19,7 +19,7 @@ const StyledGradationBackground = styled.div<{ isDarkMode: boolean }>` width: 100%; - height: 100vh; + height: 100%; background: ${({ isDarkMode }) => isDarkMode ? `linear-gradient(157deg, ${palette.BLACK} 16.84%, ${palette.DARK_GRADIENT} 40%)` diff --git a/src/components/common/ListRow/ProfileListRow.tsx b/src/components/common/ListRow/ProfileListRow.tsx index d687e016..55e8cbbf 100644 --- a/src/components/common/ListRow/ProfileListRow.tsx +++ b/src/components/common/ListRow/ProfileListRow.tsx @@ -5,48 +5,34 @@ 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 + moveFromProfileListRow?: () => void } + +/** + * @param firstIcon - 첫번째 아이콘 (ReactNode) + * @param title - 제목 (string) + * @param additionalContent - 추가적인 내용 (string | ReactNode) + * @param isDarkMode - 다크모드 여부 + * @param moveFromProfileListRow - 클릭 시 이동할 경로 + */ + const ProfileListRow = ({ firstIcon, title, additionalContent, isDarkMode, + moveFromProfileListRow, }: ProfileListRowProps) => { const isAdditionalContentString = typeof additionalContent === 'string' const additionalContentColor = isAdditionalContentString ? palette.GRAY300 : undefined return ( - + ` + width: ${({ width }) => (width ? `${width}px` : '100%')}; + height: ${({ height }) => (height ? `${height}px` : '')}; + display: flex; + justify-content: space-between; + cursor: pointer; +` + +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}; +` + export default ProfileListRow diff --git a/src/components/common/Modal/index.tsx b/src/components/common/Modal/index.tsx index 59cf7b8a..2a56cd74 100644 --- a/src/components/common/Modal/index.tsx +++ b/src/components/common/Modal/index.tsx @@ -1,36 +1,122 @@ import styled from '@emotion/styled' +import { motion } from 'framer-motion' +import { AnimatePresence } from 'framer-motion' import ExclamationIcon from '@/assets/icons/Exclamation.svg' import WarningIcon from '@/assets/icons/Warning.svg' import NormalButton from '@/components/common/Buttons/NormalButton' +import { Text } from '@/components/common/Text' import useModalStore from '@/store/ModalStore' import { palette } from '@/styles/palette' -import { typo } from '@/styles/typo' + +const wrapperVariants = { + hidden: { opacity: 0 }, + visible: { opacity: 1 }, + exit: { opacity: 0 }, +} + +const modalVariants = { + hidden: { + scale: 0.8, + opacity: 0, + transition: { + duration: 0.5, + type: 'spring', + damping: 25, + stiffness: 500, + }, + }, + visible: { + scale: 1, + opacity: 1, + transition: { + duration: 0.5, + type: 'spring', + damping: 25, + stiffness: 500, + }, + }, + exit: { + scale: 0.8, + opacity: 0, + transition: { + duration: 0.5, + type: 'spring', + damping: 25, + stiffness: 500, + }, + }, +} const Modal = () => { - const { modalState, setModalState, okFunc, mainText, subText, type } = useModalStore() + const { + modalState, + setModalState, + okFunc, + mainText, + subText, + type, + acceptText, + cancelText, + isDarkMode, + } = useModalStore() const OkAndClose = () => { okFunc() - closeModal() + handleCloseModal() } - const closeModal = () => { + const handleCloseModal = () => { setModalState(false) } + + const handleModalClick = (event: React.MouseEvent) => { + event.stopPropagation() + } + return ( - <> + {modalState ? ( - - + + {type == 'confirm' ? ( - + ) : ( - + )} - {mainText} - {subText} + + {mainText} + + + {subText} + {type === 'confirm' ? ( - + { {'취소'} - + ) : ( - - + - {'예, 나가겠습니다.'} - - + - {'아니오, 돌아가겠습니다.'} - - + {cancelText ? cancelText : '아니오, 돌아가겠습니다.'} + + )} - - + + ) : ( '' )} - + ) } -const StyleModalWrapper = styled.div` +const StyledModalWrapper = styled(motion.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; + max-width: 414px; + background-color: rgba(0, 0, 0, 0.5); top: 0; left: 0; right: 0; bottom: 0; ` -const StyleModal = styled.div<{ type: string }>` - width: 344px; +const StyledModal = styled(motion.div)<{ type: string; isDarkMode?: boolean }>` + max-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; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; border-radius: 10px; - box-shadow: 3px 3px 3px ${palette.GRAY400}; text-align: center; + + @media (max-width: 280px) { + max-width: 260px; + } +` + +const StyledIcon = styled.img` + margin: 22px; ` -const StyleButtonWrapper = styled.span` +const StyledButtonWrapper = styled.span` justify-content: center; margin: 10px; display: flex; ` -const StyleMainText = styled.div<{ subTrue: boolean }>` - color: ${palette.BLACK}; +const StyledMainText = styled(Text)<{ subTrue: boolean; isDarkMode?: boolean }>` + color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : 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}; +const StyledSubText = styled(Text)<{ type: string; isDarkMode?: boolean }>` + color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.GRAY500)}; text-align: center; - font-size: ${typo.Body_14()}; + + @media (max-width: 280px) { + font-size: 10.5px; + } ` -const StyleIcon = styled.img` - margin: 22px; + +const StyledWarningAcceptButton = styled(NormalButton)` + @media (max-width: 280px) { + font-size: 10px; + } ` + export default Modal diff --git a/src/components/common/NavigationBar/index.tsx b/src/components/common/NavigationBar/index.tsx index 1d94eac5..962e8fb2 100644 --- a/src/components/common/NavigationBar/index.tsx +++ b/src/components/common/NavigationBar/index.tsx @@ -6,8 +6,8 @@ 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 { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -import { typo } from '@/styles/typo' type NavigationBarProps = { isDarkMode: boolean @@ -29,7 +29,14 @@ const NavigationBar = ({ isDarkMode }: NavigationBarProps) => { color: isDarkMode ? `${palette.DARK_WHITE}` : `${palette.DARK_BLUE}`, }} /> - {'이전대화방'} + + {'이전대화방'} + moveFromNavigationBar('')}> @@ -40,7 +47,14 @@ const NavigationBar = ({ isDarkMode }: NavigationBarProps) => { color: isDarkMode ? `${palette.DARK_WHITE}` : `${palette.DARK_BLUE}`, }} /> - {'홈'} + + {'홈'} + moveFromNavigationBar('profile')}> @@ -48,7 +62,14 @@ const NavigationBar = ({ isDarkMode }: NavigationBarProps) => { - {'프로필'} + + {'프로필'} + @@ -59,11 +80,10 @@ const StyledWrapper = styled(FlexBox)` position: sticky; bottom: 0px; ` -const StyledNavigationText = styled.span<{ +const StyledNavigationText = styled(Text)<{ isDarkMode: boolean }>` color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.DARK_BLUE)}; - font-size: ${typo.Body_10()}; ` const StyledNavigation = styled(FlexBox)<{ isDarkMode: boolean diff --git a/src/components/common/PageHeader/index.tsx b/src/components/common/PageHeader/index.tsx index ca5cd0d1..de25cfb5 100644 --- a/src/components/common/PageHeader/index.tsx +++ b/src/components/common/PageHeader/index.tsx @@ -3,46 +3,6 @@ import styled from '@emotion/styled' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -const StyledPageHeader = styled.div<{ - isDarkMode?: boolean - hasBackground?: boolean -}>` - max-width: 414px; - 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 StyledIcon = styled.div` - width: 38px; - height: 38px; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; -` - type PageHeaderProps = { title: string leftIcon?: React.ReactNode @@ -106,4 +66,43 @@ const PageHeader = ({ ) } +const StyledPageHeader = styled.div<{ + isDarkMode?: boolean + hasBackground?: boolean +}>` + max-width: 414px; + width: 100%; + height: 72px; + 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 StyledIcon = styled.div` + width: 38px; + height: 38px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +` + export default PageHeader diff --git a/src/components/common/Text/index.tsx b/src/components/common/Text/index.tsx index 10aa093c..7c438b65 100644 --- a/src/components/common/Text/index.tsx +++ b/src/components/common/Text/index.tsx @@ -1,14 +1,25 @@ import { css } from '@emotion/react' import styled from '@emotion/styled' +import { palette } from '@/styles/palette' import { KeyOfTypo } from '@/styles/theme' import { typo } from '@/styles/typo' -export const TextWrapper = styled.div` +/** + * @param display: (기본값: flex) + * @param flexDirection: (기본값: column) + * @param justifyContent: (기본값: center) + * @param alignItems: (기본값: flex-start) + */ + +export const TextWrapper = styled.div<{ + isDarkMode?: boolean +}>` display: flex; flex-direction: column; justify-content: center; align-items: flex-start; + color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.WHITE)}; ` export const Text = styled.div<{ diff --git a/src/components/home/Card.tsx b/src/components/home/Card.tsx index 1087ed60..40d3e007 100644 --- a/src/components/home/Card.tsx +++ b/src/components/home/Card.tsx @@ -1,84 +1,78 @@ 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 { useEffect } from 'react' 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 useTimerStore from '@/store/TimerStore' import { palette } from '@/styles/palette' -import Tip from './Tip' +import CardBottom from './CardBottom' +import CardHeader from './CardHeader' +import CardMiddle from './CardMiddle' -type TimerRefType = ReturnType | null +const StyledCard = styled(motion.div)<{ + isDarkMode: boolean +}>` + width: 100%; + height: 348px; + border-radius: 20px; + box-shadow: ${palette.SHADOW}; + display: flex; + flex-direction: column; + margin: 0 auto; + justify-content: center; + align-items: center; + padding: 19px 7px 15px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; +` + +const StyledWatingWrapper = styled(motion.div)` + width: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1; +` + +const watingCounter = { + hidden: { opacity: 0 }, + visible: { opacity: 1, transition: { type: 'spring', damping: 12, duration: 0.5 } }, + exit: { opacity: 0, transition: { duration: 1 } }, +} 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 Card = ({ isDarkMode }: CardProps) => { + const { time, isRunning, startTimer, resetTimer } = useTimerStore() - const handleCancelClick = () => { - setTime(0) - if (timerRef.current) { - timerRef.current.stop() + window.onload = () => { + const navigationType = ( + performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming + ).type + if (navigationType !== 'reload') { + resetTimer() } - 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() - } + if (isRunning) { + startTimer() + } else if (!isRunning) { + resetTimer() } - - return () => { - if (timerRef.current) { - timerRef.current.stop() - } - } - }, [isMatching]) + }, [isRunning, startTimer, resetTimer]) return ( - - {!isMatching ? ( + + {!isRunning ? ( { exit={'exit'} variants={watingCounter} > - + ) : ( - - - - - {'3'} - - - {'/5'} - - - + - - - {formatTime(time)} - - - - {'매칭 취소'} - - - - {'매칭 중'}    - - - - - - - - + + + + )} - + ) } -const StyleCard = styled(motion.div)<{ - isDarkMode: boolean -}>` - width: 100%; - height: 348px; - border-radius: 20px; - background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; - box-shadow: ${palette.SHADOW}; - display: flex; - flex-direction: column; - margin: 0 auto; - justify-content: center; - align-items: center; - padding: 5% 1% 5%; -` - -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 5%; - position: relative; -` - -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`` - export default Card diff --git a/src/hooks/useModal.tsx b/src/hooks/useModal.tsx index de18310e..e8d921b2 100644 --- a/src/hooks/useModal.tsx +++ b/src/hooks/useModal.tsx @@ -6,16 +6,39 @@ type ModalConfirmPropsType = { okFunc: () => void mainText: string subText?: string + acceptText?: string + cancelText?: string + isDarkMode?: boolean } export const useModal = () => { - const { setModalState, setOkFunc, setMainText, setSubText, setType } = useModalStore() + const { + setModalState, + setOkFunc, + setMainText, + setSubText, + setType, + setAcceptText, + setCancelText, + setDarkMode, + } = useModalStore() - const openModal = ({ mainText, subText, okFunc, type }: ModalConfirmPropsType) => { + const openModal = ({ + mainText, + subText, + okFunc, + type, + acceptText, + cancelText, + isDarkMode, + }: ModalConfirmPropsType) => { setModalState(true) setType(type) setMainText(mainText) setSubText(subText) setOkFunc(okFunc) + setAcceptText(acceptText) + setCancelText(cancelText) + setDarkMode(isDarkMode) } return { openModal, Modal } diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 60ebacce..9bea8f50 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,6 +1,13 @@ import { http, HttpResponse } from 'msw' const nickname = '주다다' export const handlers = [ + + http.get(`/v1/users/duplicate?nickname=${nickname}`, async () => { + return HttpResponse.json({ + duplicate: true, + }) + }), + http.get('/v1/histories', () => { return HttpResponse.json([ { diff --git a/src/pages/chatList/ChatList.tsx b/src/pages/chatList/ChatList.tsx index 595943d7..015f1f86 100644 --- a/src/pages/chatList/ChatList.tsx +++ b/src/pages/chatList/ChatList.tsx @@ -1,3 +1,4 @@ +import styled from '@emotion/styled' import { useQuery } from '@tanstack/react-query' import { motion } from 'framer-motion' import { useNavigate } from 'react-router-dom' @@ -33,7 +34,7 @@ const ChatList = () => { - { } isDarkMode={isDarkMode} hasBackground={true} - style={{ - position: 'fixed', - zIndex: 1, - }} /> {isSuccess && ( @@ -62,4 +59,8 @@ const ChatList = () => { ) } +const StyledPageHeader = styled(PageHeader)` + padding: 0 18px; +` + export default ChatList diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 97813355..7bd7ee97 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -1,5 +1,3 @@ -import { useState } from 'react' - import AppHeader from '@/components/common/AppHeader' import { ParticularTopicButton } from '@/components/common/Buttons/IconButton' import GradationBackground from '@/components/common/GradationBackground' @@ -15,7 +13,6 @@ const Home = () => { const nickname = '우땅' const isDarkMode = useThemeStore((state) => state.isDarkMode) const toggleDarkMode = useThemeStore((state) => state.toggleDarkMode) - const [isMatching, setIsMatching] = useState(false) const { showToast } = useToast() @@ -35,25 +32,19 @@ const Home = () => { letterSpacing={-0.5} style={{ margin: '33px 0 22px 0', - color: isDarkMode ? `${palette.DARK_WHITE}` : `${palette.BLACK}`, + color: isDarkMode ? `${palette.DARK_WHITE}` : `${palette.DARK_BLUE}`, }} > {'진행중인 매칭'} - { - setIsMatching((prev) => !prev) - }} - isDarkMode={isDarkMode} - /> + {'커피밋의 추천기능'} diff --git a/src/pages/notFound/NotFound.tsx b/src/pages/notFound/NotFound.tsx index 46518bd1..4cadb72f 100644 --- a/src/pages/notFound/NotFound.tsx +++ b/src/pages/notFound/NotFound.tsx @@ -1,15 +1,17 @@ import styled from '@emotion/styled' +import { useNavigate } from 'react-router-dom' import NotFoundIcon from '@/assets/icons/NotFoundIcon' import NormalButton from '@/components/common/Buttons/NormalButton' import GradationBackground from '@/components/common/GradationBackground' import Spacing from '@/components/common/Spacing' import { Text } from '@/components/common/Text' +import useThemeStore from '@/store/ThemeStore' import { palette } from '@/styles/palette' const NotFoundPage = () => { - // TODO: zustand로 darkMode 상태 받기 - const isDarkMode = true + const isDarkMode = useThemeStore((state) => state.isDarkMode) + const navigate = useNavigate() return ( @@ -40,7 +42,14 @@ const NotFoundPage = () => { - {'홈으로 돌아가기'} + { + navigate('/') + }} + > + {'홈으로 돌아가기'} + diff --git a/src/pages/profile/ProfileDefault.tsx b/src/pages/profile/ProfileDefault.tsx index df7e8164..365946ec 100644 --- a/src/pages/profile/ProfileDefault.tsx +++ b/src/pages/profile/ProfileDefault.tsx @@ -1,19 +1,274 @@ +import styled from '@emotion/styled' +import { AiOutlineInfoCircle } from 'react-icons/ai' +import { BiBuildings, BiChevronRight, BiPencil } from 'react-icons/bi' +import { MdOutlineRecordVoiceOver } from 'react-icons/md' +import { PiIdentificationCardBold } from 'react-icons/pi' import { useNavigate } from 'react-router-dom' +import NaverIcon from '@/assets/icons/NaverIcon' +import Avatar from '@/components/common/Avatar' +import BackChevron from '@/components/common/BackChevron' +import { InterestButton } from '@/components/common/Buttons/IconButton' +import { Divider } from '@/components/common/Divider' +import { FlexBox } from '@/components/common/Flexbox' +import GradationBackground from '@/components/common/GradationBackground' +import ProfileListRow from '@/components/common/ListRow/ProfileListRow' +import NavigationBar from '@/components/common/NavigationBar' +import PageContainer from '@/components/common/PageContainer' +import PageHeader from '@/components/common/PageHeader' +import Spacing from '@/components/common/Spacing' +import { Text, TextWrapper } from '@/components/common/Text' +import { useModal } from '@/hooks/useModal' +import useToast from '@/hooks/useToast' +import useThemeStore from '@/store/ThemeStore' +import { palette } from '@/styles/palette' + const ProfileDefault = () => { const navigate = useNavigate() + const isDarkMode = useThemeStore((store) => store.isDarkMode) + const { openModal } = useModal() + + const { showToast } = useToast() + + const handleLogout = () => { + openModal({ + mainText: '로그아웃 하시겠습니까?', + subText: '로그아웃 시 로그인 화면으로 이동합니다.', + okFunc: () => { + navigate('/login') + }, + type: 'warn', + acceptText: '네, 로그아웃 하겠습니다.', + cancelText: '아니오. 취소하겠습니다.', + isDarkMode, + }) + } + + const handleDeleteAccount = () => { + openModal({ + mainText: '계정을 삭제하시겠습니까?', + subText: '계정을 삭제하시면 모든 정보가 삭제되며 복구할 수 없습니다.', + okFunc: () => { + navigate('/login') + }, + type: 'warn', + acceptText: '네, 삭제하겠습니다.', + cancelText: '아니오. 취소하겠습니다.', + isDarkMode, + }) + } return ( -
- -
+ + + {'개인 및 어플리케이션 정보'} + + + + } + title={'회사 정보 변경'} + isDarkMode={isDarkMode} + additionalContent={} + moveFromProfileListRow={() => { + navigate('/profile/privacy') + }} + /> + + } + title={'불편사항 접수'} + isDarkMode={isDarkMode} + additionalContent={} + moveFromProfileListRow={() => { + showToast({ + message: '아직 준비중인 기능입니다!', + type: 'info', + isDarkMode, + }) + }} + /> + + } + title={'소셜 로그인 계정'} + isDarkMode={isDarkMode} + additionalContent={ + + } + /> + + } + title={'커피밋'} + isDarkMode={isDarkMode} + additionalContent={'v 0.1'} + /> + + + + {'⚠️ 주의'} + + + + + {'로그아웃'} + + + + {'계정 삭제'} + + + + + ) } +const StyledProfilePageHeader = styled.div` + margin: 33px 26.5px 0; +` + +const StyledProfilePrimaryInfo = styled.div<{ + isDarkMode: boolean +}>` + display: flex; + height: 100%; + justify-content: space-between; + align-items: center; + flex: 1; + color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.WHITE)}; +` + +const StyledProfilePrimaryInfoTextWrapper = styled.div` + margin-left: 23px; + height: 49px; + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-around; + + @media (max-width: 280px) { + margin-left: 10px; + } +` + +const StyledProfilePageContentCard = styled.div<{ + isDarkMode: boolean + height: number +}>` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border-radius: 20px; + padding: 0 18px; + height: ${({ height }) => height}px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; + box-shadow: 0px 4px 20px 0px rgba(0, 0, 0, 0.05); +` + +const StyledProfileWarningText = styled(Text)` + width: 100%; + color: ${palette.RED}; + display: flex; + justify-content: center; + align-items: center; + flex: 1; + cursor: pointer; +` + export default ProfileDefault diff --git a/src/pages/profile/ProfileEdit.tsx b/src/pages/profile/ProfileEdit.tsx index 60da403f..fe740a1e 100644 --- a/src/pages/profile/ProfileEdit.tsx +++ b/src/pages/profile/ProfileEdit.tsx @@ -1,22 +1,7 @@ -import { testWithBtn } from '@/apis/test' -import NormalButton from '@/components/common/Buttons/NormalButton' - const ProfileEdit = () => { - const testClick = async () => { - try { - const response = await testWithBtn('길동이') - console.log(response.data) - // 로그인 성공 시 필요한 로직을 추가합니다. - } catch (error) { - console.error('카카오 로그인에 실패했습니다.', error) - } - } return (
- {'ProfileEdit'} - - {'닉네임 중복 테스트'} - +

{'ProfileEdit'}

) } diff --git a/src/store/ModalStore.tsx b/src/store/ModalStore.tsx index c87e8751..d67b129b 100644 --- a/src/store/ModalStore.tsx +++ b/src/store/ModalStore.tsx @@ -11,6 +11,12 @@ type ModalState = { setModalState: (state: boolean) => void setMainText: (text: string) => void setOkFunc: (func: () => void) => void + acceptText?: string + cancelText?: string + isDarkMode?: boolean + setAcceptText: (text: string | undefined) => void + setCancelText: (text: string | undefined) => void + setDarkMode: (isDarkMode: boolean | undefined) => void } const useModalStore = create((set) => ({ @@ -19,10 +25,16 @@ const useModalStore = create((set) => ({ mainText: '', subText: '', type: 'confirm', + acceptText: '네', + cancelText: '아니오', 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 }), + setAcceptText: (text) => set({ acceptText: text }), + setCancelText: (text) => set({ cancelText: text }), + setDarkMode: (isDarkMode) => set({ isDarkMode }), })) + export default useModalStore From 40df3cba72177106f91f1dcc5f5f27c124ad2e5c Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Fri, 17 Nov 2023 03:35:56 +0900 Subject: [PATCH 055/180] =?UTF-8?q?[Feature]=20=EC=8B=A4=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=20=EC=B1=84=ED=8C=85=20=EA=B8=B0=EB=8A=A5=20=EB=B0=8F=20?= =?UTF-8?q?=EC=83=81=EB=8C=80=EB=B0=A9=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EC=83=81=EB=8C=80=EB=B0=A9=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20(#125)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore : 충돌 수정 * chore : 충돌 수정 * chore : 충돌 수정 * fix : 에러 주석 처리 * fix : 채팅방 통신 수정 * style : 채팅 버블 스타일 변경 및 첫 렌더 링 시 스크롤 맨 아래로 이동 * feat : 채팅방에서 상대방 프로필 클릭 시 정보 조회 기능 추가 * chore : 사용되지 않는 import 문 제거 * Update handlers.ts * Update handlers.ts --- setupProxy.ts | 18 +- src/apis/axios.ts | 3 +- src/apis/chatting/chattingApi.ts | 9 + src/apis/chatting/chattingType.ts | 8 + src/assets/icons/Send.svg | 3 + src/components/chatBubbleListLow/index.tsx | 118 +++++++ src/components/common/Avatar/index.tsx | 2 +- .../common/BottomSheet/ProfileSheet.tsx | 37 +- src/components/common/ChattingText/index.tsx | 11 +- src/components/common/TextArea/index.tsx | 53 +++ .../common/chattingBubble/index.tsx | 20 +- src/components/dateTime/index.tsx | 46 +++ src/components/messageArea/index.tsx | 62 ++++ src/mocks/handlers.ts | 323 +++++++++++++++++- src/pages/chatting/Chatting.tsx | 199 ++++++++++- src/store/BottomSheetStore.tsx | 15 + 16 files changed, 895 insertions(+), 32 deletions(-) create mode 100644 src/apis/chatting/chattingApi.ts create mode 100644 src/apis/chatting/chattingType.ts create mode 100644 src/assets/icons/Send.svg create mode 100644 src/components/chatBubbleListLow/index.tsx create mode 100644 src/components/common/TextArea/index.tsx create mode 100644 src/components/dateTime/index.tsx create mode 100644 src/components/messageArea/index.tsx create mode 100644 src/store/BottomSheetStore.tsx diff --git a/setupProxy.ts b/setupProxy.ts index b7c3139b..84d0e909 100644 --- a/setupProxy.ts +++ b/setupProxy.ts @@ -1,10 +1,10 @@ -import { createProxyMiddleware } from 'http-proxy-middleware' +// import { createProxyMiddleware } from 'http-proxy-middleware' -module.exports = (app) => { - app.use( - createProxyMiddleware('/api/chat-stomp', { - target: process.env.VITE_BASE_URL, - ws: true, - }), - ) -} +// module.exports = (app) => { +// app.use( +// createProxyMiddleware('/api/chat-stomp', { +// target: process.env.VITE_BASE_URL, +// ws: true, +// }), +// ) +// } diff --git a/src/apis/axios.ts b/src/apis/axios.ts index d1c72937..fe99eba1 100644 --- a/src/apis/axios.ts +++ b/src/apis/axios.ts @@ -8,12 +8,13 @@ export const axiosAPI = axios.create({ ? 'http://localhost:5173' : import.meta.env.VITE_BASE_URL, }) - +// const { authTokens } = useAuthStore() // Add a request interceptor axiosAPI.interceptors.request.use( function (config) { // 요청 바로 직전 // axios 설정값에 대해 작성합니다. + // const { authTokens } = useAuthStore() // config.headers['Authorization'] = `Bearer ${authTokens}` diff --git a/src/apis/chatting/chattingApi.ts b/src/apis/chatting/chattingApi.ts new file mode 100644 index 00000000..6fab185d --- /dev/null +++ b/src/apis/chatting/chattingApi.ts @@ -0,0 +1,9 @@ +import { axiosAPI } from '@/apis/axios' +import { Messages } from '@/apis/chatting/chattingType' + +export const ChattingApi = { + GET_DETAIL_MESSAGES: async (): Promise => { + const response = await axiosAPI.get(`/v1/chatting/rooms/1`) + return response.data + }, +} diff --git a/src/apis/chatting/chattingType.ts b/src/apis/chatting/chattingType.ts new file mode 100644 index 00000000..72ff1ccf --- /dev/null +++ b/src/apis/chatting/chattingType.ts @@ -0,0 +1,8 @@ +export type Messages = { + messageId: string + userId: string + profileImageUrl: string + nickname: string + content: string + createdAt: string +} diff --git a/src/assets/icons/Send.svg b/src/assets/icons/Send.svg new file mode 100644 index 00000000..3e234e5d --- /dev/null +++ b/src/assets/icons/Send.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/chatBubbleListLow/index.tsx b/src/components/chatBubbleListLow/index.tsx new file mode 100644 index 00000000..c8292f83 --- /dev/null +++ b/src/components/chatBubbleListLow/index.tsx @@ -0,0 +1,118 @@ +import styled from '@emotion/styled' +import { type ComponentProps, type ReactNode } from 'react' + +import defaultProfileImage from '@/assets/images/defaultProfileImage.png' +import Avatar from '@/components/common/Avatar' +import { ChattingText } from '@/components/common/ChattingText' +import useBottomSheetStore from '@/store/BottomSheetStore' +import { type KeyOfPalette, type KeyOfTypo } from '@/styles/theme' + +interface ListRowProps extends ComponentProps<'div'> { + rightElement: ReactNode + userId: string + profileImageUrl: string + leftImage?: string | undefined + mainText: ReactNode + textTypo?: KeyOfTypo + textColor?: KeyOfPalette + imageGap?: number + gap?: number + subElement?: ReactNode + fullWidth?: boolean +} + +/** + * @param rightElement : 오른쪽에 위치시킬 React Element + * @param leftImage : 왼쪽에 위치시킬 Image 컴포넌트 + * @param mainText: 왼쪽에 위치시킬 main Text + * @param textTypo : main Text에 적용할 typo + * @param textColor: main Text에 적용시킬 color + * @param imageGap : image와 Text 사이의 gap + * @param gap : text와 subText 사이의 gap 결정 + * @param subElement: main Text 아래에 위치할 React Element + * @param fullWidth : true로 설정할 경우 width: 100%, 기본값 true + */ + +const ChatBubbleListLow = ({ + rightElement, + userId, + profileImageUrl, + leftImage = defaultProfileImage, + mainText, + textTypo = 'Body_12', + textColor = 'GRAY500', + gap = 4, + imageGap = 5, + subElement, + fullWidth = true, + ...props +}: ListRowProps) => { + const { setBottomSheetState, setUserId } = useBottomSheetStore() + const handleBottomSheet = () => { + setBottomSheetState(true) + setUserId(userId) + } + return ( + <> + + + + {leftImage ? : ''} + + + + {subElement && subElement} + + + {rightElement} + + + ) +} + +const MainFlexBox = styled.div<{ fullWidth: boolean }>` + display: flex; + justify-content: space-between; + align-items: center; + width: ${({ fullWidth }) => (fullWidth ? '100%' : undefined)}; +` + +const SubFlexBox = styled.div<{ gap: number }>` + display: flex; + justify-content: flex-start; + align-items: center; + gap: ${({ gap }) => `${gap}px`}; +` +const StyleAvatarWrapper = styled.span`` +const TextFlexBox = styled.div<{ gap: number }>` + display: flex; + flex-direction: column; + justify-content: center; + gap: ${({ gap }) => `${gap}px`}; + align-items: flex-start; + width: 330px; +` + +const StyledText = ({ + text, + typo, + color, + ...props +}: { + text: ReactNode + typo: KeyOfTypo + color: KeyOfPalette +} & ComponentProps<'div'>) => { + return ( + <> + {typeof text === 'string' ? ( + + {text} + + ) : ( +
{text}
+ )} + + ) +} +export default ChatBubbleListLow diff --git a/src/components/common/Avatar/index.tsx b/src/components/common/Avatar/index.tsx index f1b5786b..49865b4f 100644 --- a/src/components/common/Avatar/index.tsx +++ b/src/components/common/Avatar/index.tsx @@ -59,7 +59,7 @@ const StyledAvatar = styled.div` 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')}; - + cursor: pointer; @media (max-width: 280px) { width: ${(props) => typeof props.width === 'number' ? `${props.width * 0.95}px` : `calc(${props.width} * 0.95)`}; diff --git a/src/components/common/BottomSheet/ProfileSheet.tsx b/src/components/common/BottomSheet/ProfileSheet.tsx index 54adefca..3338563a 100644 --- a/src/components/common/BottomSheet/ProfileSheet.tsx +++ b/src/components/common/BottomSheet/ProfileSheet.tsx @@ -1,14 +1,21 @@ import styled from '@emotion/styled' import { AnimatePresence, motion } from 'framer-motion' -import { MouseEvent, useState } from 'react' +import { MouseEvent, useEffect, useState } from 'react' import { AiOutlineClose } from 'react-icons/ai' +import { axiosAPI } from '@/apis/axios' import Avatar from '@/components/common/Avatar' import { Text } from '@/components/common/Text' +import useBottomSheetStore from '@/store/BottomSheetStore' import { palette } from '@/styles/palette' import { InterestButton } from '../Buttons/IconButton' - +type OpponentUserData = { + nickname: string + profileImageUrl: string + department: string + interests: string[] +} const Background = styled(motion.div)` width: 100%; height: 100%; @@ -64,15 +71,29 @@ type ProfileSheetProps = { const ProfileSheet = ({ title, isDarkMode }: ProfileSheetProps) => { const [isOpen, setIsOpen] = useState(true) // ProfileSheet의 상태 - + const { setBottomSheetState, userId } = useBottomSheetStore() + const [opponentUserProfile, setOpponentUserProfile] = useState({ + nickname: '', + profileImageUrl: '', + department: '', + interests: [], + }) const handleWrapperClick = (e: MouseEvent) => { e.stopPropagation() } const toggleProfileSheet = () => { setIsOpen(false) + setBottomSheetState(false) } - + const getOpponentUserProfile = async () => { + const response = await axiosAPI.get(`/api/v1/users/${userId}`) + console.log(response) + setOpponentUserProfile(response.data) + } + useEffect(() => { + getOpponentUserProfile() + }) const slideUp = { hidden: { y: '100%', opacity: 0 }, visible: { y: '0%', opacity: 1, transition: { type: 'spring', damping: 15, stiffness: 100 } }, @@ -133,12 +154,14 @@ const ProfileSheet = ({ title, isDarkMode }: ProfileSheetProps) => { diff --git a/src/components/common/ChattingText/index.tsx b/src/components/common/ChattingText/index.tsx index 24032bd9..7ea2c505 100644 --- a/src/components/common/ChattingText/index.tsx +++ b/src/components/common/ChattingText/index.tsx @@ -17,13 +17,7 @@ export interface TextProps extends HTMLAttributes { export type TextPropsKey = 'typo' | 'color' -export const ChattingText = ({ - typo = 'Body_16', - as = 'h1', - color, - children, - ...props -}: TextProps) => { +export const ChattingText = ({ typo, as = 'h1', color, children, ...props }: TextProps) => { return ( {children} @@ -36,6 +30,9 @@ const StyledText = styled.span<{ colorKey?: TextType['color'] }>` white-space: pre-wrap; + font-size: ${({ typoKey }) => { + return typoKey && theme.typo[typoKey]() + }}; color: ${({ colorKey }) => { return colorKey && theme.palette[colorKey] }}; diff --git a/src/components/common/TextArea/index.tsx b/src/components/common/TextArea/index.tsx new file mode 100644 index 00000000..8f48c4bb --- /dev/null +++ b/src/components/common/TextArea/index.tsx @@ -0,0 +1,53 @@ +import styled from '@emotion/styled' +import { ComponentProps, forwardRef } from 'react' + +import { FlexBox } from '@/components/common/Flexbox' +import { palette } from '@/styles/palette' +import { typo } from '@/styles/typo' + +interface TextAreaProps extends ComponentProps<'textarea'> { + width?: number + height?: number + placeholder?: string + value?: string +} + +const TextArea = forwardRef(function Textarea( + { width, height, placeholder = '메세지를 입력해주세요.', value, ...props }: TextAreaProps, + textareaRef, +) { + return ( + + + + ) +}) + +const StyledTextArea = styled.textarea<{ widthProps?: number; heightProps?: number }>` + width: ${({ widthProps }) => (widthProps ? `${widthProps}px` : '100%')}; + height: ${({ heightProps }) => (heightProps ? `${heightProps}px` : '50px')}; + background-color: ${palette.WHITE}; + border-radius: 10px; + padding: 8px; + border: none; + color: ${palette.GRAY700}; + border-radius: 10px; + border: none; + resize: none; + /* line-height: 270%; */ + font-size: ${typo.Body_14()}; + ${({ theme }) => theme.typo.Body_14()}; + ::placeholder { + ${({ theme }) => theme.typo.Caption_11()} + color: ${({ theme }) => theme.palette.GRAY500}; + } +` + +export default TextArea diff --git a/src/components/common/chattingBubble/index.tsx b/src/components/common/chattingBubble/index.tsx index a5b4fcd2..b3d72c02 100644 --- a/src/components/common/chattingBubble/index.tsx +++ b/src/components/common/chattingBubble/index.tsx @@ -9,6 +9,8 @@ import { type KeyOfPalette, type KeyOfTypo } from '@/styles/theme' interface ChattingBubbleProps extends ComponentProps<'div'> { isMyChat?: boolean message: string + userId: string + userProfile: string time: string messageTypo?: KeyOfTypo messageColor?: KeyOfPalette @@ -31,33 +33,38 @@ const ChattingBubble = ({ isMyChat = false, message, time, + userId, + userProfile, messageTypo = 'Body_12', messageColor = 'BLACK', timeTypo = 'Caption_11', timeColor = 'GRAY500', ...props }: ChattingBubbleProps) => { + const hourAndMinute = time.slice(11, 16) + return ( 17} {...props} > - + {message} - - {time} + + {hourAndMinute} ) } -const BubbleContainer = styled(FlexBox)<{ isMyChat: boolean }>` +const BubbleContainer = styled(FlexBox)<{ isMyChat: boolean; messageLength: boolean }>` justify-content: ${(props) => props.isMyChat && 'flex-end'}; ` @@ -66,14 +73,17 @@ const StyledText = styled.div<{ }>` border-radius: 10px; background-color: ${palette.WHITE}; - padding: 7px 12px; + padding: 6px 8px; word-wrap: break-word; order: ${(props) => (props.isMyChat ? '2' : '1')}; + margin-right: ${(props) => (props.isMyChat ? '12px' : '0px')}; + margin-left: ${(props) => (props.isMyChat ? '0px' : '5px')}; ` const TimeText = styled(ChattingText)` order: 1; line-height: 150%; + font-family: '200'; ` const MessageText = styled(ChattingText)` diff --git a/src/components/dateTime/index.tsx b/src/components/dateTime/index.tsx new file mode 100644 index 00000000..0e4cf765 --- /dev/null +++ b/src/components/dateTime/index.tsx @@ -0,0 +1,46 @@ +import styled from '@emotion/styled' +import { ComponentProps } from 'react' + +import { ChattingText } from '@/components/common/ChattingText' +import { FlexBox } from '@/components/common/Flexbox' +import { KeyOfPalette, KeyOfTypo, theme } from '@/styles/theme' + +export interface DateTimeProps extends ComponentProps<'div'> { + typo?: KeyOfTypo + color?: KeyOfPalette + backgroundColor?: KeyOfPalette + width?: number + height?: number + content: string +} + +const Datetime = ({ + typo = 'Body_14', + color = 'BLACK', + backgroundColor = 'GRAY300', + width = 220, + height = 30, + content, +}: DateTimeProps) => { + return ( + + + {content} + + + ) +} + +const DateTimeWrapper = styled(FlexBox)<{ + backgroundColor: KeyOfPalette + width: number + height: number +}>` + background-color: ${({ backgroundColor }) => theme.palette[backgroundColor]}; + width: ${({ width }) => `${width}px`}; + height: ${({ height }) => `${height}px`}; + padding: 15px; + border-radius: 10px; +` + +export default Datetime diff --git a/src/components/messageArea/index.tsx b/src/components/messageArea/index.tsx new file mode 100644 index 00000000..a486bbc8 --- /dev/null +++ b/src/components/messageArea/index.tsx @@ -0,0 +1,62 @@ +import styled from '@emotion/styled' + +import { Messages } from '@/apis/chatting/chattingType' +import ChatBubbleListLow from '@/components/chatBubbleListLow' +import ChattingBubble from '@/components/common/chattingBubble' +import { FlexBox } from '@/components/common/Flexbox' +import Spacing from '@/components/common/Spacing' + +interface MessageProps { + messageData: Messages[] +} +const MessageArea = ({ messageData }: MessageProps) => { + return ( + <> + + + {messageData.map((message, i) => + //로그인 성공 후 nickname 전역에 저장하면 내 닉네임 불러오기 + message.nickname === 'CKYRRMOSSX' || message.nickname === 'LLYRSRAA' ? ( + + + + ) : ( + + + } + rightElement={null} + /> + + ), + )} + + + + ) +} + +const StyleChattingBubbleWrapper = styled.div<{ isMyChat: boolean }>` + width: 97%; + margin-left: ${(props) => (props.isMyChat ? '' : '15px')}; + /* padding-left: ${(props) => props.isMyChat && '10%'}; + padding-right: ${(props) => !props.isMyChat && '10%'}; */ +` +export default MessageArea diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 9bea8f50..f95cd45c 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,4 +1,5 @@ import { http, HttpResponse } from 'msw' + const nickname = '주다다' export const handlers = [ @@ -10,6 +11,7 @@ export const handlers = [ http.get('/v1/histories', () => { return HttpResponse.json([ +//https://github.com/coffee-meet/frontend/pull/125/conflict?name=src%252Fmocks%252Fhandlers.ts&ancestor_oid=754b9948060f7dc0a13eabf5683fc01c1cc625f2&base_oid=9d66112fef597345b51b9ba8a5c9e672cb7f165d&head_oid=9bea8f50e6adda8dafdc0d062f4ca3a31657fb0c , { title: '🥤️ 차가운 아메리카노-6', participants: ['우땅', '빅맘', '롤로노아 조로'], @@ -33,7 +35,325 @@ export const handlers = [ { title: '🍦 고소한 아이스크림-2', participants: ['우땅', '빅맘', '우솝'], - createdAt: '2023-11-06T00:10:50', + }, + ]) + }), + http.delete(`/api/v1/chatrooms/1`, () => { + return new HttpResponse(null, { + status: 200, + statusText: '삭제 완료', + }) + }), + http.get(`/v1/chatting/rooms/1`, () => { + return HttpResponse.json([ + { + messageId: 7446, + nickname: 'ZWREDID', + content: 'JCKSJFM', + createdAt: '2080-04-24T13:01:25.104160649', + }, + { + messageId: 7654, + nickname: 'EMKCMQFJMN', + content: 'GMIQT', + createdAt: '2005-05-29T23:40:42.380207854', + }, + { + messageId: 4582, + nickname: 'NOHMUDVUSC', + content: 'WPX와라라라라랄ㄹ라라라랄', + createdAt: '2013-02-01T17:44:39.934666315', + }, + { + messageId: 7933, + nickname: 'IDZNJHE', + content: 'WNZUZBRL와라라라라랄ㄹ라라라랄', + createdAt: '2073-10-08T07:45:49.877123645', + }, + { + messageId: 812, + nickname: 'PPMQ', + content: 'MGHAWBFEP와라라라라랄ㄹ라라라랄', + createdAt: '1971-09-26T14:23:16.425577078', + }, + { + messageId: 9171, + nickname: 'BMNB', + content: 'LSHL와라라라라랄ㄹ라라라랄', + createdAt: '2008-07-13T23:36:55.533629238', + }, + { + messageId: 3718, + nickname: 'VICJDZOOGF', + content: 'WFTWGWTKBY와라라라라랄ㄹ라라라랄', + createdAt: '2056-12-17T21:18:44.262192453', + }, + { + messageId: 3455, + nickname: 'PVCFA', + content: 'JTN와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄', + createdAt: '2018-10-31T05:13:20.968140667', + }, + { + messageId: 5703, + nickname: 'THUIO', + content: 'XZXHBIK와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄', + createdAt: '2023-05-27T03:26:51.124659514', + }, + { + messageId: 6914, + nickname: 'LCSLFI', + content: 'QDH와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄', + createdAt: '2008-12-14T06:30:12.657522165', + }, + { + messageId: 5255, + nickname: 'MPCACR', + content: + 'OEYI와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄', + createdAt: '2012-09-01T20:45:54.946951577', + }, + { + messageId: 2544, + nickname: 'KPV', + content: + 'GTTGHKZORK와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄 ', + createdAt: '2007-12-05T00:24:19.484941744', + }, + { + messageId: 1325, + nickname: 'RNEPLM', + content: 'DCQJIX와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄', + createdAt: '2082-05-17T07:53:03.051337821', + }, + { + messageId: 1932, + nickname: 'EENVI', + content: + 'IEKNKHJ와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄', + createdAt: '2064-11-14T20:55:24.851249176', + }, + { + messageId: 474, + nickname: 'NLCNYWD', + content: + 'QNNJY와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄', + createdAt: '1991-12-23T16:00:39.675189942', + }, + { + messageId: 9269, + nickname: 'FLEE', + content: 'QQE', + createdAt: '2030-10-10T00:43:31.72951732', + }, + { + messageId: 7394, + nickname: 'ITCDVGPEX', + content: + 'ANMGSXLYL와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄', + createdAt: '2070-02-27T15:12:32.544116729', + }, + { + messageId: 7344, + nickname: 'QSAJ', + content: + 'BIK와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄와라라라라랄ㄹ라라라랄', + createdAt: '2065-10-08T21:49:48.130735954', + }, + { + messageId: 9822, + nickname: 'XDPBJXCZ', + content: + 'FQYUOH으갸갸갸갸ㅑ갸갸갸갸갹으갸갸갸갸ㅑ갸갸갸갸갹으갸갸갸갸ㅑ갸갸갸갸갹으갸갸갸갸ㅑ갸갸갸갸갹으갸갸갸갸ㅑ갸갸갸갸갹', + createdAt: '1985-07-14T09:04:29.122377397', + }, + { + messageId: 1321, + nickname: 'RYBMBCGQY', + content: + 'KSBDDBOFIX으갸갸갸갸ㅑ갸갸갸갸갹으갸갸갸갸ㅑ갸갸갸갸갹으갸갸갸갸ㅑ갸갸갸갸갹으갸갸갸갸ㅑ갸갸갸갸갹', + createdAt: '2082-04-22T15:36:23.636192181', + }, + { + messageId: 9357, + nickname: 'MKWE', + content: + 'FXBHYMLBVO으갸갸갸갸ㅑ갸갸갸갸갹으갸갸갸갸ㅑ갸갸갸갸갹으갸갸갸갸ㅑ갸갸갸갸갹으갸갸갸갸ㅑ갸갸갸갸갹', + createdAt: '2049-06-08T03:43:18.715910816', + }, + { + messageId: 8214, + nickname: 'VDWE', + content: 'RKJOORTPXF', + createdAt: '1994-08-04T23:39:15.226775267', + }, + { + messageId: 8556, + nickname: 'CKN', + content: 'TKRFFGJWPY', + createdAt: '1984-04-03T03:16:24.273319631', + }, + { + messageId: 2272, + nickname: 'JPEMOIZR', + content: 'ZUIIFMZGV', + createdAt: '1993-10-30T16:45:11.160251527', + }, + { + messageId: 9452, + nickname: 'OASS', + content: 'GCM', + createdAt: '2045-11-15T08:08:42.193992168', + }, + { + messageId: 2439, + nickname: 'QCDJF', + content: 'EYXB', + createdAt: '2064-10-01T23:15:16.865270273', + }, + { + messageId: 5004, + nickname: 'VNHS', + content: 'RCZI', + createdAt: '2016-11-13T10:17:11.492493489', + }, + { + messageId: 9879, + nickname: 'HHDYPTYOZ', + content: 'IQSLJX', + createdAt: '2045-01-26T13:11:05.916388939', + }, + { + messageId: 9589, + nickname: 'MJCNPWG', + content: 'LYMJ', + createdAt: '2029-02-16T07:23:29.753928566', + }, + { + messageId: 9786, + nickname: 'UMLJC', + content: 'LZYUCYSV', + createdAt: '1993-05-13T17:11:42.900222395', + }, + { + messageId: 7973, + nickname: 'REIQW', + content: 'CLKCE', + createdAt: '2071-01-22T00:17:02.571309299', + }, + { + messageId: 2757, + nickname: 'UBKIXQFERG', + content: 'QCZGRCGRB', + createdAt: '2074-09-28T03:32:38.329864507', + }, + { + messageId: 2426, + nickname: 'KEOSPMMHO', + content: 'ZGBIR', + createdAt: '2084-10-21T03:35:15.439134581', + }, + { + messageId: 7269, + nickname: 'AJFGLK', + content: 'DBTCYUHHV', + createdAt: '2085-05-05T05:15:40.533347276', + }, + { + messageId: 8045, + nickname: 'KHQ', + content: 'KRQBWRYWM', + createdAt: '2086-01-13T09:25:23.355797009', + }, + { + messageId: 3805, + nickname: 'CMZRJBBOFQ', + content: 'XZDNCJ', + createdAt: '2086-01-14T15:25:28.784256705', + }, + { + messageId: 4446, + nickname: 'YHUJOPCYOZ', + content: 'VOCXWVR', + createdAt: '2072-12-10T04:05:38.377948843', + }, + { + messageId: 6992, + nickname: 'SNICEXO', + content: 'DEAAAOIERA', + createdAt: '2084-01-19T02:31:28.10156004', + }, + { + messageId: 8837, + nickname: 'YMHZCIXQYS', + content: 'BLEUICQJBX', + createdAt: '2080-03-20T12:48:43.985685155', + }, + { + messageId: 9272, + nickname: 'FDQ', + content: 'BGRLRIXRY', + createdAt: '2065-05-18T01:14:53.643586616', + }, + { + messageId: 1127, + nickname: 'HKNX', + content: 'CFFVOIQ', + createdAt: '2039-05-22T17:42:37.222510635', + }, + { + messageId: 559, + nickname: 'ZOHSOV', + content: 'IGLIDFM', + createdAt: '2030-10-01T02:33:38.932464891', + }, + { + messageId: 3617, + nickname: 'BZY', + content: 'LNW', + createdAt: '1984-07-29T21:39:13.409232953', + }, + { + messageId: 6745, + nickname: 'EUKMHU', + content: 'XTTVPEPVF', + createdAt: '1972-09-11T04:47:08.429625468', + }, + { + messageId: 7898, + nickname: 'LSZBLE', + content: 'IPONZ', + createdAt: '2064-11-23T22:30:13.542865588', + }, + { + messageId: 740, + nickname: 'RUTDX', + content: 'QNJXJ', + createdAt: '1982-10-25T04:12:13.674779827', + }, + { + messageId: 4071, + nickname: 'CKYRRMOSSX', + content: 'NDQ', + createdAt: '2023-01-15T00:00:51.84289022', + }, + { + messageId: 2077, + nickname: 'ODYRXZ', + content: 'YYYJZ', + createdAt: '1998-08-26T21:06:04.675372254', + }, + { + messageId: 7836, + nickname: 'OGTP', + content: 'VIOFGHWR', + createdAt: '2016-08-03T14:48:03.095309125', + }, + { + messageId: 4653, + nickname: 'LLYRSRAA', + content: 'JHDA', + createdAt: '2003-06-20T09:42:20.444764003', }, ]) }), @@ -44,4 +364,5 @@ export const handlers = [ statusText: 'Out Of Apples', }) }), + ] diff --git a/src/pages/chatting/Chatting.tsx b/src/pages/chatting/Chatting.tsx index 49e33ee1..cc4a586b 100644 --- a/src/pages/chatting/Chatting.tsx +++ b/src/pages/chatting/Chatting.tsx @@ -1,5 +1,202 @@ +import styled from '@emotion/styled' +import * as Stomp from '@stomp/stompjs' +import { FormEvent, useEffect, useRef, useState } from 'react' +import { BsArrowLeftShort } from 'react-icons/bs' +import { useNavigate } from 'react-router-dom' + +import { axiosAPI } from '@/apis/axios' +import { ChattingApi } from '@/apis/chatting/chattingApi' +import { Messages } from '@/apis/chatting/chattingType' +import ExitIcon from '@/assets/icons/ExitIcon' +import Send from '@/assets/icons/Send.svg' +import ProfileSheet from '@/components/common/BottomSheet/ProfileSheet' +import { FlexBox } from '@/components/common/Flexbox' +import GradationBackground from '@/components/common/GradationBackground' +import PageContainer from '@/components/common/PageContainer' +import PageHeader from '@/components/common/PageHeader' +import Spacing from '@/components/common/Spacing' +import TextArea from '@/components/common/TextArea' +import MessageArea from '@/components/messageArea' +import { useModal } from '@/hooks/useModal' +import useAuthStore from '@/store/AuthStore' +import useBottomSheetStore from '@/store/BottomSheetStore' +import { palette } from '@/styles/palette' + const Chatting = () => { - return
{'Chatting'}
+ const { openModal } = useModal() + const navigate = useNavigate() + const chatroomId = '1' + const [messages, setMessages] = useState([] as Messages[]) + const [inputValue, setInputValue] = useState('') + const { authTokens } = useAuthStore() + const messageRef = useRef(null) + const messageWrapperRef = useRef(null) + const divRef = useRef(null) + + console.log(authTokens?.accessToken) + // const { data, isLoading } = useQuery(['messages'], () => getDetailMessages, { + // onSuccess: (responseData: Messages[]) => { + // setMessages(responseData) + // console.log(responseData) + // }, + // }) + + const getDetailMessages = async () => { + try { + const response = await ChattingApi.GET_DETAIL_MESSAGES() + console.log(response) + setMessages(response) + } catch (error) { + console.error('Message fetching error') + } + } + + const client = useRef() + + const connect = () => { + client.current = new Stomp.Client({ + brokerURL: `wss://${import.meta.env.VITE_CHAT_URL}/stomp`, + onConnect: () => { + console.log('소켓 연결완료') + subscribe() + }, + onDisconnect: (response) => { + console.log(response) + }, + connectHeaders: { + Authorization: `Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiZXhwIjoxNjk5MzQ3NjQ5fQ.pc39bNfs2NXsFmkRvOM0JzhAgdvec585fZ5zotPBupWAsFW_DEWcETcdz7VPa9vJ9zzNukO4yUcZo-Gf9sB12Q`, + }, + }) + client.current.activate() + } + + const subscribe = () => { + console.log('구독 함수 실행') + if (client.current) { + if (!client.current.connected) return + client.current.subscribe(`/sub/chatting/rooms/1`, (response) => { + const JsonBody = JSON.parse(response.body) + console.log(response.body) + setMessages((_chatList) => [..._chatList, JsonBody]) + }) + } + } + const handleSubmit = (e: FormEvent) => { + e.preventDefault() + send(inputValue) + } + const handleClickExitRoom = () => { + openModal({ + mainText: '채팅방을 나가시겠습니까?', + subText: '채팅방을 1명이라도 나가면 해당 채팅방은 폭파됩니다.', + okFunc: () => deleteChattingRoom(), + type: 'confirm', + }) + } + const deleteChattingRoom = async () => { + navigate('/') + return await axiosAPI.delete(`/v1/chatrooms/${chatroomId}`) + } + const navigateHome = () => { + navigate('/') + } + const send = (message: string) => { + if (client.current) { + if (!client.current.connected) return + console.log(message) + client.current.publish({ + destination: '/pub/chatting/messages', + body: JSON.stringify({ + roomId: 1, + content: inputValue, + }), + headers: { + Authorization: `Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiZXhwIjoxNjk5MzQ3NjQ5fQ.pc39bNfs2NXsFmkRvOM0JzhAgdvec585fZ5zotPBupWAsFW_DEWcETcdz7VPa9vJ9zzNukO4yUcZo-Gf9sB12Q`, + }, + }) + } + if (messageWrapperRef.current) { + messageWrapperRef.current.scrollTop = messageWrapperRef.current.scrollHeight + } + setInputValue('') + } + const disconnect = () => { + if (client.current) client.current.deactivate() + } + + useEffect(() => { + if (messageWrapperRef.current !== null) + messageWrapperRef.current.scrollTop = messageWrapperRef.current.scrollHeight + }) + + useEffect(() => { + connect() + return () => disconnect() + }, [chatroomId]) + + useEffect(() => { + getDetailMessages() + }, []) + const { bottomSheetState } = useBottomSheetStore() + return ( + <> + {bottomSheetState && } + + + + + + } + rightIcon={} + > + {/* {isLoading ? ( + + ) : ( */} + + {messages && } + + {/* )} */} + + {/* */} + {/* setInputValue(e.target.value)} value={inputValue} /> */} + + handleSubmit(e)}> + + + + + + + + ) } +const StyleIcon = styled.img` + width: 30px; + height: 30px; +` +const StyleTypingFlexBox = styled(FlexBox)` + padding: 10px; + border-radius: 10px; +` +const StyleChattingWrapper = styled.span`` +// const StyleInput = styled.input` +// width: 321px; +// height: 36px; +// border-radius: 10px; +// padding: 0 12px; +// border: none; +// ` +const StyleMessageWrapper = styled.div` + height: calc(100% - 145px); + flex: 1; + overflow-y: scroll; + scroll-behavior: smooth; +` + +const StyleSubmitButton = styled.button`` export default Chatting diff --git a/src/store/BottomSheetStore.tsx b/src/store/BottomSheetStore.tsx new file mode 100644 index 00000000..4f5b4a13 --- /dev/null +++ b/src/store/BottomSheetStore.tsx @@ -0,0 +1,15 @@ +import { create } from 'zustand' +type BottomSheetState = { + bottomSheetState: boolean + userId: string + setUserId: (id: string) => void + setBottomSheetState: (state: boolean) => void +} +const useBottomSheetStore = create((set) => ({ + bottomSheetState: false, + userId: '', + setUserId: (id) => set({ userId: id }), + setBottomSheetState: (state) => set({ bottomSheetState: state }), +})) + +export default useBottomSheetStore From 036f0979a7069fbc630fcbf5e8f7cb32236c5c68 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Fri, 17 Nov 2023 03:46:14 +0900 Subject: [PATCH 056/180] =?UTF-8?q?[Fix]=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EB=B6=88=EB=9F=AC=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EB=AC=B4=ED=95=9C=EB=A3=A8=ED=94=84=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20(#127)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore : lint 에러 수정 * fix : 함수 무한루프 돌던 것 수정 * chore : lint 에러 수정 * chore : lint 에러 수정 --- src/mocks/handlers.ts | 4 +--- src/pages/loginPending/LoginPending.tsx | 7 +++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index f95cd45c..0a8bd196 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -2,7 +2,6 @@ import { http, HttpResponse } from 'msw' const nickname = '주다다' export const handlers = [ - http.get(`/v1/users/duplicate?nickname=${nickname}`, async () => { return HttpResponse.json({ duplicate: true, @@ -11,7 +10,7 @@ export const handlers = [ http.get('/v1/histories', () => { return HttpResponse.json([ -//https://github.com/coffee-meet/frontend/pull/125/conflict?name=src%252Fmocks%252Fhandlers.ts&ancestor_oid=754b9948060f7dc0a13eabf5683fc01c1cc625f2&base_oid=9d66112fef597345b51b9ba8a5c9e672cb7f165d&head_oid=9bea8f50e6adda8dafdc0d062f4ca3a31657fb0c , + //https://github.com/coffee-meet/frontend/pull/125/conflict?name=src%252Fmocks%252Fhandlers.ts&ancestor_oid=754b9948060f7dc0a13eabf5683fc01c1cc625f2&base_oid=9d66112fef597345b51b9ba8a5c9e672cb7f165d&head_oid=9bea8f50e6adda8dafdc0d062f4ca3a31657fb0c , { title: '🥤️ 차가운 아메리카노-6', participants: ['우땅', '빅맘', '롤로노아 조로'], @@ -364,5 +363,4 @@ export const handlers = [ statusText: 'Out Of Apples', }) }), - ] diff --git a/src/pages/loginPending/LoginPending.tsx b/src/pages/loginPending/LoginPending.tsx index c9ef545e..764a2437 100644 --- a/src/pages/loginPending/LoginPending.tsx +++ b/src/pages/loginPending/LoginPending.tsx @@ -1,4 +1,5 @@ import styled from '@emotion/styled' +import { useEffect } from 'react' import { useNavigate, useSearchParams } from 'react-router-dom' import { PulseLoader } from 'react-spinners' @@ -19,6 +20,7 @@ const LoginPending = () => { .then((res) => { console.log(res.data.accessToken) localStorage.setItem('jwt', res.data.accessToken) + localStorage.setItem('nickname', res.data.nickname) setToken({ accessToken: res.data.accessToken, refreshToken: res.data.refreshToken, @@ -30,8 +32,9 @@ const LoginPending = () => { } }) } - - routeAuthInfo() + useEffect(() => { + routeAuthInfo() + }, []) return ( From 36bb6f80e74b1705ae7bc5ddf6b2c3e45455f7c6 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Fri, 17 Nov 2023 04:04:32 +0900 Subject: [PATCH 057/180] Update index.tsx (#129) --- src/components/common/AppHeader/index.tsx | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/components/common/AppHeader/index.tsx b/src/components/common/AppHeader/index.tsx index 9bca970d..c26866c9 100644 --- a/src/components/common/AppHeader/index.tsx +++ b/src/components/common/AppHeader/index.tsx @@ -42,29 +42,6 @@ const StyledAppHeaderSmallText = styled(Text) } ` -const StyledAppHeaderLargeText = styled(Text)>` - font: Body_24; - font-weight: 600; - letter-spacing: -0.5; - color: ${({ isDarkMode }) => (isDarkMode ? palette.WHITE : palette.DARK_WHITE)}; - margin-right: 5px; - - @media (max-width: 280px) { - font-size: 1.25rem; - } -` - -const StyledAppHeaderSmallText = styled(Text)>` - font: Body_18; - font-weight: 600; - letter-spacing: -0.5; - color: ${({ isDarkMode }) => (isDarkMode ? palette.WHITE : palette.DARK_WHITE)}; - - @media (max-width: 280px) { - font-size: 0.85rem; - } -` - type AppHeaderProps = { nickname: string isDarkMode: boolean From 6b31cf493312046c42e4f8118ed9e62a80857a39 Mon Sep 17 00:00:00 2001 From: judahhh Date: Fri, 17 Nov 2023 04:18:53 +0900 Subject: [PATCH 058/180] =?UTF-8?q?chore=20:=20baseURL=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/loginPending/LoginPending.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/loginPending/LoginPending.tsx b/src/pages/loginPending/LoginPending.tsx index 6824a5b0..6f3f1bd9 100644 --- a/src/pages/loginPending/LoginPending.tsx +++ b/src/pages/loginPending/LoginPending.tsx @@ -13,10 +13,9 @@ const LoginPending = () => { const authCode = searchParams.get('code') const setToken = useAuthStore((state) => state.setAuthTokens) const provider = useAuthStore((state) => state.provider) - const routeAuthInfo = async () => { await axiosAPI - .get(`${import.meta.env.VITE_BASE_URL}/v1/users/login/${provider}?authCode=${authCode}`) + .get(`/v1/users/login/${provider}?authCode=${authCode}`) .then((res) => { console.log(res.data.accessToken) localStorage.setItem('jwt', res.data.accessToken) @@ -30,6 +29,7 @@ const LoginPending = () => { .catch((err) => { if (err.response.status === 404) { navigate('/register/user', { state: { authCode } }) + console.log('실패패패') } }) } From 159708247f02909eef3961a9336e92cb34fc3521 Mon Sep 17 00:00:00 2001 From: judahhh Date: Fri, 17 Nov 2023 04:19:41 +0900 Subject: [PATCH 059/180] =?UTF-8?q?chore=20:=20prettier=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Avatar/index.tsx | 1 - src/components/common/PageHeader/index.tsx | 1 - src/pages/chatList/ChatList.tsx | 1 - 3 files changed, 3 deletions(-) diff --git a/src/components/common/Avatar/index.tsx b/src/components/common/Avatar/index.tsx index 3b769974..49865b4f 100644 --- a/src/components/common/Avatar/index.tsx +++ b/src/components/common/Avatar/index.tsx @@ -13,7 +13,6 @@ type AvatarProps = { style?: React.CSSProperties } - /** * `Avatar` component for displaying profile images. * @param width - 아바타의 너비 (픽셀 또는 유효한 CSS 단위). diff --git a/src/components/common/PageHeader/index.tsx b/src/components/common/PageHeader/index.tsx index 424b1136..de25cfb5 100644 --- a/src/components/common/PageHeader/index.tsx +++ b/src/components/common/PageHeader/index.tsx @@ -3,7 +3,6 @@ import styled from '@emotion/styled' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' - type PageHeaderProps = { title: string leftIcon?: React.ReactNode diff --git a/src/pages/chatList/ChatList.tsx b/src/pages/chatList/ChatList.tsx index c9fe8dd7..c5bf8d9e 100644 --- a/src/pages/chatList/ChatList.tsx +++ b/src/pages/chatList/ChatList.tsx @@ -1,4 +1,3 @@ - import styled from '@emotion/styled' import { useQuery } from '@tanstack/react-query' import { motion } from 'framer-motion' From b6060304372caf904139552ba4f7fd021b4d880e Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Fri, 17 Nov 2023 16:12:10 +0900 Subject: [PATCH 060/180] =?UTF-8?q?feature:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20MSW=EB=A5=BC=20=ED=99=9C?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20API=20=EB=AA=85=EC=84=B8=EC=99=80=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20(#117)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: AdminAppHeader 컴포넌트 추가 * style: AdminNavigationBar 컴포넌트 생성 * style: AdminNavigationBar 색 변경 * feat: AdminTabs 컴포넌트 추가 * refactor: 컴포넌트 변수명 변경 * refactor: 컴포넌트 변수명 변경 * feat: AdminApprovalList 컴포넌트 추가 * feat: outerwrapper 제거 * feat: AdminReportListRow 컴포넌트 예시 추가 * feat: AdminReportList 컴포넌트 예시 추가 * feat: AdminTabs 컴포넌트로 다른 컴포넌트들 연결 및 디자인 수정, 변수명 수정 * refactor: admin 컴포넌트 이전 * refactor: eslint 에러 수정 * refactor: import 경로 변경 * feat: AdminApprovalInfo 컴포넌트 1 * style: 예시 이미지 추가 * feat: AdminApprovalInfo 컴포넌트 추가 1 * feat: input text fontweight 속성 추가 * style: Input 속성 추가 * style: AdminApprovalInfo 컴포넌트 디자인 2 * style: AdminReportInfo 컴포넌트 1 * refactor: props명 수정 * style: AdminReportInfo 컴포넌트 1 * style: AdminReportInfoListRow 컴포넌트 * feat: useModal 컴포넌트 적용, 기타 스타일 수정 * feat: AdminReportInfo 컴포넌트 userModal 적용 * refactor: 불필요 컴포넌트 삭제 * refactor: 이름 수정 * feat: AdminReportListRow 속성 추가 * feat: 관리자페이지 신고자 목록 클릭 시, 정보 출력 * feat: AdminReportList 특정 목록 클릭 시, 해당 AdminReportInfo 출력 * feat: AdminReportList mockdata 추가, 함수 연결 * refactor: 이름 예시 변경 * refactor: 변수명 수정 (adminapprovalinfo와 구분) * feat: AdminTabs 승인 탭 추가 * refactor: 변수명 수정(구별필요) * feat: 승인 페이지 이름 적용, 불필요 컴포넌트 제거 * refactor: mock data 적용. 변수명 수정. * feat: 승인페이지 mock data 적용. 목록 클릭 함수 추가. interface 추가. * feat: adminapprovallistrow onclick 함수 속성 추가 * feat: msw handlers interface 파일들 * feat: 관리자페이지 - MSW 활용한 API request, response 명세. API 테스트. * style: 오류 관련 주석 처리 * feat: AdminPageHeader * feat: 관리자페이지 승인요청 사용자들 목록 불러오기 MSW API 요청 코드 완료. 테스트 완료. * feat: 관리자페이지 신고당한자 목록 API 코드 추가. 테스트 완료. * feat: 관리자페이지 승인요청자 세부정보 출력 1 * refactor: 불필요 코드 삭제 * refactor: 불필요코드 제거 2 --- src/apis/adminApproval/AdminApprovalApi.ts | 18 +++ src/apis/adminReport/AdminReportApi.ts | 12 ++ .../common/ListRow/AdminApprovalList.tsx | 53 ++++---- .../common/ListRow/AdminReportList.tsx | 42 +++---- src/mocks/handlers.ts | 115 ++++++++++++++++++ src/mocks/handlersInterface.ts | 29 +++++ .../admin/components/AdminApprovalInfo.tsx | 7 ++ src/pages/admin/components/AdminTabs.tsx | 6 +- 8 files changed, 230 insertions(+), 52 deletions(-) create mode 100644 src/apis/adminApproval/AdminApprovalApi.ts create mode 100644 src/apis/adminReport/AdminReportApi.ts create mode 100644 src/mocks/handlersInterface.ts diff --git a/src/apis/adminApproval/AdminApprovalApi.ts b/src/apis/adminApproval/AdminApprovalApi.ts new file mode 100644 index 00000000..88fd31ab --- /dev/null +++ b/src/apis/adminApproval/AdminApprovalApi.ts @@ -0,0 +1,18 @@ +import { axiosAPI } from '@/apis/axios' + +const AdminApprovalAPI = { + GET_APPROVAL_REQUEST_LIST: async () => { + const response = await axiosAPI.get(`/admin/approvals`) + return { + data: response.data, + } + }, + GET_APPROVAL_INFO: async () => { + const response = await axiosAPI.get(`/admin/approvals/1`) + return { + data: response.data, + } + }, +} + +export default AdminApprovalAPI diff --git a/src/apis/adminReport/AdminReportApi.ts b/src/apis/adminReport/AdminReportApi.ts new file mode 100644 index 00000000..6df5495b --- /dev/null +++ b/src/apis/adminReport/AdminReportApi.ts @@ -0,0 +1,12 @@ +import { axiosAPI } from '@/apis/axios' + +const AdminReportAPI = { + GET_REPORT_LIST: async () => { + const response = await axiosAPI.get(`/admin/reports`) + return { + data: response.data, + } + }, +} + +export default AdminReportAPI diff --git a/src/components/common/ListRow/AdminApprovalList.tsx b/src/components/common/ListRow/AdminApprovalList.tsx index f0059201..ba759e18 100644 --- a/src/components/common/ListRow/AdminApprovalList.tsx +++ b/src/components/common/ListRow/AdminApprovalList.tsx @@ -1,43 +1,44 @@ import styled from '@emotion/styled' +import { useQuery } from '@tanstack/react-query' +import AdminApprovalAPI from '@/apis/adminApproval/AdminApprovalApi' import AdminApprovalListRow from '@/components/common/ListRow/AdminApprovalListRow' import { palette } from '@/styles/palette' interface AdminApprovalListProps { - onApproveSelect: (nickname: string) => void + onApproveSelectUserName: (nickname: string) => void } +interface ApprovalListData { + approvalRequestUser: string + approvalRequestUserStatus: string +} + +const AdminApprovalList = ({ onApproveSelectUserName }: AdminApprovalListProps) => { + // API 요청 코드 + const { data, isSuccess } = useQuery( + ['ApprovalRequestList'], + AdminApprovalAPI.GET_APPROVAL_REQUEST_LIST, + ) -const AdminApprovalList = ({ onApproveSelect }: AdminApprovalListProps) => { const handlePersonApproval = (nickname: string) => { - onApproveSelect(nickname) + onApproveSelectUserName(nickname) } - const AdminApprovalListData = [ - { nickname: '유명한', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '박상민', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '박은지', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '주다현', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '남궁호수', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '우창욱', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '대기 중', isDarkMode: false }, - ] + const ApprovalDatas = data?.data.approvals return ( - {AdminApprovalListData.map((data, index) => ( - handlePersonApproval(data.nickname)} - /> - ))} + {isSuccess && + ApprovalDatas.map((approvalListData: ApprovalListData, index: number) => ( + handlePersonApproval(approvalListData.approvalRequestUser)} + /> + ))} ) diff --git a/src/components/common/ListRow/AdminReportList.tsx b/src/components/common/ListRow/AdminReportList.tsx index fcf02145..d2c62ee7 100644 --- a/src/components/common/ListRow/AdminReportList.tsx +++ b/src/components/common/ListRow/AdminReportList.tsx @@ -1,42 +1,38 @@ import styled from '@emotion/styled' +import { useQuery } from '@tanstack/react-query' +import AdminReportAPI from '@/apis/adminReport/AdminReportApi' import AdminReportListRow from '@/components/common/ListRow/AdminReportListRow' import { palette } from '@/styles/palette' interface AdminReportListProps { onReportSelect: (nickname: string) => void } +interface ReportListData { + reportedUserName: string + reportCount: number +} const AdminReportList = ({ onReportSelect }: AdminReportListProps) => { + const { data, isSuccess } = useQuery(['ReportedUserList'], AdminReportAPI.GET_REPORT_LIST) const handlePersonReported = (nickname: string) => { onReportSelect(nickname) } - const AdminReportListData = [ - { nickname: '유명한', height: 71, infoMessage: '누적 1회', isDarkMode: false }, - { nickname: '박상민', height: 71, infoMessage: '누적 2회', isDarkMode: false }, - { nickname: '박은지', height: 71, infoMessage: '누적 2회', isDarkMode: false }, - { nickname: '주다현', height: 71, infoMessage: '누적 1회', isDarkMode: false }, - { nickname: '남궁호수', height: 71, infoMessage: '누적 1회', isDarkMode: false }, - { nickname: '우창욱', height: 71, infoMessage: '누적 2회', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, - { nickname: '홍길동', height: 71, infoMessage: '누적 1회', isDarkMode: false }, - ] + const ReportDatas = data?.data.reports return ( - {AdminReportListData.map((data, index) => ( - handlePersonReported(data.nickname)} - /> - ))} + {isSuccess && + ReportDatas.map((reportListData: ReportListData, index: number) => ( + handlePersonReported(reportListData.reportedUserName)} + /> + ))} ) diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index f95a399d..cfd9ecbb 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,6 +1,14 @@ import { http, HttpResponse } from 'msw' + +import { Approval, ApprovalInfo, ReportInfo, Reports } from './handlersInterface' const nickname = '주다다' + export const handlers = [ + // example + http.get('/pets', () => { + return HttpResponse.json(['Tom', 'Jerry', 'Spike']) + }), + http.get('/v1/histories', () => { return HttpResponse.json([ { @@ -355,4 +363,111 @@ export const handlers = [ statusText: 'Out Of Apples', }) }), + + // example + http.get('/pets', () => { + return HttpResponse.json(['Tom', 'Jerry', 'Spike']) + }), + // 승인 목록 API 핸들러 + http.get('/admin/approvals', () => { + const approvals: Approval[] = [ + { approvalRequestUser: '박상민', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '박은지', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '주다현', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '남궁호수', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '우창욱', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, + ] + return HttpResponse.json({ approvals }) + }), + + // 승인 상세 정보 API 핸들러 + http.get('/admin/approvals/:userId', (req) => { + const { userId } = req.params + const approvalInfo: ApprovalInfo = { + approvalRequestUserName: `userId:${userId}에 해당하는 userName`, + approvalRequestUserEmail: `userId:${userId}에 해당하는 userName의 Email`, + approvalRequestUserBusinessCardImage: `https://www.imageExample.jpg`, + } + + return HttpResponse.json({ approvalInfo }) + }), + + // 승인/거절 처리 API 핸들러 + // req.body 오류 해결이 필요한 부분 + // http.post('/admin/approvals/:userId/action', (req) => { + // const { userId } = req.params + // const { action } = req.body + // const approvalResult: ApprovalResult = { + // result: action === 'accept' ? 'accepted' : 'rejected', + // } + // return HttpResponse.json({ approvalResult }) + // }), + + // 신고 목록 API 핸들러 + http.get('/admin/reports', () => { + const reports: Reports[] = [ + { reportedUserName: '유명한', reportCount: 1 }, + { reportedUserName: '박상민', reportCount: 2 }, + { reportedUserName: '박은지', reportCount: 1 }, + { reportedUserName: '주다현', reportCount: 1 }, + { reportedUserName: '남궁호수', reportCount: 1 }, + { reportedUserName: '우창욱', reportCount: 1 }, + { reportedUserName: '홍길동', reportCount: 0 }, + { reportedUserName: '홍길동', reportCount: 2 }, + { reportedUserName: '홍길동', reportCount: 3 }, + { reportedUserName: '홍길동', reportCount: 1 }, + { reportedUserName: '홍길동', reportCount: 2 }, + { reportedUserName: '홍길동', reportCount: 3 }, + { reportedUserName: '홍길동', reportCount: 1 }, + { reportedUserName: '홍길동', reportCount: 0 }, + ] + return HttpResponse.json({ reports }) + }), + + // 신고 상세 정보 API 핸들러 + http.get('/admin/reports/:userId', (req) => { + const { userId } = req.params + const reportInfo: ReportInfo = { + reportedUserName: `userName, userId:${userId}`, + reporterUserName: 'reporterUserName', + reportDate: new Date().toISOString(), + reason: 'saying swear words', + reportCount: 3, + email: `userId:${userId}@example.com`, + } + return HttpResponse.json({ + reportInfo, + }) + }), + + // // 신고 처리 API 핸들러 + // req.body 오류 해결이 필요한 부분 + // http.post('/admin/reports/:userId/action', (req) => { + // const { userId } = req.params + // const { action } = req.body + // const reportResult: ReportResult = { + // result: action === 'addCount' ? 'countAdded' : 'ignored', + // } + // return HttpResponse.json({ reportResult }) + // }), ] diff --git a/src/mocks/handlersInterface.ts b/src/mocks/handlersInterface.ts new file mode 100644 index 00000000..89da3154 --- /dev/null +++ b/src/mocks/handlersInterface.ts @@ -0,0 +1,29 @@ +export interface Approval { + approvalRequestUser: string + approvalRequestUserStatus: string +} +export interface ApprovalInfo { + approvalRequestUserName: string + approvalRequestUserEmail: string + approvalRequestUserBusinessCardImage: string +} +// req.body 오류 해결이 필요한 부분 +// export interface ApprovalResult { +// result: string +// } +export interface Reports { + reportedUserName: string + reportCount: number +} +export interface ReportInfo { + reportedUserName: string + reporterUserName: string + reportDate: string + reason: string + reportCount: number + email: string +} +// req.body 오류 해결이 필요한 부분 +// export interface ReportResult { +// result: string +// } diff --git a/src/pages/admin/components/AdminApprovalInfo.tsx b/src/pages/admin/components/AdminApprovalInfo.tsx index 1a4faa2e..1a52f08a 100644 --- a/src/pages/admin/components/AdminApprovalInfo.tsx +++ b/src/pages/admin/components/AdminApprovalInfo.tsx @@ -1,5 +1,7 @@ import styled from '@emotion/styled' +import { useQuery } from '@tanstack/react-query' +import AdminApprovalAPI from '@/apis/adminApproval/AdminApprovalApi' import businessCardExample from '@/assets/images/businessCardExample.jpg' import NormalButton from '@/components/common/Buttons/NormalButton' import Spacing from '@/components/common/Spacing' @@ -14,6 +16,11 @@ interface AdminApprovalInfoProps { } const AdminApprovalInfo = ({ selectedApprovalNickname }: AdminApprovalInfoProps) => { + const { data, isSuccess } = useQuery( + ['ApprovalRequestUserInfo'], + AdminApprovalAPI.GET_APPROVAL_INFO, + ) + console.log(isSuccess && data) const { openModal } = useModal() const handleAcceptCertificationBtn = () => { openModal({ diff --git a/src/pages/admin/components/AdminTabs.tsx b/src/pages/admin/components/AdminTabs.tsx index 1179a63c..28955d62 100644 --- a/src/pages/admin/components/AdminTabs.tsx +++ b/src/pages/admin/components/AdminTabs.tsx @@ -30,13 +30,13 @@ const AdminTabs = () => { setSelectedReportNickname(reportNickname) setActiveTab('reportInfo') } - const handleApprovalSelectNickname = (approvalNickname: string) => { + const handleApprovalSelectUserName = (approvalNickname: string) => { setSelectedApprovalNickname(approvalNickname) setActiveTab('approvalInfo') } const ApprovalList = ({ onPersonApprovalSelected }: ApprovalListProps) => ( - + ) const ReportList = ({ onPersonReportedSelected }: ReportListProps) => ( @@ -70,7 +70,7 @@ const AdminTabs = () => { {activeTab === 'approval' && ( - + )} {activeTab === 'report' && ( From 6dd4e7b4d36fc2a5d42c4d50246eddc5c78c9a56 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Fri, 17 Nov 2023 18:47:04 +0900 Subject: [PATCH 061/180] =?UTF-8?q?[Feature]=20=ED=9A=8C=EC=82=AC=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EB=93=B1=EB=A1=9D=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B5=AC=ED=98=84=20(#132)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : 회사 이메일 인증 및 직무 정보 등록 페이지 구현 * feat : 회사 정보 등록 페이지 구현 * chore : 안 쓰이는 변수 제거 --- src/apis/axios.ts | 2 +- src/pages/register/RegisterCompany.tsx | 338 ++++++++++++++++++++++++- src/pages/register/RegisterUser.tsx | 29 ++- 3 files changed, 366 insertions(+), 3 deletions(-) diff --git a/src/apis/axios.ts b/src/apis/axios.ts index fe99eba1..285d9728 100644 --- a/src/apis/axios.ts +++ b/src/apis/axios.ts @@ -14,7 +14,7 @@ axiosAPI.interceptors.request.use( function (config) { // 요청 바로 직전 // axios 설정값에 대해 작성합니다. - + config.headers['Authorization'] = `Bearer ${localStorage.getItem('token')}` // const { authTokens } = useAuthStore() // config.headers['Authorization'] = `Bearer ${authTokens}` diff --git a/src/pages/register/RegisterCompany.tsx b/src/pages/register/RegisterCompany.tsx index add880d8..16355278 100644 --- a/src/pages/register/RegisterCompany.tsx +++ b/src/pages/register/RegisterCompany.tsx @@ -1,5 +1,341 @@ +import styled from '@emotion/styled' +import { useMutation } from '@tanstack/react-query' +import { RefObject, useRef, useState } from 'react' +import { MdWbSunny } from 'react-icons/md' +import { useNavigate } from 'react-router-dom' + +import { axiosAPI } from '@/apis/axios' +import AlertText from '@/components/common/AlertText' +import BackChevron from '@/components/common/BackChevron' +import NormalButton from '@/components/common/Buttons/NormalButton' +import { FlexBox } from '@/components/common/Flexbox' +import RegisterInput from '@/components/common/RegisterInput' +import SelectorButtonContainer from '@/components/common/SelectorButtonContainer' +import Spacing from '@/components/common/Spacing' +import useToast from '@/hooks/useToast' +import useInterestStore from '@/store/InterestStore' +import useThemeStore from '@/store/ThemeStore' +import { palette } from '@/styles/palette' +import { typo } from '@/styles/typo' + const RegisterCompany = () => { - return
{'RegisterCompany'}
+ const JobList = [ + '경영', + '영업', + '물류/무역', + 'IT', + '디자인', + '전문직', + '미디어', + '생산/제조', + '연구/개발', + '기획/마케팅', + '광고', + '의약/바이오', + '유통', + '법률/집행기관', + ] + const navigate = useNavigate() + const companyName = useRef(null) + const emailRef = useRef(null) + const codeRef = useRef(null) + const [isCodeSame, setIsCodeSame] = useState(false) + const [codeChecked, setCodeChecked] = useState(null) + const { interestList } = useInterestStore() //여기서 회사 직무 list 저장한거 불러오기 + const { showToast } = useToast() + const isDarkMode = useThemeStore((state) => state.isDarkMode) + const formData = new FormData() + const imgRef = useRef(null) as RefObject + const [uploadedURL, setUploadedURL] = useState('') + + const handleClickEmailVerify = async (email: string) => { + console.log(email) + return await axiosAPI.post(`/v1/certification/users/me/company-mail`, { + companyEmail: emailRef.current && emailRef.current.value, + }) + } + const emailVerifyMutation = useMutation((email: string) => handleClickEmailVerify(email), { + onSuccess: (response) => { + console.log(response) + }, + onError: (err) => { + console.log(err) + }, + }) + + const getAlertMessage = (isCodeSame: boolean | null, codeChecked: boolean | null) => { + if (isCodeSame === null && codeChecked === null) { + return { message: '닉네임 중복검사를 해주세요!', color: palette.RED } + } else if (isCodeSame === false && codeChecked) { + return { message: '사용 가능한 닉네임입니다.', color: palette.PRIMARY } + } else if (isCodeSame === true && codeChecked) { + return { message: '이미 사용 중인 닉네임입니다.', color: palette.RED } + } else { + return null // 혹은 기본 메시지 객체를 반환 + } + } + + const alertInfo = getAlertMessage(isCodeSame, codeChecked) + + //이메일 인증 버튼 누르면 실행되는 함수 + const handleEmailCertification = async () => { + emailRef.current && emailVerifyMutation.mutate(emailRef.current.value) + } + + //인증 코드 입력하고 확인 버튼 누르면 실행되는 함수 + const checkEmailCode = async () => { + setCodeChecked(true) + const response = await axiosAPI.post('/v1/certification/users/me/company-mail/verification', { + verificationCode: codeRef.current && codeRef.current.value, + }) + if (response.status == 200) setIsCodeSame(true) + //이 부분 코드 다시 짜야함 + else console.log('인증 코드 불일치') + } + + const submitUserCompanyData = () => { + console.log(formData) + //다 체크 됐나 확인하고 + if (!codeChecked) { + showToast({ + message: '이메일 인증을 해주세요 ', + type: 'warning', + isDarkMode, + }) + return + } else if (!isCodeSame) { + showToast({ + message: '인증코드가 일치하지 않습니다. ', + type: 'warning', + isDarkMode, + }) + return + } + companyName.current && formData.append('companyName', companyName.current.value) + emailRef.current && formData.append('companyEmail', emailRef.current.value) + formData.append('department', JSON.stringify(interestList)) + + // const body = { + // companyName: companyName.current && companyName.current.value, + // companyEmail: emailRef.current && emailRef.current.value, + // department: interestList, + // businessCard: '', + // } + registerCompanyData(formData) + // registerCompanyMutation.mutate(formData) + } + + const registerCompanyData = async (body: object) => { + console.log(body) + await axiosAPI.post('/v1/certification/users/me/company-info', body, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + } + // const registerCompanyMutation = useMutation((body: object) => registerCompanyData(body), { + // onSuccess: (response) => { + // console.log(response) + // navigate('/') + // }, + // onError: () => { + // showToast({ + // message: '회사 정보 등록에 실패했습니다.', + // type: 'error', + // isDarkMode: false, + // }) + // }, + // }) + const handleImageChange = () => { + if (imgRef.current && imgRef.current?.files) { + if (!imgRef.current.files[0]) return + console.log(imgRef.current?.files[0]) + formData.append('businessCard', imgRef.current?.files[0]) + console.log(formData) + const url = URL.createObjectURL(imgRef.current?.files[0]) + setUploadedURL(url) + } + } + const handleClickUpload = () => { + if (!imgRef.current) return + imgRef.current.click() + } + + return ( + + + + + + { + navigate('/register/user') + }} + /> + + {'회사 인증'} + + + + + {/* */} + + + + + + + + + + + handleEmailCertification()} + > + {'이메일 인증'} + + + + + {alertInfo ? ( + + {alertInfo.message} + + ) : ( + + )} + + + checkEmailCode()}> + {'확인'} + + + {alertInfo && ( + + {alertInfo.message} + + )} + + {'직무정보'} + + + + + + + {'명함을 업로드 해주세요!'} + + {uploadedURL ? ( + {'사용자가 + ) : ( + '+' + )} + + + + + + + + {'등록 완료'} + + + + + ) } +const StyleRegisterWrapper = styled.div` + background-color: ${palette.GRAY100}; + height: 100%; + overflow: scroll; +` +const StyleDataWrapper = styled.div`` +const StyleImageCard = styled.button` + width: 250px; + height: 150px; + background: ${palette.WHITE}; + border: 1px dashed ${palette.GRAY600}; + border-radius: 10px; + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: center; + /* @media (max-width: 786px) { + width: 90vw; + height: 50vh; + } */ +` +const StyleRegisterHeader = styled.div`` +const StyleHeaderText = styled.span` + font-size: ${typo.Body_24()}; +` +const StyleDivider = styled.hr` + height: 1px; + background-color: ${palette.GRAY200}; + border: 0; +` +const StyleInterestText = styled.div` + padding: 10px; + margin-left: 25px; + font-size: ${typo.Body_18()}; +` +const StyleSubmitButtonWrapper = styled.div` + display: flex; + width: 100%; + justify-content: center; + position: relative; +` +const StyleText = styled.div` + font-size: ${typo.Body_12()}; + margin: 10px; +` +const StyleIcon = styled.button` + cursor: pointer; +` +const StyleVerificationEmailButton = styled.button` + width: 42px; + height: 25px; + background-color: ${palette.TERTIARY}; + position: absolute; + right: 30px; + color: ${palette.WHITE}; + border-radius: 10px; + font-size: 12px; + font-family: 'Pretendard-Regular'; + letter-spacing: -1px; + box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.15); +` export default RegisterCompany diff --git a/src/pages/register/RegisterUser.tsx b/src/pages/register/RegisterUser.tsx index 238f0c02..4b75d38a 100644 --- a/src/pages/register/RegisterUser.tsx +++ b/src/pages/register/RegisterUser.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled' import { useMutation } from '@tanstack/react-query' -import { useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { MdWbSunny } from 'react-icons/md' import { useNavigate } from 'react-router-dom' import { useLocation } from 'react-router-dom' @@ -45,6 +45,31 @@ const RegisterUser = () => { const { provider } = useAuthStore() const { showToast } = useToast() const isDarkMode = useThemeStore((state) => state.isDarkMode) + const setToken = useAuthStore((state) => state.setAuthTokens) + + const routeAuthInfo = async () => { + await axiosAPI + .get(`/v1/users/login/${provider}?authCode=${authCode}`) + .then((res) => { + console.log(res.data.accessToken) + localStorage.setItem('jwt', res.data.accessToken) + localStorage.setItem('nickname', res.data.nickname) + + setToken({ + accessToken: res.data.accessToken, + refreshToken: res.data.refreshToken, + }) + }) + .catch((err) => { + if (err.response.status === 404) { + navigate('/register/user', { state: { authCode } }) + console.log('실패패패') + } + }) + } + useEffect(() => { + routeAuthInfo() + }, []) const getNicknameValid = async (nickname: string) => { return await axiosAPI.get(`/v1/users/duplicate?nickname=${nickname}`) @@ -125,6 +150,8 @@ const RegisterUser = () => { const registerMutation = useMutation((body: object) => registerPost(body), { onSuccess: (response) => { console.log(response) + console.log(response.data.accessToken) + localStorage.setItem('jwt', response.data.accessToken) showToast({ message: '닉네임, 관심사 정보 등록을 완료했습니다!', type: 'success', From 4c7a7fb87c86e0a0d01174d2647b1ad58a171a51 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Sun, 19 Nov 2023 00:29:30 +0900 Subject: [PATCH 062/180] =?UTF-8?q?[Fix]=20provider=20=EB=8C=80=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EB=A1=9C=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=ED=9A=8C?= =?UTF-8?q?=EC=82=AC=20=EB=93=B1=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?form=20=EB=B3=84=EB=A1=9C=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20(#135)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix : provider 대문자로 수정 및 회사 정보 등록 페이지 각 폼 별로 분기 처리 * chore : 사용되지 않는 데이터 제거 * chore : console.log(response) 삭제 --- src/apis/axios.ts | 6 +- src/pages/login/Login.tsx | 6 +- src/pages/loginPending/LoginPending.tsx | 3 +- src/pages/register/RegisterCompany.tsx | 90 ++++++++++++++----------- src/store/AuthStore.tsx | 2 +- 5 files changed, 59 insertions(+), 48 deletions(-) diff --git a/src/apis/axios.ts b/src/apis/axios.ts index 285d9728..0d10d6d0 100644 --- a/src/apis/axios.ts +++ b/src/apis/axios.ts @@ -8,15 +8,13 @@ export const axiosAPI = axios.create({ ? 'http://localhost:5173' : import.meta.env.VITE_BASE_URL, }) -// const { authTokens } = useAuthStore() + // Add a request interceptor axiosAPI.interceptors.request.use( function (config) { // 요청 바로 직전 // axios 설정값에 대해 작성합니다. - config.headers['Authorization'] = `Bearer ${localStorage.getItem('token')}` - // const { authTokens } = useAuthStore() - // config.headers['Authorization'] = `Bearer ${authTokens}` + config.headers['Authorization'] = `Bearer ${localStorage.getItem('jwt')}` return config }, diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index df5b3806..024c2d25 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -8,7 +8,7 @@ import { Text } from '@/components/common/Text' import useAuthStore from '@/store/AuthStore' import { palette } from '@/styles/palette' -export type Provider = 'naver' | 'kakao' +export type Provider = 'NAVER' | 'KAKAO' const BASE_URL = import.meta.env.VITE_BASE_URL @@ -78,7 +78,7 @@ const Login = () => { { - handleMoveToAuthProvider('naver') + handleMoveToAuthProvider('NAVER') }} /> { /> { - handleMoveToAuthProvider('kakao') + handleMoveToAuthProvider('KAKAO') }} /> diff --git a/src/pages/loginPending/LoginPending.tsx b/src/pages/loginPending/LoginPending.tsx index 6f3f1bd9..39180998 100644 --- a/src/pages/loginPending/LoginPending.tsx +++ b/src/pages/loginPending/LoginPending.tsx @@ -25,11 +25,12 @@ const LoginPending = () => { accessToken: res.data.accessToken, refreshToken: res.data.refreshToken, }) + navigate('/') }) .catch((err) => { if (err.response.status === 404) { navigate('/register/user', { state: { authCode } }) - console.log('실패패패') + console.log('카카오 로그인 완료 & 정보 등록 안됨') } }) } diff --git a/src/pages/register/RegisterCompany.tsx b/src/pages/register/RegisterCompany.tsx index 16355278..2620eb2d 100644 --- a/src/pages/register/RegisterCompany.tsx +++ b/src/pages/register/RegisterCompany.tsx @@ -2,6 +2,7 @@ import styled from '@emotion/styled' import { useMutation } from '@tanstack/react-query' import { RefObject, useRef, useState } from 'react' import { MdWbSunny } from 'react-icons/md' +import { MdOutlinePhotoCamera } from 'react-icons/md' import { useNavigate } from 'react-router-dom' import { axiosAPI } from '@/apis/axios' @@ -39,9 +40,9 @@ const RegisterCompany = () => { const companyName = useRef(null) const emailRef = useRef(null) const codeRef = useRef(null) - const [isCodeSame, setIsCodeSame] = useState(false) + const [isCodeSame, setIsCodeSame] = useState(null) const [codeChecked, setCodeChecked] = useState(null) - const { interestList } = useInterestStore() //여기서 회사 직무 list 저장한거 불러오기 + const { interestList } = useInterestStore() //여기서 회사 직무 list 저장한거 get해옴 const { showToast } = useToast() const isDarkMode = useThemeStore((state) => state.isDarkMode) const formData = new FormData() @@ -57,6 +58,11 @@ const RegisterCompany = () => { const emailVerifyMutation = useMutation((email: string) => handleClickEmailVerify(email), { onSuccess: (response) => { console.log(response) + showToast({ + message: '메일로 인증코드가 전송되었습니다.', + type: 'info', + isDarkMode, + }) }, onError: (err) => { console.log(err) @@ -64,12 +70,8 @@ const RegisterCompany = () => { }) const getAlertMessage = (isCodeSame: boolean | null, codeChecked: boolean | null) => { - if (isCodeSame === null && codeChecked === null) { - return { message: '닉네임 중복검사를 해주세요!', color: palette.RED } - } else if (isCodeSame === false && codeChecked) { - return { message: '사용 가능한 닉네임입니다.', color: palette.PRIMARY } - } else if (isCodeSame === true && codeChecked) { - return { message: '이미 사용 중인 닉네임입니다.', color: palette.RED } + if (isCodeSame === false && codeChecked) { + return { message: '인증코드가 확인되지 않았습니다.', color: palette.PRIMARY } } else { return null // 혹은 기본 메시지 객체를 반환 } @@ -89,16 +91,14 @@ const RegisterCompany = () => { verificationCode: codeRef.current && codeRef.current.value, }) if (response.status == 200) setIsCodeSame(true) - //이 부분 코드 다시 짜야함 - else console.log('인증 코드 불일치') + else setIsCodeSame(false) } const submitUserCompanyData = () => { - console.log(formData) //다 체크 됐나 확인하고 if (!codeChecked) { showToast({ - message: '이메일 인증을 해주세요 ', + message: '인증코드가 확인되지 않았습니다. ', type: 'warning', isDarkMode, }) @@ -110,28 +110,51 @@ const RegisterCompany = () => { isDarkMode, }) return + } else if (companyName.current && companyName.current.value.length === 0) { + showToast({ + message: '회사명을 입력해주세요.', + type: 'warning', + isDarkMode, + }) + return + } else if (imgRef.current && !imgRef.current?.files) { + showToast({ + message: '명함을 업로드 해주세요!', + type: 'warning', + isDarkMode, + }) + return } companyName.current && formData.append('companyName', companyName.current.value) emailRef.current && formData.append('companyEmail', emailRef.current.value) formData.append('department', JSON.stringify(interestList)) - // const body = { - // companyName: companyName.current && companyName.current.value, - // companyEmail: emailRef.current && emailRef.current.value, - // department: interestList, - // businessCard: '', - // } registerCompanyData(formData) - // registerCompanyMutation.mutate(formData) } const registerCompanyData = async (body: object) => { console.log(body) - await axiosAPI.post('/v1/certification/users/me/company-info', body, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }) + await axiosAPI + .post('/v1/certification/users/me/company-info', body, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + .then(() => { + showToast({ + message: '회사 정보 등록 완료! 메인 홈으로 이동합니다.', + type: 'success', + isDarkMode: false, + }) + navigate('/') + }) + .catch(() => { + showToast({ + message: '회사 정보 등록에 실패했습니다.', + type: 'error', + isDarkMode: false, + }) + }) } // const registerCompanyMutation = useMutation((body: object) => registerCompanyData(body), { // onSuccess: (response) => { @@ -186,7 +209,7 @@ const RegisterCompany = () => { - + @@ -198,19 +221,8 @@ const RegisterCompany = () => { + - {alertInfo ? ( - - {alertInfo.message} - - ) : ( - - )} { {'사용자가 ) : ( - '+' + )} ( (set) => ({ - provider: 'kakao', + provider: 'KAKAO', setProvider: (provider: Provider) => set(() => ({ provider })), setAuthTokens: (authTokens: Tokens) => set(() => ({ From 889eab8fcc3895dd200737ef5250b4a1656f5aa2 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Sun, 19 Nov 2023 21:35:51 +0900 Subject: [PATCH 063/180] =?UTF-8?q?fix:=20login=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=86=8D=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#140)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: AdminAppHeader 컴포넌트 추가 * style: AdminNavigationBar 컴포넌트 생성 * style: AdminNavigationBar 색 변경 * feat: AdminTabs 컴포넌트 추가 * refactor: 컴포넌트 변수명 변경 * refactor: 컴포넌트 변수명 변경 * feat: AdminApprovalList 컴포넌트 추가 * feat: outerwrapper 제거 * feat: AdminReportListRow 컴포넌트 예시 추가 * feat: AdminReportList 컴포넌트 예시 추가 * feat: AdminTabs 컴포넌트로 다른 컴포넌트들 연결 및 디자인 수정, 변수명 수정 * refactor: admin 컴포넌트 이전 * refactor: eslint 에러 수정 * refactor: import 경로 변경 * feat: AdminApprovalInfo 컴포넌트 1 * style: 예시 이미지 추가 * feat: AdminApprovalInfo 컴포넌트 추가 1 * feat: input text fontweight 속성 추가 * style: Input 속성 추가 * style: AdminApprovalInfo 컴포넌트 디자인 2 * style: AdminReportInfo 컴포넌트 1 * refactor: props명 수정 * style: AdminReportInfo 컴포넌트 1 * style: AdminReportInfoListRow 컴포넌트 * feat: useModal 컴포넌트 적용, 기타 스타일 수정 * feat: AdminReportInfo 컴포넌트 userModal 적용 * refactor: 불필요 컴포넌트 삭제 * refactor: 이름 수정 * feat: AdminReportListRow 속성 추가 * feat: 관리자페이지 신고자 목록 클릭 시, 정보 출력 * feat: AdminReportList 특정 목록 클릭 시, 해당 AdminReportInfo 출력 * feat: AdminReportList mockdata 추가, 함수 연결 * refactor: 이름 예시 변경 * refactor: 변수명 수정 (adminapprovalinfo와 구분) * feat: AdminTabs 승인 탭 추가 * refactor: 변수명 수정(구별필요) * feat: 승인 페이지 이름 적용, 불필요 컴포넌트 제거 * refactor: mock data 적용. 변수명 수정. * feat: 승인페이지 mock data 적용. 목록 클릭 함수 추가. interface 추가. * feat: adminapprovallistrow onclick 함수 속성 추가 * feat: msw handlers interface 파일들 * feat: 관리자페이지 - MSW 활용한 API request, response 명세. API 테스트. * style: 오류 관련 주석 처리 * feat: AdminPageHeader * feat: 관리자페이지 승인요청 사용자들 목록 불러오기 MSW API 요청 코드 완료. 테스트 완료. * feat: 관리자페이지 신고당한자 목록 API 코드 추가. 테스트 완료. * feat: 관리자페이지 승인요청자 세부정보 출력 1 * refactor: 불필요 코드 삭제 * refactor: 불필요코드 제거 2 * feat: 숨겨진 관리자 로그인 버튼 추가 * feat: 관리자 페이지 라우터 설정 * feat: 관리자페이지 로그인 MSW API 요청 코드 추가, 관리자페이지 로그인 UI(react hook form) * refactor: 타입 오류 임시 주석처리 * refactor: import문 삭제 * feat: 관리자페이지 로그인 api 요청 1 * feat: 관리자페이지 로그인 API 요청 2 * feat: 관리자 로그인 페이지 post API 요청 * feat: 관리자 로그인 페이지 이동 * feat: 관리자 페이지 이동 * feat: 관리자 로그인 페이지 - 피그마 디자인, 구현 * feat: 문구수정 * refactor: useNavigate, api 요청 주소 변경 * feat: 관리자로그인페이지 디자인 수정 - emotion, register속성 적용 * feat: 관리자 승인 처리 API 요청. * feat: scroll --- src/apis/adminApproval/AdminApprovalApi.ts | 16 ++- src/apis/adminLogin/AdminLoginApi.ts | 19 +++ src/apis/adminReport/AdminReportApi.ts | 8 +- .../common/ListRow/AdminApprovalList.tsx | 9 +- src/mocks/handlers.ts | 71 ++++++++--- src/mocks/handlersInterface.ts | 13 +- src/pages/admin/AdminLogin.tsx | 118 +++++++++++++++--- src/pages/admin/AdminMain.tsx | 29 +++++ .../admin/components/AdminApprovalInfo.tsx | 13 +- .../admin/components/AdminReportInfo.tsx | 4 + src/pages/admin/index.tsx | 8 +- src/pages/login/Login.tsx | 18 ++- 12 files changed, 272 insertions(+), 54 deletions(-) create mode 100644 src/apis/adminLogin/AdminLoginApi.ts create mode 100644 src/pages/admin/AdminMain.tsx diff --git a/src/apis/adminApproval/AdminApprovalApi.ts b/src/apis/adminApproval/AdminApprovalApi.ts index 88fd31ab..89180e99 100644 --- a/src/apis/adminApproval/AdminApprovalApi.ts +++ b/src/apis/adminApproval/AdminApprovalApi.ts @@ -2,17 +2,29 @@ import { axiosAPI } from '@/apis/axios' const AdminApprovalAPI = { GET_APPROVAL_REQUEST_LIST: async () => { - const response = await axiosAPI.get(`/admin/approvals`) + const response = await axiosAPI.get(`/api/v1/users/inquries`) return { data: response.data, } }, GET_APPROVAL_INFO: async () => { - const response = await axiosAPI.get(`/admin/approvals/1`) + const response = await axiosAPI.get(`/api/v1/useres/inquries/:inquryId`) return { data: response.data, } }, + POST_APPROVAL_ACCEPT: async () => { + const response = await axiosAPI.post( + '/api/v1/certification/users/:userId/accept', + { decision: 'accept' }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ) + return response.data + }, } export default AdminApprovalAPI diff --git a/src/apis/adminLogin/AdminLoginApi.ts b/src/apis/adminLogin/AdminLoginApi.ts new file mode 100644 index 00000000..0ce7518f --- /dev/null +++ b/src/apis/adminLogin/AdminLoginApi.ts @@ -0,0 +1,19 @@ +import { axiosAPI } from '@/apis/axios' + +interface AdminLoginData { + adminId: string + adminPw: string +} + +const AdminLoginAPI = { + POST_ADMIN_LOGIN: async (adminLoginData: AdminLoginData) => { + const response = await axiosAPI.post('/api/v1/admins/login', adminLoginData, { + headers: { + 'Content-Type': 'application/json', + }, + }) + return response.data + }, +} + +export default AdminLoginAPI diff --git a/src/apis/adminReport/AdminReportApi.ts b/src/apis/adminReport/AdminReportApi.ts index 6df5495b..2154327e 100644 --- a/src/apis/adminReport/AdminReportApi.ts +++ b/src/apis/adminReport/AdminReportApi.ts @@ -2,7 +2,13 @@ import { axiosAPI } from '@/apis/axios' const AdminReportAPI = { GET_REPORT_LIST: async () => { - const response = await axiosAPI.get(`/admin/reports`) + const response = await axiosAPI.get(`/api/v1/reports`) + return { + data: response.data, + } + }, + GET_REPORT_INFO: async () => { + const response = await axiosAPI.get(`/api/v1/reports/:reportId`) return { data: response.data, } diff --git a/src/components/common/ListRow/AdminApprovalList.tsx b/src/components/common/ListRow/AdminApprovalList.tsx index ba759e18..be822896 100644 --- a/src/components/common/ListRow/AdminApprovalList.tsx +++ b/src/components/common/ListRow/AdminApprovalList.tsx @@ -8,6 +8,11 @@ import { palette } from '@/styles/palette' interface AdminApprovalListProps { onApproveSelectUserName: (nickname: string) => void } +interface ApprovalListData { + approvalRequestUser: string + approvalRequestUserStatus: string + onApproveSelectUserName: (nickname: string) => void +} interface ApprovalListData { approvalRequestUser: string approvalRequestUserStatus: string @@ -23,13 +28,13 @@ const AdminApprovalList = ({ onApproveSelectUserName }: AdminApprovalListProps) const handlePersonApproval = (nickname: string) => { onApproveSelectUserName(nickname) } - const ApprovalDatas = data?.data.approvals + const approvalDatas = data?.data.approvals return ( {isSuccess && - ApprovalDatas.map((approvalListData: ApprovalListData, index: number) => ( + approvalDatas.map((approvalListData: ApprovalListData, index: number) => ( { + return HttpResponse.json(['Tom', 'Jerry', 'Spike']) + }), + http.get('/v1/histories', () => { return HttpResponse.json([ { @@ -369,7 +381,7 @@ export const handlers = [ return HttpResponse.json(['Tom', 'Jerry', 'Spike']) }), // 승인 목록 API 핸들러 - http.get('/admin/approvals', () => { + http.get('/api/v1/users/inquries', () => { const approvals: Approval[] = [ { approvalRequestUser: '박상민', approvalRequestUserStatus: '대기 중' }, { approvalRequestUser: '박은지', approvalRequestUserStatus: '대기 중' }, @@ -401,7 +413,7 @@ export const handlers = [ }), // 승인 상세 정보 API 핸들러 - http.get('/admin/approvals/:userId', (req) => { + http.get('/api/v1/useres/inquries/:inquryId', (req) => { const { userId } = req.params const approvalInfo: ApprovalInfo = { approvalRequestUserName: `userId:${userId}에 해당하는 userName`, @@ -411,20 +423,44 @@ export const handlers = [ return HttpResponse.json({ approvalInfo }) }), + // 관리자 로그인 요청 API 핸들러 + http.post('/api/v1/admins/login', async ({ request }) => { + const adminData = await request.text() + const { adminId, adminPw } = JSON.parse(adminData) + const isValidUser = adminId === 'expectedId' && adminPw === 'expectedPassword' + const adminLoginInfo: AdminLoginInfo = { + adminLoginResult: isValidUser ? 'success' : 'error', + adminLoginMessage: isValidUser ? 'Authentication successful' : 'Invalid credentials', + } + return HttpResponse.json({ + adminLoginInfo, + }) + }), - // 승인/거절 처리 API 핸들러 - // req.body 오류 해결이 필요한 부분 - // http.post('/admin/approvals/:userId/action', (req) => { - // const { userId } = req.params - // const { action } = req.body - // const approvalResult: ApprovalResult = { - // result: action === 'accept' ? 'accepted' : 'rejected', - // } - // return HttpResponse.json({ approvalResult }) - // }), + // 승인 처리 API 핸들러 + http.post('/api/v1/certification/users/:userId/accept', async ({ request }) => { + const decisionString = await request.text() + const { decision } = JSON.parse(decisionString) + const decisionMaking = decision === 'approve' + const approvalResult: ApprovalResult = { + result: decisionMaking === true ? 'accepted' : 'error', + } + return HttpResponse.json({ approvalResult }) + }), + + // 거절 처리 API 핸들러 + http.post('/api/v1/certification/users/:userId/reject', async ({ request }) => { + const decisionString = await request.text() + const { decision } = JSON.parse(decisionString) + const decisionMaking = decision === 'reject' + const approvalResult: ApprovalResult = { + result: decisionMaking === true ? 'rejected' : 'error', + } + return HttpResponse.json({ approvalResult }) + }), // 신고 목록 API 핸들러 - http.get('/admin/reports', () => { + http.get('/api/v1/reports', () => { const reports: Reports[] = [ { reportedUserName: '유명한', reportCount: 1 }, { reportedUserName: '박상민', reportCount: 2 }, @@ -445,11 +481,10 @@ export const handlers = [ }), // 신고 상세 정보 API 핸들러 - http.get('/admin/reports/:userId', (req) => { + http.get('/api/v1/reports/:reportId', (req) => { const { userId } = req.params const reportInfo: ReportInfo = { - reportedUserName: `userName, userId:${userId}`, - reporterUserName: 'reporterUserName', + reportedUserName: `reporterUserName, userId:${userId}`, reportDate: new Date().toISOString(), reason: 'saying swear words', reportCount: 3, @@ -461,7 +496,7 @@ export const handlers = [ }), // // 신고 처리 API 핸들러 - // req.body 오류 해결이 필요한 부분 + // // req.body 오류 해결이 필요한 부분 // http.post('/admin/reports/:userId/action', (req) => { // const { userId } = req.params // const { action } = req.body diff --git a/src/mocks/handlersInterface.ts b/src/mocks/handlersInterface.ts index 89da3154..e4be3420 100644 --- a/src/mocks/handlersInterface.ts +++ b/src/mocks/handlersInterface.ts @@ -7,17 +7,15 @@ export interface ApprovalInfo { approvalRequestUserEmail: string approvalRequestUserBusinessCardImage: string } -// req.body 오류 해결이 필요한 부분 -// export interface ApprovalResult { -// result: string -// } +export interface ApprovalResult { + result: string +} export interface Reports { reportedUserName: string reportCount: number } export interface ReportInfo { reportedUserName: string - reporterUserName: string reportDate: string reason: string reportCount: number @@ -27,3 +25,8 @@ export interface ReportInfo { // export interface ReportResult { // result: string // } + +export interface AdminLoginInfo { + adminLoginResult: string + adminLoginMessage: string +} diff --git a/src/pages/admin/AdminLogin.tsx b/src/pages/admin/AdminLogin.tsx index 46f38fb1..0371c716 100644 --- a/src/pages/admin/AdminLogin.tsx +++ b/src/pages/admin/AdminLogin.tsx @@ -1,29 +1,111 @@ import styled from '@emotion/styled' +import { useMutation } from '@tanstack/react-query' +import { useState } from 'react' +import { useForm } from 'react-hook-form' +import { useNavigate } from 'react-router-dom' -import AdminAppHeader from '@/components/common/AppHeader/AdminAppHeader' +import AdminLoginAPI from '@/apis/adminLogin/AdminLoginApi' +import Spacing from '@/components/common/Spacing' +import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -import AdminTabs from './components/AdminTabs' const AdminLogin = () => { - //msw 테스트 코드 - fetch('/admin/reports/2') - .then((response) => { - if (!response.ok) { - throw new Error('네트워크 상태가 불안정합니다.') + // form 제출 -> API 요청 -> 결과값에 따라 페이지 이동 + const { register, handleSubmit } = useForm() + const [adminData, setAdminData] = useState('') + + const navigate = useNavigate() + + const mutation = useMutation(AdminLoginAPI.POST_ADMIN_LOGIN, { + onSuccess: (data) => { + if (data.adminLoginInfo.adminLoginResult == 'error') { + navigate('/admin') } - return response.json() - }) - .then((data) => console.log(data)) - .catch((error) => console.error('fetch 오류 발생.', error)) + }, + }) + + const onSubmitAdminLoginData = (AdminLoginData: string) => { + mutation.mutate(JSON.parse(AdminLoginData)) + } + return ( - - - - + +
{ + setAdminData(JSON.stringify(data)) + onSubmitAdminLoginData(adminData) + })} + > + + + + + {'Admin Login'} + + + + + + + + + + + + + + + + + + + {'로그인'} + + + +
+
) } - -const StyledAdminLoginOuterWrapper = styled.div` - background-color: ${palette.PRIMARY}; +const AdminLoginOuterWrapper = styled.div` + background-color: ${palette.GRAY100}; +` +const StyledTextWrapper = styled.div` + text-align: center; +` +const StyledInputWrapper = styled.div` + text-align: center; ` +const StyledInput = styled.input` + width: 230px; + height: 39px; + border: 1px solid #000; + border-radius: 10px; + margin-bottom: 10px; + box-sizing: border-box; + padding: 0 0 0 10px; + border-color: ${palette.GRAY200}; + box-shadow: 4px 4px 1px 0px ${palette.GRAY200}; + color: ${palette.GRAY400}; + ::placeholder { + color: ${palette.GRAY300}; + } +` +const StyledBtnWrapper = styled.div` + text-align: center; +` +const StyledButton = styled.button` + width: 265px; + height: 44px; + border: 1px solid #000; + border-radius: 11px; + margin-bottom: 10px; + box-sizing: border-box; + padding: 0 0 0 10px; + background-color: ${palette.SECONDARY}; + border-color: ${palette.GRAY300}; + color: ${palette.WHITE}; + font-weight: 900; +` + export default AdminLogin diff --git a/src/pages/admin/AdminMain.tsx b/src/pages/admin/AdminMain.tsx new file mode 100644 index 00000000..444020a6 --- /dev/null +++ b/src/pages/admin/AdminMain.tsx @@ -0,0 +1,29 @@ +import styled from '@emotion/styled' + +import AdminAppHeader from '@/components/common/AppHeader/AdminAppHeader' +import { palette } from '@/styles/palette' + +import AdminTabs from './components/AdminTabs' +const AdminMain = () => { + //msw 테스트 코드 + // fetch('/admin/reports/2') + // .then((response) => { + // if (!response.ok) { + // throw new Error('네트워크 상태가 불안정합니다.') + // } + // return response.json() + // }) + // .then((data) => console.log(data)) + // .catch((error) => console.error('fetch 오류 발생.', error)) + return ( + + + + + ) +} + +const StyledAdminMainOuterWrapper = styled.div` + background-color: ${palette.PRIMARY}; +` +export default AdminMain diff --git a/src/pages/admin/components/AdminApprovalInfo.tsx b/src/pages/admin/components/AdminApprovalInfo.tsx index 1a52f08a..10823f53 100644 --- a/src/pages/admin/components/AdminApprovalInfo.tsx +++ b/src/pages/admin/components/AdminApprovalInfo.tsx @@ -1,5 +1,6 @@ import styled from '@emotion/styled' import { useQuery } from '@tanstack/react-query' +import { useMutation } from '@tanstack/react-query' import AdminApprovalAPI from '@/apis/adminApproval/AdminApprovalApi' import businessCardExample from '@/assets/images/businessCardExample.jpg' @@ -16,6 +17,16 @@ interface AdminApprovalInfoProps { } const AdminApprovalInfo = ({ selectedApprovalNickname }: AdminApprovalInfoProps) => { + const mutation = useMutation(AdminApprovalAPI.POST_APPROVAL_ACCEPT, { + onSuccess: (data) => { + console.log(data) + }, + }) + + const onAcceptAdminApproval = () => { + mutation.mutate() + } + const { data, isSuccess } = useQuery( ['ApprovalRequestUserInfo'], AdminApprovalAPI.GET_APPROVAL_INFO, @@ -26,7 +37,7 @@ const AdminApprovalInfo = ({ selectedApprovalNickname }: AdminApprovalInfoProps) openModal({ type: 'confirm', mainText: '인증을 수락하시겠습니까?', - okFunc: () => console.log('okFunc'), + okFunc: onAcceptAdminApproval, }) } diff --git a/src/pages/admin/components/AdminReportInfo.tsx b/src/pages/admin/components/AdminReportInfo.tsx index bb014ab9..9b0c76a3 100644 --- a/src/pages/admin/components/AdminReportInfo.tsx +++ b/src/pages/admin/components/AdminReportInfo.tsx @@ -1,5 +1,7 @@ import styled from '@emotion/styled' +import { useQuery } from '@tanstack/react-query' +import AdminReportAPI from '@/apis/adminReport/AdminReportApi' import NormalButton from '@/components/common/Buttons/NormalButton' import AdminReportInfoListRow from '@/components/common/ListRow/AdminReportInfoListRow' import Spacing from '@/components/common/Spacing' @@ -13,6 +15,8 @@ interface AdminReportInfoProps { } const AdminReportInfo = ({ selectedReportNickname }: AdminReportInfoProps) => { + const { data, isSuccess } = useQuery(['ReportedUserInfo'], AdminReportAPI.GET_REPORT_INFO) + console.log(isSuccess && data) const { openModal } = useModal() const handleAccumulationAddBtn = () => { openModal({ diff --git a/src/pages/admin/index.tsx b/src/pages/admin/index.tsx index e960cbe0..32cd608a 100644 --- a/src/pages/admin/index.tsx +++ b/src/pages/admin/index.tsx @@ -1,12 +1,12 @@ import { Route, Routes } from 'react-router-dom' -import AdminLogin from '@/pages/admin/AdminLogin' +import AdminMain from '@/pages/admin/AdminMain' -const AdminLoginPage = () => { +const AdminPage = () => { return ( - }> + }> ) } -export default AdminLoginPage +export default AdminPage diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index 024c2d25..777048fb 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -1,7 +1,9 @@ import { css } from '@emotion/react' import styled from '@emotion/styled' +import { useNavigate } from 'react-router-dom' import { KakaoButton, NaverButton } from '@/components/common/Buttons/IconButton' +import NormalButton from '@/components/common/Buttons/NormalButton' import HeroImage from '@/components/common/HeroImage' import Spacing from '@/components/common/Spacing' import { Text } from '@/components/common/Text' @@ -13,12 +15,16 @@ export type Provider = 'NAVER' | 'KAKAO' const BASE_URL = import.meta.env.VITE_BASE_URL const Login = () => { + const navigate = useNavigate() const setProvider = useAuthStore((state) => state.setProvider) const handleMoveToAuthProvider = async (provider: Provider) => { window.location.assign(`${BASE_URL}/v1/oauth2.0/${provider}`) setProvider(provider) } + const handleMoveToAdminLogin = () => { + navigate('/admin-login') + } return ( @@ -50,7 +56,9 @@ const Login = () => { } `} /> - + + {'관리자 로그인'} + {'회사의 경계를 넘어, '} @@ -62,7 +70,6 @@ const Login = () => { } `} /> - {' 새로운 대화의 세계를 탐험하세요!'} @@ -74,7 +81,6 @@ const Login = () => { } `} /> - { @@ -107,6 +113,7 @@ const StyledLoginOuterWrapper = styled.div` flex-direction: column; justify-content: center; align-items: center; + overflow: scroll; ` const StyledOauthWrapper = styled.div` @@ -137,4 +144,9 @@ const StyledSubText = styled(Text)` font-size: 14px; } ` +const StyledAdminLoginBtn = styled.div` + position: absolute; + cursor: pointer; + opacity: 0; +` export default Login From 058f9fd8a21e9751b8d8ad5c82ac1dbb7961d435 Mon Sep 17 00:00:00 2001 From: Cheshier Date: Mon, 20 Nov 2023 00:38:43 +0900 Subject: [PATCH 064/180] =?UTF-8?q?=F0=9F=90=9B=20[Bug]:=20OAuth=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=BD=94=EB=93=9C=20=EB=B6=84=EB=A6=AC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20(#141)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Chore]: 불필요한 개행 제거 - lint fix * [Feature]: 최초 로그인 여부 전역 상태 추가 - isNewUser, setIsNewUser * [Feature]: LoginPending에 분기 처리 * [Chore]: 회원가입 완료 후 isNewUser 초기화 --- src/apis/axios.ts | 2 -- src/pages/login/Login.tsx | 2 +- src/pages/loginPending/LoginPending.tsx | 14 ++++++++---- src/pages/register/RegisterCompany.tsx | 5 +++-- src/pages/register/RegisterUser.tsx | 30 +------------------------ src/store/AuthStore.tsx | 4 ++++ 6 files changed, 19 insertions(+), 38 deletions(-) diff --git a/src/apis/axios.ts b/src/apis/axios.ts index 3de064cd..538a83e5 100644 --- a/src/apis/axios.ts +++ b/src/apis/axios.ts @@ -16,8 +16,6 @@ axiosAPI.interceptors.request.use( // axios 설정값에 대해 작성합니다. config.headers['Authorization'] = `Bearer ${localStorage.getItem('jwt')}` - - return config }, function (error) { diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index 777048fb..e25c4fbc 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -12,7 +12,7 @@ import { palette } from '@/styles/palette' export type Provider = 'NAVER' | 'KAKAO' -const BASE_URL = import.meta.env.VITE_BASE_URL +export const BASE_URL = import.meta.env.VITE_BASE_URL const Login = () => { const navigate = useNavigate() diff --git a/src/pages/loginPending/LoginPending.tsx b/src/pages/loginPending/LoginPending.tsx index 610a3d97..247a5231 100644 --- a/src/pages/loginPending/LoginPending.tsx +++ b/src/pages/loginPending/LoginPending.tsx @@ -4,6 +4,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom' import { PulseLoader } from 'react-spinners' import { axiosAPI } from '@/apis/axios' +import { BASE_URL } from '@/pages/login/Login.tsx' import useAuthStore from '@/store/AuthStore' import { palette } from '@/styles/palette' @@ -13,6 +14,7 @@ const LoginPending = () => { const authCode = searchParams.get('code') const setToken = useAuthStore((state) => state.setAuthTokens) const provider = useAuthStore((state) => state.provider) + const { isNewUser, setIsNewUser } = useAuthStore() const routeAuthInfo = async () => { await axiosAPI .get(`/v1/users/login/${provider}?authCode=${authCode}`) @@ -29,15 +31,19 @@ const LoginPending = () => { }) .catch((err) => { if (err.response.status === 404) { - navigate('/register/user', { state: { authCode } }) - console.log('카카오 로그인 완료 & 정보 등록 안됨') - + setIsNewUser(true) + window.location.replace(`${BASE_URL}/v1/oauth2.0/${provider}`) } }) } useEffect(() => { - routeAuthInfo() + if (isNewUser) { + const authCode = searchParams.get('code') + navigate('/register/user', { state: { authCode } }) + } else { + routeAuthInfo() + } }, []) return ( diff --git a/src/pages/register/RegisterCompany.tsx b/src/pages/register/RegisterCompany.tsx index bfbaa089..422024eb 100644 --- a/src/pages/register/RegisterCompany.tsx +++ b/src/pages/register/RegisterCompany.tsx @@ -3,7 +3,6 @@ import { useMutation } from '@tanstack/react-query' import { RefObject, useRef, useState } from 'react' import { MdWbSunny } from 'react-icons/md' import { MdOutlinePhotoCamera } from 'react-icons/md' - import { useNavigate } from 'react-router-dom' import { axiosAPI } from '@/apis/axios' @@ -15,6 +14,7 @@ import RegisterInput from '@/components/common/RegisterInput' import SelectorButtonContainer from '@/components/common/SelectorButtonContainer' import Spacing from '@/components/common/Spacing' import useToast from '@/hooks/useToast' +import useAuthStore from '@/store/AuthStore.tsx' import useInterestStore from '@/store/InterestStore' import useThemeStore from '@/store/ThemeStore' import { palette } from '@/styles/palette' @@ -49,6 +49,7 @@ const RegisterCompany = () => { const formData = new FormData() const imgRef = useRef(null) as RefObject const [uploadedURL, setUploadedURL] = useState('') + const { setIsNewUser } = useAuthStore() const handleClickEmailVerify = async (email: string) => { console.log(email) @@ -148,6 +149,7 @@ const RegisterCompany = () => { type: 'success', isDarkMode: false, }) + setIsNewUser(false) navigate('/') }) .catch(() => { @@ -157,7 +159,6 @@ const RegisterCompany = () => { isDarkMode: false, }) }) - } // const registerCompanyMutation = useMutation((body: object) => registerCompanyData(body), { // onSuccess: (response) => { diff --git a/src/pages/register/RegisterUser.tsx b/src/pages/register/RegisterUser.tsx index 4b75d38a..b8a3e1c0 100644 --- a/src/pages/register/RegisterUser.tsx +++ b/src/pages/register/RegisterUser.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled' import { useMutation } from '@tanstack/react-query' -import { useEffect, useRef, useState } from 'react' +import { useRef, useState } from 'react' import { MdWbSunny } from 'react-icons/md' import { useNavigate } from 'react-router-dom' import { useLocation } from 'react-router-dom' @@ -45,31 +45,6 @@ const RegisterUser = () => { const { provider } = useAuthStore() const { showToast } = useToast() const isDarkMode = useThemeStore((state) => state.isDarkMode) - const setToken = useAuthStore((state) => state.setAuthTokens) - - const routeAuthInfo = async () => { - await axiosAPI - .get(`/v1/users/login/${provider}?authCode=${authCode}`) - .then((res) => { - console.log(res.data.accessToken) - localStorage.setItem('jwt', res.data.accessToken) - localStorage.setItem('nickname', res.data.nickname) - - setToken({ - accessToken: res.data.accessToken, - refreshToken: res.data.refreshToken, - }) - }) - .catch((err) => { - if (err.response.status === 404) { - navigate('/register/user', { state: { authCode } }) - console.log('실패패패') - } - }) - } - useEffect(() => { - routeAuthInfo() - }, []) const getNicknameValid = async (nickname: string) => { return await axiosAPI.get(`/v1/users/duplicate?nickname=${nickname}`) @@ -78,7 +53,6 @@ const RegisterUser = () => { onSuccess: (response) => { if (response.status == 200) { //사용가능한 닉네임일 경우 - console.log(response) setDoubleChecked(true) setNicknameDuplicated(false) } else { @@ -149,8 +123,6 @@ const RegisterUser = () => { const registerMutation = useMutation((body: object) => registerPost(body), { onSuccess: (response) => { - console.log(response) - console.log(response.data.accessToken) localStorage.setItem('jwt', response.data.accessToken) showToast({ message: '닉네임, 관심사 정보 등록을 완료했습니다!', diff --git a/src/store/AuthStore.tsx b/src/store/AuthStore.tsx index 6b0baee7..3ac9593d 100644 --- a/src/store/AuthStore.tsx +++ b/src/store/AuthStore.tsx @@ -10,7 +10,9 @@ type Tokens = { type AuthState = { provider: Provider + isNewUser: boolean authTokens?: Tokens + setIsNewUser: (isNewUser: boolean) => void setProvider: (provider: Provider) => void setAuthTokens: (authTokens: Tokens) => void } @@ -18,7 +20,9 @@ type AuthState = { const useAuthStore = create( persist( (set) => ({ + isNewUser: false, provider: 'KAKAO', + setIsNewUser: (isNewUser: boolean) => set(() => ({ isNewUser })), setProvider: (provider: Provider) => set(() => ({ provider })), setAuthTokens: (authTokens: Tokens) => set(() => ({ From 07d239fa1afeca4d10cf16548ddcbab78561e677 Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 20 Nov 2023 01:12:54 +0900 Subject: [PATCH 065/180] =?UTF-8?q?chore=20:=20useEffect=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EB=B0=B0=EC=97=B4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/loginPending/LoginPending.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/loginPending/LoginPending.tsx b/src/pages/loginPending/LoginPending.tsx index 247a5231..aa6d5a64 100644 --- a/src/pages/loginPending/LoginPending.tsx +++ b/src/pages/loginPending/LoginPending.tsx @@ -44,7 +44,7 @@ const LoginPending = () => { } else { routeAuthInfo() } - }, []) + }) return ( From 782e384bb79d3aaebd3489d150cab85e6cef7409 Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 20 Nov 2023 01:14:10 +0900 Subject: [PATCH 066/180] =?UTF-8?q?fix=20:=20eslint=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/register/RegisterCompany.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/register/RegisterCompany.tsx b/src/pages/register/RegisterCompany.tsx index dda15120..422024eb 100644 --- a/src/pages/register/RegisterCompany.tsx +++ b/src/pages/register/RegisterCompany.tsx @@ -3,7 +3,6 @@ import { useMutation } from '@tanstack/react-query' import { RefObject, useRef, useState } from 'react' import { MdWbSunny } from 'react-icons/md' import { MdOutlinePhotoCamera } from 'react-icons/md' - import { useNavigate } from 'react-router-dom' import { axiosAPI } from '@/apis/axios' From 8954579a0f4313a82f276afe73684f453a2d9606 Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 20 Nov 2023 02:01:33 +0900 Subject: [PATCH 067/180] =?UTF-8?q?fix=20:=20navigate=20state=20=EB=84=98?= =?UTF-8?q?=EA=B8=B0=EB=8A=94=20=EB=B0=A9=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/loginPending/LoginPending.tsx | 2 +- src/pages/register/RegisterUser.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/loginPending/LoginPending.tsx b/src/pages/loginPending/LoginPending.tsx index aa6d5a64..288e4ef1 100644 --- a/src/pages/loginPending/LoginPending.tsx +++ b/src/pages/loginPending/LoginPending.tsx @@ -40,7 +40,7 @@ const LoginPending = () => { useEffect(() => { if (isNewUser) { const authCode = searchParams.get('code') - navigate('/register/user', { state: { authCode } }) + navigate('/register/user', { state: authCode }) } else { routeAuthInfo() } diff --git a/src/pages/register/RegisterUser.tsx b/src/pages/register/RegisterUser.tsx index b8a3e1c0..baece48e 100644 --- a/src/pages/register/RegisterUser.tsx +++ b/src/pages/register/RegisterUser.tsx @@ -36,7 +36,7 @@ const RegisterUser = () => { '반려동물', ] const navigate = useNavigate() - const { authCode } = useLocation().state || {} + const authCode = useLocation().state const inputRef = useRef(null) const [doubleChecked, setDoubleChecked] = useState(false) const [nicknameDuplicated, setNicknameDuplicated] = useState(null) @@ -56,7 +56,7 @@ const RegisterUser = () => { setDoubleChecked(true) setNicknameDuplicated(false) } else { - //이미 사용 중인 닉네임일 경우 + //이미 사용 중 인 닉네임일 경우 setDoubleChecked(true) setNicknameDuplicated(true) } From 06e0f07e86d8d9a3db08830f6495ffeaa77f18a6 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Tue, 21 Nov 2023 15:02:47 +0900 Subject: [PATCH 068/180] =?UTF-8?q?feature:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?(=ED=98=84=EC=9E=AC=EA=B9=8C=EC=A7=80=20=EC=A7=84=ED=96=89=20?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20PR)=20(#131)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: AdminAppHeader 컴포넌트 추가 * style: AdminNavigationBar 컴포넌트 생성 * style: AdminNavigationBar 색 변경 * feat: AdminTabs 컴포넌트 추가 * refactor: 컴포넌트 변수명 변경 * refactor: 컴포넌트 변수명 변경 * feat: AdminApprovalList 컴포넌트 추가 * feat: outerwrapper 제거 * feat: AdminReportListRow 컴포넌트 예시 추가 * feat: AdminReportList 컴포넌트 예시 추가 * feat: AdminTabs 컴포넌트로 다른 컴포넌트들 연결 및 디자인 수정, 변수명 수정 * refactor: admin 컴포넌트 이전 * refactor: eslint 에러 수정 * refactor: import 경로 변경 * feat: AdminApprovalInfo 컴포넌트 1 * style: 예시 이미지 추가 * feat: AdminApprovalInfo 컴포넌트 추가 1 * feat: input text fontweight 속성 추가 * style: Input 속성 추가 * style: AdminApprovalInfo 컴포넌트 디자인 2 * style: AdminReportInfo 컴포넌트 1 * refactor: props명 수정 * style: AdminReportInfo 컴포넌트 1 * style: AdminReportInfoListRow 컴포넌트 * feat: useModal 컴포넌트 적용, 기타 스타일 수정 * feat: AdminReportInfo 컴포넌트 userModal 적용 * refactor: 불필요 컴포넌트 삭제 * refactor: 이름 수정 * feat: AdminReportListRow 속성 추가 * feat: 관리자페이지 신고자 목록 클릭 시, 정보 출력 * feat: AdminReportList 특정 목록 클릭 시, 해당 AdminReportInfo 출력 * feat: AdminReportList mockdata 추가, 함수 연결 * refactor: 이름 예시 변경 * refactor: 변수명 수정 (adminapprovalinfo와 구분) * feat: AdminTabs 승인 탭 추가 * refactor: 변수명 수정(구별필요) * feat: 승인 페이지 이름 적용, 불필요 컴포넌트 제거 * refactor: mock data 적용. 변수명 수정. * feat: 승인페이지 mock data 적용. 목록 클릭 함수 추가. interface 추가. * feat: adminapprovallistrow onclick 함수 속성 추가 * feat: msw handlers interface 파일들 * feat: 관리자페이지 - MSW 활용한 API request, response 명세. API 테스트. * style: 오류 관련 주석 처리 * feat: AdminPageHeader * feat: 관리자페이지 승인요청 사용자들 목록 불러오기 MSW API 요청 코드 완료. 테스트 완료. * feat: 관리자페이지 신고당한자 목록 API 코드 추가. 테스트 완료. * feat: 관리자페이지 승인요청자 세부정보 출력 1 * refactor: 불필요 코드 삭제 * refactor: 불필요코드 제거 2 * feat: 숨겨진 관리자 로그인 버튼 추가 * feat: 관리자 페이지 라우터 설정 * feat: 관리자페이지 로그인 MSW API 요청 코드 추가, 관리자페이지 로그인 UI(react hook form) * refactor: 타입 오류 임시 주석처리 * refactor: import문 삭제 * feat: 관리자페이지 로그인 api 요청 1 * feat: 관리자페이지 로그인 API 요청 2 * feat: 관리자 로그인 페이지 post API 요청 * feat: 관리자 로그인 페이지 이동 * feat: 관리자 페이지 이동 * feat: 관리자 로그인 페이지 - 피그마 디자인, 구현 * feat: 문구수정 * refactor: useNavigate, api 요청 주소 변경 * feat: 관리자로그인페이지 디자인 수정 - emotion, register속성 적용 * feat: 관리자 승인 처리 API 요청. * feat: 관리자 승인요청, 신고 API 요청 * refactor: dev pull * feat: Admin Inquiry 추가 * style: 문의페이지 아이콘 추가 * refactor: import 절대 경로 수정 * refactor: MSW handler /api 경로 삭제 --- src/App.tsx | 3 + src/apis/adminApproval/AdminApprovalApi.ts | 12 +++ src/apis/adminReport/AdminReportApi.ts | 16 ++++ src/assets/images/inquiry.svg | 7 ++ src/components/common/AppHeader/index.tsx | 3 +- .../common/BusinessCardContainer/index.tsx | 5 +- .../common/ListRow/AdminInquiryListRow.tsx | 95 +++++++++++++++++++ src/mocks/handlers.ts | 48 ++++++---- src/mocks/handlersInterface.ts | 3 + src/pages/admin/AdminInquiry.tsx | 24 +++++ src/pages/admin/AdminMain.tsx | 3 +- .../admin/components/AdminApprovalInfo.tsx | 16 +++- .../admin/components/AdminInquiryBtn.tsx | 48 ++++++++++ .../admin/components/AdminReportInfo.tsx | 23 ++++- src/pages/admin/components/AdminTabs.tsx | 7 +- 15 files changed, 278 insertions(+), 35 deletions(-) create mode 100644 src/assets/images/inquiry.svg create mode 100644 src/components/common/ListRow/AdminInquiryListRow.tsx create mode 100644 src/pages/admin/AdminInquiry.tsx create mode 100644 src/pages/admin/components/AdminInquiryBtn.tsx diff --git a/src/App.tsx b/src/App.tsx index fc7fbbb1..db703e3c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,6 +14,8 @@ import ProfilePage from '@/pages/profile' import PrivateRoute from '@/pages/redirect/PrivateRoute' import RegisterPage from '@/pages/register' +import AdminInquiry from './pages/admin/AdminInquiry' + const App = () => { return ( @@ -37,6 +39,7 @@ const App = () => { }> }> + }> }> }> diff --git a/src/apis/adminApproval/AdminApprovalApi.ts b/src/apis/adminApproval/AdminApprovalApi.ts index 89180e99..b2c2743e 100644 --- a/src/apis/adminApproval/AdminApprovalApi.ts +++ b/src/apis/adminApproval/AdminApprovalApi.ts @@ -25,6 +25,18 @@ const AdminApprovalAPI = { ) return response.data }, + POST_APPROVAL_REJECT: async () => { + const response = await axiosAPI.post( + '/api/v1/certification/users/:userId/reject', + { decision: 'reject' }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ) + return response.data + }, } export default AdminApprovalAPI diff --git a/src/apis/adminReport/AdminReportApi.ts b/src/apis/adminReport/AdminReportApi.ts index 2154327e..d507767c 100644 --- a/src/apis/adminReport/AdminReportApi.ts +++ b/src/apis/adminReport/AdminReportApi.ts @@ -13,6 +13,22 @@ const AdminReportAPI = { data: response.data, } }, + POST_REPORT_ADD: async () => { + const response = await axiosAPI.post( + '/api/v1/reports/accept/:reportId', + { decision: 'addReportCount' }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ) + return response.data + }, + POST_REPORT_IGNORE: async () => { + const response = await axiosAPI.delete('/api/v1/reports/reject/:reportId') + return response.data + }, } export default AdminReportAPI diff --git a/src/assets/images/inquiry.svg b/src/assets/images/inquiry.svg new file mode 100644 index 00000000..5340667d --- /dev/null +++ b/src/assets/images/inquiry.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/common/AppHeader/index.tsx b/src/components/common/AppHeader/index.tsx index c26866c9..b953985e 100644 --- a/src/components/common/AppHeader/index.tsx +++ b/src/components/common/AppHeader/index.tsx @@ -4,11 +4,10 @@ import { RiSunFill } from 'react-icons/ri' import { useNavigate } from 'react-router-dom' import Avatar from '@/components/common/Avatar' +import { FlexBox } from '@/components/common/Flexbox' 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; diff --git a/src/components/common/BusinessCardContainer/index.tsx b/src/components/common/BusinessCardContainer/index.tsx index 34a87af0..1914c791 100644 --- a/src/components/common/BusinessCardContainer/index.tsx +++ b/src/components/common/BusinessCardContainer/index.tsx @@ -3,11 +3,10 @@ import { useState } from 'react' import { TiDelete } from 'react-icons/ti' import camera from '@/assets/images/camera.svg' +import Spacing from '@/components/common/Spacing' +import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -import Spacing from '../Spacing' -import { Text } from '../Text' - type BusinessCardContainerProps = { isDarkMode: boolean } diff --git a/src/components/common/ListRow/AdminInquiryListRow.tsx b/src/components/common/ListRow/AdminInquiryListRow.tsx new file mode 100644 index 00000000..06100447 --- /dev/null +++ b/src/components/common/ListRow/AdminInquiryListRow.tsx @@ -0,0 +1,95 @@ +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 AdminReportInfoListRowProps = { + height: number + nickname: string + infoMessage: string | number + isDarkMode: boolean +} +const AdminInquiryListRow = ({ + height, + nickname, + infoMessage, + isDarkMode, +}: AdminReportInfoListRowProps) => { + const renderInfoMessage = () => { + if (typeof infoMessage === 'number') { + return ( + + + {'불편 사항 접수 날짜 '} + + + {infoMessage} + + + {' 회'} + + + ) + } + return ( + + {infoMessage} + + ) + } + + return ( + + + {nickname} + + {renderInfoMessage()} + + ) +} + +export default AdminInquiryListRow diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 87cf9b82..6a27d660 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -6,6 +6,7 @@ import { ApprovalInfo, ApprovalResult, ReportInfo, + ReportResult, Reports, } from './handlersInterface' const nickname = '주다다' @@ -49,7 +50,7 @@ export const handlers = [ }, ]) }), - http.delete(`/api/v1/chatrooms/1`, () => { + http.delete(`/v1/chatrooms/1`, () => { return new HttpResponse(null, { status: 200, statusText: '삭제 완료', @@ -381,7 +382,7 @@ export const handlers = [ return HttpResponse.json(['Tom', 'Jerry', 'Spike']) }), // 승인 목록 API 핸들러 - http.get('/api/v1/users/inquries', () => { + http.get('/v1/users/inquries', () => { const approvals: Approval[] = [ { approvalRequestUser: '박상민', approvalRequestUserStatus: '대기 중' }, { approvalRequestUser: '박은지', approvalRequestUserStatus: '대기 중' }, @@ -413,7 +414,7 @@ export const handlers = [ }), // 승인 상세 정보 API 핸들러 - http.get('/api/v1/useres/inquries/:inquryId', (req) => { + http.get('/v1/useres/inquries/:inquryId', (req) => { const { userId } = req.params const approvalInfo: ApprovalInfo = { approvalRequestUserName: `userId:${userId}에 해당하는 userName`, @@ -424,7 +425,7 @@ export const handlers = [ return HttpResponse.json({ approvalInfo }) }), // 관리자 로그인 요청 API 핸들러 - http.post('/api/v1/admins/login', async ({ request }) => { + http.post('/v1/admins/login', async ({ request }) => { const adminData = await request.text() const { adminId, adminPw } = JSON.parse(adminData) const isValidUser = adminId === 'expectedId' && adminPw === 'expectedPassword' @@ -437,8 +438,8 @@ export const handlers = [ }) }), - // 승인 처리 API 핸들러 - http.post('/api/v1/certification/users/:userId/accept', async ({ request }) => { + // 관리자 회사승인 동의 API 핸들러 + http.post('/v1/certification/users/:userId/accept', async ({ request }) => { const decisionString = await request.text() const { decision } = JSON.parse(decisionString) const decisionMaking = decision === 'approve' @@ -448,8 +449,8 @@ export const handlers = [ return HttpResponse.json({ approvalResult }) }), - // 거절 처리 API 핸들러 - http.post('/api/v1/certification/users/:userId/reject', async ({ request }) => { + // 관리자 회사승인 거절 API 핸들러 + http.post('/v1/certification/users/:userId/reject', async ({ request }) => { const decisionString = await request.text() const { decision } = JSON.parse(decisionString) const decisionMaking = decision === 'reject' @@ -460,7 +461,7 @@ export const handlers = [ }), // 신고 목록 API 핸들러 - http.get('/api/v1/reports', () => { + http.get('/v1/reports', () => { const reports: Reports[] = [ { reportedUserName: '유명한', reportCount: 1 }, { reportedUserName: '박상민', reportCount: 2 }, @@ -481,7 +482,7 @@ export const handlers = [ }), // 신고 상세 정보 API 핸들러 - http.get('/api/v1/reports/:reportId', (req) => { + http.get('/v1/reports/:reportId', (req) => { const { userId } = req.params const reportInfo: ReportInfo = { reportedUserName: `reporterUserName, userId:${userId}`, @@ -495,14 +496,21 @@ export const handlers = [ }) }), - // // 신고 처리 API 핸들러 - // // req.body 오류 해결이 필요한 부분 - // http.post('/admin/reports/:userId/action', (req) => { - // const { userId } = req.params - // const { action } = req.body - // const reportResult: ReportResult = { - // result: action === 'addCount' ? 'countAdded' : 'ignored', - // } - // return HttpResponse.json({ reportResult }) - // }), + // 신고 승인 처리 API 핸들러 + http.post('/v1/reports/accept/:reportId', async ({ request }) => { + const decisionString = await request.text() + const { decision } = JSON.parse(decisionString) + const decisionMaking = decision === 'addReportCount' + const reportResult: ReportResult = { + result: decisionMaking === true ? 'reportCountAdded' : 'error', + } + return HttpResponse.json({ reportResult }) + }), + + // 신고 거절 처리 API 핸들러 + http.delete('/v1/reports/reject/:reportId', async () => { + return HttpResponse.json({ + result: 'reportDeleted', + }) + }), ] diff --git a/src/mocks/handlersInterface.ts b/src/mocks/handlersInterface.ts index e4be3420..f201c83d 100644 --- a/src/mocks/handlersInterface.ts +++ b/src/mocks/handlersInterface.ts @@ -10,6 +10,9 @@ export interface ApprovalInfo { export interface ApprovalResult { result: string } +export interface ReportResult { + result: string +} export interface Reports { reportedUserName: string reportCount: number diff --git a/src/pages/admin/AdminInquiry.tsx b/src/pages/admin/AdminInquiry.tsx new file mode 100644 index 00000000..b29ad300 --- /dev/null +++ b/src/pages/admin/AdminInquiry.tsx @@ -0,0 +1,24 @@ +import styled from '@emotion/styled' + +import AdminAppHeader from '@/components/common/AppHeader/AdminAppHeader' +import HomeNavigationBar from '@/components/common/NavigationBar/AdminNavigationBar' +import AdminPageHeader from '@/pages/admin/components/AdminPageHeader' +import { palette } from '@/styles/palette' + +const AdminInquiry = () => { + return ( + <> + + + + {'admin inquiry 페이지 입니다'} + + + + ) +} + +const AdminInquiryWrapper = styled.div` + background-color: ${palette.PRIMARY}; +` +export default AdminInquiry diff --git a/src/pages/admin/AdminMain.tsx b/src/pages/admin/AdminMain.tsx index 444020a6..db283b03 100644 --- a/src/pages/admin/AdminMain.tsx +++ b/src/pages/admin/AdminMain.tsx @@ -1,9 +1,8 @@ import styled from '@emotion/styled' import AdminAppHeader from '@/components/common/AppHeader/AdminAppHeader' +import AdminTabs from '@/pages/admin/components/AdminTabs' import { palette } from '@/styles/palette' - -import AdminTabs from './components/AdminTabs' const AdminMain = () => { //msw 테스트 코드 // fetch('/admin/reports/2') diff --git a/src/pages/admin/components/AdminApprovalInfo.tsx b/src/pages/admin/components/AdminApprovalInfo.tsx index 10823f53..99660fda 100644 --- a/src/pages/admin/components/AdminApprovalInfo.tsx +++ b/src/pages/admin/components/AdminApprovalInfo.tsx @@ -17,14 +17,22 @@ interface AdminApprovalInfoProps { } const AdminApprovalInfo = ({ selectedApprovalNickname }: AdminApprovalInfoProps) => { - const mutation = useMutation(AdminApprovalAPI.POST_APPROVAL_ACCEPT, { + const mutationApprovalRequestAccept = useMutation(AdminApprovalAPI.POST_APPROVAL_ACCEPT, { + onSuccess: (data) => { + console.log(data) + }, + }) + const mutationReject = useMutation(AdminApprovalAPI.POST_APPROVAL_REJECT, { onSuccess: (data) => { console.log(data) }, }) const onAcceptAdminApproval = () => { - mutation.mutate() + mutationApprovalRequestAccept.mutate() + } + const onRejectAdminApproval = () => { + mutationReject.mutate() } const { data, isSuccess } = useQuery( @@ -79,7 +87,9 @@ const AdminApprovalInfo = ({ selectedApprovalNickname }: AdminApprovalInfoProps) {'인증 수락'} - {'거절'} + + {'거절'} + diff --git a/src/pages/admin/components/AdminInquiryBtn.tsx b/src/pages/admin/components/AdminInquiryBtn.tsx new file mode 100644 index 00000000..e25887bc --- /dev/null +++ b/src/pages/admin/components/AdminInquiryBtn.tsx @@ -0,0 +1,48 @@ +import styled from '@emotion/styled' +import { useNavigate } from 'react-router-dom' + +import InquiryImage from '@/assets/images/inquiry.svg' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' +const AdminInquiryBtn = () => { + const navigate = useNavigate() + + const handleInquiryBtnClick = () => { + navigate('/admin/inquiry') + } + + return ( + <> + + + + + + + {'불편 사항 접수'} + + + + + ) +} + +const StyledInquiryBox = styled.div` + padding-bottom: 10px; + display: flex; + align-items: center; + width: 100%; + height: 71px; + background-color: ${palette.WHITE}; + backdrop-filter: blur(27.182817459106445px); + cursor: pointer; +` +const StyledInquiryImage = styled.img`` +const StyledInquiryImageWrapper = styled.div` + padding-left: 28px; +` +const StyledTextWrapper = styled.div` + padding-left: 78px; +` + +export default AdminInquiryBtn diff --git a/src/pages/admin/components/AdminReportInfo.tsx b/src/pages/admin/components/AdminReportInfo.tsx index 9b0c76a3..82e0083d 100644 --- a/src/pages/admin/components/AdminReportInfo.tsx +++ b/src/pages/admin/components/AdminReportInfo.tsx @@ -1,5 +1,6 @@ import styled from '@emotion/styled' import { useQuery } from '@tanstack/react-query' +import { useMutation } from '@tanstack/react-query' import AdminReportAPI from '@/apis/adminReport/AdminReportApi' import NormalButton from '@/components/common/Buttons/NormalButton' @@ -15,6 +16,22 @@ interface AdminReportInfoProps { } const AdminReportInfo = ({ selectedReportNickname }: AdminReportInfoProps) => { + const mutationReportAddCount = useMutation(AdminReportAPI.POST_REPORT_ADD, { + onSuccess: (data) => { + console.log(data) + }, + }) + const mutationReportIgnore = useMutation(AdminReportAPI.POST_REPORT_IGNORE, { + onSuccess: (data) => { + console.log(data) + }, + }) + const onReportAddCount = () => { + mutationReportAddCount.mutate() + } + const onReportIgnore = () => { + mutationReportIgnore.mutate() + } const { data, isSuccess } = useQuery(['ReportedUserInfo'], AdminReportAPI.GET_REPORT_INFO) console.log(isSuccess && data) const { openModal } = useModal() @@ -22,7 +39,7 @@ const AdminReportInfo = ({ selectedReportNickname }: AdminReportInfoProps) => { openModal({ type: 'confirm', mainText: '신고를 누적하시겠습니까?', - okFunc: () => console.log('okFunc'), + okFunc: onReportAddCount, }) } return ( @@ -69,7 +86,9 @@ const AdminReportInfo = ({ selectedReportNickname }: AdminReportInfoProps) => { {'누적 추가'} - {'무시'} + + {'무시'} + diff --git a/src/pages/admin/components/AdminTabs.tsx b/src/pages/admin/components/AdminTabs.tsx index 28955d62..405aeb27 100644 --- a/src/pages/admin/components/AdminTabs.tsx +++ b/src/pages/admin/components/AdminTabs.tsx @@ -5,11 +5,11 @@ import AdminApprovalList from '@/components/common/ListRow/AdminApprovalList' import AdminReportList from '@/components/common/ListRow/AdminReportList' import HomeNavigationBar from '@/components/common/NavigationBar/AdminNavigationBar' import { Text } from '@/components/common/Text' +import AdminApprovalInfo from '@/pages/admin/components/AdminApprovalInfo' +import AdminInquiryBtn from '@/pages/admin/components/AdminInquiryBtn' +import AdminReportInfo from '@/pages/admin/components/AdminReportInfo' import { palette } from '@/styles/palette' -import AdminApprovalInfo from './AdminApprovalInfo' -import AdminReportInfo from './AdminReportInfo' - interface TabProps { isActive: boolean } @@ -82,6 +82,7 @@ const AdminTabs = () => { )}
+ ) From 1a15cc287a5f85428de75156f8606c4c6bc2db27 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Tue, 21 Nov 2023 15:22:42 +0900 Subject: [PATCH 069/180] =?UTF-8?q?Fix:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20API=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=A3=BC=EC=86=8C=20=EC=88=98=EC=A0=95=20(#152)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: AdminAppHeader 컴포넌트 추가 * style: AdminNavigationBar 컴포넌트 생성 * style: AdminNavigationBar 색 변경 * feat: AdminTabs 컴포넌트 추가 * refactor: 컴포넌트 변수명 변경 * refactor: 컴포넌트 변수명 변경 * feat: AdminApprovalList 컴포넌트 추가 * feat: outerwrapper 제거 * feat: AdminReportListRow 컴포넌트 예시 추가 * feat: AdminReportList 컴포넌트 예시 추가 * feat: AdminTabs 컴포넌트로 다른 컴포넌트들 연결 및 디자인 수정, 변수명 수정 * refactor: admin 컴포넌트 이전 * refactor: eslint 에러 수정 * refactor: import 경로 변경 * feat: AdminApprovalInfo 컴포넌트 1 * style: 예시 이미지 추가 * feat: AdminApprovalInfo 컴포넌트 추가 1 * feat: input text fontweight 속성 추가 * style: Input 속성 추가 * style: AdminApprovalInfo 컴포넌트 디자인 2 * style: AdminReportInfo 컴포넌트 1 * refactor: props명 수정 * style: AdminReportInfo 컴포넌트 1 * style: AdminReportInfoListRow 컴포넌트 * feat: useModal 컴포넌트 적용, 기타 스타일 수정 * feat: AdminReportInfo 컴포넌트 userModal 적용 * refactor: 불필요 컴포넌트 삭제 * refactor: 이름 수정 * feat: AdminReportListRow 속성 추가 * feat: 관리자페이지 신고자 목록 클릭 시, 정보 출력 * feat: AdminReportList 특정 목록 클릭 시, 해당 AdminReportInfo 출력 * feat: AdminReportList mockdata 추가, 함수 연결 * refactor: 이름 예시 변경 * refactor: 변수명 수정 (adminapprovalinfo와 구분) * feat: AdminTabs 승인 탭 추가 * refactor: 변수명 수정(구별필요) * feat: 승인 페이지 이름 적용, 불필요 컴포넌트 제거 * refactor: mock data 적용. 변수명 수정. * feat: 승인페이지 mock data 적용. 목록 클릭 함수 추가. interface 추가. * feat: adminapprovallistrow onclick 함수 속성 추가 * feat: msw handlers interface 파일들 * feat: 관리자페이지 - MSW 활용한 API request, response 명세. API 테스트. * style: 오류 관련 주석 처리 * feat: AdminPageHeader * feat: 관리자페이지 승인요청 사용자들 목록 불러오기 MSW API 요청 코드 완료. 테스트 완료. * feat: 관리자페이지 신고당한자 목록 API 코드 추가. 테스트 완료. * feat: 관리자페이지 승인요청자 세부정보 출력 1 * refactor: 불필요 코드 삭제 * refactor: 불필요코드 제거 2 * feat: 숨겨진 관리자 로그인 버튼 추가 * feat: 관리자 페이지 라우터 설정 * feat: 관리자페이지 로그인 MSW API 요청 코드 추가, 관리자페이지 로그인 UI(react hook form) * refactor: 타입 오류 임시 주석처리 * refactor: import문 삭제 * feat: 관리자페이지 로그인 api 요청 1 * feat: 관리자페이지 로그인 API 요청 2 * feat: 관리자 로그인 페이지 post API 요청 * feat: 관리자 로그인 페이지 이동 * feat: 관리자 페이지 이동 * feat: 관리자 로그인 페이지 - 피그마 디자인, 구현 * feat: 문구수정 * refactor: useNavigate, api 요청 주소 변경 * feat: 관리자로그인페이지 디자인 수정 - emotion, register속성 적용 * feat: 관리자 승인 처리 API 요청. * feat: 관리자 승인요청, 신고 API 요청 * refactor: dev pull * feat: Admin Inquiry 추가 * style: 문의페이지 아이콘 추가 * refactor: import 절대 경로 수정 * refactor: MSW handler /api 경로 삭제 * fix: 관리자 페이지 Admin API 요청 주소 수정 --- src/apis/adminApproval/AdminApprovalApi.ts | 8 ++++---- src/apis/adminLogin/AdminLoginApi.ts | 2 +- src/apis/adminReport/AdminReportApi.ts | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/apis/adminApproval/AdminApprovalApi.ts b/src/apis/adminApproval/AdminApprovalApi.ts index b2c2743e..db2841e6 100644 --- a/src/apis/adminApproval/AdminApprovalApi.ts +++ b/src/apis/adminApproval/AdminApprovalApi.ts @@ -2,20 +2,20 @@ import { axiosAPI } from '@/apis/axios' const AdminApprovalAPI = { GET_APPROVAL_REQUEST_LIST: async () => { - const response = await axiosAPI.get(`/api/v1/users/inquries`) + const response = await axiosAPI.get(`/v1/users/inquries`) return { data: response.data, } }, GET_APPROVAL_INFO: async () => { - const response = await axiosAPI.get(`/api/v1/useres/inquries/:inquryId`) + const response = await axiosAPI.get(`/v1/useres/inquries/:inquryId`) return { data: response.data, } }, POST_APPROVAL_ACCEPT: async () => { const response = await axiosAPI.post( - '/api/v1/certification/users/:userId/accept', + '/v1/certification/users/:userId/accept', { decision: 'accept' }, { headers: { @@ -27,7 +27,7 @@ const AdminApprovalAPI = { }, POST_APPROVAL_REJECT: async () => { const response = await axiosAPI.post( - '/api/v1/certification/users/:userId/reject', + '/v1/certification/users/:userId/reject', { decision: 'reject' }, { headers: { diff --git a/src/apis/adminLogin/AdminLoginApi.ts b/src/apis/adminLogin/AdminLoginApi.ts index 0ce7518f..6b2d8666 100644 --- a/src/apis/adminLogin/AdminLoginApi.ts +++ b/src/apis/adminLogin/AdminLoginApi.ts @@ -7,7 +7,7 @@ interface AdminLoginData { const AdminLoginAPI = { POST_ADMIN_LOGIN: async (adminLoginData: AdminLoginData) => { - const response = await axiosAPI.post('/api/v1/admins/login', adminLoginData, { + const response = await axiosAPI.post('/v1/admins/login', adminLoginData, { headers: { 'Content-Type': 'application/json', }, diff --git a/src/apis/adminReport/AdminReportApi.ts b/src/apis/adminReport/AdminReportApi.ts index d507767c..1e6a53d9 100644 --- a/src/apis/adminReport/AdminReportApi.ts +++ b/src/apis/adminReport/AdminReportApi.ts @@ -2,20 +2,20 @@ import { axiosAPI } from '@/apis/axios' const AdminReportAPI = { GET_REPORT_LIST: async () => { - const response = await axiosAPI.get(`/api/v1/reports`) + const response = await axiosAPI.get(`/v1/reports`) return { data: response.data, } }, GET_REPORT_INFO: async () => { - const response = await axiosAPI.get(`/api/v1/reports/:reportId`) + const response = await axiosAPI.get(`/v1/reports/:reportId`) return { data: response.data, } }, POST_REPORT_ADD: async () => { const response = await axiosAPI.post( - '/api/v1/reports/accept/:reportId', + '/v1/reports/accept/:reportId', { decision: 'addReportCount' }, { headers: { @@ -26,7 +26,7 @@ const AdminReportAPI = { return response.data }, POST_REPORT_IGNORE: async () => { - const response = await axiosAPI.delete('/api/v1/reports/reject/:reportId') + const response = await axiosAPI.delete('/v1/reports/reject/:reportId') return response.data }, } From 58c37fa488ef67719a5e889b1a90450776d5e187 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:50:50 +0900 Subject: [PATCH 070/180] =?UTF-8?q?feature:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20-=20=EB=AC=B8=EC=9D=98=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80,=20=EB=AC=B8=EC=9D=98=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#153)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: AdminAppHeader 컴포넌트 추가 * style: AdminNavigationBar 컴포넌트 생성 * style: AdminNavigationBar 색 변경 * feat: AdminTabs 컴포넌트 추가 * refactor: 컴포넌트 변수명 변경 * refactor: 컴포넌트 변수명 변경 * feat: AdminApprovalList 컴포넌트 추가 * feat: outerwrapper 제거 * feat: AdminReportListRow 컴포넌트 예시 추가 * feat: AdminReportList 컴포넌트 예시 추가 * feat: AdminTabs 컴포넌트로 다른 컴포넌트들 연결 및 디자인 수정, 변수명 수정 * refactor: admin 컴포넌트 이전 * refactor: eslint 에러 수정 * refactor: import 경로 변경 * feat: AdminApprovalInfo 컴포넌트 1 * style: 예시 이미지 추가 * feat: AdminApprovalInfo 컴포넌트 추가 1 * feat: input text fontweight 속성 추가 * style: Input 속성 추가 * style: AdminApprovalInfo 컴포넌트 디자인 2 * style: AdminReportInfo 컴포넌트 1 * refactor: props명 수정 * style: AdminReportInfo 컴포넌트 1 * style: AdminReportInfoListRow 컴포넌트 * feat: useModal 컴포넌트 적용, 기타 스타일 수정 * feat: AdminReportInfo 컴포넌트 userModal 적용 * refactor: 불필요 컴포넌트 삭제 * refactor: 이름 수정 * feat: AdminReportListRow 속성 추가 * feat: 관리자페이지 신고자 목록 클릭 시, 정보 출력 * feat: AdminReportList 특정 목록 클릭 시, 해당 AdminReportInfo 출력 * feat: AdminReportList mockdata 추가, 함수 연결 * refactor: 이름 예시 변경 * refactor: 변수명 수정 (adminapprovalinfo와 구분) * feat: AdminTabs 승인 탭 추가 * refactor: 변수명 수정(구별필요) * feat: 승인 페이지 이름 적용, 불필요 컴포넌트 제거 * refactor: mock data 적용. 변수명 수정. * feat: 승인페이지 mock data 적용. 목록 클릭 함수 추가. interface 추가. * feat: adminapprovallistrow onclick 함수 속성 추가 * feat: msw handlers interface 파일들 * feat: 관리자페이지 - MSW 활용한 API request, response 명세. API 테스트. * style: 오류 관련 주석 처리 * feat: AdminPageHeader * feat: 관리자페이지 승인요청 사용자들 목록 불러오기 MSW API 요청 코드 완료. 테스트 완료. * feat: 관리자페이지 신고당한자 목록 API 코드 추가. 테스트 완료. * feat: 관리자페이지 승인요청자 세부정보 출력 1 * refactor: 불필요 코드 삭제 * refactor: 불필요코드 제거 2 * feat: 숨겨진 관리자 로그인 버튼 추가 * feat: 관리자 페이지 라우터 설정 * feat: 관리자페이지 로그인 MSW API 요청 코드 추가, 관리자페이지 로그인 UI(react hook form) * refactor: 타입 오류 임시 주석처리 * refactor: import문 삭제 * feat: 관리자페이지 로그인 api 요청 1 * feat: 관리자페이지 로그인 API 요청 2 * feat: 관리자 로그인 페이지 post API 요청 * feat: 관리자 로그인 페이지 이동 * feat: 관리자 페이지 이동 * feat: 관리자 로그인 페이지 - 피그마 디자인, 구현 * feat: 문구수정 * refactor: useNavigate, api 요청 주소 변경 * feat: 관리자로그인페이지 디자인 수정 - emotion, register속성 적용 * feat: 관리자 승인 처리 API 요청. * feat: 관리자 승인요청, 신고 API 요청 * refactor: dev pull * feat: Admin Inquiry 추가 * style: 문의페이지 아이콘 추가 * refactor: import 절대 경로 수정 * refactor: MSW handler /api 경로 삭제 * feat: 관리자페이지 문의 API, UI - 1 * feat: 관리자 페이지 문의 상세 페이지 추가 * feat: 관리자 페이지 문의 UI, API 추가 2 --- src/apis/adminApproval/AdminApprovalApi.ts | 2 +- src/apis/adminInquiry/AdminInquiryApi.ts | 18 +++++ src/assets/images/inquiry.svg | 7 -- src/assets/images/inquiryImage.svg | 7 ++ .../common/ListRow/AdminInquiryList.tsx | 61 +++++++++++++++ .../common/ListRow/AdminInquiryListRow.tsx | 17 ++-- src/mocks/handlers.ts | 37 ++++++++- src/mocks/handlersInterface.ts | 5 ++ src/pages/admin/AdminInquiry.tsx | 10 ++- .../admin/components/AdminInquiryInfo.tsx | 78 +++++++++++++++++++ 10 files changed, 225 insertions(+), 17 deletions(-) create mode 100644 src/apis/adminInquiry/AdminInquiryApi.ts delete mode 100644 src/assets/images/inquiry.svg create mode 100644 src/assets/images/inquiryImage.svg create mode 100644 src/components/common/ListRow/AdminInquiryList.tsx create mode 100644 src/pages/admin/components/AdminInquiryInfo.tsx diff --git a/src/apis/adminApproval/AdminApprovalApi.ts b/src/apis/adminApproval/AdminApprovalApi.ts index db2841e6..9c075373 100644 --- a/src/apis/adminApproval/AdminApprovalApi.ts +++ b/src/apis/adminApproval/AdminApprovalApi.ts @@ -2,7 +2,7 @@ import { axiosAPI } from '@/apis/axios' const AdminApprovalAPI = { GET_APPROVAL_REQUEST_LIST: async () => { - const response = await axiosAPI.get(`/v1/users/inquries`) + const response = await axiosAPI.get(`/v1/users/approvals`) return { data: response.data, } diff --git a/src/apis/adminInquiry/AdminInquiryApi.ts b/src/apis/adminInquiry/AdminInquiryApi.ts new file mode 100644 index 00000000..ae0caa76 --- /dev/null +++ b/src/apis/adminInquiry/AdminInquiryApi.ts @@ -0,0 +1,18 @@ +import { axiosAPI } from '@/apis/axios' + +const AdminInquiryAPI = { + GET_INQUIRY_LIST: async () => { + const response = await axiosAPI.get(`/v1/users/inquiries`) + return { + data: response.data, + } + }, + GET_INQUIRY_INFO: async () => { + const response = await axiosAPI.get(`/v1/useres/inquries/:inquryId`) + return { + data: response.data, + } + }, +} + +export default AdminInquiryAPI diff --git a/src/assets/images/inquiry.svg b/src/assets/images/inquiry.svg deleted file mode 100644 index 5340667d..00000000 --- a/src/assets/images/inquiry.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/assets/images/inquiryImage.svg b/src/assets/images/inquiryImage.svg new file mode 100644 index 00000000..b29f4f6c --- /dev/null +++ b/src/assets/images/inquiryImage.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/common/ListRow/AdminInquiryList.tsx b/src/components/common/ListRow/AdminInquiryList.tsx new file mode 100644 index 00000000..e9b9c1bf --- /dev/null +++ b/src/components/common/ListRow/AdminInquiryList.tsx @@ -0,0 +1,61 @@ +import styled from '@emotion/styled' +import { useQuery } from '@tanstack/react-query' + +import AdminInquiryAPI from '@/apis/adminInquiry/AdminInquiryApi' +import AdminApprovalListRow from '@/components/common/ListRow/AdminApprovalListRow' +import { palette } from '@/styles/palette' + +interface AdminInquiryListProps { + onApproveSelectUserName: (nickname: string) => void +} +interface InquiryListData { + inquiryRequestUser: string + inquiryRequestDate: string + onApproveSelectUserName: (nickname: string) => void +} +interface InquiryListData { + approvalRequestUser: string + approvalRequestUserStatus: string +} + +const AdminInquiryList = ({ onApproveSelectUserName }: AdminInquiryListProps) => { + // API 요청 코드 + const { data, isSuccess } = useQuery(['AdminInquiryList'], AdminInquiryAPI.GET_INQUIRY_LIST) + + const handlePersonApproval = (nickname: string) => { + onApproveSelectUserName(nickname) + } + const inquiryDatas = data?.data.inquiries + + return ( + + + {isSuccess && + inquiryDatas.map((inquiryListData: InquiryListData, index: number) => ( + handlePersonApproval(inquiryListData.inquiryRequestUser)} + /> + ))} + + + ) +} +const AdminInquiryListContainerOuterWrapper = styled.div` + background-color: ${palette.WHITE}; + width: 100%; +` +const AdminInquiryListContainer = styled.div` + background-color: ${palette.WHITE}; + overflow: scroll; + height: 662px; + width: 80%; + margin: auto; + cursor: pointer; +` + +export default AdminInquiryList diff --git a/src/components/common/ListRow/AdminInquiryListRow.tsx b/src/components/common/ListRow/AdminInquiryListRow.tsx index 06100447..eeae1565 100644 --- a/src/components/common/ListRow/AdminInquiryListRow.tsx +++ b/src/components/common/ListRow/AdminInquiryListRow.tsx @@ -3,18 +3,22 @@ import { StyleList } from '@/components/common/ListRow/ProfileListRow' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -type AdminReportInfoListRowProps = { +type AdminApprovalListRowProps = { height: number nickname: string infoMessage: string | number isDarkMode: boolean + + onClick?: () => void } const AdminInquiryListRow = ({ height, nickname, infoMessage, isDarkMode, -}: AdminReportInfoListRowProps) => { + + onClick, +}: AdminApprovalListRowProps) => { const renderInfoMessage = () => { if (typeof infoMessage === 'number') { return ( @@ -28,7 +32,7 @@ const AdminInquiryListRow = ({ marginRight: '2px', }} > - {'불편 사항 접수 날짜 '} + {'누적 '}
{infoMessage} @@ -70,12 +74,13 @@ const AdminInquiryListRow = ({ return ( { + http.get('/v1/users/approvals', () => { const approvals: Approval[] = [ { approvalRequestUser: '박상민', approvalRequestUserStatus: '대기 중' }, { approvalRequestUser: '박은지', approvalRequestUserStatus: '대기 중' }, @@ -417,7 +418,7 @@ export const handlers = [ http.get('/v1/useres/inquries/:inquryId', (req) => { const { userId } = req.params const approvalInfo: ApprovalInfo = { - approvalRequestUserName: `userId:${userId}에 해당하는 userName`, + approvalRequestUserName: `userId:${userId}에 해당하는 userName`, approvalRequestUserEmail: `userId:${userId}에 해당하는 userName의 Email`, approvalRequestUserBusinessCardImage: `https://www.imageExample.jpg`, } @@ -513,4 +514,36 @@ export const handlers = [ result: 'reportDeleted', }) }), + + // 문의 목록 API 핸들러 + http.get('/v1/users/inquiries', () => { + const inquiries: Inquiry[] = [ + { inquiryRequestUser: '박상민', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '박은지', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '주다현', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '남궁호수', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '우창욱', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + ] + return HttpResponse.json({ inquiries }) + }), ] diff --git a/src/mocks/handlersInterface.ts b/src/mocks/handlersInterface.ts index f201c83d..01277139 100644 --- a/src/mocks/handlersInterface.ts +++ b/src/mocks/handlersInterface.ts @@ -33,3 +33,8 @@ export interface AdminLoginInfo { adminLoginResult: string adminLoginMessage: string } + +export interface Inquiry { + inquiryRequestUser: string + inquiryRequestDate: string +} diff --git a/src/pages/admin/AdminInquiry.tsx b/src/pages/admin/AdminInquiry.tsx index b29ad300..68b26994 100644 --- a/src/pages/admin/AdminInquiry.tsx +++ b/src/pages/admin/AdminInquiry.tsx @@ -1,17 +1,25 @@ import styled from '@emotion/styled' import AdminAppHeader from '@/components/common/AppHeader/AdminAppHeader' +import AdminInquiryList from '@/components/common/ListRow/AdminInquiryList' import HomeNavigationBar from '@/components/common/NavigationBar/AdminNavigationBar' import AdminPageHeader from '@/pages/admin/components/AdminPageHeader' import { palette } from '@/styles/palette' +// import AdminInquiryInfo from './components/AdminInquiryInfo' + const AdminInquiry = () => { + const handleInquiryId = () => { + // inquiryId: string + // inquiryId에 대한 처리 + } return ( <> - {'admin inquiry 페이지 입니다'} + {/* */} + diff --git a/src/pages/admin/components/AdminInquiryInfo.tsx b/src/pages/admin/components/AdminInquiryInfo.tsx new file mode 100644 index 00000000..e1862906 --- /dev/null +++ b/src/pages/admin/components/AdminInquiryInfo.tsx @@ -0,0 +1,78 @@ +import styled from '@emotion/styled' +import { useQuery } from '@tanstack/react-query' + +import AdminApprovalAPI from '@/apis/adminApproval/AdminApprovalApi' +import InquiryImage from '@/assets/images/inquiryImage.svg' +import Spacing from '@/components/common/Spacing' +// import Spacing from '@/components/common/Spacing' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +const AdminInquiryInfo = () => { + const { data, isSuccess } = useQuery( + ['ApprovalRequestUserInfo'], + AdminApprovalAPI.GET_APPROVAL_INFO, + ) + console.log(isSuccess && data) + + return ( + <> + + + + + {'유명한'} + + + + + + + {'회사 인증 왜 안해주세요?'} + + + + + + + {'2023.7.30'} + + + + + + + ) +} + +const StyledAdminInquiryInfo = styled.div` + background-color: ${palette.WHITE}; +` +const StyledInquiryContent = styled.div` + position: relative; + padding: 30px 20px 30px 20px; + background-color: ${palette.GRAY100}; + width: 345px; + height: 384px; + border-radius: 20px; +` +const StyledInquiryContentWrapper = styled.div` + display: flex; + justify-content: center; +` +const StyledInquiryImage = styled.img` + position: absolute; + top: 320px; + right: 14px; +` +const StyledTextNameWrapper = styled.div` + padding-left: 35px; + padding-bottom: 12px; +` +const StyledTextDateWrapper = styled.div` + padding-left: 310px; + padding-top: 8px; + color: ${palette.GRAY400}; +` + +export default AdminInquiryInfo From cc33338927fefbf97a5e055b4edfc919a4a5c209 Mon Sep 17 00:00:00 2001 From: Cheshier Date: Wed, 22 Nov 2023 13:21:01 +0900 Subject: [PATCH 071/180] =?UTF-8?q?[Feature]:=20BE=20OAuth=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=EC=A0=81=EC=9A=A9=20(#154)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Chore]: isNewUser 전역 상태 및 Setter 제거(#145) * [Feature]: useEffect 내부 분기 처리 로직 제거 (#145) * [Feature]: 2차 인증 로직 구현 (#145) * [Fix]: 회원가입 요청 body 수정 (#145) * [Fix]: setIsNewUser 추가 제거 (#145) * [Fix]: 셀렉터 변경 (#145) - 관심사 / 직무 선택 상황에 각각 활용할 수 있도록 변경 (type 프롭) - 직무 선택 정보를 저장하는 JobStore 상태 Store 정의 - 매우매우 하드코딩이라 리팩토링 필요 * [Fix]: 회사 정보 등록 시 액세스 토큰 대신 userId를 전송하도록 변경 (#145) * [Chore]: 로그인 요청 성공 시 응답 데이터 저장 또는 전달 (#145) - 스토리지에 저장 또는 홈 페이지로 전달 - 어떤 정보를 저장하고 전달할지는 논의 필요 * [Fix]: 회사 정보 등록 시 전달하는 formData 수정 (#145) * [Feature]: 로그인 상태가 아니면 로그인 페이지로 이동하도록 변경 (#145) * [Chore]: onSuccess 로직 변경 (#145) - 이제 응답으로 accessToken이 전송되지 않아 해당 코드 제거 * [Chore]: 회사 정보 등록 완료 시 로그인 페이지 리다이렉트 (#145) * [Feature]: 헤더 구성 정보 로그인한 계정 기반으로 표시하도록 일부 수정 (#145) * [Chore]: 로그아웃 적용 (#145) - 선언형 로직으로 변경 필요 * [Fix]: InqueryImage import 경로 수정 - Build Error fix --- src/components/common/AppHeader/index.tsx | 12 +++- .../common/SelectorButton/index.tsx | 25 ++++++- .../common/SelectorButtonContainer/index.tsx | 3 + .../admin/components/AdminInquiryBtn.tsx | 2 +- src/pages/home/Home.tsx | 31 ++++++++- src/pages/loginPending/LoginPending.tsx | 67 ++++++++++++------- src/pages/profile/ProfileDefault.tsx | 7 ++ src/pages/register/RegisterCompany.tsx | 25 ++++--- src/pages/register/RegisterUser.tsx | 13 ++-- src/store/AuthStore.tsx | 5 +- src/store/JobStore.tsx | 13 ++++ 11 files changed, 149 insertions(+), 54 deletions(-) create mode 100644 src/store/JobStore.tsx diff --git a/src/components/common/AppHeader/index.tsx b/src/components/common/AppHeader/index.tsx index b953985e..2bbd3760 100644 --- a/src/components/common/AppHeader/index.tsx +++ b/src/components/common/AppHeader/index.tsx @@ -43,6 +43,7 @@ const StyledAppHeaderSmallText = styled(Text) type AppHeaderProps = { nickname: string + profileImageUrl: string isDarkMode: boolean height?: string toggleDarkMode: () => void @@ -50,12 +51,19 @@ type AppHeaderProps = { /** * @param nickname - 유저 닉네임 + * @param profileImageUrl - 유저 프로필 이미지 URL * @param isDarkMode - 다크모드 여부 * @param height - 컴포넌트 높이 * @param toggleDarkMode - 다크모드 토글 함수 */ -const AppHeader = ({ nickname, isDarkMode, height, toggleDarkMode }: AppHeaderProps) => { +const AppHeader = ({ + nickname, + profileImageUrl, + isDarkMode, + height, + toggleDarkMode, +}: AppHeaderProps) => { const navigate = useNavigate() const moveFromAppHeader = (path: string) => { navigate(`/${path}`) @@ -72,7 +80,7 @@ const AppHeader = ({ nickname, isDarkMode, height, toggleDarkMode }: AppHeaderPr void isButtonSelected?: boolean isMaxLengthReached: boolean @@ -14,6 +17,7 @@ type SelectorButtonProps = { const SelectorButton = ({ isDarkMode, buttonName, + type, isButtonClicked, isButtonSelected: propIsButtonSelected = false, isMaxLengthReached = false, @@ -37,6 +41,7 @@ const SelectorButton = ({ const [backgroundColor, setBackgroundColor] = useState(initialBackgroundColor) const [currentTextColor, setCurrentTextColor] = useState(defaultSettings.textColor) const { interestList, setInterestList } = useInterestStore() + const { setJobInfo } = useJobStore() const handleButtonClick = () => { const isSelected = backgroundColor !== defaultSettings.selectedButtonColor @@ -47,7 +52,19 @@ const SelectorButton = ({ } setIsButtonSelected(isSelected) - setInterestList([...interestList, buttonName]) + if (type === 'interest') { + if (isSelected) { + setInterestList([...interestList, buttonName]) + } else { + setInterestList(interestList.filter((v) => v != buttonName)) + } + } else { + if (isSelected) { + setJobInfo(buttonName) + } else { + setJobInfo('') + } + } setBackgroundColor( isSelected ? defaultSettings.selectedButtonColor : defaultSettings.defaultButtonColor, ) @@ -57,7 +74,11 @@ const SelectorButton = ({ setCurrentTextColor(isSelected ? palette.WHITE : defaultSettings.textColor) } if (isButtonClicked) isButtonClicked(isSelected) - if (!isSelected) setInterestList(interestList.filter((v) => v != buttonName)) + if (type === 'interest') { + if (!isSelected) setInterestList(interestList.filter((v) => v != buttonName)) + } else { + if (!isSelected) setJobInfo('') + } } return ( diff --git a/src/components/common/SelectorButtonContainer/index.tsx b/src/components/common/SelectorButtonContainer/index.tsx index cdc94cae..7f239449 100644 --- a/src/components/common/SelectorButtonContainer/index.tsx +++ b/src/components/common/SelectorButtonContainer/index.tsx @@ -8,12 +8,14 @@ import SelectorButton from '@/components/common/SelectorButton' import { palette } from '@/styles/palette' type SelectorButtonContainerProps = { isDarkMode: boolean + type: 'interest' | 'job' buttonNames: string[] maxLength: number } const SelectorButtonContainer = ({ isDarkMode, + type, buttonNames, maxLength, }: SelectorButtonContainerProps) => { @@ -53,6 +55,7 @@ const SelectorButtonContainer = ({ key={index} isDarkMode={isDarkMode} buttonName={name} + type={type} isButtonClicked={handleButtonSelection} isMaxLengthReached={selectedCount >= maxLength} isButtonSelected={false} diff --git a/src/pages/admin/components/AdminInquiryBtn.tsx b/src/pages/admin/components/AdminInquiryBtn.tsx index e25887bc..0a644f93 100644 --- a/src/pages/admin/components/AdminInquiryBtn.tsx +++ b/src/pages/admin/components/AdminInquiryBtn.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled' import { useNavigate } from 'react-router-dom' -import InquiryImage from '@/assets/images/inquiry.svg' +import InquiryImage from '@/assets/images/inquiryImage.svg' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' const AdminInquiryBtn = () => { diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index eed88127..6076430a 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -1,4 +1,5 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' +import { useNavigate } from 'react-router-dom' import AppHeader from '@/components/common/AppHeader' import { ParticularTopicButton } from '@/components/common/Buttons/IconButton' @@ -8,20 +9,44 @@ import PageContainer from '@/components/common/PageContainer' import { Text } from '@/components/common/Text' import Card from '@/components/home/Card' import useToast from '@/hooks/useToast' +import useAuthStore from '@/store/AuthStore.tsx' import useThemeStore from '@/store/ThemeStore' import { palette } from '@/styles/palette' const Home = () => { - const nickname = '우땅' + const [nickname, setNickname] = useState('') + const [profileImageUrl, setProfileImageUrl] = useState('') const isDarkMode = useThemeStore((state) => state.isDarkMode) const toggleDarkMode = useThemeStore((state) => state.toggleDarkMode) + const { authTokens } = useAuthStore() const [isMatching, setIsMatching] = useState(false) + const navigate = useNavigate() const { showToast } = useToast() + useEffect(() => { + if (!authTokens) { + showToast({ + message: '로그인이 필요한 서비스입니다.', + type: 'warning', + isDarkMode, + }) + navigate('/login') + } + if (authTokens) { + setNickname(localStorage.getItem('nickname') || '') + setProfileImageUrl(localStorage.getItem('profileImageUrl') || '') + } + }, []) + return ( - + { const navigate = useNavigate() const [searchParams] = useSearchParams() const authCode = searchParams.get('code') const setToken = useAuthStore((state) => state.setAuthTokens) const provider = useAuthStore((state) => state.provider) - const { isNewUser, setIsNewUser } = useAuthStore() + + const { showToast } = useToast() const routeAuthInfo = async () => { await axiosAPI - .get(`/v1/users/login/${provider}?authCode=${authCode}`) + .get(`/v1/users/login/${provider}?authCode=${authCode}`) .then((res) => { - console.log(res.data.accessToken) - localStorage.setItem('jwt', res.data.accessToken) - localStorage.setItem('nickname', res.data.nickname) + const { userId, accessToken, refreshToken, isRegistered, nickname, profileImageUrl } = + res.data - setToken({ - accessToken: res.data.accessToken, - refreshToken: res.data.refreshToken, - }) - navigate('/') - }) - .catch((err) => { - if (err.response.status === 404) { - console.log('카카오 로그인 완료 & 정보 등록 안됨') - setIsNewUser(true) - window.location.replace(`${BASE_URL}/v1/oauth2.0/${provider}`) + if (!isRegistered) { + navigate('/register/user', { state: { userId } }) } + + if (isRegistered) { + localStorage.setItem('jwt', accessToken) + // TODO: 아래 3개의 정보는 추후 AuthStore에서 관리? + localStorage.setItem('userId', userId.toString()) + localStorage.setItem('nickname', nickname) + localStorage.setItem('profileImageUrl', profileImageUrl) + setToken({ + accessToken: accessToken, + refreshToken: refreshToken, + }) + navigate('/', { + state: { userId, nickname, profileImageUrl }, + }) + } + }) + .catch((error) => { + console.error(error) + showToast({ + message: '로그인에 실패했습니다.', + type: 'error', + isDarkMode: false, + }) + navigate('/login') }) } useEffect(() => { - if (isNewUser) { - const authCode = searchParams.get('code') - navigate('/register/user', { state: authCode }) - } else { - routeAuthInfo() - } - }) + routeAuthInfo() + }, []) return ( diff --git a/src/pages/profile/ProfileDefault.tsx b/src/pages/profile/ProfileDefault.tsx index 365946ec..c91aa192 100644 --- a/src/pages/profile/ProfileDefault.tsx +++ b/src/pages/profile/ProfileDefault.tsx @@ -20,6 +20,7 @@ import Spacing from '@/components/common/Spacing' import { Text, TextWrapper } from '@/components/common/Text' import { useModal } from '@/hooks/useModal' import useToast from '@/hooks/useToast' +import useAuthStore from '@/store/AuthStore.tsx' import useThemeStore from '@/store/ThemeStore' import { palette } from '@/styles/palette' @@ -35,6 +36,12 @@ const ProfileDefault = () => { mainText: '로그아웃 하시겠습니까?', subText: '로그아웃 시 로그인 화면으로 이동합니다.', okFunc: () => { + // TODO: 명령형 -> 선언형 로직으로 변경 + localStorage.setItem('jwt', '') + localStorage.removeItem('userId') + localStorage.removeItem('nickname') + localStorage.removeItem('profileImageUrl') + useAuthStore.persist.clearStorage() navigate('/login') }, type: 'warn', diff --git a/src/pages/register/RegisterCompany.tsx b/src/pages/register/RegisterCompany.tsx index 422024eb..64b0b812 100644 --- a/src/pages/register/RegisterCompany.tsx +++ b/src/pages/register/RegisterCompany.tsx @@ -3,7 +3,7 @@ import { useMutation } from '@tanstack/react-query' import { RefObject, useRef, useState } from 'react' import { MdWbSunny } from 'react-icons/md' import { MdOutlinePhotoCamera } from 'react-icons/md' -import { useNavigate } from 'react-router-dom' +import { useLocation, useNavigate } from 'react-router-dom' import { axiosAPI } from '@/apis/axios' import AlertText from '@/components/common/AlertText' @@ -14,8 +14,7 @@ import RegisterInput from '@/components/common/RegisterInput' import SelectorButtonContainer from '@/components/common/SelectorButtonContainer' import Spacing from '@/components/common/Spacing' import useToast from '@/hooks/useToast' -import useAuthStore from '@/store/AuthStore.tsx' -import useInterestStore from '@/store/InterestStore' +import useJobStore from '@/store/JobStore.tsx' import useThemeStore from '@/store/ThemeStore' import { palette } from '@/styles/palette' import { typo } from '@/styles/typo' @@ -38,22 +37,23 @@ const RegisterCompany = () => { '법률/집행기관', ] const navigate = useNavigate() + const userId = useLocation().state.userId const companyName = useRef(null) const emailRef = useRef(null) const codeRef = useRef(null) const [isCodeSame, setIsCodeSame] = useState(null) const [codeChecked, setCodeChecked] = useState(null) - const { interestList } = useInterestStore() //여기서 회사 직무 list 저장한거 get해옴 + const { jobInfo } = useJobStore() const { showToast } = useToast() const isDarkMode = useThemeStore((state) => state.isDarkMode) const formData = new FormData() const imgRef = useRef(null) as RefObject const [uploadedURL, setUploadedURL] = useState('') - const { setIsNewUser } = useAuthStore() const handleClickEmailVerify = async (email: string) => { console.log(email) return await axiosAPI.post(`/v1/certification/users/me/company-mail`, { + userId: userId, companyEmail: emailRef.current && emailRef.current.value, }) } @@ -90,6 +90,7 @@ const RegisterCompany = () => { const checkEmailCode = async () => { setCodeChecked(true) const response = await axiosAPI.post('/v1/certification/users/me/company-mail/verification', { + userId: userId, verificationCode: codeRef.current && codeRef.current.value, }) if (response.status == 200) setIsCodeSame(true) @@ -127,9 +128,13 @@ const RegisterCompany = () => { }) return } + formData.append('userId', userId) companyName.current && formData.append('companyName', companyName.current.value) emailRef.current && formData.append('companyEmail', emailRef.current.value) - formData.append('department', JSON.stringify(interestList)) + formData.append('department', jobInfo) + if (imgRef.current && imgRef.current?.files) { + formData.append('businessCard', imgRef.current?.files[0]) + } registerCompanyData(formData) } @@ -145,12 +150,11 @@ const RegisterCompany = () => { }) .then(() => { showToast({ - message: '회사 정보 등록 완료! 메인 홈으로 이동합니다.', + message: '회사 정보 등록 완료! 다시 로그인 해주세요!', type: 'success', isDarkMode: false, }) - setIsNewUser(false) - navigate('/') + navigate('/login') }) .catch(() => { showToast({ @@ -252,8 +256,9 @@ const RegisterCompany = () => { diff --git a/src/pages/register/RegisterUser.tsx b/src/pages/register/RegisterUser.tsx index baece48e..a5647c1c 100644 --- a/src/pages/register/RegisterUser.tsx +++ b/src/pages/register/RegisterUser.tsx @@ -13,7 +13,6 @@ import RegisterInput from '@/components/common/RegisterInput' import SelectorButtonContainer from '@/components/common/SelectorButtonContainer' import Spacing from '@/components/common/Spacing' import useToast from '@/hooks/useToast' -import useAuthStore from '@/store/AuthStore' import useInterestStore from '@/store/InterestStore' import useThemeStore from '@/store/ThemeStore' import { palette } from '@/styles/palette' @@ -36,13 +35,12 @@ const RegisterUser = () => { '반려동물', ] const navigate = useNavigate() - const authCode = useLocation().state + const userId = useLocation().state.userId const inputRef = useRef(null) const [doubleChecked, setDoubleChecked] = useState(false) const [nicknameDuplicated, setNicknameDuplicated] = useState(null) let nickname = '' const { interestList } = useInterestStore() - const { provider } = useAuthStore() const { showToast } = useToast() const isDarkMode = useThemeStore((state) => state.isDarkMode) @@ -107,10 +105,9 @@ const RegisterUser = () => { console.log(nickname, interestList) if (doubleChecked && inputRef.current !== null && interestList.length > 0) { const body = { - authCode: authCode, + userId: userId, nickname: inputRef.current.value, keywords: interestList, - oAuthProvider: provider, } console.log(body) registerMutation.mutate(body) @@ -122,15 +119,14 @@ const RegisterUser = () => { } const registerMutation = useMutation((body: object) => registerPost(body), { - onSuccess: (response) => { - localStorage.setItem('jwt', response.data.accessToken) + onSuccess: () => { showToast({ message: '닉네임, 관심사 정보 등록을 완료했습니다!', type: 'success', isDarkMode, }) - navigate('/register/company') + navigate('/register/company', { state: { userId: userId } }) }, onError: (err) => { console.log(err) @@ -192,6 +188,7 @@ const RegisterUser = () => { diff --git a/src/store/AuthStore.tsx b/src/store/AuthStore.tsx index 3ac9593d..25b7e437 100644 --- a/src/store/AuthStore.tsx +++ b/src/store/AuthStore.tsx @@ -10,9 +10,7 @@ type Tokens = { type AuthState = { provider: Provider - isNewUser: boolean authTokens?: Tokens - setIsNewUser: (isNewUser: boolean) => void setProvider: (provider: Provider) => void setAuthTokens: (authTokens: Tokens) => void } @@ -20,9 +18,8 @@ type AuthState = { const useAuthStore = create( persist( (set) => ({ - isNewUser: false, + authTokens: undefined, provider: 'KAKAO', - setIsNewUser: (isNewUser: boolean) => set(() => ({ isNewUser })), setProvider: (provider: Provider) => set(() => ({ provider })), setAuthTokens: (authTokens: Tokens) => set(() => ({ diff --git a/src/store/JobStore.tsx b/src/store/JobStore.tsx new file mode 100644 index 00000000..16cb0464 --- /dev/null +++ b/src/store/JobStore.tsx @@ -0,0 +1,13 @@ +import { create } from 'zustand' + +type JobInfo = { + jobInfo: string + setJobInfo: (info: string) => void +} + +const useJobStore = create((set) => ({ + jobInfo: '', + setJobInfo: (info) => set({ jobInfo: info }), +})) + +export default useJobStore From 9479f35fa8bea20fff0a3557289c27b81374e11b Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Wed, 22 Nov 2023 18:50:00 +0900 Subject: [PATCH 072/180] =?UTF-8?q?feature:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20=EA=B0=9C=ED=8E=B8=20(#15?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: AdminAppHeader 컴포넌트 추가 * style: AdminNavigationBar 컴포넌트 생성 * style: AdminNavigationBar 색 변경 * feat: AdminTabs 컴포넌트 추가 * refactor: 컴포넌트 변수명 변경 * refactor: 컴포넌트 변수명 변경 * feat: AdminApprovalList 컴포넌트 추가 * feat: outerwrapper 제거 * feat: AdminReportListRow 컴포넌트 예시 추가 * feat: AdminReportList 컴포넌트 예시 추가 * feat: AdminTabs 컴포넌트로 다른 컴포넌트들 연결 및 디자인 수정, 변수명 수정 * refactor: admin 컴포넌트 이전 * refactor: eslint 에러 수정 * refactor: import 경로 변경 * feat: AdminApprovalInfo 컴포넌트 1 * style: 예시 이미지 추가 * feat: AdminApprovalInfo 컴포넌트 추가 1 * feat: input text fontweight 속성 추가 * style: Input 속성 추가 * style: AdminApprovalInfo 컴포넌트 디자인 2 * style: AdminReportInfo 컴포넌트 1 * refactor: props명 수정 * style: AdminReportInfo 컴포넌트 1 * style: AdminReportInfoListRow 컴포넌트 * feat: useModal 컴포넌트 적용, 기타 스타일 수정 * feat: AdminReportInfo 컴포넌트 userModal 적용 * refactor: 불필요 컴포넌트 삭제 * refactor: 이름 수정 * feat: AdminReportListRow 속성 추가 * feat: 관리자페이지 신고자 목록 클릭 시, 정보 출력 * feat: AdminReportList 특정 목록 클릭 시, 해당 AdminReportInfo 출력 * feat: AdminReportList mockdata 추가, 함수 연결 * refactor: 이름 예시 변경 * refactor: 변수명 수정 (adminapprovalinfo와 구분) * feat: AdminTabs 승인 탭 추가 * refactor: 변수명 수정(구별필요) * feat: 승인 페이지 이름 적용, 불필요 컴포넌트 제거 * refactor: mock data 적용. 변수명 수정. * feat: 승인페이지 mock data 적용. 목록 클릭 함수 추가. interface 추가. * feat: adminapprovallistrow onclick 함수 속성 추가 * feat: msw handlers interface 파일들 * feat: 관리자페이지 - MSW 활용한 API request, response 명세. API 테스트. * style: 오류 관련 주석 처리 * feat: AdminPageHeader * feat: 관리자페이지 승인요청 사용자들 목록 불러오기 MSW API 요청 코드 완료. 테스트 완료. * feat: 관리자페이지 신고당한자 목록 API 코드 추가. 테스트 완료. * feat: 관리자페이지 승인요청자 세부정보 출력 1 * refactor: 불필요 코드 삭제 * refactor: 불필요코드 제거 2 * feat: 숨겨진 관리자 로그인 버튼 추가 * feat: 관리자 페이지 라우터 설정 * feat: 관리자페이지 로그인 MSW API 요청 코드 추가, 관리자페이지 로그인 UI(react hook form) * refactor: 타입 오류 임시 주석처리 * refactor: import문 삭제 * feat: 관리자페이지 로그인 api 요청 1 * feat: 관리자페이지 로그인 API 요청 2 * feat: 관리자 로그인 페이지 post API 요청 * feat: 관리자 로그인 페이지 이동 * feat: 관리자 페이지 이동 * feat: 관리자 로그인 페이지 - 피그마 디자인, 구현 * feat: 문구수정 * refactor: useNavigate, api 요청 주소 변경 * feat: 관리자로그인페이지 디자인 수정 - emotion, register속성 적용 * feat: 관리자 승인 처리 API 요청. * feat: 관리자 승인요청, 신고 API 요청 * refactor: dev pull * feat: Admin Inquiry 추가 * style: 문의페이지 아이콘 추가 * refactor: import 절대 경로 수정 * refactor: MSW handler /api 경로 삭제 * feat: 관리자페이지 문의 API, UI - 1 * feat: 관리자 페이지 문의 상세 페이지 추가 * feat: 관리자 페이지 문의 UI, API 추가 2 * feat: 관리자 문의 컴포넌트 수정 * feat: 관리자 페이지 탭 구조 개편 * feat: 불필요 라우팅 삭제 * feat: 문의 페이지 헤더 추가 * feat: 관리자 신고페이지 목록 * feat: 관리자 페이지 개편 * feat: 변수명사용-빌드 * refactor: import문 정렬 (dev pull) --- src/App.tsx | 3 - src/apis/adminReport/AdminReportApi.ts | 6 + .../common/ListRow/AdminInquiryList.tsx | 15 +- .../common/ListRow/AdminReportList.tsx | 32 +++-- .../common/ListRow/AdminReportListRow.tsx | 114 ++++++--------- .../ListRow/AdminReportListRowTitle.tsx | 73 ++++++++++ .../common/ListRow/AdminReportersList.tsx | 104 ++++++++++++++ .../common/ListRow/AdminReportersListRow.tsx | 63 ++++++++ src/mocks/handlers.ts | 12 +- src/mocks/handlersInterface.ts | 4 + src/pages/admin/AdminInquiry.tsx | 32 ----- .../admin/components/AdminInquiryInfo.tsx | 12 +- .../admin/components/AdminReportInfo.tsx | 89 +++++------- src/pages/admin/components/AdminTabs.tsx | 136 +++++++++--------- src/pages/register/RegisterCompany.tsx | 1 - 15 files changed, 446 insertions(+), 250 deletions(-) create mode 100644 src/components/common/ListRow/AdminReportListRowTitle.tsx create mode 100644 src/components/common/ListRow/AdminReportersList.tsx create mode 100644 src/components/common/ListRow/AdminReportersListRow.tsx delete mode 100644 src/pages/admin/AdminInquiry.tsx diff --git a/src/App.tsx b/src/App.tsx index db703e3c..fc7fbbb1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,8 +14,6 @@ import ProfilePage from '@/pages/profile' import PrivateRoute from '@/pages/redirect/PrivateRoute' import RegisterPage from '@/pages/register' -import AdminInquiry from './pages/admin/AdminInquiry' - const App = () => { return ( @@ -39,7 +37,6 @@ const App = () => { }> }> - }> }> }> diff --git a/src/apis/adminReport/AdminReportApi.ts b/src/apis/adminReport/AdminReportApi.ts index 1e6a53d9..f14bc2d5 100644 --- a/src/apis/adminReport/AdminReportApi.ts +++ b/src/apis/adminReport/AdminReportApi.ts @@ -7,6 +7,12 @@ const AdminReportAPI = { data: response.data, } }, + GET_REPORTERS_LIST: async () => { + const response = await axiosAPI.get(`/v1/reporters`) + return { + data: response.data, + } + }, GET_REPORT_INFO: async () => { const response = await axiosAPI.get(`/v1/reports/:reportId`) return { diff --git a/src/components/common/ListRow/AdminInquiryList.tsx b/src/components/common/ListRow/AdminInquiryList.tsx index e9b9c1bf..201368f4 100644 --- a/src/components/common/ListRow/AdminInquiryList.tsx +++ b/src/components/common/ListRow/AdminInquiryList.tsx @@ -3,32 +3,34 @@ import { useQuery } from '@tanstack/react-query' import AdminInquiryAPI from '@/apis/adminInquiry/AdminInquiryApi' import AdminApprovalListRow from '@/components/common/ListRow/AdminApprovalListRow' +import AdminPageHeader from '@/pages/admin/components/AdminPageHeader' import { palette } from '@/styles/palette' interface AdminInquiryListProps { - onApproveSelectUserName: (nickname: string) => void + onInquirySelectUserName: (nickname: string) => void } interface InquiryListData { inquiryRequestUser: string inquiryRequestDate: string - onApproveSelectUserName: (nickname: string) => void + onInquirySelectUserName: (nickname: string) => void } interface InquiryListData { approvalRequestUser: string approvalRequestUserStatus: string } -const AdminInquiryList = ({ onApproveSelectUserName }: AdminInquiryListProps) => { +const AdminInquiryList = ({ onInquirySelectUserName }: AdminInquiryListProps) => { // API 요청 코드 const { data, isSuccess } = useQuery(['AdminInquiryList'], AdminInquiryAPI.GET_INQUIRY_LIST) - const handlePersonApproval = (nickname: string) => { - onApproveSelectUserName(nickname) + const handlePersonInquiry = (inquiryNickname: string) => { + onInquirySelectUserName(inquiryNickname) } const inquiryDatas = data?.data.inquiries return ( + {isSuccess && inquiryDatas.map((inquiryListData: InquiryListData, index: number) => ( @@ -38,7 +40,7 @@ const AdminInquiryList = ({ onApproveSelectUserName }: AdminInquiryListProps) => nickname={inquiryListData.inquiryRequestUser} infoMessage={inquiryListData.inquiryRequestDate} isDarkMode={false} - onClick={() => handlePersonApproval(inquiryListData.inquiryRequestUser)} + onClick={() => handlePersonInquiry(inquiryListData.inquiryRequestUser)} /> ))} @@ -48,6 +50,7 @@ const AdminInquiryList = ({ onApproveSelectUserName }: AdminInquiryListProps) => const AdminInquiryListContainerOuterWrapper = styled.div` background-color: ${palette.WHITE}; width: 100%; + height: 662px; ` const AdminInquiryListContainer = styled.div` background-color: ${palette.WHITE}; diff --git a/src/components/common/ListRow/AdminReportList.tsx b/src/components/common/ListRow/AdminReportList.tsx index d2c62ee7..1d054cc5 100644 --- a/src/components/common/ListRow/AdminReportList.tsx +++ b/src/components/common/ListRow/AdminReportList.tsx @@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query' import AdminReportAPI from '@/apis/adminReport/AdminReportApi' import AdminReportListRow from '@/components/common/ListRow/AdminReportListRow' +import AdminReportListRowTitle from '@/components/common/ListRow/AdminReportListRowTitle' import { palette } from '@/styles/palette' interface AdminReportListProps { @@ -10,7 +11,7 @@ interface AdminReportListProps { } interface ReportListData { reportedUserName: string - reportCount: number + reportCount: string } const AdminReportList = ({ onReportSelect }: AdminReportListProps) => { const { data, isSuccess } = useQuery(['ReportedUserList'], AdminReportAPI.GET_REPORT_LIST) @@ -22,17 +23,28 @@ const AdminReportList = ({ onReportSelect }: AdminReportListProps) => { return ( - {isSuccess && - ReportDatas.map((reportListData: ReportListData, index: number) => ( - + handlePersonReported(reportListData.reportedUserName)} - /> - ))} + reportedNickname={'신고 대상 닉네임'} + reportedDate={'신고 날짜'} + > + {ReportDatas.map((reportListData: ReportListData, index: number) => ( + handlePersonReported(reportListData.reportedUserName)} + /> + ))} + + )} ) diff --git a/src/components/common/ListRow/AdminReportListRow.tsx b/src/components/common/ListRow/AdminReportListRow.tsx index 22338ba0..edba70c4 100644 --- a/src/components/common/ListRow/AdminReportListRow.tsx +++ b/src/components/common/ListRow/AdminReportListRow.tsx @@ -5,72 +5,26 @@ import { palette } from '@/styles/palette' type AdminReportListRowProps = { height: number - nickname: string - infoMessage: string | number + chattingRoomName: string + reportedNickname: string + reportedDate: string isDarkMode: boolean onClick?: () => void } + const AdminReportListRow = ({ height, - nickname, - infoMessage, + chattingRoomName, + reportedNickname, + reportedDate, isDarkMode, onClick, }: AdminReportListRowProps) => { - const renderInfoMessage = () => { - if (typeof infoMessage === 'number') { - return ( - - - {'누적 '} - - - {infoMessage} - - - {' 회'} - - - ) - } - return ( - - {infoMessage} - - ) - } - + const displayChattingRoomName = + chattingRoomName.length > 4 ? `${chattingRoomName.substring(0, 4)}...` : chattingRoomName + const displayReportedNickname = + reportedNickname.length > 3 ? `${reportedNickname.substring(0, 3)}..` : reportedNickname return ( - - {nickname} - - {renderInfoMessage()} + + + {displayChattingRoomName} + + + {displayReportedNickname} + + + {reportedDate} + + ) } diff --git a/src/components/common/ListRow/AdminReportListRowTitle.tsx b/src/components/common/ListRow/AdminReportListRowTitle.tsx new file mode 100644 index 00000000..803f14f8 --- /dev/null +++ b/src/components/common/ListRow/AdminReportListRowTitle.tsx @@ -0,0 +1,73 @@ +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 AdminReportListRowProps = { + height: number + chattingRoomName: string + reportedNickname: string + reportedDate: string + isDarkMode: boolean + + onClick?: () => void +} + +const AdminReportListRowTitle = ({ + height, + chattingRoomName, + reportedNickname, + reportedDate, + isDarkMode, + onClick, +}: AdminReportListRowProps) => { + return ( + + + + {chattingRoomName} + + + {reportedNickname} + + + {reportedDate} + + + + ) +} + +export default AdminReportListRowTitle diff --git a/src/components/common/ListRow/AdminReportersList.tsx b/src/components/common/ListRow/AdminReportersList.tsx new file mode 100644 index 00000000..2ba6907c --- /dev/null +++ b/src/components/common/ListRow/AdminReportersList.tsx @@ -0,0 +1,104 @@ +import styled from '@emotion/styled' +import { useQuery } from '@tanstack/react-query' +import { useMutation } from '@tanstack/react-query' + +import AdminReportAPI from '@/apis/adminReport/AdminReportApi' +import AdminReportersListRow from '@/components/common/ListRow/AdminReportersListRow' +import Spacing from '@/components/common/Spacing' +import { useModal } from '@/hooks/useModal' +import AdminPageHeader from '@/pages/admin/components/AdminPageHeader' +import { palette } from '@/styles/palette' + +import NormalButton from '../Buttons/NormalButton' + +interface AdminReportersListProps { + onReportSelect: (nickname: string) => void + selectedReportNickname: string +} +interface ReportersListData { + reporterUserName: string + reportedDate: string +} +const AdminReportersList = ({ + onReportSelect, + selectedReportNickname, +}: AdminReportersListProps) => { + const mutationReportAddCount = useMutation(AdminReportAPI.POST_REPORT_ADD, { + onSuccess: (data) => { + console.log(data) + }, + }) + const mutationReportIgnore = useMutation(AdminReportAPI.POST_REPORT_IGNORE, { + onSuccess: (data) => { + console.log(data) + }, + }) + const onReportAddCount = () => { + mutationReportAddCount.mutate() + } + const onReportIgnore = () => { + mutationReportIgnore.mutate() + } + const { openModal } = useModal() + const handleAccumulationAddBtn = () => { + openModal({ + type: 'confirm', + mainText: '신고를 누적하시겠습니까?', + okFunc: onReportAddCount, + }) + } + const { data, isSuccess } = useQuery(['ReporterUserList'], AdminReportAPI.GET_REPORTERS_LIST) + const handlePersonReported = (nickname: string) => { + onReportSelect(nickname) + } + const ReportersDatas = data?.data.reporters + return ( + + + + {isSuccess && ( + <> + {ReportersDatas.map((reporterListData: ReportersListData, index: number) => ( + handlePersonReported(reporterListData.reporterUserName)} + /> + ))} + + )} + + + + {'누적 추가'} + + + {'무시'} + + + + + ) +} +const StyledAdminReportersListContainer = styled.div` + background-color: ${palette.WHITE}; + overflow: scroll; + height: 662px; + width: 80%; + margin: auto; + cursor: pointer; +` +const StyledAdminReportersListContainerOuterWrapper = styled.div` + background-color: ${palette.WHITE}; + width: 100%; + height: 662px; +` +const StyledButtonsWrapper = styled.div` + display: flex; + justify-content: space-evenly; + padding: 60px 0 25px 0; +` +export default AdminReportersList diff --git a/src/components/common/ListRow/AdminReportersListRow.tsx b/src/components/common/ListRow/AdminReportersListRow.tsx new file mode 100644 index 00000000..f6196e42 --- /dev/null +++ b/src/components/common/ListRow/AdminReportersListRow.tsx @@ -0,0 +1,63 @@ +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 AdminReportersListRowProps = { + height: number + reporterNickname: string + reportedDate: string + isDarkMode: boolean + + onClick?: () => void +} + +const AdminReportersListRow = ({ + height, + reporterNickname, + reportedDate, + isDarkMode, + onClick, +}: AdminReportersListRowProps) => { + const displayReporterNickname = + reporterNickname.length > 4 ? `${reporterNickname.substring(0, 4)}...` : reporterNickname + + return ( + + + + {displayReporterNickname} + + + {reportedDate} + + + + ) +} + +export default AdminReportersListRow diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 56b2bbf5..51e8dbd3 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -6,6 +6,7 @@ import { ApprovalInfo, ApprovalResult, Inquiry, + Reporters, ReportInfo, ReportResult, Reports, @@ -468,8 +469,8 @@ export const handlers = [ { reportedUserName: '박상민', reportCount: 2 }, { reportedUserName: '박은지', reportCount: 1 }, { reportedUserName: '주다현', reportCount: 1 }, - { reportedUserName: '남궁호수', reportCount: 1 }, { reportedUserName: '우창욱', reportCount: 1 }, + { reportedUserName: '남궁호수', reportCount: 1 }, { reportedUserName: '홍길동', reportCount: 0 }, { reportedUserName: '홍길동', reportCount: 2 }, { reportedUserName: '홍길동', reportCount: 3 }, @@ -481,6 +482,15 @@ export const handlers = [ ] return HttpResponse.json({ reports }) }), + // 신고자 목록 API 핸들러 + http.get('/v1/reporters', () => { + const reporters: Reporters[] = [ + { reporterUserName: '유명한', reportedDate: '2021.07.30' }, + { reporterUserName: '박상민', reportedDate: '2021.07.30' }, + { reporterUserName: '박은지', reportedDate: '2021.07.30' }, + ] + return HttpResponse.json({ reporters }) + }), // 신고 상세 정보 API 핸들러 http.get('/v1/reports/:reportId', (req) => { diff --git a/src/mocks/handlersInterface.ts b/src/mocks/handlersInterface.ts index 01277139..d661d4c6 100644 --- a/src/mocks/handlersInterface.ts +++ b/src/mocks/handlersInterface.ts @@ -17,6 +17,10 @@ export interface Reports { reportedUserName: string reportCount: number } +export interface Reporters { + reporterUserName: string + reportedDate: string +} export interface ReportInfo { reportedUserName: string reportDate: string diff --git a/src/pages/admin/AdminInquiry.tsx b/src/pages/admin/AdminInquiry.tsx deleted file mode 100644 index 68b26994..00000000 --- a/src/pages/admin/AdminInquiry.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import styled from '@emotion/styled' - -import AdminAppHeader from '@/components/common/AppHeader/AdminAppHeader' -import AdminInquiryList from '@/components/common/ListRow/AdminInquiryList' -import HomeNavigationBar from '@/components/common/NavigationBar/AdminNavigationBar' -import AdminPageHeader from '@/pages/admin/components/AdminPageHeader' -import { palette } from '@/styles/palette' - -// import AdminInquiryInfo from './components/AdminInquiryInfo' - -const AdminInquiry = () => { - const handleInquiryId = () => { - // inquiryId: string - // inquiryId에 대한 처리 - } - return ( - <> - - - - {/* */} - - - - - ) -} - -const AdminInquiryWrapper = styled.div` - background-color: ${palette.PRIMARY}; -` -export default AdminInquiry diff --git a/src/pages/admin/components/AdminInquiryInfo.tsx b/src/pages/admin/components/AdminInquiryInfo.tsx index e1862906..2be4a96d 100644 --- a/src/pages/admin/components/AdminInquiryInfo.tsx +++ b/src/pages/admin/components/AdminInquiryInfo.tsx @@ -6,22 +6,29 @@ import InquiryImage from '@/assets/images/inquiryImage.svg' import Spacing from '@/components/common/Spacing' // import Spacing from '@/components/common/Spacing' import { Text } from '@/components/common/Text' +import AdminPageHeader from '@/pages/admin/components/AdminPageHeader' import { palette } from '@/styles/palette' -const AdminInquiryInfo = () => { +interface AdminInquiryInfoProps { + selectedInquiryNickname: string +} +const AdminInquiryInfo = (selectedInquiryNickname: AdminInquiryInfoProps) => { const { data, isSuccess } = useQuery( ['ApprovalRequestUserInfo'], AdminApprovalAPI.GET_APPROVAL_INFO, ) console.log(isSuccess && data) + const inquiryNickname = selectedInquiryNickname.selectedInquiryNickname return ( <> + + - {'유명한'} + {inquiryNickname} @@ -47,6 +54,7 @@ const AdminInquiryInfo = () => { const StyledAdminInquiryInfo = styled.div` background-color: ${palette.WHITE}; + height: 662px; ` const StyledInquiryContent = styled.div` position: relative; diff --git a/src/pages/admin/components/AdminReportInfo.tsx b/src/pages/admin/components/AdminReportInfo.tsx index 82e0083d..041c79bc 100644 --- a/src/pages/admin/components/AdminReportInfo.tsx +++ b/src/pages/admin/components/AdminReportInfo.tsx @@ -1,50 +1,25 @@ import styled from '@emotion/styled' import { useQuery } from '@tanstack/react-query' -import { useMutation } from '@tanstack/react-query' import AdminReportAPI from '@/apis/adminReport/AdminReportApi' -import NormalButton from '@/components/common/Buttons/NormalButton' import AdminReportInfoListRow from '@/components/common/ListRow/AdminReportInfoListRow' import Spacing from '@/components/common/Spacing' -import { useModal } from '@/hooks/useModal' +import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' import AdminPageHeader from './AdminPageHeader' interface AdminReportInfoProps { - selectedReportNickname: string + selectedReporterNickname: string } -const AdminReportInfo = ({ selectedReportNickname }: AdminReportInfoProps) => { - const mutationReportAddCount = useMutation(AdminReportAPI.POST_REPORT_ADD, { - onSuccess: (data) => { - console.log(data) - }, - }) - const mutationReportIgnore = useMutation(AdminReportAPI.POST_REPORT_IGNORE, { - onSuccess: (data) => { - console.log(data) - }, - }) - const onReportAddCount = () => { - mutationReportAddCount.mutate() - } - const onReportIgnore = () => { - mutationReportIgnore.mutate() - } +const AdminReportInfo = ({ selectedReporterNickname }: AdminReportInfoProps) => { const { data, isSuccess } = useQuery(['ReportedUserInfo'], AdminReportAPI.GET_REPORT_INFO) console.log(isSuccess && data) - const { openModal } = useModal() - const handleAccumulationAddBtn = () => { - openModal({ - type: 'confirm', - mainText: '신고를 누적하시겠습니까?', - okFunc: onReportAddCount, - }) - } + return ( - + { infoMessage={'채팅방 내 잠수'} isDarkMode={false} /> - + { isDarkMode={false} /> + + + {'신고 상세 사유'} + + + + + {' '} + {'채팅방 내 잠수 기네스북 기록을 세웠어요 어쩌구 저쩌구'} + + + - - - - - {'누적 추가'} - - - {'무시'} - - - - ) @@ -103,15 +73,6 @@ const StyledAdminReportInfoOuterWrapper = styled.div` height: 662px; ` -const StyledAdminReportInfoContainer = styled.div` - background-color: ${palette.WHITE}; -` - -const StyledButtonsWrapper = styled.div` - display: flex; - justify-content: space-evenly; - padding: 60px 0 25px 0; -` const StyledReportInfoListWrapper = styled.div` background-color: ${palette.WHITE}; width: 100%; @@ -127,4 +88,18 @@ const StyledReportInfoListOuterWrapper = styled.div` const StyledBelowWhiteSpace = styled.div` background-color: ${palette.WHITE}; ` +const StyledTextWrapper = styled.div` + padding-left: 35px; + padding-bottom: 16px; + padding-top: 30px; +` +const StyledReportSpecificContent = styled.div` + margin: 0 auto; + background-color: ${palette.GRAY100}; + width: 327px; + height: 170px; + border-radius: 20px; + padding: 20px; + overflow: scroll; +` export default AdminReportInfo diff --git a/src/pages/admin/components/AdminTabs.tsx b/src/pages/admin/components/AdminTabs.tsx index 405aeb27..5dc56656 100644 --- a/src/pages/admin/components/AdminTabs.tsx +++ b/src/pages/admin/components/AdminTabs.tsx @@ -2,88 +2,91 @@ import styled from '@emotion/styled' import { useState } from 'react' import AdminApprovalList from '@/components/common/ListRow/AdminApprovalList' +import AdminInquiryList from '@/components/common/ListRow/AdminInquiryList' +import AdminReportersList from '@/components/common/ListRow/AdminReportersList' import AdminReportList from '@/components/common/ListRow/AdminReportList' -import HomeNavigationBar from '@/components/common/NavigationBar/AdminNavigationBar' import { Text } from '@/components/common/Text' import AdminApprovalInfo from '@/pages/admin/components/AdminApprovalInfo' -import AdminInquiryBtn from '@/pages/admin/components/AdminInquiryBtn' import AdminReportInfo from '@/pages/admin/components/AdminReportInfo' import { palette } from '@/styles/palette' +import AdminInquiryInfo from './AdminInquiryInfo' + interface TabProps { isActive: boolean } -interface ReportListProps { - onPersonReportedSelected: (reportNickname: string) => void -} -interface ApprovalListProps { - onPersonApprovalSelected: (approvalNickname: string) => void -} - const AdminTabs = () => { const [activeTab, setActiveTab] = useState('approval') - const [selectedReportNickname, setSelectedReportNickname] = useState(null) - const [selectedApprovalNickname, setSelectedApprovalNickname] = useState(null) + const [selectedApprovalNickname, setSelectedApprovalNickname] = useState('') + const [selectedInquiryNickname, setSelectedInquiryNickname] = useState('') + const [selectedReportedNickname, setSelectedReportedNickname] = useState('') + const [selectedReporterNickname, setSelectedReporterNickname] = useState('') + console.log('신고자 닉네임: ' + selectedReporterNickname) - const handleReportSelectNickname = (reportNickname: string) => { - setSelectedReportNickname(reportNickname) - setActiveTab('reportInfo') - } const handleApprovalSelectUserName = (approvalNickname: string) => { setSelectedApprovalNickname(approvalNickname) setActiveTab('approvalInfo') } + const handleInquirySelectUserName = (inquiryNickname: string) => { + setSelectedInquiryNickname(inquiryNickname) + setActiveTab('inquiryInfo') + } + const handleReportSelectUserName = (reportedNickname: string) => { + setSelectedReportedNickname(reportedNickname) + setActiveTab('reportersList') + } + const handleReportersSelectUserName = (reporterNickname: string) => { + setSelectedReporterNickname(reporterNickname) + setActiveTab('reportInfo') + } - const ApprovalList = ({ onPersonApprovalSelected }: ApprovalListProps) => ( - - ) - const ReportList = ({ onPersonReportedSelected }: ReportListProps) => ( - - ) // 탭에서 보여줄 컴포넌트들 - + const renderTabContent = () => { + switch (activeTab) { + case 'approval': + return + case 'approvalInfo': + return + case 'inquiry': + return + case 'inquiryInfo': + return + case 'report': + return + case 'reportersList': + return ( + + ) + case 'reportInfo': + return + default: + return null + } + } return ( <> + {renderTabContent()} - setActiveTab('approval')}> - + setActiveTab('approval')}> + {'승인 대기 목록'} - - setActiveTab('report')}> - - {'사용자 신고 내역'} + + setActiveTab('inquiry')}> + + {'불편 사항 처리'} + + + setActiveTab('report')}> + + {'사용자 신고내역'} - + - - - {activeTab === 'approval' && ( - - )} - {activeTab === 'report' && ( - - )} - {activeTab === 'reportInfo' && selectedReportNickname && ( - - )} - {activeTab === 'approvalInfo' && selectedApprovalNickname && ( - - )} - - - ) } @@ -92,31 +95,24 @@ const StyledTabsContainer = styled.div` background-color: ${palette.PRIMARY}; display: flex; width: 100%; + height: 71px; + align-items: center; ` const StyledListContainer = styled.div` width: 100%; ` -const StyledLeftTab = styled.button` - flex: 1; - - padding: 27px 25px 23px 25px; - cursor: pointer; - background: ${(props) => (props.isActive ? palette.GRAY100 : palette.WHITE)}; - border: 1.5px solid ${palette.GRAY100}; - box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); - border-radius: 30px 0 0 0; -` -const StyledRightTab = styled.button` - padding: 27px 25px 23px 25px; +const StyledTab = styled.div` flex: 1; + justify-content: center; + align-items: center; + padding: 27px 10px 24px 16px; cursor: pointer; background: ${(props) => (props.isActive ? palette.GRAY100 : palette.WHITE)}; border: 1.5px solid ${palette.GRAY100}; box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); - - border-radius: 0 30px 0 0; + color: ${(props) => (props.isActive ? palette.GRAY300 : palette.GRAY300)}; ` export default AdminTabs diff --git a/src/pages/register/RegisterCompany.tsx b/src/pages/register/RegisterCompany.tsx index 556cd129..64b0b812 100644 --- a/src/pages/register/RegisterCompany.tsx +++ b/src/pages/register/RegisterCompany.tsx @@ -5,7 +5,6 @@ import { MdWbSunny } from 'react-icons/md' import { MdOutlinePhotoCamera } from 'react-icons/md' import { useLocation, useNavigate } from 'react-router-dom' - import { axiosAPI } from '@/apis/axios' import AlertText from '@/components/common/AlertText' import BackChevron from '@/components/common/BackChevron' From 1b00b24402e6d62d1faeb67f0723195ca34ab840 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Thu, 23 Nov 2023 09:37:30 +0900 Subject: [PATCH 073/180] =?UTF-8?q?[Feature]=20=EC=9D=B4=EC=A0=84=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=EB=B0=A9=20=EB=94=94=ED=85=8C=EC=9D=BC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84=20(#147)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : 이전 채팅방 Detail 페이지 구현 * chore : 주석제거 * fix : 이전 채팅방이 없을 때 처리 * chore : dev 브랜치 merge 과정에서 충돌 해결 * chore : import 문 lint 에러 수정 --- src/App.tsx | 2 + src/apis/chatList/ChatListApi.ts | 4 +- src/apis/queryClient.ts | 4 +- src/components/chatList/ChatRoomBubbles.tsx | 23 +++-- src/pages/chatList/ChatList.tsx | 8 +- src/pages/chatListDetail/ChatListDetail.tsx | 99 +++++++++++++++++++++ src/pages/chatListDetail/index.tsx | 13 +++ src/pages/chatting/Chatting.tsx | 1 + vite.config.ts | 12 +-- 9 files changed, 145 insertions(+), 21 deletions(-) create mode 100644 src/pages/chatListDetail/ChatListDetail.tsx create mode 100644 src/pages/chatListDetail/index.tsx diff --git a/src/App.tsx b/src/App.tsx index fc7fbbb1..db20055a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import Layout from '@/components/layouts/Layout' import AdminPage from '@/pages/admin' import AdminLoginPage from '@/pages/admin/AdminLogin' import ChatListPage from '@/pages/chatList' +import ChatListDetailPage from '@/pages/chatListDetail' import ChattingPage from '@/pages/chatting' import HomePage from '@/pages/home' import LandingPage from '@/pages/landing' @@ -23,6 +24,7 @@ const App = () => { } /> } /> } /> + } /> }> diff --git a/src/apis/chatList/ChatListApi.ts b/src/apis/chatList/ChatListApi.ts index fa588a31..16cf9c2f 100644 --- a/src/apis/chatList/ChatListApi.ts +++ b/src/apis/chatList/ChatListApi.ts @@ -4,9 +4,7 @@ const ChatListApi = { // TODO: zustand로 AccessToken 받아서 요청하기 (백엔드 개발 완료 후) GET_CHAT_LIST: async () => { const response = await axiosAPI.get(`/v1/histories`) - return { - data: response.data, - } + return response.data }, } diff --git a/src/apis/queryClient.ts b/src/apis/queryClient.ts index 3fc3cdaf..afe9d5c8 100644 --- a/src/apis/queryClient.ts +++ b/src/apis/queryClient.ts @@ -14,8 +14,8 @@ export const queryClient = new QueryClient({ }, queryCache: new QueryCache({ onError: () => { - alert('에러 발생! 올바른 경로로 서비스를 이용해주세요.') - window.location.href = '/' + // alert('에러 발생! 올바른 경로로 서비스를 이용해주세요.') + // window.location.href = '/' }, }), }) diff --git a/src/components/chatList/ChatRoomBubbles.tsx b/src/components/chatList/ChatRoomBubbles.tsx index 3d50c91a..34626013 100644 --- a/src/components/chatList/ChatRoomBubbles.tsx +++ b/src/components/chatList/ChatRoomBubbles.tsx @@ -1,4 +1,5 @@ import styled from '@emotion/styled' +import { useNavigate } from 'react-router-dom' import ChatRoomBubble from './ChatRoomBubble' @@ -18,8 +19,9 @@ const StyledChatRoomBubbleWrapper = styled.div` padding: 30% 5% 5%; } ` - +const ChatRoomBubbleWrapper = styled.span`` type ChatRoom = { + id: string title: string participants: string[] createdAt: string @@ -31,17 +33,24 @@ type ChatRoomBubblesProps = { } const ChatRoomBubbles = ({ chatRoomList, isDarkMode }: ChatRoomBubblesProps) => { + const navigate = useNavigate() return ( {chatRoomList.map((chatRoom, idx) => { return ( - + onClick={() => { + navigate('/chat-list-detail', { state: { chatroomId: chatRoom.id } }) + }} + > + + ) })} diff --git a/src/pages/chatList/ChatList.tsx b/src/pages/chatList/ChatList.tsx index c5bf8d9e..f2e4f5f6 100644 --- a/src/pages/chatList/ChatList.tsx +++ b/src/pages/chatList/ChatList.tsx @@ -17,8 +17,6 @@ const ChatList = () => { const isDarkMode = useThemeStore((state) => state.isDarkMode) const navigate = useNavigate() - // TODO: TanStack의 useQuery를 사용하여 채팅방 목록 가져오기 - // 일단 MSW로 mock data를 만들어서 사용 const { data, isSuccess } = useQuery(['chatRoomList'], ChatListApi.GET_CHAT_LIST) const containerVariants = { @@ -54,7 +52,11 @@ const ChatList = () => { /> {isSuccess && ( - + {data?.data.length == 0 ? ( + '이전 채팅방이 없습니다!' + ) : ( + + )} )} diff --git a/src/pages/chatListDetail/ChatListDetail.tsx b/src/pages/chatListDetail/ChatListDetail.tsx new file mode 100644 index 00000000..86c2a1e2 --- /dev/null +++ b/src/pages/chatListDetail/ChatListDetail.tsx @@ -0,0 +1,99 @@ +import styled from '@emotion/styled' +import { useEffect, useRef, useState } from 'react' +import { BsArrowLeftShort } from 'react-icons/bs' +import { useLocation, useNavigate } from 'react-router-dom' + +import { axiosAPI } from '@/apis/axios' +import { Messages } from '@/apis/chatting/chattingType' +import Send from '@/assets/icons/Send.svg' +import { FlexBox } from '@/components/common/Flexbox' +import GradationBackground from '@/components/common/GradationBackground' +import PageContainer from '@/components/common/PageContainer' +import PageHeader from '@/components/common/PageHeader' +import Spacing from '@/components/common/Spacing' +import TextArea from '@/components/common/TextArea' +import MessageArea from '@/components/messageArea' +import { palette } from '@/styles/palette' + +const ChatListDetail = () => { + const navigate = useNavigate() + const { chatroomId } = useLocation().state + // const chatroomId = '1' + const [messages, setMessages] = useState([] as Messages[]) + + const messageWrapperRef = useRef(null) + const messageRef = useRef(null) + const divRef = useRef(null) + const navigateChatList = () => { + navigate('/chat-list') + } + const getChattingHistory = async () => { + await axiosAPI + .get(`/api/v1/histories/${chatroomId}`) + .then((response) => { + setMessages(response.data) + }) + .catch((err) => console.log(err)) + } + useEffect(() => { + getChattingHistory() + }, []) + return ( + <> + + + + + + } + rightIcon={null} + > + + {messages && } + + + + + + + + + + + + ) +} + +const StyleIcon = styled.img` + width: 30px; + height: 30px; +` +const StyleTypingFlexBox = styled(FlexBox)` + padding: 10px; + border-radius: 10px; +` +const StyleChattingWrapper = styled.span`` + +const StyleMessageWrapper = styled.div` + height: calc(100% - 145px); + flex: 1; + overflow-y: scroll; + scroll-behavior: smooth; +` + +const StyleSubmitButton = styled.button`` + +export default ChatListDetail diff --git a/src/pages/chatListDetail/index.tsx b/src/pages/chatListDetail/index.tsx new file mode 100644 index 00000000..32997d9c --- /dev/null +++ b/src/pages/chatListDetail/index.tsx @@ -0,0 +1,13 @@ +import { Route, Routes } from 'react-router-dom' + +import ChatListDetail from '@/pages/chatListDetail/ChatListDetail' + +const chatListDetailPage = () => { + return ( + + }> + + ) +} + +export default chatListDetailPage diff --git a/src/pages/chatting/Chatting.tsx b/src/pages/chatting/Chatting.tsx index cc4a586b..bda41ce1 100644 --- a/src/pages/chatting/Chatting.tsx +++ b/src/pages/chatting/Chatting.tsx @@ -25,6 +25,7 @@ import { palette } from '@/styles/palette' const Chatting = () => { const { openModal } = useModal() const navigate = useNavigate() + // const { chatroomId } = useLocation().state const chatroomId = '1' const [messages, setMessages] = useState([] as Messages[]) const [inputValue, setInputValue] = useState('') diff --git a/vite.config.ts b/vite.config.ts index 2553920d..56b7e22f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,12 +9,12 @@ export default defineConfig({ alias: { '@/': `${process.cwd()}/src/` }, }, server: { - proxy: { - '/api': { - target: process.env.SERVER_EC2_URL, - changeOrigin: true, - }, - }, + // proxy: { + // '/api': { + // target: process.env.SERVER_EC2_URL, + // changeOrigin: true, + // }, + // }, // port: 3000, // https: true, // hmr: { From 4c076cab66f8e493a51a5735e258157ba8f8f662 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Sun, 26 Nov 2023 19:56:56 +0900 Subject: [PATCH 074/180] =?UTF-8?q?[Feature]=20fcm=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=9B=8C=EC=BB=A4=20=EC=84=B8=ED=8C=85=20=EB=B0=8F?= =?UTF-8?q?=20=EB=9E=9C=EB=8D=A4=20=EB=A7=A4=EC=B9=AD=20=EC=84=B1=EA=B3=B5?= =?UTF-8?q?=20=EC=8B=9C=20web=20push=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#148)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : firebase 설치 및 service worker 설정 * chore : dev 브랜치 충돌 수정 반영 * feat : 서버에서 fcm push 받았을 경우 웹푸시 알림구현 * feat : 포그라운드 시 알림 처리 * feat : 서비스 워커에서 push 받았을 경우 indexedDB에 상태 저장 * chore : 주석제거 * chore : 사용되지 않는 함수 제거 * chore : 사용되지 않는 매개변수 제거 * chore : 사용되지 않는 함수 제거 * feat : 매칭 로직 수정 및 서버에서 유저의 상태 받아오기 * fix : 매칭 시작시간 정보가 없을 경우 현재 시간을 매칭 시작 시간으로 세팅 * chore : 사용되지 않는 props 제거 --- package-lock.json | 734 +++++++++++++++++- package.json | 1 + public/firebase-messaging-sw.js | 61 ++ setupProxy.ts | 10 - src/App.tsx | 13 + src/apis/axios.ts | 1 + .../IconButton/ParticularTopicButton.tsx | 7 +- .../IconButton/RandomMatchingButton.tsx | 8 +- .../NormalButton/NormalButtonStyles.ts | 12 +- src/components/home/Card.tsx | 206 +++-- src/components/home/Tip.tsx | 2 +- src/firebase-messaging-sw.ts | 89 +++ src/pages/chatting/Chatting.tsx | 5 +- src/pages/home/Home.tsx | 13 +- tsconfig.json | 4 +- 15 files changed, 1041 insertions(+), 125 deletions(-) create mode 100644 public/firebase-messaging-sw.js create mode 100644 src/firebase-messaging-sw.ts diff --git a/package-lock.json b/package-lock.json index 14692153..604433e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "axios": "^1.5.1", "d3": "^7.8.5", "emotion-reset": "^3.0.1", + "firebase": "^8.10.0", "framer-motion": "^10.16.4", "http-proxy-middleware": "^2.0.6", "react": "^18.2.0", @@ -1127,6 +1128,520 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@firebase/analytics": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.6.18.tgz", + "integrity": "sha512-FXNtYDxbs9ynPbzUVuG94BjFPOPpgJ7156660uvCBuKgoBCIVcNqKkJQQ7TH8384fqvGjbjdcgARY9jgAHbtog==", + "dependencies": { + "@firebase/analytics-types": "0.6.0", + "@firebase/component": "0.5.6", + "@firebase/installations": "0.4.32", + "@firebase/logger": "0.2.6", + "@firebase/util": "1.3.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.6.0.tgz", + "integrity": "sha512-kbMawY0WRPyL/lbknBkme4CNLl+Gw+E9G4OpNeXAauqoQiNkBgpIvZYy7BRT4sNGhZbxdxXxXbruqUwDzLmvTw==" + }, + "node_modules/@firebase/app": { + "version": "0.6.30", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.30.tgz", + "integrity": "sha512-uAYEDXyK0mmpZ8hWQj5TNd7WVvfsU8PgsqKpGljbFBG/HhsH8KbcykWAAA+c1PqL7dt/dbt0Reh1y9zEdYzMhg==", + "dependencies": { + "@firebase/app-types": "0.6.3", + "@firebase/component": "0.5.6", + "@firebase/logger": "0.2.6", + "@firebase/util": "1.3.0", + "dom-storage": "2.1.0", + "tslib": "^2.1.0", + "xmlhttprequest": "1.8.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.3.2.tgz", + "integrity": "sha512-YjpsnV1xVTO1B836IKijRcDeceLgHQNJ/DWa+Vky9UHkm1Mi4qosddX8LZzldaWRTWKX7BN1MbZOLY8r7M/MZQ==", + "dependencies": { + "@firebase/app-check-interop-types": "0.1.0", + "@firebase/app-check-types": "0.3.1", + "@firebase/component": "0.5.6", + "@firebase/logger": "0.2.6", + "@firebase/util": "1.3.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.1.0.tgz", + "integrity": "sha512-uZfn9s4uuRsaX5Lwx+gFP3B6YsyOKUE+Rqa6z9ojT4VSRAsZFko9FRn6OxQUA1z5t5d08fY4pf+/+Dkd5wbdbA==" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.3.1.tgz", + "integrity": "sha512-KJ+BqJbdNsx4QT/JIT1yDj5p6D+QN97iJs3GuHnORrqL+DU3RWc9nSYQsrY6Tv9jVWcOkMENXAgDT484vzsm2w==" + }, + "node_modules/@firebase/app-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", + "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==" + }, + "node_modules/@firebase/auth": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.16.8.tgz", + "integrity": "sha512-mR0UXG4LirWIfOiCWxVmvz1o23BuKGxeItQ2cCUgXLTjNtWJXdcky/356iTUsd7ZV5A78s2NHeN5tIDDG6H4rg==", + "dependencies": { + "@firebase/auth-types": "0.10.3" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/auth-types": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.3.tgz", + "integrity": "sha512-zExrThRqyqGUbXOFrH/sowuh2rRtfKHp9SBVY2vOqKWdCX1Ztn682n9WLtlUDsiYVIbBcwautYWk2HyCGFv0OA==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.6.tgz", + "integrity": "sha512-GyQJ+2lrhsDqeGgd1VdS7W+Y6gNYyI0B51ovNTxeZVG/W8I7t9MwEiCWsCvfm5wQgfsKp9dkzOcJrL5k8oVO/Q==", + "dependencies": { + "@firebase/util": "1.3.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.11.0.tgz", + "integrity": "sha512-b/kwvCubr6G9coPlo48PbieBDln7ViFBHOGeVt/bt82yuv5jYZBEYAac/mtOVSxpf14aMo/tAN+Edl6SWqXApw==", + "dependencies": { + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.6", + "@firebase/database-types": "0.8.0", + "@firebase/logger": "0.2.6", + "@firebase/util": "1.3.0", + "faye-websocket": "0.11.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.8.0.tgz", + "integrity": "sha512-7IdjAFRfPWyG3b4wcXyghb3Y1CLCSJFZIg1xl5GbTVMttSQFT4B5NYdhsfA34JwAsv5pMzPpjOaS3/K9XJ2KiA==", + "dependencies": { + "@firebase/app-types": "0.6.3", + "@firebase/util": "1.3.0" + } + }, + "node_modules/@firebase/firestore": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-2.4.0.tgz", + "integrity": "sha512-PQ6+lWNrvh74GvFTHT4gCutFipDmtu8D1tNNawKe+/SyL6XFgeuMYgZIpKQgkTSezVDogC7EGQTJBFnewF9pOg==", + "dependencies": { + "@firebase/component": "0.5.6", + "@firebase/firestore-types": "2.4.0", + "@firebase/logger": "0.2.6", + "@firebase/util": "1.3.0", + "@firebase/webchannel-wrapper": "0.5.1", + "@grpc/grpc-js": "^1.3.2", + "@grpc/proto-loader": "^0.6.0", + "node-fetch": "2.6.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.4.0.tgz", + "integrity": "sha512-0dgwfuNP7EN6/OlK2HSNSQiQNGLGaRBH0gvgr1ngtKKJuJFuq0Z48RBMeJX9CGjV4TP9h2KaB+KrUKJ5kh1hMg==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/firestore/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@firebase/functions": { + "version": "0.6.15", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.6.15.tgz", + "integrity": "sha512-b7RpLwFXi0N+HgkfK8cmkarSOoBeSrc1jNdadkCacQt+vIePkKM3E9EJXF4roWSa8GwTruodpBsvH+lK9iCAKQ==", + "dependencies": { + "@firebase/component": "0.5.6", + "@firebase/functions-types": "0.4.0", + "@firebase/messaging-types": "0.5.0", + "node-fetch": "2.6.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.4.0.tgz", + "integrity": "sha512-3KElyO3887HNxtxNF1ytGFrNmqD+hheqjwmT3sI09FaDCuaxGbOnsXAXH2eQ049XRXw9YQpHMgYws/aUNgXVyQ==" + }, + "node_modules/@firebase/functions/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@firebase/installations": { + "version": "0.4.32", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.32.tgz", + "integrity": "sha512-K4UlED1Vrhd2rFQQJih+OgEj8OTtrtH4+Izkx7ip2bhXSc+unk8ZhnF69D0kmh7zjXAqEDJrmHs9O5fI3rV6Tw==", + "dependencies": { + "@firebase/component": "0.5.6", + "@firebase/installations-types": "0.3.4", + "@firebase/util": "1.3.0", + "idb": "3.0.2", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.4.tgz", + "integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/installations/node_modules/idb": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", + "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==" + }, + "node_modules/@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==" + }, + "node_modules/@firebase/messaging": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.8.0.tgz", + "integrity": "sha512-hkFHDyVe1kMcY9KEG+prjCbvS6MtLUgVFUbbQqq7JQfiv58E07YCzRUcMrJolbNi/1QHH6Jv16DxNWjJB9+/qA==", + "dependencies": { + "@firebase/component": "0.5.6", + "@firebase/installations": "0.4.32", + "@firebase/messaging-types": "0.5.0", + "@firebase/util": "1.3.0", + "idb": "3.0.2", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/messaging-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.5.0.tgz", + "integrity": "sha512-QaaBswrU6umJYb/ZYvjR5JDSslCGOH6D9P136PhabFAHLTR4TWjsaACvbBXuvwrfCXu10DtcjMxqfhdNIB1Xfg==", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/messaging/node_modules/idb": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", + "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==" + }, + "node_modules/@firebase/performance": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.4.18.tgz", + "integrity": "sha512-lvZW/TVDne2TyOpWbv++zjRn277HZpbjxbIPfwtnmKjVY1gJ+H77Qi1c2avVIc9hg80uGX/5tNf4pOApNDJLVg==", + "dependencies": { + "@firebase/component": "0.5.6", + "@firebase/installations": "0.4.32", + "@firebase/logger": "0.2.6", + "@firebase/performance-types": "0.0.13", + "@firebase/util": "1.3.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.13.tgz", + "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==" + }, + "node_modules/@firebase/polyfill": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.36.tgz", + "integrity": "sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==", + "dependencies": { + "core-js": "3.6.5", + "promise-polyfill": "8.1.3", + "whatwg-fetch": "2.0.4" + } + }, + "node_modules/@firebase/remote-config": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.43.tgz", + "integrity": "sha512-laNM4MN0CfeSp7XCVNjYOC4DdV6mj0l2rzUh42x4v2wLTweCoJ/kc1i4oWMX9TI7Jw8Am5Wl71Awn1J2pVe5xA==", + "dependencies": { + "@firebase/component": "0.5.6", + "@firebase/installations": "0.4.32", + "@firebase/logger": "0.2.6", + "@firebase/remote-config-types": "0.1.9", + "@firebase/util": "1.3.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz", + "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==" + }, + "node_modules/@firebase/storage": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.7.0.tgz", + "integrity": "sha512-ebDFKJbM5HOxVtZV+RhVEBVtlWHK+Z5L3kA5uDBA2jMYcn+8NV/crozJnEE+iRsGEco6dLK5JS+Er4qtKLpH5A==", + "dependencies": { + "@firebase/component": "0.5.6", + "@firebase/storage-types": "0.5.0", + "@firebase/util": "1.3.0", + "node-fetch": "2.6.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.5.0.tgz", + "integrity": "sha512-6Wv3Lu7s18hsgW7HG4BFwycTquZ3m/C8bjBoOsmPu0TD6M1GKwCzOC7qBdN7L6tRYPh8ipTj5+rPFrmhGfUVKA==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/storage/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.3.0.tgz", + "integrity": "sha512-SESvmYwuKOVCZ1ZxLbberbx+9cnbxpCa4CG2FUSQYqN6Ab8KyltegMDIsqMw5KyIBZ4n1phfHoOa22xo5NzAlQ==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.5.1.tgz", + "integrity": "sha512-dZMzN0uAjwJXWYYAcnxIwXqRTZw3o14hGe7O6uhwjD1ZQWPVYA5lASgnNskEBra0knVBsOXB4KXg+HnlKewN/A==" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.11", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.11.tgz", + "integrity": "sha512-QDhMfbTROOXUhLHMroow8f3EHiCKUOh6UwxMP5S3EuXMnWMNSVIhatGZRwkpg9OUTYdZPsDUVH3cOAkWhGFUJw==", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz", + "integrity": "sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.4", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", + "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/@grpc/proto-loader/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==" + }, + "node_modules/@grpc/proto-loader/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/proto-loader/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/proto-loader/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==", + "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/@grpc/proto-loader/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.12", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.12.tgz", @@ -1342,6 +1857,60 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@remix-run/router": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", @@ -2518,6 +3087,11 @@ "dev": true, "peer": true }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "node_modules/@types/node": { "version": "20.8.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz", @@ -3077,7 +3651,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -3086,7 +3659,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3627,7 +4199,6 @@ "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", @@ -3640,14 +4211,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 + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "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" } @@ -3656,7 +4225,6 @@ "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", @@ -3670,7 +4238,6 @@ "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", @@ -3704,7 +4271,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3715,8 +4281,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/colorette": { "version": "2.0.20", @@ -3779,6 +4344,17 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -4393,6 +4969,14 @@ "node": ">=6.0.0" } }, + "node_modules/dom-storage": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", + "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==", + "engines": { + "node": "*" + } + }, "node_modules/dot-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", @@ -4618,7 +5202,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -5196,6 +5779,17 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -5264,6 +5858,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-8.10.0.tgz", + "integrity": "sha512-GCABTbJdo88QgzX5OH/vsfKBWvTRbLUylGlYXtO7uYo1VErfGd2BWW9ATlJP5Gxx+ClDfyvVTvcs2rcNWn3uUA==", + "dependencies": { + "@firebase/analytics": "0.6.18", + "@firebase/app": "0.6.30", + "@firebase/app-check": "0.3.2", + "@firebase/app-types": "0.6.3", + "@firebase/auth": "0.16.8", + "@firebase/database": "0.11.0", + "@firebase/firestore": "2.4.0", + "@firebase/functions": "0.6.15", + "@firebase/installations": "0.4.32", + "@firebase/messaging": "0.8.0", + "@firebase/performance": "0.4.18", + "@firebase/polyfill": "0.3.36", + "@firebase/remote-config": "0.1.43", + "@firebase/storage": "0.7.0", + "@firebase/util": "1.3.0" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, "node_modules/flat-cache": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", @@ -5432,7 +6051,6 @@ "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.*" } @@ -5710,6 +6328,11 @@ "react-is": "^16.7.0" } }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, "node_modules/http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", @@ -6731,6 +7354,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6799,6 +7427,11 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7554,6 +8187,11 @@ "node": ">=6.0.0" } }, + "node_modules/promise-polyfill": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", + "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -7565,6 +8203,31 @@ "react-is": "^16.13.1" } }, + "node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -7779,7 +8442,6 @@ "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" } @@ -8110,7 +8772,6 @@ "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", @@ -8454,7 +9115,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -8986,6 +9646,32 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -9149,11 +9835,18 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", + "engines": { + "node": ">=0.4.0" + } + }, "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" } @@ -9176,7 +9869,6 @@ "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", @@ -9194,7 +9886,6 @@ "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" } @@ -9202,14 +9893,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 + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "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" } @@ -9218,7 +9907,6 @@ "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", diff --git a/package.json b/package.json index a821a0af..c73d7707 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "axios": "^1.5.1", "d3": "^7.8.5", "emotion-reset": "^3.0.1", + "firebase": "^8.10.0", "framer-motion": "^10.16.4", "http-proxy-middleware": "^2.0.6", "react": "^18.2.0", diff --git a/public/firebase-messaging-sw.js b/public/firebase-messaging-sw.js new file mode 100644 index 00000000..8779ac00 --- /dev/null +++ b/public/firebase-messaging-sw.js @@ -0,0 +1,61 @@ +self.addEventListener('install', function (e) { + console.log('fcm sw install..') + self.skipWaiting() +}) + +self.addEventListener('activate', function (e) { + console.log('fcm sw activate..') +}) + +self.addEventListener('push', function (e) { + console.log('push: ', e.data.json()) + if (!e.data.json()) return + + const resultData = e.data.json().notification + const notificationTitle = resultData.title + const notificationOptions = { + body: resultData.body, + icon: resultData.image, + tag: resultData.tag, + ...resultData, + } + self.registration.showNotification(notificationTitle, notificationOptions) + console.log('push: ', { resultData, notificationTitle, notificationOptions }) + //indexedDB에 상태 저장하는 기능 추가 + saveDataToIndexedDB('isMatchingSuccess', true) +}) + +// IndexedDB에 데이터를 저장하는 함수 +function saveDataToIndexedDB(key, value) { + const request = indexedDB.open('matching-database') + + request.onupgradeneeded = function (event) { + const db = event.target.result + + db.createObjectStore('my-store', { autoIncrement: true }) + } + + request.onsuccess = function (event) { + const db = event.target.result + const tx = db.transaction('my-store', 'readwrite') + const store = tx.objectStore('my-store') + store.put(value, key) + + tx.oncomplete = function () { + db.close() + } + } + + request.onerror = function (event) { + console.error('Error opening IndexedDB:', event.target.error) + } +} + +self.addEventListener('notificationclick', function (event) { + console.log('notification click') + const url = '/' + event.notification.close() + + // eslint-disable-next-line no-undef + event.waitUntil(clients.openWindow(url)) +}) diff --git a/setupProxy.ts b/setupProxy.ts index 84d0e909..e69de29b 100644 --- a/setupProxy.ts +++ b/setupProxy.ts @@ -1,10 +0,0 @@ -// 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/App.tsx b/src/App.tsx index db20055a..749f75e1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,6 @@ +import './firebase-messaging-sw.ts' + +import { useEffect } from 'react' import { Route, Routes } from 'react-router-dom' import Layout from '@/components/layouts/Layout' @@ -15,7 +18,17 @@ import ProfilePage from '@/pages/profile' import PrivateRoute from '@/pages/redirect/PrivateRoute' import RegisterPage from '@/pages/register' +import { getToken } from './firebase-messaging-sw.ts' + const App = () => { + const firebaseMessageToken = async () => { + const token = await getToken() + console.log('token === ', token) + //추후 서버에 토큰을 저장하는 기능을 여기에 추가 + } + useEffect(() => { + firebaseMessageToken() + }, []) return ( }> diff --git a/src/apis/axios.ts b/src/apis/axios.ts index 538a83e5..0547fcfe 100644 --- a/src/apis/axios.ts +++ b/src/apis/axios.ts @@ -16,6 +16,7 @@ axiosAPI.interceptors.request.use( // axios 설정값에 대해 작성합니다. config.headers['Authorization'] = `Bearer ${localStorage.getItem('jwt')}` + return config }, function (error) { diff --git a/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx b/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx index 39d92e99..11cffb8a 100644 --- a/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx +++ b/src/components/common/Buttons/IconButton/ParticularTopicButton.tsx @@ -56,7 +56,12 @@ const ParticularTopicButton = ({ flex: 1, }} > - + {'특정 주제로 대화하기'} void } @@ -20,7 +20,7 @@ type RandomMatchingButtonProps = { * @param onClick - 랜덤매칭 버튼 클릭 이벤트 */ -const RandomMatchingButton = ({ date, isDarkMode, onClick }: RandomMatchingButtonProps) => { +const RandomMatchingButton = ({ isDarkMode, onClick }: RandomMatchingButtonProps) => { const setButtonType = isDarkMode ? 'random-matching-dark' : 'random-matching' return ( @@ -70,7 +70,7 @@ const RandomMatchingButton = ({ date, isDarkMode, onClick }: RandomMatchingButto alignItems: 'center', }} > - {'마지막 채팅: '} {`${getTimeDelta(date)}`} + {/* {'마지막 채팅: '} {`${getTimeDelta(date)}`} */} = { borderRadius: 50, }, matching: { - width: 123, - height: 45, - fontColor: palette.WHITE, - backgroundColor: palette.TERTIARY, - font: 'Body_14', + width: 63, + height: 30, + fontColor: palette.GRAY300, + backgroundColor: palette.GRAY100, + font: 'Body_12', fontWeight: 500, letterSpacing: -1, - boxShadow: '0px 4px 20px rgba(0, 0, 0, 0.15)', + borderRadius: 8, }, 'matching-dark': { diff --git a/src/components/home/Card.tsx b/src/components/home/Card.tsx index 1087ed60..090e7aaa 100644 --- a/src/components/home/Card.tsx +++ b/src/components/home/Card.tsx @@ -2,22 +2,26 @@ import styled from '@emotion/styled' import { timer } from 'd3' import { AnimatePresence, motion } from 'framer-motion' import { useEffect, useRef, useState } from 'react' +import { IoIosArrowForward } from 'react-icons/io' +import { useNavigate } from 'react-router-dom' import { PulseLoader } from 'react-spinners' +import { axiosAPI } from '@/apis/axios' import { RandomMatchingButton } from '@/components/common/Buttons/IconButton' import NormalButton from '@/components/common/Buttons/NormalButton' +import { FlexBox } from '@/components/common/Flexbox' import Spacing from '@/components/common/Spacing' import { Text } from '@/components/common/Text' +import useToast from '@/hooks/useToast' import { palette } from '@/styles/palette' +import { typo } from '@/styles/typo' import Tip from './Tip' type TimerRefType = ReturnType | null type CardProps = { - isMatching: boolean isDarkMode: boolean - onClick: () => void } /** @@ -26,16 +30,37 @@ type CardProps = { * @param onClick - 매칭 버튼 클릭 이벤트 */ -const Card = ({ isMatching, isDarkMode, onClick }: CardProps) => { +const Card = ({ isDarkMode }: CardProps) => { const [time, setTime] = useState(0) const timerRef = useRef(null) + const [matchingStartedAt, setMatchingStartedAt] = useState('') + const [isMatching, setIsMatching] = useState(false) + const [currentState, setCurrentState] = useState('IDLE') + const [chatroomId, setChatroomId] = useState('33') + const navigate = useNavigate() + const { showToast } = useToast() - const handleCancelClick = () => { + const handleMoveChatting = () => { + navigate('/chatting', { state: { chatroomId: chatroomId } }) + } + + const handleMatchingStart = async () => { + setIsMatching((prev) => !prev) + setCurrentState('MATCHING') + await axiosAPI.post('/v1/matching/start').then((response) => { + console.log(response) + setCurrentState('MATCHING') + }) + } + + const handleCancelClick = async () => { + setIsMatching((prev) => !prev) setTime(0) if (timerRef.current) { timerRef.current.stop() } - onClick() + setCurrentState('IDLE') + await axiosAPI.post('/v1/matchings/cancel') } const formatTime = (time: number) => { @@ -48,15 +73,38 @@ const Card = ({ isMatching, isDarkMode, onClick }: CardProps) => { return `${minutes}:${seconds}` } - const watingCounter = { + const waitingCounter = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { type: 'spring', damping: 12, duration: 0.5 } }, exit: { opacity: 0, transition: { duration: 1 } }, } + const getCurrentMatchingState = async () => { + await axiosAPI + .get('/v1/users/status') + .then((response) => { + if (currentState == response.data.userStatus) + showToast({ + message: '아직 매칭이 성사되지 않았습니다!', + type: 'info', + isDarkMode: isDarkMode, + }) + setCurrentState(response.data.userStatus) + response.data.userStatus === 'CHATTING_UNCONNECTED' && + setChatroomId(response.data.chattingRoomId) + response.data.userStatus === 'MATCHING' && setMatchingStartedAt(response.data.startedAt) + }) + .catch((err) => { + console.log(err) + }) + } useEffect(() => { - if (isMatching) { - const startTime = Date.now() + if (currentState === 'MATCHING') { + //startTime에 서버에서 시작시간 받아와 new Date객체 안에 넣은 후 스트링으로 바꿔서 Date.parse한 후 Date.now()에서 뺄 것 + const date = new Date(matchingStartedAt) + date.setHours(date.getHours() + 9) + const startTime = matchingStartedAt.length === 0 ? Date.now() : Date.parse(date.toString()) + const updateTimer = () => { const elapsedTime = Date.now() - startTime setTime(elapsedTime) @@ -75,46 +123,36 @@ const Card = ({ isMatching, isDarkMode, onClick }: CardProps) => { } }, [isMatching]) + useEffect(() => { + getCurrentMatchingState() + }, [currentState]) + return ( - {!isMatching ? ( + {currentState === 'IDLE' ? ( - + - ) : ( - - - - - {'3'} - - - {'/5'} - - - - - + { > {formatTime(time)} - - - {'매칭 취소'} - + + {'매칭 확인'} + + + { }} /> - - - + + + + {'매칭 취소'} + + + - - + + + ) : ( + <> + + {'매칭이 완료되었습니다!'} + + + + + + {'채팅방으로 이동'} + + + + + + )} ) } - +const StyleMoveChatButton = styled.button` + width: 200px; + height: 70px; + border-radius: 20px; + color: ${palette.WHITE}; + font-size: ${typo.Body_16(600)}; + box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.25); + background-image: linear-gradient( + to right, + ${palette.SECONDARY} 0%, + ${palette.SECONDARY} 50%, + ${palette.GRADIENT} 110% + ); +` +const StyleMatchingCancelWrapper = styled.span` + display: flex; + position: relative; + margin: 5px; +` +const StyleMatchingCheckButton = styled.button<{ isDarkMode: boolean }>` + width: 200px; + height: 60px; + border-radius: 20px; + color: ${palette.WHITE}; + font-size: ${typo.Body_16(600)}; + background-color: ${palette.SECONDARY}; +` const StyleCard = styled(motion.div)<{ isDarkMode: boolean }>` @@ -180,7 +275,7 @@ const StyleCard = styled(motion.div)<{ padding: 5% 1% 5%; ` -const StyleWatingWrapper = styled(motion.div)` +const StyleWaitingWrapper = styled(motion.div)` width: 100%; display: flex; flex-direction: column; @@ -188,24 +283,7 @@ const StyleWatingWrapper = styled(motion.div)` flex: 1; ` -const StyleWatingTopWrapper = styled.div` - width: 100%; - height: 38px; - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 5%; - position: relative; -` - -const StyleWatingTopTextWrapper = styled.div` - display: flex; - height: inherit; - justify-content: center; - align-items: flex-end; -` - -const StyleWatingMidWrapper = styled.div` +const StyleWaitingMidWrapper = styled.div` flex: 1; display: flex; flex-direction: column; @@ -213,6 +291,6 @@ const StyleWatingMidWrapper = styled.div` align-items: center; ` -const StyleWatingBottomWrapper = styled.div`` +const StyleWaitingBottomWrapper = styled.div`` export default Card diff --git a/src/components/home/Tip.tsx b/src/components/home/Tip.tsx index 2230fe4a..cdc4e903 100644 --- a/src/components/home/Tip.tsx +++ b/src/components/home/Tip.tsx @@ -9,7 +9,7 @@ const StyledTipHeader = styled.div` flex-direction: column; height: 63.525px; padding: 11px 18px 0px; - border-top: 1px solid ${palette.GRAY200}; + border-top: 0.5px dotted ${palette.GRAY400}; ` const Tip = () => { diff --git a/src/firebase-messaging-sw.ts b/src/firebase-messaging-sw.ts new file mode 100644 index 00000000..d2f80395 --- /dev/null +++ b/src/firebase-messaging-sw.ts @@ -0,0 +1,89 @@ +// import 'firebase/messaging' + +// import firebase from 'firebase/app' + +// const firebaseConfig = { +// apiKey: import.meta.env.VITE_apiKey, +// authDomain: import.meta.env.VITE_authDomain, +// projectId: import.meta.env.VITE_projectId, +// storageBucket: import.meta.env.VITE_storageBucket, +// messagingSenderId: import.meta.env.VITE_messagingSenderId, +// appId: import.meta.env.VITE_appId, +// measurementId: import.meta.env.VITE_measurementId, +// } + +// firebase.initializeApp(firebaseConfig) + +// const messaging = firebase.messaging() + +// export function requestPermission() { +// void Notification.requestPermission().then((permission) => { +// if (permission === 'granted') { +// messaging +// .getToken({ vapidKey: import.meta.env.VITE_VAPID_KEY }) +// .then((token: string) => { +// console.log(`푸시 토큰 발급 완료 : ${token}`) +// return token +// }) +// // .then(function (token) { +// // console.log('알림 왜 안 울려') +// // messaging.onMessage((payload) => { +// // alert('알림:' + payload.notification.body) +// // }) +// // return token +// // }) +// .catch((err) => { +// console.log('푸시 토큰 가져오는 중에 에러 발생 : ', err) +// }) +// } else if (permission === 'denied') { +// console.log('푸시 권한 차단') +// } +// }) +// } +// requestPermission() + +// import 'firebase/messaging' + +import firebase from 'firebase' +const firebaseConfig = { + apiKey: import.meta.env.VITE_apiKey, + authDomain: import.meta.env.VITE_authDomain, + projectId: import.meta.env.VITE_projectId, + storageBucket: import.meta.env.VITE_storageBucket, + messagingSenderId: import.meta.env.VITE_messagingSenderId, + appId: import.meta.env.VITE_appId, + measurementId: import.meta.env.VITE_measurementId, +} + +firebase.initializeApp(firebaseConfig) +export async function getToken() { + if (firebase.messaging.isSupported() === false) { + console.log('isSupported: ', firebase.messaging.isSupported()) + return null + } + + const messaging = firebase.messaging() + const token = await messaging + .requestPermission() + .then(function () { + return messaging.getToken({ vapidKey: import.meta.env.VITE_VAPID_KEY }) + }) + .then(function (token) { + messaging.onMessage((payload) => { + console.log('onMessage!!') + alert('알림:' + payload.notification.body) + }) + return token + }) + .catch(function (err) { + console.debug('에러 : ', err) + return null + }) + messaging.onMessage((payload) => { + console.log('onMessage!!') + alert('알림:' + payload.notification.body) + }) + console.log('token:', token) + return token +} +getToken() diff --git a/src/pages/chatting/Chatting.tsx b/src/pages/chatting/Chatting.tsx index bda41ce1..e8625b40 100644 --- a/src/pages/chatting/Chatting.tsx +++ b/src/pages/chatting/Chatting.tsx @@ -2,7 +2,7 @@ import styled from '@emotion/styled' import * as Stomp from '@stomp/stompjs' import { FormEvent, useEffect, useRef, useState } from 'react' import { BsArrowLeftShort } from 'react-icons/bs' -import { useNavigate } from 'react-router-dom' +import { useLocation, useNavigate } from 'react-router-dom' import { axiosAPI } from '@/apis/axios' import { ChattingApi } from '@/apis/chatting/chattingApi' @@ -25,8 +25,7 @@ import { palette } from '@/styles/palette' const Chatting = () => { const { openModal } = useModal() const navigate = useNavigate() - // const { chatroomId } = useLocation().state - const chatroomId = '1' + const { chatroomId } = useLocation().state const [messages, setMessages] = useState([] as Messages[]) const [inputValue, setInputValue] = useState('') const { authTokens } = useAuthStore() diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 6076430a..f01c1a6f 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -1,5 +1,4 @@ import { useEffect, useState } from 'react' -import { useNavigate } from 'react-router-dom' import AppHeader from '@/components/common/AppHeader' import { ParticularTopicButton } from '@/components/common/Buttons/IconButton' @@ -19,8 +18,6 @@ const Home = () => { const isDarkMode = useThemeStore((state) => state.isDarkMode) const toggleDarkMode = useThemeStore((state) => state.toggleDarkMode) const { authTokens } = useAuthStore() - const [isMatching, setIsMatching] = useState(false) - const navigate = useNavigate() const { showToast } = useToast() @@ -31,7 +28,7 @@ const Home = () => { type: 'warning', isDarkMode, }) - navigate('/login') + // navigate('/login') } if (authTokens) { setNickname(localStorage.getItem('nickname') || '') @@ -65,13 +62,7 @@ const Home = () => { > {'진행중인 매칭'} - { - setIsMatching((prev) => !prev) - }} - isDarkMode={isDarkMode} - /> + Date: Sun, 26 Nov 2023 20:23:12 +0900 Subject: [PATCH 075/180] =?UTF-8?q?[Feat]=20=EB=A7=88=EC=9D=B4=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20(#162)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : 마이 프로필 페이지 조회 기능 * style : 프로필 이미지 url 삽입 및 이메일 텍스트 fontWeight 축소 --- src/pages/profile/ProfileDefault.tsx | 52 +++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/pages/profile/ProfileDefault.tsx b/src/pages/profile/ProfileDefault.tsx index c91aa192..16835b7c 100644 --- a/src/pages/profile/ProfileDefault.tsx +++ b/src/pages/profile/ProfileDefault.tsx @@ -1,10 +1,12 @@ import styled from '@emotion/styled' +import { useEffect, useState } from 'react' import { AiOutlineInfoCircle } from 'react-icons/ai' import { BiBuildings, BiChevronRight, BiPencil } from 'react-icons/bi' import { MdOutlineRecordVoiceOver } from 'react-icons/md' import { PiIdentificationCardBold } from 'react-icons/pi' import { useNavigate } from 'react-router-dom' +import { axiosAPI } from '@/apis/axios' import NaverIcon from '@/assets/icons/NaverIcon' import Avatar from '@/components/common/Avatar' import BackChevron from '@/components/common/BackChevron' @@ -24,13 +26,42 @@ import useAuthStore from '@/store/AuthStore.tsx' import useThemeStore from '@/store/ThemeStore' import { palette } from '@/styles/palette' +type MyProfileData = { + nickname: string + email: string + profileImageUrl: string + reportedCount: number + sanctionPeriod: string + department: string + interests: string[] +} + const ProfileDefault = () => { const navigate = useNavigate() const isDarkMode = useThemeStore((store) => store.isDarkMode) + const [myProfileData, setMyProfileData] = useState({ + nickname: '', + email: '', + profileImageUrl: '', + reportedCount: 0, + sanctionPeriod: '', + department: '', + interests: [''], + }) const { openModal } = useModal() const { showToast } = useToast() + const getProfileData = async () => { + await axiosAPI + .get('/v1/users/me') + .then((response) => { + setMyProfileData(response.data) + }) + .catch((err) => { + console.log(err) + }) + } const handleLogout = () => { openModal({ mainText: '로그아웃 하시겠습니까?', @@ -64,6 +95,9 @@ const ProfileDefault = () => { isDarkMode, }) } + useEffect(() => { + getProfileData() + }, []) return ( @@ -82,7 +116,11 @@ const ProfileDefault = () => { - + { alignItems: 'center', }} > - {`우땅`} + + {myProfileData && myProfileData.nickname} + - {`1998.01.20`} + + {myProfileData && myProfileData.email} + { isDarkMode={isDarkMode} > - {'커피밋 IT 부서'} + {myProfileData && myProfileData.department} @@ -122,7 +164,7 @@ const ProfileDefault = () => { From b63cf9614057bcacc28530c6558fbe81eb6ea293 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Sun, 26 Nov 2023 23:27:42 +0900 Subject: [PATCH 076/180] Update App.tsx --- src/App.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 05a280a1..749f75e1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -52,7 +52,6 @@ const App = () => { }> }> - }> }> }> From b721f45484a3ef1309df3064c964eb672e23b604 Mon Sep 17 00:00:00 2001 From: DaHyeonJu Date: Sun, 26 Nov 2023 23:30:49 +0900 Subject: [PATCH 077/180] Update AdminReportInfo.tsx --- src/pages/admin/components/AdminReportInfo.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/admin/components/AdminReportInfo.tsx b/src/pages/admin/components/AdminReportInfo.tsx index 7d527570..cc9f3a4d 100644 --- a/src/pages/admin/components/AdminReportInfo.tsx +++ b/src/pages/admin/components/AdminReportInfo.tsx @@ -1,6 +1,5 @@ import styled from '@emotion/styled' import { useQuery } from '@tanstack/react-query' -import { useMutation } from '@tanstack/react-query' import AdminReportAPI from '@/apis/adminReport/AdminReportApi' import AdminReportInfoListRow from '@/components/common/ListRow/AdminReportInfoListRow' From 74cd2b1eb42f3e0cb2dc290016bd44f3a507856e Mon Sep 17 00:00:00 2001 From: judahhh Date: Sun, 26 Nov 2023 23:44:10 +0900 Subject: [PATCH 078/180] =?UTF-8?q?chore=20:=20props=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EB=B6=88=EC=9D=BC=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/ListRow/AdminInquiryList.tsx | 1 - src/pages/admin/AdminInquiry.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/common/ListRow/AdminInquiryList.tsx b/src/components/common/ListRow/AdminInquiryList.tsx index 5217bc35..201368f4 100644 --- a/src/components/common/ListRow/AdminInquiryList.tsx +++ b/src/components/common/ListRow/AdminInquiryList.tsx @@ -3,7 +3,6 @@ import { useQuery } from '@tanstack/react-query' import AdminInquiryAPI from '@/apis/adminInquiry/AdminInquiryApi' import AdminApprovalListRow from '@/components/common/ListRow/AdminApprovalListRow' - import AdminPageHeader from '@/pages/admin/components/AdminPageHeader' import { palette } from '@/styles/palette' diff --git a/src/pages/admin/AdminInquiry.tsx b/src/pages/admin/AdminInquiry.tsx index 68b26994..e8dfb70d 100644 --- a/src/pages/admin/AdminInquiry.tsx +++ b/src/pages/admin/AdminInquiry.tsx @@ -19,7 +19,7 @@ const AdminInquiry = () => { {/* */} - + From 671248b83a43300db329a4c89d6665166406dc6e Mon Sep 17 00:00:00 2001 From: judahhh Date: Sun, 26 Nov 2023 23:45:09 +0900 Subject: [PATCH 079/180] =?UTF-8?q?chore=20:=20import=EB=AC=B8=20lint=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/home/Home.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 46bb4a2d..ecda8e72 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -1,8 +1,6 @@ import { useEffect, useState } from 'react' - import { useNavigate } from 'react-router-dom' - import AppHeader from '@/components/common/AppHeader' import { ParticularTopicButton } from '@/components/common/Buttons/IconButton' import GradationBackground from '@/components/common/GradationBackground' @@ -22,6 +20,7 @@ const Home = () => { const toggleDarkMode = useThemeStore((state) => state.toggleDarkMode) const { authTokens } = useAuthStore() const { showToast } = useToast() + const navigate = useNavigate() useEffect(() => { if (!authTokens) { From a801094537d52002b41a1e4664dcd299f208a9ff Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 02:29:51 +0900 Subject: [PATCH 080/180] =?UTF-8?q?chore=20:=20=EB=B0=B0=ED=8F=AC=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=EC=84=9C=EC=9D=98=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.production | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.env.production b/.env.production index 04849b83..bc3ef02d 100644 --- a/.env.production +++ b/.env.production @@ -1 +1,12 @@ -VITE_BASE_URL='https://api.coffee-meet.site/api' \ No newline at end of file +VITE_BASE_URL='https://api.coffee-meet.site/api' +VITE_CHAT_URL='api.coffee-meet.site' + +VITE_apiKey= 'AIzaSyBPfx4R0QsrFbS5rMv38dM1B7iPR4bUxt4' +VITE_authDomain= 'coffee-meet-d295d.firebaseapp.com' +VITE_projectId= 'coffee-meet-d295d' +VITE_storageBucket= 'coffee-meet-d295d.appspot.com' +VITE_messagingSenderId= '716922226162' +VITE_appId= '1:716922226162:web:1f3cbb9069cd525323d76b' +VITE_measurementId= 'G-2YB8FMSHM5' + +VITE_VAPID_KEY='BMwDtsjNmBgRePxytobo7zUvOiwm1k9RWNSl-8O1jBqvSJRBCIjcx3ZaBj-veAA3eVVmGeoVWOQtQMmAWTZif_A' \ No newline at end of file From 9c1063fb94384f9c2e3cc0f41e0ffddb244e105a Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 02:39:33 +0900 Subject: [PATCH 081/180] =?UTF-8?q?fix=20:=20firebase=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=A5=BC=20=EC=9D=B8=EC=8B=9D=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/firebase-messaging-sw.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/firebase-messaging-sw.ts b/src/firebase-messaging-sw.ts index d2f80395..73ca5bed 100644 --- a/src/firebase-messaging-sw.ts +++ b/src/firebase-messaging-sw.ts @@ -42,17 +42,17 @@ // } // requestPermission() -// import 'firebase/messaging' +import 'firebase/messaging' import firebase from 'firebase' const firebaseConfig = { - apiKey: import.meta.env.VITE_apiKey, - authDomain: import.meta.env.VITE_authDomain, - projectId: import.meta.env.VITE_projectId, - storageBucket: import.meta.env.VITE_storageBucket, - messagingSenderId: import.meta.env.VITE_messagingSenderId, - appId: import.meta.env.VITE_appId, - measurementId: import.meta.env.VITE_measurementId, + apiKey: 'AIzaSyBPfx4R0QsrFbS5rMv38dM1B7iPR4bUxt4', + authDomain: 'coffee-meet-d295d.firebaseapp.com', + projectId: 'coffee-meet-d295d', + storageBucket: 'coffee-meet-d295d.appspot.com', + messagingSenderId: '716922226162', + appId: '1:716922226162:web:1f3cbb9069cd525323d76b', + measurementId: 'G-2YB8FMSHM5', } firebase.initializeApp(firebaseConfig) @@ -66,7 +66,10 @@ export async function getToken() { const token = await messaging .requestPermission() .then(function () { - return messaging.getToken({ vapidKey: import.meta.env.VITE_VAPID_KEY }) + return messaging.getToken({ + vapidKey: + 'BMwDtsjNmBgRePxytobo7zUvOiwm1k9RWNSl-8O1jBqvSJRBCIjcx3ZaBj-veAA3eVVmGeoVWOQtQMmAWTZif_A', + }) }) .then(function (token) { messaging.onMessage((payload) => { From 179059f1458090052300328bd0b7c046d5761af3 Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 02:49:37 +0900 Subject: [PATCH 082/180] =?UTF-8?q?chore=20:=20data.data=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=EC=A4=91=EB=B3=B5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/chatList/ChatList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/chatList/ChatList.tsx b/src/pages/chatList/ChatList.tsx index f2e4f5f6..ce98b2b5 100644 --- a/src/pages/chatList/ChatList.tsx +++ b/src/pages/chatList/ChatList.tsx @@ -52,10 +52,10 @@ const ChatList = () => { /> {isSuccess && ( - {data?.data.length == 0 ? ( + {data?.length == 0 ? ( '이전 채팅방이 없습니다!' ) : ( - + )} )} From ef3a2a51f3b301d44cc6a2cb90fed194c0999b27 Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 02:50:58 +0900 Subject: [PATCH 083/180] =?UTF-8?q?chore=20:=20prettier=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/firebase-messaging-sw.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/firebase-messaging-sw.ts b/src/firebase-messaging-sw.ts index be7eb292..73ca5bed 100644 --- a/src/firebase-messaging-sw.ts +++ b/src/firebase-messaging-sw.ts @@ -53,7 +53,6 @@ const firebaseConfig = { messagingSenderId: '716922226162', appId: '1:716922226162:web:1f3cbb9069cd525323d76b', measurementId: 'G-2YB8FMSHM5', - } firebase.initializeApp(firebaseConfig) From a82f94d061e32578481caeebf96ae9a6dc61d5e9 Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 02:56:15 +0900 Subject: [PATCH 084/180] =?UTF-8?q?chore=20:=20Bearer=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/axios.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apis/axios.ts b/src/apis/axios.ts index 0547fcfe..a2ca2d90 100644 --- a/src/apis/axios.ts +++ b/src/apis/axios.ts @@ -15,7 +15,7 @@ axiosAPI.interceptors.request.use( // 요청 바로 직전 // axios 설정값에 대해 작성합니다. - config.headers['Authorization'] = `Bearer ${localStorage.getItem('jwt')}` + config.headers['Authorization'] = `${localStorage.getItem('jwt')}` return config }, From 682c844ea384995d4ee464bac60defd3dd0c8d0c Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 03:09:22 +0900 Subject: [PATCH 085/180] =?UTF-8?q?chore=20:=20useQuery=20=EC=95=88?= =?UTF-8?q?=EC=9D=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20fetching=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=BD=9C=EB=B0=B1=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/chatList/ChatList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/chatList/ChatList.tsx b/src/pages/chatList/ChatList.tsx index ce98b2b5..139554bc 100644 --- a/src/pages/chatList/ChatList.tsx +++ b/src/pages/chatList/ChatList.tsx @@ -17,7 +17,7 @@ const ChatList = () => { const isDarkMode = useThemeStore((state) => state.isDarkMode) const navigate = useNavigate() - const { data, isSuccess } = useQuery(['chatRoomList'], ChatListApi.GET_CHAT_LIST) + const { data, isSuccess } = useQuery(['chatRoomList'], () => ChatListApi.GET_CHAT_LIST()) const containerVariants = { initial: { opacity: 0 }, From f096a9f95a3c79a1307a0b668421191a13f6838d Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 03:18:46 +0900 Subject: [PATCH 086/180] =?UTF-8?q?chore=20:=20api=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EB=90=9C=20=EA=B2=83=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/chatList/ChatListApi.ts | 2 +- src/apis/chatting/chattingApi.ts | 4 ++-- src/pages/chatListDetail/ChatListDetail.tsx | 2 +- src/pages/chatting/Chatting.tsx | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/apis/chatList/ChatListApi.ts b/src/apis/chatList/ChatListApi.ts index 16cf9c2f..908079b4 100644 --- a/src/apis/chatList/ChatListApi.ts +++ b/src/apis/chatList/ChatListApi.ts @@ -3,7 +3,7 @@ import { axiosAPI } from '@/apis/axios' const ChatListApi = { // TODO: zustand로 AccessToken 받아서 요청하기 (백엔드 개발 완료 후) GET_CHAT_LIST: async () => { - const response = await axiosAPI.get(`/v1/histories`) + const response = await axiosAPI.get(`/v1/chatting/room/histories`) return response.data }, } diff --git a/src/apis/chatting/chattingApi.ts b/src/apis/chatting/chattingApi.ts index 6fab185d..da6a3ae6 100644 --- a/src/apis/chatting/chattingApi.ts +++ b/src/apis/chatting/chattingApi.ts @@ -2,8 +2,8 @@ import { axiosAPI } from '@/apis/axios' import { Messages } from '@/apis/chatting/chattingType' export const ChattingApi = { - GET_DETAIL_MESSAGES: async (): Promise => { - const response = await axiosAPI.get(`/v1/chatting/rooms/1`) + GET_DETAIL_MESSAGES: async (chatroomId: string): Promise => { + const response = await axiosAPI.get(`/v1/chatting/rooms/${chatroomId}`) return response.data }, } diff --git a/src/pages/chatListDetail/ChatListDetail.tsx b/src/pages/chatListDetail/ChatListDetail.tsx index 86c2a1e2..2e03ef96 100644 --- a/src/pages/chatListDetail/ChatListDetail.tsx +++ b/src/pages/chatListDetail/ChatListDetail.tsx @@ -29,7 +29,7 @@ const ChatListDetail = () => { } const getChattingHistory = async () => { await axiosAPI - .get(`/api/v1/histories/${chatroomId}`) + .get(`/v1/chatting/room/histories/${chatroomId}`) .then((response) => { setMessages(response.data) }) diff --git a/src/pages/chatting/Chatting.tsx b/src/pages/chatting/Chatting.tsx index e8625b40..dbcb3317 100644 --- a/src/pages/chatting/Chatting.tsx +++ b/src/pages/chatting/Chatting.tsx @@ -43,7 +43,7 @@ const Chatting = () => { const getDetailMessages = async () => { try { - const response = await ChattingApi.GET_DETAIL_MESSAGES() + const response = await ChattingApi.GET_DETAIL_MESSAGES(chatroomId) console.log(response) setMessages(response) } catch (error) { @@ -95,7 +95,7 @@ const Chatting = () => { } const deleteChattingRoom = async () => { navigate('/') - return await axiosAPI.delete(`/v1/chatrooms/${chatroomId}`) + return await axiosAPI.delete(`/v1/chatting/rooms/${chatroomId}`) } const navigateHome = () => { navigate('/') From d2a004e65917509b730ecd63952b9ef783b50473 Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 14:31:56 +0900 Subject: [PATCH 087/180] =?UTF-8?q?fix=20:=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=ED=9E=88=EC=8A=A4=ED=86=A0=EB=A6=AC=20get=ED=95=B4=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/chatList/ChatRoomBubbles.tsx | 12 ++++---- src/pages/chatList/ChatList.tsx | 33 ++++++++++++++++----- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/components/chatList/ChatRoomBubbles.tsx b/src/components/chatList/ChatRoomBubbles.tsx index 34626013..e68ccfd0 100644 --- a/src/components/chatList/ChatRoomBubbles.tsx +++ b/src/components/chatList/ChatRoomBubbles.tsx @@ -21,9 +21,9 @@ const StyledChatRoomBubbleWrapper = styled.div` ` const ChatRoomBubbleWrapper = styled.span`` type ChatRoom = { - id: string - title: string - participants: string[] + roomId: number + roomName: string + users: string[] createdAt: string } @@ -41,12 +41,12 @@ const ChatRoomBubbles = ({ chatRoomList, isDarkMode }: ChatRoomBubblesProps) => { - navigate('/chat-list-detail', { state: { chatroomId: chatRoom.id } }) + navigate('/chat-list-detail', { state: { chatroomId: chatRoom.roomId } }) }} > diff --git a/src/pages/chatList/ChatList.tsx b/src/pages/chatList/ChatList.tsx index 139554bc..e9a251ab 100644 --- a/src/pages/chatList/ChatList.tsx +++ b/src/pages/chatList/ChatList.tsx @@ -1,9 +1,9 @@ import styled from '@emotion/styled' -import { useQuery } from '@tanstack/react-query' import { motion } from 'framer-motion' +import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' -import ChatListApi from '@/apis/chatList/ChatListApi' +import { axiosAPI } from '@/apis/axios' import ChatRoomBubbles from '@/components/chatList/ChatRoomBubbles' import BackChevron from '@/components/common/BackChevron' import GradationBackground from '@/components/common/GradationBackground' @@ -13,11 +13,27 @@ import PageHeader from '@/components/common/PageHeader' import Spacing from '@/components/common/Spacing' import useThemeStore from '@/store/ThemeStore' +type ChatHistoryType = { + roomId: number + roomName: string + users: string[] + createdAt: string +} + const ChatList = () => { const isDarkMode = useThemeStore((state) => state.isDarkMode) const navigate = useNavigate() - - const { data, isSuccess } = useQuery(['chatRoomList'], () => ChatListApi.GET_CHAT_LIST()) + const [chatHistoryData, setChatHistoryData] = useState([]) + const getChatHistoryData = async () => { + await axiosAPI + .get('/v1/chatting/room/histories') + .then((response) => { + setChatHistoryData(response.data.chatRoomHistories) + }) + .catch((err) => { + console.log(err) + }) + } const containerVariants = { initial: { opacity: 0 }, @@ -27,6 +43,9 @@ const ChatList = () => { transition: { delay: 0, duration: 0.5 }, }, } + useEffect(() => { + getChatHistoryData() + }, []) return ( @@ -50,12 +69,12 @@ const ChatList = () => { zIndex: 1, }} /> - {isSuccess && ( + {chatHistoryData && ( - {data?.length == 0 ? ( + {chatHistoryData?.length == 0 ? ( '이전 채팅방이 없습니다!' ) : ( - + )} )} From 7a577a4afcb84860ca53e2482af40c7347cc4a54 Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 14:41:18 +0900 Subject: [PATCH 088/180] =?UTF-8?q?fix=20:=20response=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20email=20=EB=8C=80=EC=8B=A0=20=ED=9A=8C=EC=82=AC?= =?UTF-8?q?=EB=AA=85=EC=9D=84=20=EB=B3=B4=EC=97=AC=EC=A3=BC=EB=8A=94=20?= =?UTF-8?q?=EA=B2=83=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/profile/ProfileDefault.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/pages/profile/ProfileDefault.tsx b/src/pages/profile/ProfileDefault.tsx index 16835b7c..6c33aaf4 100644 --- a/src/pages/profile/ProfileDefault.tsx +++ b/src/pages/profile/ProfileDefault.tsx @@ -28,10 +28,8 @@ import { palette } from '@/styles/palette' type MyProfileData = { nickname: string - email: string profileImageUrl: string - reportedCount: number - sanctionPeriod: string + companyName: string department: string interests: string[] } @@ -41,10 +39,8 @@ const ProfileDefault = () => { const isDarkMode = useThemeStore((store) => store.isDarkMode) const [myProfileData, setMyProfileData] = useState({ nickname: '', - email: '', profileImageUrl: '', - reportedCount: 0, - sanctionPeriod: '', + companyName: '', department: '', interests: [''], }) @@ -135,7 +131,7 @@ const ProfileDefault = () => { - {myProfileData && myProfileData.email} + {myProfileData && myProfileData.companyName} Date: Mon, 27 Nov 2023 15:38:17 +0900 Subject: [PATCH 089/180] =?UTF-8?q?fix=20:=20api=20uri=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/home/Card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/home/Card.tsx b/src/components/home/Card.tsx index 090e7aaa..b3bbbce3 100644 --- a/src/components/home/Card.tsx +++ b/src/components/home/Card.tsx @@ -60,7 +60,7 @@ const Card = ({ isDarkMode }: CardProps) => { timerRef.current.stop() } setCurrentState('IDLE') - await axiosAPI.post('/v1/matchings/cancel') + await axiosAPI.post('/v1/matching/cancel') } const formatTime = (time: number) => { From 56247daa630040abc1d6e27e3a8a04b3c06dd4db Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 16:32:44 +0900 Subject: [PATCH 090/180] =?UTF-8?q?fix=20:=20matchingStartedAt=20length=20?= =?UTF-8?q?=EC=9D=B8=EC=8B=9D=20=EB=AA=BB=ED=95=98=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/home/Card.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/home/Card.tsx b/src/components/home/Card.tsx index b3bbbce3..5db8f8aa 100644 --- a/src/components/home/Card.tsx +++ b/src/components/home/Card.tsx @@ -97,13 +97,16 @@ const Card = ({ isDarkMode }: CardProps) => { console.log(err) }) } + useEffect(() => { + getCurrentMatchingState() + }, [currentState]) useEffect(() => { if (currentState === 'MATCHING') { //startTime에 서버에서 시작시간 받아와 new Date객체 안에 넣은 후 스트링으로 바꿔서 Date.parse한 후 Date.now()에서 뺄 것 const date = new Date(matchingStartedAt) date.setHours(date.getHours() + 9) - const startTime = matchingStartedAt.length === 0 ? Date.now() : Date.parse(date.toString()) + const startTime = !matchingStartedAt ? Date.now() : Date.parse(date.toString()) const updateTimer = () => { const elapsedTime = Date.now() - startTime @@ -123,10 +126,6 @@ const Card = ({ isDarkMode }: CardProps) => { } }, [isMatching]) - useEffect(() => { - getCurrentMatchingState() - }, [currentState]) - return ( From 2a0d54173b4c253fcba359db70f5e29aff20def2 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:39:21 +0900 Subject: [PATCH 091/180] =?UTF-8?q?feature:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20API=20=EC=9A=94=EC=B2=AD=20(1)=20?= =?UTF-8?q?(#175)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: AdminAppHeader 컴포넌트 추가 * style: AdminNavigationBar 컴포넌트 생성 * style: AdminNavigationBar 색 변경 * feat: AdminTabs 컴포넌트 추가 * refactor: 컴포넌트 변수명 변경 * refactor: 컴포넌트 변수명 변경 * feat: AdminApprovalList 컴포넌트 추가 * feat: outerwrapper 제거 * feat: AdminReportListRow 컴포넌트 예시 추가 * feat: AdminReportList 컴포넌트 예시 추가 * feat: AdminTabs 컴포넌트로 다른 컴포넌트들 연결 및 디자인 수정, 변수명 수정 * refactor: admin 컴포넌트 이전 * refactor: eslint 에러 수정 * refactor: import 경로 변경 * feat: AdminApprovalInfo 컴포넌트 1 * style: 예시 이미지 추가 * feat: AdminApprovalInfo 컴포넌트 추가 1 * feat: input text fontweight 속성 추가 * style: Input 속성 추가 * style: AdminApprovalInfo 컴포넌트 디자인 2 * style: AdminReportInfo 컴포넌트 1 * refactor: props명 수정 * style: AdminReportInfo 컴포넌트 1 * style: AdminReportInfoListRow 컴포넌트 * feat: useModal 컴포넌트 적용, 기타 스타일 수정 * feat: AdminReportInfo 컴포넌트 userModal 적용 * refactor: 불필요 컴포넌트 삭제 * refactor: 이름 수정 * feat: AdminReportListRow 속성 추가 * feat: 관리자페이지 신고자 목록 클릭 시, 정보 출력 * feat: AdminReportList 특정 목록 클릭 시, 해당 AdminReportInfo 출력 * feat: AdminReportList mockdata 추가, 함수 연결 * refactor: 이름 예시 변경 * refactor: 변수명 수정 (adminapprovalinfo와 구분) * feat: AdminTabs 승인 탭 추가 * refactor: 변수명 수정(구별필요) * feat: 승인 페이지 이름 적용, 불필요 컴포넌트 제거 * refactor: mock data 적용. 변수명 수정. * feat: 승인페이지 mock data 적용. 목록 클릭 함수 추가. interface 추가. * feat: adminapprovallistrow onclick 함수 속성 추가 * feat: msw handlers interface 파일들 * feat: 관리자페이지 - MSW 활용한 API request, response 명세. API 테스트. * style: 오류 관련 주석 처리 * feat: AdminPageHeader * feat: 관리자페이지 승인요청 사용자들 목록 불러오기 MSW API 요청 코드 완료. 테스트 완료. * feat: 관리자페이지 신고당한자 목록 API 코드 추가. 테스트 완료. * feat: 관리자페이지 승인요청자 세부정보 출력 1 * refactor: 불필요 코드 삭제 * refactor: 불필요코드 제거 2 * feat: 숨겨진 관리자 로그인 버튼 추가 * feat: 관리자 페이지 라우터 설정 * feat: 관리자페이지 로그인 MSW API 요청 코드 추가, 관리자페이지 로그인 UI(react hook form) * refactor: 타입 오류 임시 주석처리 * refactor: import문 삭제 * feat: 관리자페이지 로그인 api 요청 1 * feat: 관리자페이지 로그인 API 요청 2 * feat: 관리자 로그인 페이지 post API 요청 * feat: 관리자 로그인 페이지 이동 * feat: 관리자 페이지 이동 * feat: 관리자 로그인 페이지 - 피그마 디자인, 구현 * feat: 문구수정 * refactor: useNavigate, api 요청 주소 변경 * feat: 관리자로그인페이지 디자인 수정 - emotion, register속성 적용 * feat: 관리자 승인 처리 API 요청. * feat: 관리자 승인요청, 신고 API 요청 * refactor: dev pull * feat: Admin Inquiry 추가 * style: 문의페이지 아이콘 추가 * refactor: import 절대 경로 수정 * refactor: MSW handler /api 경로 삭제 * feat: 관리자페이지 문의 API, UI - 1 * feat: 관리자 페이지 문의 상세 페이지 추가 * feat: 관리자 페이지 문의 UI, API 추가 2 * feat: 관리자 문의 컴포넌트 수정 * feat: 관리자 페이지 탭 구조 개편 * feat: 불필요 라우팅 삭제 * feat: 문의 페이지 헤더 추가 * feat: 관리자 신고페이지 목록 * feat: 관리자 페이지 개편 * feat: 변수명사용-빌드 * refactor: import문 정렬 (dev pull) * style: 로그인 페이지 크기 변경 (스크롤X) * feat: 관리자 로그인 API 요청 성공 * feat: mock 데이터 수정. substring 변경. * feat: UI 내용 변경. mock 데이터 수정. * feat: 관리자 로그인 비밀번호 '안보임' 처리 * feat: UI 수정사항 반영 * feat: 인증 거절, 신고 누적 알림창 추가. 함수 추가. * feat: 관리자 로그인 API 변경 --- src/apis/adminLogin/AdminLoginApi.ts | 4 +- .../common/Buttons/IconButton/KakaoButton.tsx | 8 +- .../common/Buttons/IconButton/NaverButton.tsx | 4 +- src/components/common/HeroImage/index.tsx | 3 +- .../common/ListRow/AdminInquiryList.tsx | 2 +- .../common/ListRow/AdminReportList.tsx | 7 +- .../common/ListRow/AdminReportListRow.tsx | 2 +- .../common/ListRow/AdminReportersList.tsx | 11 ++- src/mocks/handlers.ts | 88 ++++++------------- src/mocks/handlersInterface.ts | 3 +- src/pages/admin/AdminLogin.tsx | 23 +++-- .../admin/components/AdminApprovalInfo.tsx | 57 ++++++++++-- .../admin/components/AdminInquiryInfo.tsx | 6 +- .../admin/components/AdminReportInfo.tsx | 8 +- src/pages/admin/components/AdminTabs.tsx | 2 +- src/pages/login/Login.tsx | 13 ++- 16 files changed, 131 insertions(+), 110 deletions(-) diff --git a/src/apis/adminLogin/AdminLoginApi.ts b/src/apis/adminLogin/AdminLoginApi.ts index 6b2d8666..828c9de9 100644 --- a/src/apis/adminLogin/AdminLoginApi.ts +++ b/src/apis/adminLogin/AdminLoginApi.ts @@ -1,8 +1,8 @@ import { axiosAPI } from '@/apis/axios' interface AdminLoginData { - adminId: string - adminPw: string + id: string + password: string } const AdminLoginAPI = { diff --git a/src/components/common/Buttons/IconButton/KakaoButton.tsx b/src/components/common/Buttons/IconButton/KakaoButton.tsx index 827b98f7..e470780e 100644 --- a/src/components/common/Buttons/IconButton/KakaoButton.tsx +++ b/src/components/common/Buttons/IconButton/KakaoButton.tsx @@ -8,10 +8,10 @@ import { palette } from '@/styles/palette' const KakaoButton = ({ moveToOAuthProvider }: OAuthButtonProps) => ( - + void }>` - width: 320px; - height: 60px; + width: 300px; + height: 50px; background-color: ${(props) => (props.buttonTheme === 'naver' ? palette.GREEN : palette.YELLOW)}; border-radius: 15px; display: flex; diff --git a/src/components/common/Buttons/IconButton/NaverButton.tsx b/src/components/common/Buttons/IconButton/NaverButton.tsx index 1ec3a3c0..8453820f 100644 --- a/src/components/common/Buttons/IconButton/NaverButton.tsx +++ b/src/components/common/Buttons/IconButton/NaverButton.tsx @@ -9,10 +9,10 @@ const NaverButton = ({ moveToOAuthProvider }: OAuthButtonProps) => { return ( - + return ( - + {isSuccess && inquiryDatas.map((inquiryListData: InquiryListData, index: number) => ( diff --git a/src/components/common/ListRow/AdminReportList.tsx b/src/components/common/ListRow/AdminReportList.tsx index 1d054cc5..ef055680 100644 --- a/src/components/common/ListRow/AdminReportList.tsx +++ b/src/components/common/ListRow/AdminReportList.tsx @@ -10,8 +10,9 @@ interface AdminReportListProps { onReportSelect: (nickname: string) => void } interface ReportListData { + chattingRoomName: string reportedUserName: string - reportCount: string + reportedDate: string } const AdminReportList = ({ onReportSelect }: AdminReportListProps) => { const { data, isSuccess } = useQuery(['ReportedUserList'], AdminReportAPI.GET_REPORT_LIST) @@ -37,8 +38,8 @@ const AdminReportList = ({ onReportSelect }: AdminReportListProps) => { reportedNickname={reportListData.reportedUserName} key={index} height={71} - chattingRoomName={`채팅방${index.toString()}`} - reportedDate={'2021.07.30'} + chattingRoomName={reportListData.chattingRoomName} + reportedDate={reportListData.reportedDate} isDarkMode={false} onClick={() => handlePersonReported(reportListData.reportedUserName)} /> diff --git a/src/components/common/ListRow/AdminReportListRow.tsx b/src/components/common/ListRow/AdminReportListRow.tsx index edba70c4..be782897 100644 --- a/src/components/common/ListRow/AdminReportListRow.tsx +++ b/src/components/common/ListRow/AdminReportListRow.tsx @@ -22,7 +22,7 @@ const AdminReportListRow = ({ onClick, }: AdminReportListRowProps) => { const displayChattingRoomName = - chattingRoomName.length > 4 ? `${chattingRoomName.substring(0, 4)}...` : chattingRoomName + chattingRoomName.length > 4 ? `${chattingRoomName.substring(0, 4)}..` : chattingRoomName const displayReportedNickname = reportedNickname.length > 3 ? `${reportedNickname.substring(0, 3)}..` : reportedNickname return ( diff --git a/src/components/common/ListRow/AdminReportersList.tsx b/src/components/common/ListRow/AdminReportersList.tsx index 2ba6907c..51845adb 100644 --- a/src/components/common/ListRow/AdminReportersList.tsx +++ b/src/components/common/ListRow/AdminReportersList.tsx @@ -43,10 +43,17 @@ const AdminReportersList = ({ const handleAccumulationAddBtn = () => { openModal({ type: 'confirm', - mainText: '신고를 누적하시겠습니까?', + mainText: '신고를 누적 하시겠습니까?', okFunc: onReportAddCount, }) } + const handleAccumulationIgnoreBtn = () => { + openModal({ + type: 'confirm', + mainText: '신고를 무시 하시겠습니까?', + okFunc: onReportIgnore, + }) + } const { data, isSuccess } = useQuery(['ReporterUserList'], AdminReportAPI.GET_REPORTERS_LIST) const handlePersonReported = (nickname: string) => { onReportSelect(nickname) @@ -75,7 +82,7 @@ const AdminReportersList = ({ {'누적 추가'} - + {'무시'} diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 51e8dbd3..c282afba 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -386,31 +386,12 @@ export const handlers = [ // 승인 목록 API 핸들러 http.get('/v1/users/approvals', () => { const approvals: Approval[] = [ - { approvalRequestUser: '박상민', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '유명한', approvalRequestUserStatus: '대기 중' }, { approvalRequestUser: '박은지', approvalRequestUserStatus: '대기 중' }, { approvalRequestUser: '주다현', approvalRequestUserStatus: '대기 중' }, { approvalRequestUser: '남궁호수', approvalRequestUserStatus: '대기 중' }, + { approvalRequestUser: '박상민', approvalRequestUserStatus: '대기 중' }, { approvalRequestUser: '우창욱', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, - { approvalRequestUser: '홍길동', approvalRequestUserStatus: '대기 중' }, ] return HttpResponse.json({ approvals }) }), @@ -465,29 +446,24 @@ export const handlers = [ // 신고 목록 API 핸들러 http.get('/v1/reports', () => { const reports: Reports[] = [ - { reportedUserName: '유명한', reportCount: 1 }, - { reportedUserName: '박상민', reportCount: 2 }, - { reportedUserName: '박은지', reportCount: 1 }, - { reportedUserName: '주다현', reportCount: 1 }, - { reportedUserName: '우창욱', reportCount: 1 }, - { reportedUserName: '남궁호수', reportCount: 1 }, - { reportedUserName: '홍길동', reportCount: 0 }, - { reportedUserName: '홍길동', reportCount: 2 }, - { reportedUserName: '홍길동', reportCount: 3 }, - { reportedUserName: '홍길동', reportCount: 1 }, - { reportedUserName: '홍길동', reportCount: 2 }, - { reportedUserName: '홍길동', reportCount: 3 }, - { reportedUserName: '홍길동', reportCount: 1 }, - { reportedUserName: '홍길동', reportCount: 0 }, + { chattingRoomName: '채팅방1', reportedUserName: '유명한', reportedDate: '2023.11.23' }, + { chattingRoomName: '채팅방2', reportedUserName: '박상민', reportedDate: '2023.11.22' }, + { chattingRoomName: '채팅방3', reportedUserName: '박은지', reportedDate: '2023.11.22' }, + { chattingRoomName: '채팅방4', reportedUserName: '주다현', reportedDate: '2023.11.15' }, + { chattingRoomName: '채팅5', reportedUserName: '남궁호수', reportedDate: '2023.10.29' }, + { chattingRoomName: '채팅방6', reportedUserName: '우창욱', reportedDate: '2023.10.26' }, + { chattingRoomName: '채팅방칠', reportedUserName: '유명한', reportedDate: '2023.10.26' }, + { chattingRoomName: '채팅방팔', reportedUserName: '박상민', reportedDate: '2023.10.26' }, + { chattingRoomName: '채팅방9', reportedUserName: '박은지', reportedDate: '2023.10.23' }, ] return HttpResponse.json({ reports }) }), // 신고자 목록 API 핸들러 http.get('/v1/reporters', () => { const reporters: Reporters[] = [ - { reporterUserName: '유명한', reportedDate: '2021.07.30' }, - { reporterUserName: '박상민', reportedDate: '2021.07.30' }, - { reporterUserName: '박은지', reportedDate: '2021.07.30' }, + { reporterUserName: '박은지', reportedDate: '2023.11.23' }, + { reporterUserName: '유명한', reportedDate: '2021.11.21' }, + { reporterUserName: '박상민', reportedDate: '2021.11.15' }, ] return HttpResponse.json({ reporters }) }), @@ -528,31 +504,17 @@ export const handlers = [ // 문의 목록 API 핸들러 http.get('/v1/users/inquiries', () => { const inquiries: Inquiry[] = [ - { inquiryRequestUser: '박상민', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '박은지', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '주다현', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '남궁호수', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '우창욱', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, - { inquiryRequestUser: '홍길동', inquiryRequestDate: '2023.7.30' }, + { inquiryRequestUser: '박상민', inquiryRequestDate: '2023.11.22' }, + { inquiryRequestUser: '박은지', inquiryRequestDate: '2023.11.22' }, + { inquiryRequestUser: '주다현', inquiryRequestDate: '2023.11.22' }, + { inquiryRequestUser: '남궁호수', inquiryRequestDate: '2023.11.21' }, + { inquiryRequestUser: '유명한', inquiryRequestDate: '2023.11.21' }, + { inquiryRequestUser: '박상민', inquiryRequestDate: '2023.11.17' }, + { inquiryRequestUser: '남궁호수', inquiryRequestDate: '2023.11.17' }, + { inquiryRequestUser: '주다현', inquiryRequestDate: '2023.11.17' }, + { inquiryRequestUser: '박은지', inquiryRequestDate: '2023.11.17' }, + { inquiryRequestUser: '우창욱', inquiryRequestDate: '2023.11.10' }, + { inquiryRequestUser: '유명한', inquiryRequestDate: '2023.11.10' }, ] return HttpResponse.json({ inquiries }) }), diff --git a/src/mocks/handlersInterface.ts b/src/mocks/handlersInterface.ts index d661d4c6..96b5aee1 100644 --- a/src/mocks/handlersInterface.ts +++ b/src/mocks/handlersInterface.ts @@ -14,8 +14,9 @@ export interface ReportResult { result: string } export interface Reports { + chattingRoomName: string reportedUserName: string - reportCount: number + reportedDate: string } export interface Reporters { reporterUserName: string diff --git a/src/pages/admin/AdminLogin.tsx b/src/pages/admin/AdminLogin.tsx index 0371c716..02357f69 100644 --- a/src/pages/admin/AdminLogin.tsx +++ b/src/pages/admin/AdminLogin.tsx @@ -7,9 +7,11 @@ import { useNavigate } from 'react-router-dom' import AdminLoginAPI from '@/apis/adminLogin/AdminLoginApi' import Spacing from '@/components/common/Spacing' import { Text } from '@/components/common/Text' +import useToast from '@/hooks/useToast' import { palette } from '@/styles/palette' const AdminLogin = () => { + const { showToast } = useToast() // form 제출 -> API 요청 -> 결과값에 따라 페이지 이동 const { register, handleSubmit } = useForm() const [adminData, setAdminData] = useState('') @@ -17,10 +19,15 @@ const AdminLogin = () => { const navigate = useNavigate() const mutation = useMutation(AdminLoginAPI.POST_ADMIN_LOGIN, { - onSuccess: (data) => { - if (data.adminLoginInfo.adminLoginResult == 'error') { - navigate('/admin') - } + onSuccess: () => { + navigate('/admin') + }, + onError: () => { + showToast({ + message: '올바른 id, password를 입력해주세요.', + type: 'warning', + isDarkMode: false, + }) }, }) @@ -47,13 +54,17 @@ const AdminLogin = () => { - + - + diff --git a/src/pages/admin/components/AdminApprovalInfo.tsx b/src/pages/admin/components/AdminApprovalInfo.tsx index 99660fda..8cabc74b 100644 --- a/src/pages/admin/components/AdminApprovalInfo.tsx +++ b/src/pages/admin/components/AdminApprovalInfo.tsx @@ -44,21 +44,28 @@ const AdminApprovalInfo = ({ selectedApprovalNickname }: AdminApprovalInfoProps) const handleAcceptCertificationBtn = () => { openModal({ type: 'confirm', - mainText: '인증을 수락하시겠습니까?', + mainText: '인증을 수락 하시겠습니까?', okFunc: onAcceptAdminApproval, }) } + const handleRejectCertificationBtn = () => { + openModal({ + type: 'confirm', + mainText: '인증을 거절 하시겠습니까?', + okFunc: onRejectAdminApproval, + }) + } return ( - + {'이메일'} - + - - + + + + + {'회사명'} + + + + {`홍길동 주식회사`} + {/* {`${selectedApprovalNickname}의 이메일, userId를 받을 수 있음 (API 요청)`} */} + + + + + + {'부서명'} + + + + {`품질경영팀`} + {/* {`${selectedApprovalNickname}의 이메일, userId를 받을 수 있음 (API 요청)`} */} + + + {'인증 수락'} - + {'거절'} @@ -107,18 +146,18 @@ const StyledAdminApproveFormContainer = styled.div` background-color: ${palette.WHITE}; overflow: scroll; ` -const StyledEmailTextWrapper = styled.div` +const StyledTextWrapper = styled.div` padding: 16px 34px 16px 34px; background-color: ${palette.SKY_BLUE}; width: 246px; border-radius: 20px; text-align: center; ` -const StyledEmailContainer = styled.div` +const StyledContainer = styled.div` display: flex; justify-content: space-around; align-items: center; - padding: 40px 0; + padding: 12px 0; ` const StyledImage = styled.img` width: 100%; diff --git a/src/pages/admin/components/AdminInquiryInfo.tsx b/src/pages/admin/components/AdminInquiryInfo.tsx index 2be4a96d..3f8b2434 100644 --- a/src/pages/admin/components/AdminInquiryInfo.tsx +++ b/src/pages/admin/components/AdminInquiryInfo.tsx @@ -23,7 +23,7 @@ const AdminInquiryInfo = (selectedInquiryNickname: AdminInquiryInfoProps) => { return ( <> - + @@ -35,14 +35,14 @@ const AdminInquiryInfo = (selectedInquiryNickname: AdminInquiryInfoProps) => { - {'회사 인증 왜 안해주세요?'} + {'회사 인증 부탁드립니다!'} - {'2023.7.30'} + {'2023.11.22'} diff --git a/src/pages/admin/components/AdminReportInfo.tsx b/src/pages/admin/components/AdminReportInfo.tsx index cc9f3a4d..94fb8d8f 100644 --- a/src/pages/admin/components/AdminReportInfo.tsx +++ b/src/pages/admin/components/AdminReportInfo.tsx @@ -25,13 +25,13 @@ const AdminReportInfo = ({ selectedReporterNickname }: AdminReportInfoProps) => @@ -56,7 +56,7 @@ const AdminReportInfo = ({ selectedReporterNickname }: AdminReportInfoProps) => {' '} - {'채팅방 내 잠수 기네스북 기록을 세웠어요 어쩌구 저쩌구'} + {'채팅방 내 잠수 기네스북 기록을 세웠어요'} diff --git a/src/pages/admin/components/AdminTabs.tsx b/src/pages/admin/components/AdminTabs.tsx index 5dc56656..51b52f58 100644 --- a/src/pages/admin/components/AdminTabs.tsx +++ b/src/pages/admin/components/AdminTabs.tsx @@ -78,7 +78,7 @@ const AdminTabs = () => { setActiveTab('inquiry')}> - {'불편 사항 처리'} + {'문의 사항 처리'} setActiveTab('report')}> diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index e25c4fbc..927a385b 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -45,11 +45,11 @@ const Login = () => { } `} /> - + {'☕️커피밋'} { {'관리자 로그인'} - + {'회사의 경계를 넘어, '} { } `} /> - + {' 새로운 대화의 세계를 탐험하세요!'} { const StyledLoginOuterWrapper = styled.div` background-color: ${palette.SKY_BLUE}; - height: 100%; + height: 85%; display: flex; flex-direction: column; justify-content: center; align-items: center; - overflow: scroll; ` const StyledOauthWrapper = styled.div` From f1fd360d5756f4473fad2ff9489678c06bd52186 Mon Sep 17 00:00:00 2001 From: from1to2 <124763142+from1to2@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:39:32 +0900 Subject: [PATCH 092/180] =?UTF-8?q?style:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=81=AC=EA=B8=B0=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: AdminAppHeader 컴포넌트 추가 * style: AdminNavigationBar 컴포넌트 생성 * style: AdminNavigationBar 색 변경 * feat: AdminTabs 컴포넌트 추가 * refactor: 컴포넌트 변수명 변경 * refactor: 컴포넌트 변수명 변경 * feat: AdminApprovalList 컴포넌트 추가 * feat: outerwrapper 제거 * feat: AdminReportListRow 컴포넌트 예시 추가 * feat: AdminReportList 컴포넌트 예시 추가 * feat: AdminTabs 컴포넌트로 다른 컴포넌트들 연결 및 디자인 수정, 변수명 수정 * refactor: admin 컴포넌트 이전 * refactor: eslint 에러 수정 * refactor: import 경로 변경 * feat: AdminApprovalInfo 컴포넌트 1 * style: 예시 이미지 추가 * feat: AdminApprovalInfo 컴포넌트 추가 1 * feat: input text fontweight 속성 추가 * style: Input 속성 추가 * style: AdminApprovalInfo 컴포넌트 디자인 2 * style: AdminReportInfo 컴포넌트 1 * refactor: props명 수정 * style: AdminReportInfo 컴포넌트 1 * style: AdminReportInfoListRow 컴포넌트 * feat: useModal 컴포넌트 적용, 기타 스타일 수정 * feat: AdminReportInfo 컴포넌트 userModal 적용 * refactor: 불필요 컴포넌트 삭제 * refactor: 이름 수정 * feat: AdminReportListRow 속성 추가 * feat: 관리자페이지 신고자 목록 클릭 시, 정보 출력 * feat: AdminReportList 특정 목록 클릭 시, 해당 AdminReportInfo 출력 * feat: AdminReportList mockdata 추가, 함수 연결 * refactor: 이름 예시 변경 * refactor: 변수명 수정 (adminapprovalinfo와 구분) * feat: AdminTabs 승인 탭 추가 * refactor: 변수명 수정(구별필요) * feat: 승인 페이지 이름 적용, 불필요 컴포넌트 제거 * refactor: mock data 적용. 변수명 수정. * feat: 승인페이지 mock data 적용. 목록 클릭 함수 추가. interface 추가. * feat: adminapprovallistrow onclick 함수 속성 추가 * feat: msw handlers interface 파일들 * feat: 관리자페이지 - MSW 활용한 API request, response 명세. API 테스트. * style: 오류 관련 주석 처리 * feat: AdminPageHeader * feat: 관리자페이지 승인요청 사용자들 목록 불러오기 MSW API 요청 코드 완료. 테스트 완료. * feat: 관리자페이지 신고당한자 목록 API 코드 추가. 테스트 완료. * feat: 관리자페이지 승인요청자 세부정보 출력 1 * refactor: 불필요 코드 삭제 * refactor: 불필요코드 제거 2 * feat: 숨겨진 관리자 로그인 버튼 추가 * feat: 관리자 페이지 라우터 설정 * feat: 관리자페이지 로그인 MSW API 요청 코드 추가, 관리자페이지 로그인 UI(react hook form) * refactor: 타입 오류 임시 주석처리 * refactor: import문 삭제 * feat: 관리자페이지 로그인 api 요청 1 * feat: 관리자페이지 로그인 API 요청 2 * feat: 관리자 로그인 페이지 post API 요청 * feat: 관리자 로그인 페이지 이동 * feat: 관리자 페이지 이동 * feat: 관리자 로그인 페이지 - 피그마 디자인, 구현 * feat: 문구수정 * refactor: useNavigate, api 요청 주소 변경 * feat: 관리자로그인페이지 디자인 수정 - emotion, register속성 적용 * feat: 관리자 승인 처리 API 요청. * feat: 관리자 승인요청, 신고 API 요청 * refactor: dev pull * feat: Admin Inquiry 추가 * style: 문의페이지 아이콘 추가 * refactor: import 절대 경로 수정 * refactor: MSW handler /api 경로 삭제 * feat: 관리자페이지 문의 API, UI - 1 * feat: 관리자 페이지 문의 상세 페이지 추가 * feat: 관리자 페이지 문의 UI, API 추가 2 * feat: 관리자 문의 컴포넌트 수정 * feat: 관리자 페이지 탭 구조 개편 * feat: 불필요 라우팅 삭제 * feat: 문의 페이지 헤더 추가 * feat: 관리자 신고페이지 목록 * feat: 관리자 페이지 개편 * feat: 변수명사용-빌드 * refactor: import문 정렬 (dev pull) * style: 로그인 페이지 크기 변경 (스크롤X) From 1173028910bf21df6842008cdad385dcf06ed98e Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 17:26:20 +0900 Subject: [PATCH 093/180] =?UTF-8?q?fix=20:=20=EC=84=9C=EB=B2=84=EC=97=90?= =?UTF-8?q?=20=EC=9B=B9=ED=91=B8=EC=8B=9C=20=ED=86=A0=ED=81=B0=EC=9D=84=20?= =?UTF-8?q?=EB=B3=B4=EB=82=B4=EC=A3=BC=EC=A7=80=20=EC=95=8A=EC=95=98?= =?UTF-8?q?=EB=8D=98=20=EA=B2=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/firebase-messaging-sw.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/firebase-messaging-sw.ts b/src/firebase-messaging-sw.ts index 73ca5bed..87895b69 100644 --- a/src/firebase-messaging-sw.ts +++ b/src/firebase-messaging-sw.ts @@ -45,6 +45,8 @@ import 'firebase/messaging' import firebase from 'firebase' + +import { axiosAPI } from '@/apis/axios' const firebaseConfig = { apiKey: 'AIzaSyBPfx4R0QsrFbS5rMv38dM1B7iPR4bUxt4', authDomain: 'coffee-meet-d295d.firebaseapp.com', @@ -87,6 +89,19 @@ export async function getToken() { alert('알림:' + payload.notification.body) }) console.log('token:', token) + token && sendWebPushToken(token) return token } getToken() +const sendWebPushToken = async (token: string) => { + await axiosAPI + .put('/v1/users/notification/token', { + token: token, + }) + .then(() => { + console.log('서버에 웹푸시 토큰 전송 완료') + }) + .catch((err) => { + console.log(err) + }) +} From b5ac08611ae07a364baff354b87de2b077dfe4a3 Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 17:27:19 +0900 Subject: [PATCH 094/180] =?UTF-8?q?chore=20:=20=ED=86=A0=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/home/Card.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/home/Card.tsx b/src/components/home/Card.tsx index 5db8f8aa..8060f642 100644 --- a/src/components/home/Card.tsx +++ b/src/components/home/Card.tsx @@ -83,12 +83,12 @@ const Card = ({ isDarkMode }: CardProps) => { .get('/v1/users/status') .then((response) => { if (currentState == response.data.userStatus) - showToast({ - message: '아직 매칭이 성사되지 않았습니다!', - type: 'info', - isDarkMode: isDarkMode, - }) - setCurrentState(response.data.userStatus) + // showToast({ + // message: '아직 매칭이 성사되지 않았습니다!', + // type: 'info', + // isDarkMode: isDarkMode, + // }) + setCurrentState(response.data.userStatus) response.data.userStatus === 'CHATTING_UNCONNECTED' && setChatroomId(response.data.chattingRoomId) response.data.userStatus === 'MATCHING' && setMatchingStartedAt(response.data.startedAt) From 7d0aadd489c48e3fee85cb26b5fa9f759379d73c Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 17:27:51 +0900 Subject: [PATCH 095/180] =?UTF-8?q?chore=20:=20=EC=A3=BC=EC=84=9D=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/home/Card.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/home/Card.tsx b/src/components/home/Card.tsx index 8060f642..cdc3dbd3 100644 --- a/src/components/home/Card.tsx +++ b/src/components/home/Card.tsx @@ -12,7 +12,7 @@ import NormalButton from '@/components/common/Buttons/NormalButton' import { FlexBox } from '@/components/common/Flexbox' import Spacing from '@/components/common/Spacing' import { Text } from '@/components/common/Text' -import useToast from '@/hooks/useToast' +// import useToast from '@/hooks/useToast' import { palette } from '@/styles/palette' import { typo } from '@/styles/typo' @@ -38,7 +38,7 @@ const Card = ({ isDarkMode }: CardProps) => { const [currentState, setCurrentState] = useState('IDLE') const [chatroomId, setChatroomId] = useState('33') const navigate = useNavigate() - const { showToast } = useToast() + // const { showToast } = useToast() const handleMoveChatting = () => { navigate('/chatting', { state: { chatroomId: chatroomId } }) From f4de418d563a84c569cac9a4cfb4efad846894f2 Mon Sep 17 00:00:00 2001 From: judahhh Date: Mon, 27 Nov 2023 21:12:34 +0900 Subject: [PATCH 096/180] =?UTF-8?q?fix=20:=20=EC=B1=84=ED=8C=85=20input?= =?UTF-8?q?=EA=B0=92=EC=9D=B4=20=EB=B9=84=EC=96=B4=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EC=B1=84=EB=A1=9C=20=EC=84=9C=EB=B2=84=EC=97=90=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/messageArea/index.tsx | 8 ++++---- src/pages/chatting/Chatting.tsx | 16 +++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/messageArea/index.tsx b/src/components/messageArea/index.tsx index a486bbc8..8edab7e9 100644 --- a/src/components/messageArea/index.tsx +++ b/src/components/messageArea/index.tsx @@ -16,7 +16,7 @@ const MessageArea = ({ messageData }: MessageProps) => { {messageData.map((message, i) => //로그인 성공 후 nickname 전역에 저장하면 내 닉네임 불러오기 - message.nickname === 'CKYRRMOSSX' || message.nickname === 'LLYRSRAA' ? ( + message.nickname == localStorage.getItem('nickname') ? ( { } diff --git a/src/pages/chatting/Chatting.tsx b/src/pages/chatting/Chatting.tsx index dbcb3317..4b0273f9 100644 --- a/src/pages/chatting/Chatting.tsx +++ b/src/pages/chatting/Chatting.tsx @@ -26,6 +26,7 @@ const Chatting = () => { const { openModal } = useModal() const navigate = useNavigate() const { chatroomId } = useLocation().state + // const chatroomId = '3' const [messages, setMessages] = useState([] as Messages[]) const [inputValue, setInputValue] = useState('') const { authTokens } = useAuthStore() @@ -64,7 +65,7 @@ const Chatting = () => { console.log(response) }, connectHeaders: { - Authorization: `Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiZXhwIjoxNjk5MzQ3NjQ5fQ.pc39bNfs2NXsFmkRvOM0JzhAgdvec585fZ5zotPBupWAsFW_DEWcETcdz7VPa9vJ9zzNukO4yUcZo-Gf9sB12Q`, + Authorization: `${localStorage.getItem('jwt')}`, }, }) client.current.activate() @@ -74,7 +75,7 @@ const Chatting = () => { console.log('구독 함수 실행') if (client.current) { if (!client.current.connected) return - client.current.subscribe(`/sub/chatting/rooms/1`, (response) => { + client.current.subscribe(`/sub/chatting/rooms/${chatroomId}`, (response) => { const JsonBody = JSON.parse(response.body) console.log(response.body) setMessages((_chatList) => [..._chatList, JsonBody]) @@ -83,7 +84,7 @@ const Chatting = () => { } const handleSubmit = (e: FormEvent) => { e.preventDefault() - send(inputValue) + messageRef.current && send(messageRef.current.value) } const handleClickExitRoom = () => { openModal({ @@ -104,14 +105,15 @@ const Chatting = () => { if (client.current) { if (!client.current.connected) return console.log(message) + if (messageRef.current) messageRef.current.value = '' client.current.publish({ destination: '/pub/chatting/messages', body: JSON.stringify({ - roomId: 1, - content: inputValue, + roomId: chatroomId, + content: message, }), headers: { - Authorization: `Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiZXhwIjoxNjk5MzQ3NjQ5fQ.pc39bNfs2NXsFmkRvOM0JzhAgdvec585fZ5zotPBupWAsFW_DEWcETcdz7VPa9vJ9zzNukO4yUcZo-Gf9sB12Q`, + Authorization: `${localStorage.getItem('jwt')}`, }, }) } @@ -163,7 +165,7 @@ const Chatting = () => { {/* */} {/* setInputValue(e.target.value)} value={inputValue} /> */} - + + > diff --git a/src/pages/chatting/Chatting.tsx b/src/pages/chatting/Chatting.tsx index 1db7e401..66f84171 100644 --- a/src/pages/chatting/Chatting.tsx +++ b/src/pages/chatting/Chatting.tsx @@ -13,8 +13,8 @@ import { FlexBox } from '@/components/common/Flexbox' import GradationBackground from '@/components/common/GradationBackground' import PageContainer from '@/components/common/PageContainer' import PageHeader from '@/components/common/PageHeader' +import RegisterInput from '@/components/common/RegisterInput' import Spacing from '@/components/common/Spacing' -import TextArea from '@/components/common/TextArea' import MessageArea from '@/components/messageArea' import { useModal } from '@/hooks/useModal' import useToast from '@/hooks/useToast' @@ -27,7 +27,7 @@ const Chatting = () => { const { chatroomId } = useLocation().state const { chatroomName } = useLocation().state const [messages, setMessages] = useState([] as Messages[]) - const messageRef = useRef(null) + const messageRef = useRef(null) const messageWrapperRef = useRef(null) const divRef = useRef(null) const { showToast } = useToast() @@ -87,26 +87,29 @@ const Chatting = () => { const handleClickExitRoom = () => { openModal({ mainText: '채팅방을 나가시겠습니까?', - subText: '채팅방을 1명이라도 나가면 해당 채팅방은 폭파됩니다.', + subText: '채팅방을 1명이라도 나가면 해당 채팅방은 폭파됩니다.', okFunc: () => deleteChattingRoom(), type: 'confirm', }) } const deleteChattingRoom = async () => { - navigate('/') - return await axiosAPI.delete(`/v1/chatting/rooms/${chatroomId}`) + await axiosAPI.delete(`/v1/chatting/rooms/${chatroomId}`) + navigateHome() } const navigateHome = () => { - navigate('/') - } - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.nativeEvent.isComposing) { - return - } - if (event.key == 'Enter') { - messageRef.current && send(messageRef.current.value) - } + setTimeout(() => { + navigate('/') + }, 1000) } + // const handleKeyDown = (event: React.KeyboardEvent) => { + // if (event.nativeEvent.isComposing) { + // return + // } + // if (event.key == 'Enter') { + // messageRef.current && send(messageRef.current.value) + // return false + // } + // } const send = (message: string) => { if (client.current) { if (!client.current.connected) return @@ -136,7 +139,9 @@ const Chatting = () => { if (response.data.isExisted == true) return true else return false } - + useEffect(() => { + window.location.reload() + }, []) useEffect(() => { if (messageWrapperRef.current !== null) messageWrapperRef.current.scrollTop = messageWrapperRef.current.scrollHeight @@ -149,7 +154,7 @@ const Chatting = () => { type: 'warning', isDarkMode: false, }) - navigate('/') + navigateHome() } connect() return () => disconnect() @@ -174,20 +179,23 @@ const Chatting = () => { } rightIcon={} > - {/* {isLoading ? ( - - ) : ( */} + {messages && } - {/* )} */} + - {/* */} - {/* setInputValue(e.target.value)} value={inputValue} /> */} -