diff --git a/.eslintrc.js b/.eslintrc.js index fb12795..3f8dc07 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,6 +2,7 @@ module.exports = { env: { browser: true, es2021: true, + jest: true, }, extends: [ 'eslint:recommended', @@ -35,8 +36,25 @@ module.exports = { 'react', ], rules: { + 'no-use-before-define': 'off', + '@emotion/jsx-import': 'off', + 'react/jsx-uses-react': 'off', 'react/react-in-jsx-scope': 'off', 'react/jsx-props-no-spreading': 'off', - 'react/jsx-filename-extension': ['warn', { extensions: ['.tsx'] }], + 'react/jsx-filename-extension': ['warn', { extensions: ['.ts', '.tsx'] }], + 'react/function-component-definition': [ + 'error', + { namedComponents: 'arrow-function', unnamedComponents: 'arrow-function' }, + ], + 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], + 'import/extensions': [ + 'error', + 'ignorePackages', // 패키지는 무시하도록 설정 + { + js: 'never', jsx: 'never', ts: 'never', tsx: 'never', + }, + ], + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': ['error'], }, }; diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md new file mode 100644 index 0000000..4687d1a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -0,0 +1,15 @@ +--- +name: 🔧 Documentation +about: 문서화를 해야합니다. +title: '📋 Docs: ' +labels: 'documentation' +--- + +## Description +설명을 작성해주세요. + +## Documentation +문서화의 결과를 알려주세요. + +## ETC +기타사항을 작성해주세요. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000..88a786c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,16 @@ +--- +name: ✅ Feature +about: 새로운 기능 추가 +title: '✅ Feature: ' +labels: 'feature' +--- + +## Description +설명을 작성해주세요. + +## To Do +- [ ] todo +- [ ] todo + +## ETC +기타사항을 작성해주세요. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/fix.md b/.github/ISSUE_TEMPLATE/fix.md new file mode 100644 index 0000000..03d1eaf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/fix.md @@ -0,0 +1,12 @@ +--- +name: 🐝 Fix +about: 트러블 슈팅 +title: '🐝 Fix: ' +labels: 'fix' +--- + +## Description +설명을 작성해주세요. + +## ETC +기타사항을 작성해주세요. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/hotfix.md b/.github/ISSUE_TEMPLATE/hotfix.md new file mode 100644 index 0000000..1b74e5a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/hotfix.md @@ -0,0 +1,12 @@ +--- +name: 🐛HotFix +about: 치명적인 버그 +title: '🐛 Hot-Fix: ' +labels: 'hotfix' +--- + +## Description +설명을 작성해주세요. + +## ETC +기타사항을 작성해주세요. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..30d993a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,12 @@ +--- +name: 🔧 Question +about: 궁금한 부분이 있어요. +title: '❓ Question: ' +labels: 'question' +--- + +## Description +궁금한 점을 작성해주세요. + +## ETC +기타사항을 작성해주세요. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/refactoring.md b/.github/ISSUE_TEMPLATE/refactoring.md new file mode 100644 index 0000000..8f0d800 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/refactoring.md @@ -0,0 +1,18 @@ +--- +name: 🔧 Refactoring +about: 리팩토링 +title: '🔧 Refactoring: ' +labels: 'refactoring' +--- + +## Description +리팩토링 배경을 설명해주세요. + +## AS-IS +기존에 구현되어있는 방식을 설명해주세요. + +## TO-BE +어떻게 리팩토링 되어야하는 지 설명해주세요. + +## ETC +기타사항을 작성해주세요. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/style.md b/.github/ISSUE_TEMPLATE/style.md new file mode 100644 index 0000000..9345b7b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/style.md @@ -0,0 +1,16 @@ +--- +name: 👔 Style +about: 디자인적인 요소 변경 +title: '👔 Style: ' +labels: 'style' +--- + +## Description +설명을 작성해주세요. + +## To Do +- [ ] todo +- [ ] todo + +## ETC +기타사항을 작성해주세요. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/test.md b/.github/ISSUE_TEMPLATE/test.md new file mode 100644 index 0000000..6f17a87 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/test.md @@ -0,0 +1,16 @@ +--- +name: 📋 Test +about: 테스트 코드 +title: '📋 Test: ' +labels: 'test' +--- + +## Description +설명을 작성해주세요. + +## To Do +- [ ] todo +- [ ] todo + +## ETC +기타사항을 작성해주세요. \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..924e4c2 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,21 @@ +## PR 제목 + +### PR을 한 이유 🎯 + +- (이곳에 PR을 작성한 이유를 간략하게 적어주세요.) + +### 이슈 번호 📎 + +- (해결하고자 하는 이슈의 이름과 해시태그 번호를 적어주세요. 예: `이슈명 #123`) + +### 변경사항 🛠 + +- (개발한 내용의 요약을 적어주세요. 예: "로그인 버그 수정", "새로운 기능 추가" 등) + +### 특이사항 📌 + +- (이 PR에 대한 추가적인 정보나, 리뷰어가 주의깊게 봐야할 점 등을 적어주세요.) + +### 테스트 결과 📝 + +- (테스트를 진행한 결과, 해당 결과에 따른 스크린샷 또는 기타 정보를 제공해주세요.) diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 0000000..b04650a --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,11 @@ +name: ESLint +on: pull_request +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install modules + run: yarn + - name: Run ESLint + run: yarn eslint . --ext .js,.jsx,.ts,.tsx \ No newline at end of file diff --git a/.github/workflows/jest.yml b/.github/workflows/jest.yml new file mode 100644 index 0000000..1d2dae0 --- /dev/null +++ b/.github/workflows/jest.yml @@ -0,0 +1,11 @@ +name: Jest +on: pull_request +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install modules + run: yarn + - name: Run Jest + run: yarn test \ No newline at end of file diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..6650419 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,31 @@ +name: Playwright Tests +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: yarn + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + - name: Run Server + run: yarn dev & + - name: Wait for server to start + run: sleep 10 + - name: Run Playwright tests + run: yarn playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 8f322f0..410f1a3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ yarn-error.log* # local env files .env*.local +.env*.development +.env*.production # vercel .vercel @@ -33,3 +35,12 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# PWA +/public/sw.js +/public/sw.js.map +/public/workbox-*.js +/public/workbox-*.js.map +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..d42a45e --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +./node_modules/.bin/commitlint --edit "$1" diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..5a182ef --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn lint-staged diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..bfc028b --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn test diff --git a/__test__/app.test.ts b/__test__/app.test.ts new file mode 100644 index 0000000..5315e1f --- /dev/null +++ b/__test__/app.test.ts @@ -0,0 +1,5 @@ +test('should be true', () => { + expect(true).toBe(true); +}); + +export {}; diff --git a/__test__/app2.test.ts b/__test__/app2.test.ts new file mode 100644 index 0000000..5315e1f --- /dev/null +++ b/__test__/app2.test.ts @@ -0,0 +1,5 @@ +test('should be true', () => { + expect(true).toBe(true); +}); + +export {}; diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..78352a7 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,27 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + parserPreset: './custom-parser', + rules: { + 'type-case': [2, 'always', 'sentence-case'], + 'subject-case': [0], + 'type-enum': [ + 2, + 'always', + [ + 'Build', + 'Chore', + 'CI', + 'Docs', + 'Feat', + 'Fix', + '🐝 Fix', + 'Hot-Fix', + '🐛 Hot-Fix', + 'Refactor', + 'Revert', + 'Style', + 'Test', + ], + ], + }, +}; diff --git a/custom-parser/index.js b/custom-parser/index.js new file mode 100644 index 0000000..47a676a --- /dev/null +++ b/custom-parser/index.js @@ -0,0 +1,32 @@ +const headerPattern = /^(\W*\w*)(?:\((.*)\))?: (.*)$/; + +function parse(message) { + const match = message.match(headerPattern); + + if (!match) return null; // 혹시나 매칭되지 않는 경우를 대비 + + const [, typeWithEmoji, scope, subject] = match; + + const matches = typeWithEmoji.match(/(\W*)(\w+)/); + const type = matches ? matches[2] : null; + + return { + header: message, + type, + scope, + subject, + body: null, + footer: null, + mentions: [], + references: [], + notes: [], + }; +} + +module.exports = { + parserOpts: { + headerPattern, + headerCorrespondence: ['type', 'scope', 'subject'], + parse, + }, +}; diff --git a/custom-parser/package.json b/custom-parser/package.json new file mode 100644 index 0000000..a482a6b --- /dev/null +++ b/custom-parser/package.json @@ -0,0 +1,6 @@ +{ + "name": "custom-parser", + "version": "1.0.0", + "main": "index.js", + "license": "MIT" +} diff --git a/e2e/example.spec.ts b/e2e/example.spec.ts new file mode 100644 index 0000000..c511525 --- /dev/null +++ b/e2e/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects the URL to contain intro. + await expect(page).toHaveURL(/.*intro/); +}); diff --git a/e2e/landing.spec.ts b/e2e/landing.spec.ts new file mode 100644 index 0000000..31a288d --- /dev/null +++ b/e2e/landing.spec.ts @@ -0,0 +1,17 @@ +import { test, expect } from '@playwright/test'; + +test('페이지 타이틀 확인', async ({ page }) => { + await page.goto('/'); + + await expect(page).toHaveTitle(/ToonChat/); +}); + +test('로그인 페이지 클릭', async ({ page }) => { + await page.goto('/'); + + await page.getByRole('link', { name: 'Log in' }).click(); + + await expect(page).toHaveTitle(/ToonChat/); + await expect(page).toHaveTitle(/Log in/); + await expect(page).toHaveURL(/.*login/); +}); diff --git a/jest.config.mjs b/jest.config.mjs new file mode 100644 index 0000000..4ea413a --- /dev/null +++ b/jest.config.mjs @@ -0,0 +1,20 @@ +import nextJest from 'next/jest.js'; + +const createJestConfig = nextJest({ + // Provide the path to your Next.js app to load next.config.js and .env files + // in your test environment + dir: './', +}); + +// Add any custom config to be passed to Jest +/** @type {import('jest').Config} */ +const config = { + // Add more setup options before each test is run + // setupFilesAfterEnv: ['/jest.setup.js'], + testEnvironment: 'jest-environment-jsdom', + testPathIgnorePatterns: ['/e2e/'], +}; + +// createJestConfig is exported this way to ensure +// that next/jest can load the Next.js config which is async +export default createJestConfig(config); diff --git a/next.config.js b/next.config.js index 658404a..f57aa8c 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,69 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const withPWA = require('next-pwa'); + /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const config = { + async headers() { + return [ + { + source: '/_next/image', + headers: [ + { + key: 'Cache-Control', + value: 'public, max-age=86400, immutable', + }, + ], + }, + ]; + }, + images: { + minimumCacheTTL: 86400, // 60*60*24 : 1일 + }, + env: { + // ENV는 ['local', 'development', 'production'] 중 하나 + NEXT_PUBLIC_ENV: process.env.NEXT_PUBLIC_ENV, + + // 필요할 때 끌어다 쓴 토큰 + SSR_TOKEN: process.env.SSR_TOKEN, + + // NEXT-AUTH 설정에 필요한 값 + NEXTAUTH_URL: process.env.NEXTAUTH_URL, + NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, + + // 서버 URL + NEXT_PUBLIC_SOCKET_URL: process.env.NEXT_PUBLIC_SOCKET_URL, + NEXT_PUBLIC_SERVER_URL: process.env.NEXT_PUBLIC_SERVER_URL, + NEXT_PUBLIC_CLIENT_SERVER_URL: process.env.NEXT_PUBLIC_CLIENT_SERVER_URL, + + // 소셜 로그인 + NEXT_PUBLIC_GOOGLE_CLIENT_ID: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, + NEXT_PUBLIC_GOOGLE_CLIENT_SECRET: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET, + + NEXT_PUBLIC_NAVER_CLIENT_ID: process.env.NEXT_PUBLIC_NAVER_CLIENT_ID, + NEXT_PUBLIC_NAVER_CLIENT_SECRET: process.env.NEXT_PUBLIC_NAVER_CLIENT_SECRET, + + NEXT_PUBLIC_KAKAO_CLIENT_ID: process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID, + NEXT_PUBLIC_KAKAO_CLIENT_SECRET: process.env.NEXT_PUBLIC_KAKAO_CLIENT_SECRET, + + }, +}; + +const nextConfig = withPWA({ + dest: 'public', + runtimeCaching: [ + { + urlPattern: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'static-image-assets', + expiration: { + maxEntries: 64, + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, + }, + ], + disableDevLogs: true, +})(config); module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index cfa2801..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3719 +0,0 @@ -{ - "name": "toonchat-client", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "toonchat-client", - "version": "0.1.0", - "dependencies": { - "@types/node": "20.4.0", - "@types/react": "18.2.14", - "@types/react-dom": "18.2.6", - "eslint-config-airbnb-typescript": "^17.0.0", - "eslint-config-next": "13.4.8", - "next": "13.4.8", - "react": "18.2.0", - "react-dom": "18.2.0", - "typescript": "5.1.6" - }, - "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.61.0", - "@typescript-eslint/parser": "^5.61.0", - "eslint": "^8.44.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-import-resolver-typescript": "^3.5.5", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", - "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", - "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", - "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" - }, - "node_modules/@next/env": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.8.tgz", - "integrity": "sha512-twuSf1klb3k9wXI7IZhbZGtFCWvGD4wXTY2rmvzIgVhXhs7ISThrbNyutBx3jWIL8Y/Hk9+woytFz5QsgtcRKQ==" - }, - "node_modules/@next/eslint-plugin-next": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.8.tgz", - "integrity": "sha512-cmfVHpxWjjcETFt2WHnoFU6EmY69QcPJRlRNAooQlNe53Ke90vg1Ci/dkPffryJZaxxiRziP9bQrV8lDVCn3Fw==", - "dependencies": { - "glob": "7.1.7" - } - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.8.tgz", - "integrity": "sha512-MSFplVM4dTWOuKAUv0XR9gY7AWtMSBu9os9f+kp+s5rWhM1I2CdR3obFttd6366nS/W/VZxbPM5oEIdlIa46zA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.8.tgz", - "integrity": "sha512-Reox+UXgonon9P0WNDE6w85DGtyBqGitl/ryznOvn6TvfxEaZIpTgeu3ZrJLU9dHSMhiK7YAM793mE/Zii2/Qw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.8.tgz", - "integrity": "sha512-kdyzYvAYtqQVgzIKNN7e1rLU8aZv86FDSRqPlOkKZlvqudvTO0iohuTPmnEEDlECeBM6qRPShNffotDcU/R2KA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.8.tgz", - "integrity": "sha512-oWxx4yRkUGcR81XwbI+T0zhZ3bDF6V1aVLpG+C7hSG50ULpV8gC39UxVO22/bv93ZlcfMY4zl8xkz9Klct6dpQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.8.tgz", - "integrity": "sha512-anhtvuO6eE9YRhYnaEGTfbpH3L5gT/9qPFcNoi6xS432r/4DAtpJY8kNktqkTVevVIC/pVumqO8tV59PR3zbNg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.8.tgz", - "integrity": "sha512-aR+J4wWfNgH1DwCCBNjan7Iumx0lLtn+2/rEYuhIrYLY4vnxqSVGz9u3fXcgUwo6Q9LT8NFkaqK1vPprdq+BXg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.8.tgz", - "integrity": "sha512-OWBKIrJwQBTqrat0xhxEB/jcsjJR3+diD9nc/Y8F1mRdQzsn4bPsomgJyuqPVZs6Lz3K18qdIkvywmfSq75SsQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.8.tgz", - "integrity": "sha512-agiPWGjUndXGTOn4ChbKipQXRA6/UPkywAWIkx7BhgGv48TiJfHTK6MGfBoL9tS6B4mtW39++uy0wFPnfD0JWg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.8.tgz", - "integrity": "sha512-UIRKoByVKbuR6SnFG4JM8EMFlJrfEGuUQ1ihxzEleWcNwRMMiVaCj1KyqfTOW8VTQhJ0u8P1Ngg6q1RwnIBTtw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgr/utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.1.tgz", - "integrity": "sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==", - "dependencies": { - "cross-spawn": "^7.0.3", - "fast-glob": "^3.2.12", - "is-glob": "^4.0.3", - "open": "^9.1.0", - "picocolors": "^1.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz", - "integrity": "sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==" - }, - "node_modules/@swc/helpers": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", - "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" - }, - "node_modules/@types/node": { - "version": "20.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.0.tgz", - "integrity": "sha512-jfT7iTf/4kOQ9S7CHV9BIyRaQqHu67mOjsIQBC3BKZvzvUB6zLxEwJ6sBE3ozcvP8kF6Uk5PXN0Q+c0dfhGX0g==" - }, - "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "node_modules/@types/react": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.14.tgz", - "integrity": "sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g==", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.6.tgz", - "integrity": "sha512-2et4PDvg6PVCyS7fuTc4gPoksV58bW0RwSxWKcPRcHZf0PRUGq03TKcD/rUHe3azfV6/5/biUBJw+HhCQjaP0A==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" - }, - "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz", - "integrity": "sha512-A5l/eUAug103qtkwccSCxn8ZRwT+7RXWkFECdA4Cvl1dOlDUgTpAOfSEElZn2uSUxhdDpnCdetrf0jvU4qrL+g==", - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.61.0", - "@typescript-eslint/type-utils": "5.61.0", - "@typescript-eslint/utils": "5.61.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.61.0.tgz", - "integrity": "sha512-yGr4Sgyh8uO6fSi9hw3jAFXNBHbCtKKFMdX2IkT3ZqpKmtAq3lHS4ixB/COFuAIJpwl9/AqF7j72ZDWYKmIfvg==", - "dependencies": { - "@typescript-eslint/scope-manager": "5.61.0", - "@typescript-eslint/types": "5.61.0", - "@typescript-eslint/typescript-estree": "5.61.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.61.0.tgz", - "integrity": "sha512-W8VoMjoSg7f7nqAROEmTt6LoBpn81AegP7uKhhW5KzYlehs8VV0ZW0fIDVbcZRcaP3aPSW+JZFua+ysQN+m/Nw==", - "dependencies": { - "@typescript-eslint/types": "5.61.0", - "@typescript-eslint/visitor-keys": "5.61.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.61.0.tgz", - "integrity": "sha512-kk8u//r+oVK2Aj3ph/26XdH0pbAkC2RiSjUYhKD+PExemG4XSjpGFeyZ/QM8lBOa7O8aGOU+/yEbMJgQv/DnCg==", - "dependencies": { - "@typescript-eslint/typescript-estree": "5.61.0", - "@typescript-eslint/utils": "5.61.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.61.0.tgz", - "integrity": "sha512-ldyueo58KjngXpzloHUog/h9REmHl59G1b3a5Sng1GfBo14BkS3ZbMEb3693gnP1k//97lh7bKsp6/V/0v1veQ==", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.61.0.tgz", - "integrity": "sha512-Fud90PxONnnLZ36oR5ClJBLTLfU4pIWBmnvGwTbEa2cXIqj70AEDEmOmpkFComjBZ/037ueKrOdHuYmSFVD7Rw==", - "dependencies": { - "@typescript-eslint/types": "5.61.0", - "@typescript-eslint/visitor-keys": "5.61.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.61.0.tgz", - "integrity": "sha512-mV6O+6VgQmVE6+xzlA91xifndPW9ElFW8vbSF0xCT/czPXVhwDewKila1jOyRwa9AE19zKnrr7Cg5S3pJVrTWQ==", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.61.0", - "@typescript-eslint/types": "5.61.0", - "@typescript-eslint/typescript-estree": "5.61.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.61.0.tgz", - "integrity": "sha512-50XQ5VdbWrX06mQXhy93WywSFZZGsv3EOjq+lqp6WC2t+j3mb6A9xYVdrRxafvK88vg9k9u+CT4l6D8PEatjKg==", - "dependencies": { - "@typescript-eslint/types": "5.61.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", - "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.1.3" - } - }, - "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axe-core": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", - "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/axobject-query": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", - "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/bplist-parser": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", - "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", - "dependencies": { - "big-integer": "^1.6.44" - }, - "engines": { - "node": ">= 5.10.0" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/bundle-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", - "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", - "dependencies": { - "run-applescript": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001512", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz", - "integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "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==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "node_modules/default-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", - "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", - "dependencies": { - "bundle-name": "^3.0.0", - "default-browser-id": "^3.0.0", - "execa": "^7.1.1", - "titleize": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", - "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", - "dependencies": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", - "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.1.0", - "@eslint/js": "8.44.0", - "@humanwhocodes/config-array": "^0.11.10", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.6.0", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-airbnb": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", - "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", - "dev": true, - "dependencies": { - "eslint-config-airbnb-base": "^15.0.0", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5" - }, - "engines": { - "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.3.0" - } - }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" - } - }, - "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-config-airbnb-typescript": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.0.0.tgz", - "integrity": "sha512-elNiuzD0kPAPTXjFWg+lE24nMdHMtuxgYoD30OyMD6yrW1AhFZPAg27VX7d3tzOErw+dgJTNWfRSDqEcXb4V0g==", - "dependencies": { - "eslint-config-airbnb-base": "^15.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.13.0", - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3" - } - }, - "node_modules/eslint-config-next": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.8.tgz", - "integrity": "sha512-2hE0b6lHuhtHBX8VgEXi8v4G8PVrPUBMOSLCTq8qtcQ2qQOX7+uBOLK2kU4FD2qDZzyXNlhmuH+WLT5ptY4XLA==", - "dependencies": { - "@next/eslint-plugin-next": "13.4.8", - "@rushstack/eslint-patch": "^1.1.3", - "@typescript-eslint/parser": "^5.42.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.31.7", - "eslint-plugin-react-hooks": "^4.5.0" - }, - "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0", - "typescript": ">=3.3.1" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-import-resolver-typescript": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.5.tgz", - "integrity": "sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==", - "dependencies": { - "debug": "^4.3.4", - "enhanced-resolve": "^5.12.0", - "eslint-module-utils": "^2.7.4", - "get-tsconfig": "^4.5.0", - "globby": "^13.1.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "synckit": "^0.8.5" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*" - } - }, - "node_modules/eslint-import-resolver-typescript/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-import-resolver-typescript/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", - "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", - "dependencies": { - "@babel/runtime": "^7.20.7", - "aria-query": "^5.1.3", - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.6.2", - "axobject-query": "^3.1.1", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.3", - "language-tags": "=1.0.5", - "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.32.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", - "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.8" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", - "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", - "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-glob": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", - "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-tsconfig": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz", - "integrity": "sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, - "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "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": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "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-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "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-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-wsl/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.4.tgz", - "integrity": "sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" - }, - "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", - "dependencies": { - "language-subtag-registry": "~0.3.2" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==" - }, - "node_modules/next": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.8.tgz", - "integrity": "sha512-lxUjndYKjZHGK3CWeN2RI+/6ni6EUvjiqGWXAYPxUfGIdFGQ5XoisrqAJ/dF74aP27buAfs8MKIbIMMdxjqSBg==", - "dependencies": { - "@next/env": "13.4.8", - "@swc/helpers": "0.5.1", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", - "postcss": "8.4.14", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0", - "zod": "3.21.4" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=16.8.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "13.4.8", - "@next/swc-darwin-x64": "13.4.8", - "@next/swc-linux-arm64-gnu": "13.4.8", - "@next/swc-linux-arm64-musl": "13.4.8", - "@next/swc-linux-x64-gnu": "13.4.8", - "@next/swc-linux-x64-musl": "13.4.8", - "@next/swc-win32-arm64-msvc": "13.4.8", - "@next/swc-win32-ia32-msvc": "13.4.8", - "@next/swc-win32-x64-msvc": "13.4.8" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "fibers": ">= 3.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "fibers": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", - "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", - "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.hasown": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", - "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", - "dependencies": { - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", - "dependencies": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - } - ], - "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-applescript": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", - "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/run-applescript/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/run-applescript/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/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==", - "engines": { - "node": ">=6" - } - }, - "node_modules/run-applescript/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-applescript/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", - "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/synckit": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", - "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", - "dependencies": { - "@pkgr/utils": "^2.3.1", - "tslib": "^2.5.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" - }, - "node_modules/titleize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", - "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.21.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", - "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/package.json b/package.json index 4231216..f928b74 100644 --- a/package.json +++ b/package.json @@ -6,20 +6,54 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "prepare": "husky install", + "test": "jest", + "test:e2e": "playwright test", + "test:e2e-ui": "playwright test --ui", + "test:e2e-report": "playwright show-report" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "eslint" + ] }, "dependencies": { + "@datadog/browser-rum": "^4.50.1", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@stomp/stompjs": "^7.0.0", + "@types/next-auth": "^3.15.0", "@types/node": "20.4.0", "@types/react": "18.2.14", "@types/react-dom": "18.2.6", + "@types/sockjs-client": "^1.5.1", + "axios": "^1.4.0", + "bcrypt": "^5.1.1", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-next": "13.4.8", + "jsonwebtoken": "^9.0.2", + "jwt-decode": "^3.1.2", "next": "13.4.8", + "next-auth": "^4.23.1", + "next-pwa": "^5.6.0", "react": "18.2.0", "react-dom": "18.2.0", - "typescript": "5.1.6" + "socket.io": "^4.7.2", + "socket.io-client": "^4.7.2", + "sockjs-client": "^1.6.1", + "typescript": "5.1.6", + "zustand": "^4.3.9" }, "devDependencies": { + "@commitlint/cli": "^17.7.1", + "@commitlint/config-conventional": "^17.7.0", + "@playwright/test": "^1.37.0", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^14.0.0", + "@types/bcrypt": "^5.0.0", + "@types/jsonwebtoken": "^9.0.2", + "@types/socket.io": "^3.0.2", "@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/parser": "^5.61.0", "eslint": "^8.44.0", @@ -28,6 +62,10 @@ "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0" + "eslint-plugin-react-hooks": "^4.6.0", + "husky": "^8.0.0", + "jest": "^29.6.1", + "jest-environment-jsdom": "^29.6.1", + "lint-staged": "^13.2.3" } } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..f030451 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './e2e', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/public/assets/icons/icon-128x128.png b/public/assets/icons/icon-128x128.png new file mode 100644 index 0000000..c9c6fe4 Binary files /dev/null and b/public/assets/icons/icon-128x128.png differ diff --git a/public/assets/icons/icon-144x144.png b/public/assets/icons/icon-144x144.png new file mode 100644 index 0000000..a8c0bfb Binary files /dev/null and b/public/assets/icons/icon-144x144.png differ diff --git a/public/assets/icons/icon-152x152.png b/public/assets/icons/icon-152x152.png new file mode 100644 index 0000000..e51f064 Binary files /dev/null and b/public/assets/icons/icon-152x152.png differ diff --git a/public/assets/icons/icon-192x192.png b/public/assets/icons/icon-192x192.png new file mode 100644 index 0000000..ea12060 Binary files /dev/null and b/public/assets/icons/icon-192x192.png differ diff --git a/public/assets/icons/icon-384x384.png b/public/assets/icons/icon-384x384.png new file mode 100644 index 0000000..d9aa1f6 Binary files /dev/null and b/public/assets/icons/icon-384x384.png differ diff --git a/public/assets/icons/icon-48x48.png b/public/assets/icons/icon-48x48.png new file mode 100644 index 0000000..9d51abf Binary files /dev/null and b/public/assets/icons/icon-48x48.png differ diff --git a/public/assets/icons/icon-512x512.png b/public/assets/icons/icon-512x512.png new file mode 100644 index 0000000..b71a0fa Binary files /dev/null and b/public/assets/icons/icon-512x512.png differ diff --git a/public/assets/icons/icon-72x72.png b/public/assets/icons/icon-72x72.png new file mode 100644 index 0000000..3cfc257 Binary files /dev/null and b/public/assets/icons/icon-72x72.png differ diff --git a/public/assets/icons/icon-96x96.png b/public/assets/icons/icon-96x96.png new file mode 100644 index 0000000..08dee65 Binary files /dev/null and b/public/assets/icons/icon-96x96.png differ diff --git a/public/back.svg b/public/back.svg new file mode 100644 index 0000000..9723bc5 --- /dev/null +++ b/public/back.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/chat-loading.gif b/public/chat-loading.gif new file mode 100644 index 0000000..434cd16 Binary files /dev/null and b/public/chat-loading.gif differ diff --git a/public/default-user.png b/public/default-user.png new file mode 100644 index 0000000..3fcd6fe Binary files /dev/null and b/public/default-user.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..2a43f21 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/icons/Profile.svg b/public/icons/Profile.svg new file mode 100644 index 0000000..1f8a077 --- /dev/null +++ b/public/icons/Profile.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/kimms.png b/public/kimms.png new file mode 100644 index 0000000..3ecd96e Binary files /dev/null and b/public/kimms.png differ diff --git a/public/kimmsback.png b/public/kimmsback.png new file mode 100644 index 0000000..9fd8eea Binary files /dev/null and b/public/kimmsback.png differ diff --git a/public/leeyj.png b/public/leeyj.png new file mode 100644 index 0000000..2885168 Binary files /dev/null and b/public/leeyj.png differ diff --git a/public/leeyjback.png b/public/leeyjback.png new file mode 100644 index 0000000..e5c9a33 Binary files /dev/null and b/public/leeyjback.png differ diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..eb130ec Binary files /dev/null and b/public/logo.png differ diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..de43f4c --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,59 @@ +{ + "name": "ToonChat | 최애 캐릭터와의 1:1 대화", + "short_name": "ToonChat", + "theme_color": "#FFFFFF", + "background_color": "#58C3B6", + "display": "standalone", + "scope": "./", + "start_url": "./", + "icons": [ + { + "src": "assets/icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-96x96.png", + "sizes": "96x96", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-128x128.png", + "sizes": "128x128", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-152x152.png", + "sizes": "152x152", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-384x384.png", + "sizes": "384x384", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable any" + } + ] +} \ No newline at end of file diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/providerLogo/apple.svg b/public/providerLogo/apple.svg new file mode 100644 index 0000000..e14aed8 --- /dev/null +++ b/public/providerLogo/apple.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/providerLogo/google.svg b/public/providerLogo/google.svg new file mode 100644 index 0000000..19d9f4a --- /dev/null +++ b/public/providerLogo/google.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/providerLogo/kakao.svg b/public/providerLogo/kakao.svg new file mode 100644 index 0000000..14d5873 --- /dev/null +++ b/public/providerLogo/kakao.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/providerLogo/naver.svg b/public/providerLogo/naver.svg new file mode 100644 index 0000000..950642c --- /dev/null +++ b/public/providerLogo/naver.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/send.svg b/public/send.svg new file mode 100644 index 0000000..5fd27ed --- /dev/null +++ b/public/send.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/spinnig-loading.gif b/public/spinnig-loading.gif new file mode 100644 index 0000000..5e96e3d Binary files /dev/null and b/public/spinnig-loading.gif differ diff --git a/public/thumbnail.png b/public/thumbnail.png new file mode 100644 index 0000000..6dd8eac Binary files /dev/null and b/public/thumbnail.png differ diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index d2f8422..0000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app/favicon.ico b/src/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/src/app/favicon.ico and /dev/null differ diff --git a/src/app/globals.css b/src/app/globals.css deleted file mode 100644 index d4f491e..0000000 --- a/src/app/globals.css +++ /dev/null @@ -1,107 +0,0 @@ -:root { - --max-width: 1100px; - --border-radius: 12px; - --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', - 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', - 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; - - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; - - --primary-glow: conic-gradient( - from 180deg at 50% 50%, - #16abff33 0deg, - #0885ff33 55deg, - #54d6ff33 120deg, - #0071ff33 160deg, - transparent 360deg - ); - --secondary-glow: radial-gradient( - rgba(255, 255, 255, 1), - rgba(255, 255, 255, 0) - ); - - --tile-start-rgb: 239, 245, 249; - --tile-end-rgb: 228, 232, 233; - --tile-border: conic-gradient( - #00000080, - #00000040, - #00000030, - #00000020, - #00000010, - #00000010, - #00000080 - ); - - --callout-rgb: 238, 240, 241; - --callout-border-rgb: 172, 175, 176; - --card-rgb: 180, 185, 188; - --card-border-rgb: 131, 134, 135; -} - -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - - --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); - --secondary-glow: linear-gradient( - to bottom right, - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0.3) - ); - - --tile-start-rgb: 2, 13, 46; - --tile-end-rgb: 2, 5, 19; - --tile-border: conic-gradient( - #ffffff80, - #ffffff40, - #ffffff30, - #ffffff20, - #ffffff10, - #ffffff10, - #ffffff80 - ); - - --callout-rgb: 20, 20, 20; - --callout-border-rgb: 108, 108, 108; - --card-rgb: 100, 100, 100; - --card-border-rgb: 200, 200, 200; - } -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); -} - -a { - color: inherit; - text-decoration: none; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx deleted file mode 100644 index cf0ce25..0000000 --- a/src/app/layout.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import './globals.css'; -import { Inter } from 'next/font/google'; -import * as React from 'react'; - -const inter = Inter({ subsets: ['latin'] }); - -export const metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -}; - -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - {children} - - ); -} diff --git a/src/app/page.module.css b/src/app/page.module.css deleted file mode 100644 index 9411a5e..0000000 --- a/src/app/page.module.css +++ /dev/null @@ -1,229 +0,0 @@ -.main { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - padding: 6rem; - min-height: 100vh; -} - -.description { - display: inherit; - justify-content: inherit; - align-items: inherit; - font-size: 0.85rem; - max-width: var(--max-width); - width: 100%; - z-index: 2; - font-family: var(--font-mono); -} - -.description a { - display: flex; - justify-content: center; - align-items: center; - gap: 0.5rem; -} - -.description p { - position: relative; - margin: 0; - padding: 1rem; - background-color: rgba(var(--callout-rgb), 0.5); - border: 1px solid rgba(var(--callout-border-rgb), 0.3); - border-radius: var(--border-radius); -} - -.code { - font-weight: 700; - font-family: var(--font-mono); -} - -.grid { - display: grid; - grid-template-columns: repeat(4, minmax(25%, auto)); - width: var(--max-width); - max-width: 100%; -} - -.card { - padding: 1rem 1.2rem; - border-radius: var(--border-radius); - background: rgba(var(--card-rgb), 0); - border: 1px solid rgba(var(--card-border-rgb), 0); - transition: background 200ms, border 200ms; -} - -.card span { - display: inline-block; - transition: transform 200ms; -} - -.card h2 { - font-weight: 600; - margin-bottom: 0.7rem; -} - -.card p { - margin: 0; - opacity: 0.6; - font-size: 0.9rem; - line-height: 1.5; - max-width: 30ch; -} - -.center { - display: flex; - justify-content: center; - align-items: center; - position: relative; - padding: 4rem 0; -} - -.center::before { - background: var(--secondary-glow); - border-radius: 50%; - width: 480px; - height: 360px; - margin-left: -400px; -} - -.center::after { - background: var(--primary-glow); - width: 240px; - height: 180px; - z-index: -1; -} - -.center::before, -.center::after { - content: ''; - left: 50%; - position: absolute; - filter: blur(45px); - transform: translateZ(0); -} - -.logo { - position: relative; -} -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - .card:hover { - background: rgba(var(--card-rgb), 0.1); - border: 1px solid rgba(var(--card-border-rgb), 0.15); - } - - .card:hover span { - transform: translateX(4px); - } -} - -@media (prefers-reduced-motion) { - .card:hover span { - transform: none; - } -} - -/* Mobile */ -@media (max-width: 700px) { - .content { - padding: 4rem; - } - - .grid { - grid-template-columns: 1fr; - margin-bottom: 120px; - max-width: 320px; - text-align: center; - } - - .card { - padding: 1rem 2.5rem; - } - - .card h2 { - margin-bottom: 0.5rem; - } - - .center { - padding: 8rem 0 6rem; - } - - .center::before { - transform: none; - height: 300px; - } - - .description { - font-size: 0.8rem; - } - - .description a { - padding: 1rem; - } - - .description p, - .description div { - display: flex; - justify-content: center; - position: fixed; - width: 100%; - } - - .description p { - align-items: center; - inset: 0 0 auto; - padding: 2rem 1rem 1.4rem; - border-radius: 0; - border: none; - border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); - background: linear-gradient( - to bottom, - rgba(var(--background-start-rgb), 1), - rgba(var(--callout-rgb), 0.5) - ); - background-clip: padding-box; - backdrop-filter: blur(24px); - } - - .description div { - align-items: flex-end; - pointer-events: none; - inset: auto 0 0; - padding: 2rem; - height: 200px; - background: linear-gradient( - to bottom, - transparent 0%, - rgb(var(--background-end-rgb)) 40% - ); - z-index: 1; - } -} - -/* Tablet and Smaller Desktop */ -@media (min-width: 701px) and (max-width: 1120px) { - .grid { - grid-template-columns: repeat(2, 50%); - } -} - -@media (prefers-color-scheme: dark) { - .vercelLogo { - filter: invert(1); - } - - .logo { - filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); - } -} - -@keyframes rotate { - from { - transform: rotate(360deg); - } - to { - transform: rotate(0deg); - } -} diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index 90f524d..0000000 --- a/src/app/page.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import Image from 'next/image'; -import styles from './page.module.css'; - -export default function Home() { - return ( -
-
-

- Get started by editing  - src/app/page.tsx -

-
- - By - Vercel Logo - -
-
- -
- Next.js Logo -
- -
- -

- Docs - -> -

-

Find in-depth information about Next.js features and API.

-
- - -

- Learn - -> -

-

Learn about Next.js in an interactive course with quizzes!

-
- - -

- Templates - -> -

-

Explore the Next.js 13 playground.

-
- - -

- Deploy - -> -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-
-
-
- ); -} diff --git a/src/components/account/SocialLoginButtons.tsx b/src/components/account/SocialLoginButtons.tsx new file mode 100644 index 0000000..06fdb13 --- /dev/null +++ b/src/components/account/SocialLoginButtons.tsx @@ -0,0 +1,23 @@ +import { css } from '@emotion/react'; +// TODO: 애플로그인을 할 수 있는 상황이 되면 다시 복구하기 +// import AppleButton from './socialLoginButton/AppleButton'; +import GoogleButton from './socialLoginButton/GoogleButton'; +import NaverButton from './socialLoginButton/NaverButton'; +import KakaoButton from './socialLoginButton/KakaoButton'; + +const SocialLoginButtons = () => ( +
+ + + {/* */} + +
+); + +export default SocialLoginButtons; + +const socialLoginContainer = css` + display: flex; + justify-content: center; + margin-bottom: 2rem; +`; diff --git a/src/components/account/ToForgetPassword.tsx b/src/components/account/ToForgetPassword.tsx new file mode 100644 index 0000000..128a44e --- /dev/null +++ b/src/components/account/ToForgetPassword.tsx @@ -0,0 +1,23 @@ +import { css } from '@emotion/react'; +import color from '@/styles/color'; +import LinkWrapper from '@/components/common/link/LinkWrapper'; + +const ToForgetPassword = () => ( + // TODO: 비밀번호 찾기 페이지 구현하고 라우팅해야함 +
+ + {/* Forget password? */} + 바로 영준이랑 대화하기 + +
+); + +export default ToForgetPassword; + +const textCSS = css` + margin: auto; + text-align: center; + font-size: 1rem; + padding: 0.5rem; + color: ${color.darkGreen}; +`; diff --git a/src/components/account/socialLoginButton/AppleButton.tsx b/src/components/account/socialLoginButton/AppleButton.tsx new file mode 100644 index 0000000..483806a --- /dev/null +++ b/src/components/account/socialLoginButton/AppleButton.tsx @@ -0,0 +1,38 @@ +import { css } from '@emotion/react'; +import Image from 'next/image'; +import color from '@/styles/color'; + +const AppleButton = () => { + // TODO: 애플 소셜로그인 절차 구현 + // TODO: 애플 로그인은 Apple Developer 과금이 되어야 가능한 상태라서 지금은 카카오로 막아놓음 + const appleLoginHandler = () => { + console.log('애플 소셜로그인 절차 진행'); + }; + + return ( + + ); +}; + +export default AppleButton; + +const providerButtonCSS = css` + background: none; + height: 3rem; + width: 3rem; + border-radius: 1.5rem; + border: 1px solid ${color.black}; + margin: 0.5rem; +`; + +const imageCSS = css` + margin: 0.5rem auto; +`; diff --git a/src/components/account/socialLoginButton/GoogleButton.tsx b/src/components/account/socialLoginButton/GoogleButton.tsx new file mode 100644 index 0000000..f6bf59d --- /dev/null +++ b/src/components/account/socialLoginButton/GoogleButton.tsx @@ -0,0 +1,53 @@ +import { css } from '@emotion/react'; +import Image from 'next/image'; +import color from '@/styles/color'; +// import { signIn } from 'next-auth/react'; +import { useState } from 'react'; +import Toast from '@/components/common/toast/Toast'; + +const GoogleButton = () => { + // TODO: 구글로그인 절차가 성공하면 꼭 돌아올게... + const [toastMessage, setToastMessage] = useState(''); + const handleToastClose = () => { + setToastMessage(''); + }; + const googleLoginHandler = () => { + setToastMessage('소셜로그인은 추후에 제공될 예정입니다. :)'); + // signIn('google', { + // callbackUrl: '/', + // redirect: false, + // }); + }; + + return ( + + ); +}; + +export default GoogleButton; + +const providerButtonCSS = css` + background: none; + height: 3rem; + width: 3rem; + border-radius: 1.5rem; + border: 1px solid ${color.black}; + margin: 0.5rem; +`; + +const imageCSS = css` + margin: 0.5rem auto; +`; diff --git a/src/components/account/socialLoginButton/KakaoButton.tsx b/src/components/account/socialLoginButton/KakaoButton.tsx new file mode 100644 index 0000000..89d7e42 --- /dev/null +++ b/src/components/account/socialLoginButton/KakaoButton.tsx @@ -0,0 +1,53 @@ +import { css } from '@emotion/react'; +import Image from 'next/image'; +import color from '@/styles/color'; +// import { signIn } from 'next-auth/react'; +import { useState } from 'react'; +import Toast from '@/components/common/toast/Toast'; + +const KakaoButton = () => { + // TODO: 카카오 로그인 절차가 성공하면 꼭 돌아올게... + const [toastMessage, setToastMessage] = useState(''); + const handleToastClose = () => { + setToastMessage(''); + }; + const kakaoLoginHandler = () => { + setToastMessage('소셜로그인은 추후에 제공될 예정입니다. :)'); + // signIn('kakao', { + // callbackUrl: '/', + // redirect: false, + // }); + }; + + return ( + + ); +}; + +export default KakaoButton; + +const providerButtonCSS = css` + background: #FEE500; + height: 3rem; + width: 3rem; + border-radius: 1.5rem; + border: 1px solid ${color.black}; + margin: 0.5rem; +`; + +const imageCSS = css` + margin: 0.5rem auto; +`; diff --git a/src/components/account/socialLoginButton/NaverButton.tsx b/src/components/account/socialLoginButton/NaverButton.tsx new file mode 100644 index 0000000..58a4141 --- /dev/null +++ b/src/components/account/socialLoginButton/NaverButton.tsx @@ -0,0 +1,53 @@ +import { css } from '@emotion/react'; +import Image from 'next/image'; +import color from '@/styles/color'; +// import { signIn } from 'next-auth/react'; +import Toast from '@/components/common/toast/Toast'; +import { useState } from 'react'; + +const NaverButton = () => { + // TODO: 네이버 로그인 절차가 성공하면 꼭 돌아올게... + const [toastMessage, setToastMessage] = useState(''); + const handleToastClose = () => { + setToastMessage(''); + }; + const naverLoginHandler = () => { + setToastMessage('소셜로그인은 추후에 제공될 예정입니다. :)'); + // signIn('naver', { + // callbackUrl: '/', + // redirect: false, + // }); + }; + + return ( + + ); +}; + +export default NaverButton; + +const providerButtonCSS = css` + background: none; + height: 3rem; + width: 3rem; + border-radius: 1.5rem; + border: 1px solid ${color.black}; + margin: 0.5rem; +`; + +const imageCSS = css` + margin: 0.5rem auto; +`; diff --git a/src/components/chat/Header.tsx b/src/components/chat/Header.tsx new file mode 100644 index 0000000..c0f9e13 --- /dev/null +++ b/src/components/chat/Header.tsx @@ -0,0 +1,63 @@ +import { FC, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import useSocketStore from '@/store/socket'; +import useChatStore from '@/store/chat'; +import color from '@/styles/color'; +import { useSession } from 'next-auth/react'; +import { CharacterStateProps } from '@/types/characterInfo'; +import FriendShip from './characterHeader/FriendShip'; +import CharacterInfo from './characterHeader/CharacterInfo'; + +// TODO: Back 버튼을 누르면 지금 홈으로 돌아가지만 채팅 리스트뷰가 완성되면 그쪽으로 Link 될 예정 +const Header : FC = ({ + characterId, characterName, hashTag, profileImageUrl, +}) => { + // TODO: 친밀도를 API로 받아와야 작업이 가능함! + const [userStatus, setUserStatus] = useState({ + friendShipExp: 0, maxFriendShipExp: 1, friendShipLv: 0, + }); + const { connect, setChatStore } = useSocketStore(); + const chatStore = useChatStore(); + const { data: session } = useSession(); + useEffect(() => { + fetch(`/api/userStatus/${characterId}`) + .then((res) => res.json()) + .then((data) => { setUserStatus(data); }); + setChatStore(chatStore); + }, []); + useEffect(() => { + // WebSocket 연결하는 부분 + // TODO: /chat 페이지가 생기면 거기로 옮기는게 좋아보임 + // root페이지에 넣기에는 이후 로그인 기능이 완성되면 + // 인증정보까지 같이 보내 연결해야 하기에 좋지 않음 + if (session?.accessToken) { + connect(session.accessToken); + } + }, [session]); + return ( +
+ + +
+ ); +}; + +export default Header; + +const headerCSS = css` + position: sticky; + top: 0; + z-index: 100; // 채팅보다 위에 존재해야하기 때문에 필요함 + width: 400px; + max-width: 400px; + padding: 0.25rem; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-around; + background-color: ${color.white}; +`; diff --git a/src/components/chat/Main.tsx b/src/components/chat/Main.tsx new file mode 100644 index 0000000..c990250 --- /dev/null +++ b/src/components/chat/Main.tsx @@ -0,0 +1,69 @@ +// 주요 채팅 내용이 들어올 예정 +import { + useRef, useEffect, FC, useState, +} from 'react'; +import { css } from '@emotion/react'; +import useChatStore from '@/store/chat'; +import getHistory from '@/utils/services/chats'; +import MySpeak from './messageBox/MySpeak'; +import CharacterSpeak from './messageBox/CharacterSpeak'; +import Loading from '../common/dialog/Loading'; + +interface MainProps { + characterId: number, + characterName: string, + profileImageUrl: string +} + +const Main:FC = ({ characterId, characterName, profileImageUrl }) => { + const { chatContents, clearChatContents, initChatContents } = useChatStore(); + const messageEndRef = useRef(null); + const [loadingHistory, setLoadingHistory] = useState(false); + + useEffect(() => { + // TODO: Loading으로 채팅을 못치게 막아야할 것 같음 + clearChatContents(); + setLoadingHistory(true); + getHistory(setLoadingHistory, characterId, characterName, initChatContents); + }, [characterId, characterName, clearChatContents, initChatContents]); + useEffect(() => { + messageEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [chatContents]); + + return ( +
+ {loadingHistory && } + {chatContents.map( + (chat) => { + if (chat.speaker === 'me') { + return ; + } + return ( + + ); + }, + )} +
+
+ ); +}; + +export default Main; + +const mainCSS = css` + padding: 0.25rem; + max-width: 400px; + width: 100%; + height: 100%; + overflow: auto; + display: flex; + flex-direction: column; + margin-bottom: 3rem; +`; diff --git a/src/components/chat/MessageInput.tsx b/src/components/chat/MessageInput.tsx new file mode 100644 index 0000000..13061c0 --- /dev/null +++ b/src/components/chat/MessageInput.tsx @@ -0,0 +1,136 @@ +// 채팅 입력하는 부분이 생길 예정 +import { + useState, ChangeEvent, FormEvent, FC, useRef, useEffect, +} from 'react'; +import { css } from '@emotion/react'; +import Image from 'next/image'; +import color from '@/styles/color'; +import useSocketStore from '@/store/socket'; +import Dialog from '../common/dialog/Dialog'; + +interface CharacterState { + characterId: number, + characterName: string +} + +const MessageInput : FC = ({ characterId, characterName }) => { +// const MessageInput : FC = () => { + const [message, setMessage] = useState(''); + const [loading, setLoading] = useState(false); + const [isModalOpen, setModalOpen] = useState(false); + const inputRef = useRef(null); + const { sendMessage } = useSocketStore(); + + useEffect(() => { + const handleFocus = () => { + if (inputRef.current) { + inputRef.current.scrollIntoView({ behavior: 'smooth', block: 'end' }); + } + }; + if (inputRef.current) { + inputRef.current.addEventListener('focus', handleFocus); + } + return () => { + if (inputRef.current) { + inputRef.current.removeEventListener('focus', handleFocus); + } + }; + }, []); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + if (isModalOpen) { + return; + } + + if (loading) { + openModal(); + return; + } + if (message) { + setLoading(true); + /** + * AI의 대답 호출 + */ + // TODO: 메시지가 도착할 떄까지 loading이 true여야하는데. 소켓이라 컨트롤이 필요함. + sendMessage(characterId, characterName, message); + setMessage(''); + setLoading(false); + } + }; + + const openModal = () => { + setModalOpen(true); + }; + + const closeModal = () => { + setModalOpen(false); + inputRef.current?.focus(); + }; + + const handleChange = (e: ChangeEvent) => { + setMessage(e.target.value); + }; + + return ( +
+
+ + +
+ + {isModalOpen && 대답을 생각하는 중이에요!} +
+ ); +}; + +export default MessageInput; + +const footerCSS = css` + position: fixed; + bottom: 0; + width: 400px; + padding-top: 0.5rem; + background-color: ${color.white}; + text-align: left; + font-size: 0.75rem; + color: ${color.black}; +`; + +const formCSS = css` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: stretch; + padding-left: 0.25rem; + padding-right: 0.25rem; +`; + +const buttonCSS = css` + background: none; + border: none; + padding: 0; + margin: 0; +`; + +const inputCSS = css` + width: 100%; + border: none; + border-radius: 0.75rem; + background-color: ${color.offWhite}; + height: 2.5rem; + padding: 0.75rem; + margin-right: 0.625rem; + + &:focus { + outline: none; + } +`; diff --git a/src/components/chat/characterHeader/CharacterInfo.tsx b/src/components/chat/characterHeader/CharacterInfo.tsx new file mode 100644 index 0000000..3c0d9f8 --- /dev/null +++ b/src/components/chat/characterHeader/CharacterInfo.tsx @@ -0,0 +1,81 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import { css } from '@emotion/react'; +import { FC } from 'react'; +import color from '@/styles/color'; + +interface CharacterInfoProps { + characterName: string, + hashTag: string, + profileImageUrl: string, + link: string, +} + +const CharacterInfo: FC = ({ + characterName, hashTag, profileImageUrl, link, +}) => ( + <> + +
+ back +
+ +
+ {`/${characterName}`} +
+
+
{characterName}
+
{hashTag}
+
+ +); + +export default CharacterInfo; + +const backWrapperCSS = css` + width: 1.5rem; + height: 1.5rem; + position: relative; + margin: 0.25rem; +`; + +const imageWrapperCSS = css` + width: 2.75rem; + height: 2.75rem; + position: relative; +`; + +const imageCSS = css` + border-radius: 50%; +`; + +const textWrapperCSS = css` + padding: 0.25rem; +`; + +const characterNameCSS = css` + text-align: left; + font-size: 1rem; + color: ${color.black}; + margin-bottom: 0.25rem; +`; + +const characterBackgroundCSS = css` + font-size: 0.75rem; + color: ${color.greenGray}; + overflow: hidden; // 넘치면 영역을 감추기 + text-overflow: ellipsis; // ... 으로 마무리 + white-space: nowrap; // 아래줄로 내려가는 것을 막기 + max-width: 10rem; +`; diff --git a/src/components/chat/characterHeader/FriendShip.tsx b/src/components/chat/characterHeader/FriendShip.tsx new file mode 100644 index 0000000..8dba7a9 --- /dev/null +++ b/src/components/chat/characterHeader/FriendShip.tsx @@ -0,0 +1,67 @@ +import { css } from '@emotion/react'; +import type { FC } from 'react'; +import color from '@/styles/color'; + +interface UserStatusProps { + friendShipExp: number, + maxFriendShipExp: number, + friendShipLv: number, +} + +const FriendShip:FC = ({ friendShipExp, maxFriendShipExp, friendShipLv }) => ( +
+
+
친밀도
+ + {`Lv. ${friendShipLv}`} + +
+
+
+
+
+
+); + +export default FriendShip; + +const FriendShipCSS = css` + height: 1.5rem; + text-align: left; + font-size: 0.625rem; + color: ${color.greenGray}; + display: flex; + flex-direction: column; + align-items: center; +`; + +const friendShipTextAreaCSS = css` +display:flex; +align-items: center; +`; + +const expBarAreaCSS = css` + display: flex; + padding: 0.25rem; +`; + +const maxExpBarCSS = (friendShipExp : number, maxFriendShipExp : number) => css` +border-top: 0.25rem solid #cdd1d0; +width: ${(1 - (friendShipExp / maxFriendShipExp)) * 3.75}rem; +height: 0.25rem; +opacity: 0.5; +border-radius: 0.25rem; +`; + +const expBarCSS = (friendShipExp : number, maxFriendShipExp : number) => css` +border-top: 0.25rem solid ${color.lightGreen}; +width: ${(friendShipExp / maxFriendShipExp) * 3.75}rem; +height: 0.25rem; +border-radius: 0.25rem; +`; diff --git a/src/components/chat/messageBox/CharacterSpeak.tsx b/src/components/chat/messageBox/CharacterSpeak.tsx new file mode 100644 index 0000000..5a2b2f0 --- /dev/null +++ b/src/components/chat/messageBox/CharacterSpeak.tsx @@ -0,0 +1,62 @@ +import type { FC } from 'react'; +import Image from 'next/image'; +import { css } from '@emotion/react'; +import LoadingContent from './characterChatContent/LoadingContent'; +import ChatContent from './characterChatContent/ChatContent'; + +interface CharacterSpeakProps { + speaker:string, content: string, timestamp: number, profileImageUrl: string, loading: boolean, +} + +const CharacterSpeak: FC = ({ + speaker, content, timestamp, profileImageUrl, loading = false, +}) => ( + +
+ {speaker} +
+ + {speaker} + {loading ? + : } + +
+); + +export default CharacterSpeak; + +const characterSpeakCSS = css` + width: 100%; + margin-top: 0.25rem; + margin-bottom: 0.75rem; + display: flex; + align-self: flex-start; +`; + +const chatContainerCSS = css` + display: flex; + flex-direction: column; + margin-right: 0.25rem +`; + +const characterNameCSS = css` + font-size: 0.875rem; + padding: 0.25rem; +`; + +const imageWrapperCSS = css` + margin: 0.25rem; + width: 2.5rem; + min-width: 2.5rem; + height: 2.5rem; + position: relative; +`; + +const imageCSS = css` + border-radius: 50%; +`; diff --git a/src/components/chat/messageBox/MySpeak.tsx b/src/components/chat/messageBox/MySpeak.tsx new file mode 100644 index 0000000..a9eb21b --- /dev/null +++ b/src/components/chat/messageBox/MySpeak.tsx @@ -0,0 +1,44 @@ +import type { FC } from 'react'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; +import TimeStamp from '@/components/common/timeStamp/TimeStamp'; + +interface MySpeakProps { + content: string, timestamp: number +} + +const MySpeak: FC = ({ content, timestamp }) => ( + +
+ + {content} + + +
+
+); + +export default MySpeak; + +const mySpeakCSS = css` + width: 100%; + margin-top: 0.25rem; +`; + +const chatContainerCSS = css` + display: flex; + flex-direction: column; + align-items: flex-end; + align-self: flex-end; + margin-right: 1rem; +`; + +const myChatBoxCSS = css` + margin-left: 0.25rem; + text-align: right; + font-size: 0.75rem; + color: #fff; + background-color: ${color.lightGreen}; + border-radius: 0.75rem 0 0.75rem 0.75rem; + padding: 0.75rem; +`; diff --git a/src/components/chat/messageBox/characterChatContent/ChatContent.tsx b/src/components/chat/messageBox/characterChatContent/ChatContent.tsx new file mode 100644 index 0000000..b52167e --- /dev/null +++ b/src/components/chat/messageBox/characterChatContent/ChatContent.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; +import TimeStamp from '@/components/common/timeStamp/TimeStamp'; + +interface ChatContentProps { + content: string, timestamp: number +} + +const ChatContent: FC = ({ content, timestamp }) => ( + <> + + {content} + + + +); + +export default ChatContent; + +const characterChatBoxCSS = css` + float: left; + text-align: left; + font-size: 0.75rem; + color: ${color.black}; + background-color: ${color.offWhite}; + border-radius: 0 0.75rem 0.75rem 0.75rem; + padding: 0.75rem; +`; diff --git a/src/components/chat/messageBox/characterChatContent/LoadingContent.tsx b/src/components/chat/messageBox/characterChatContent/LoadingContent.tsx new file mode 100644 index 0000000..720abb7 --- /dev/null +++ b/src/components/chat/messageBox/characterChatContent/LoadingContent.tsx @@ -0,0 +1,26 @@ +import Image from 'next/image'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; + +const LoadingContent = () => ( + + 로딩 + +); + +export default LoadingContent; + +const characterChatBoxCSS = css` + float: left; + text-align: left; + font-size: 0.75rem; + color: ${color.black}; + background-color: #F2F7FB; + border-radius: 0.75rem 0.75rem 0.75rem; + padding: 0.75rem; +`; diff --git a/src/components/chat/tuneSetting/TuneSetting.tsx b/src/components/chat/tuneSetting/TuneSetting.tsx new file mode 100644 index 0000000..166a245 --- /dev/null +++ b/src/components/chat/tuneSetting/TuneSetting.tsx @@ -0,0 +1,83 @@ +import { + ChangeEvent, FC, FormEvent, useEffect, useState, +} from 'react'; +import useAIParameterStore from '@/store/aiParameter'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; + +interface tuneSettingProps { + closeModal : () => void, +} + +const TuneSetting: FC = ({ closeModal }) => { + const [temperature, setTemperature] = useState(0.3); + const [repetitionPenalty, setRepetitionPenalty] = useState(1.5); + const { temperatureParam, repetitionPenaltyParam, updateParameter } = useAIParameterStore(); + + useEffect(() => { + setTemperature(temperatureParam); + setRepetitionPenalty(repetitionPenaltyParam); + }, []); + + const temperatureHandler = (e: ChangeEvent) => { + setTemperature(parseFloat(e.target.value)); + }; + const repetitionPenaltyHandler = (e: ChangeEvent) => { + setRepetitionPenalty(parseFloat(e.target.value)); + }; + + // TODO: AI 파라미터 값을 백엔드에서 저장하고 있어야 할 것 같음. + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + updateParameter(temperature, repetitionPenalty); + console.log('AI 파라미터 값:', temperature, repetitionPenalty); + closeModal(); + }; + + return ( +
+
+ + +
+
+ + +
+ +
+ ); +}; +export default TuneSetting; + +const ButtonCSS = () => css` + width: 30%; + background-color: ${color.lightGray}; + text-transform: uppercase; + border: none; + padding: 0.625rem; + margin-top: 1.875rem; + border-radius: 0.75rem; + margin-top: 0.625rem; + font-size: 0.75rem; + font-weight: 400; + color: ${color.black}; +`; diff --git a/src/components/common/bottomNavBar/BottomNavBar.tsx b/src/components/common/bottomNavBar/BottomNavBar.tsx new file mode 100644 index 0000000..d56b4aa --- /dev/null +++ b/src/components/common/bottomNavBar/BottomNavBar.tsx @@ -0,0 +1,76 @@ +import { css } from '@emotion/react'; +import { FC, useEffect, useState } from 'react'; +import color from '@/styles/color'; +import SectionLine from '@/components/common/sectionLine/SectionLine'; +import HomeIcon from '@/components/icons/HomeIcon'; +import ChatIcon from '@/components/icons/ChatIcon'; +import CommunityIcon from '@/components/icons/CommunityIcon'; +import ProfileIcon from '@/components/icons/ProfileIcon'; +import NavButtonWrapper from './navButtonWrapper/NavButtonWrapper'; + +interface NavProps { + pageName: 'Friends' | 'Chat' | 'Community' | 'Profile' +} + +const BottomNavBar: FC = ({ pageName }) => { + const [homeColor, setHomeColor] = useState(color.greenGray); + const [chatColor, setChatColor] = useState(color.greenGray); + const [communityColor, setCommunityColor] = useState(color.greenGray); + const [profileColor, setProfileColor] = useState(color.greenGray); + + useEffect(() => { + if (pageName === 'Friends') { + setHomeColor(color.black); + } else if (pageName === 'Chat') { + setChatColor(color.black); + } else if (pageName === 'Community') { + setCommunityColor(color.black); + } else { + setProfileColor(color.black); + } + }, []); + + return ( +
+ +
+ + + Friends + + + {/* TODO: 커뮤니티, 프로필 페이지 제작을 해야함 */} + + + Chat + + + + Community + + + + Profile + +
+
+ ); +}; + +export default BottomNavBar; + +const navContainerCSS = css` + position: sticky; + bottom: 0; + display: flex; + flex-direction: column; + align-items: center; + width: 400px; + background-color: #FFFFFF; +`; + +const BottomNavCSS = css` + padding: 0.75rem; + display: grid; + grid-template-columns: repeat(4, 1fr); +`; diff --git a/src/components/common/bottomNavBar/navButtonWrapper/NavButtonWrapper.tsx b/src/components/common/bottomNavBar/navButtonWrapper/NavButtonWrapper.tsx new file mode 100644 index 0000000..3e45229 --- /dev/null +++ b/src/components/common/bottomNavBar/navButtonWrapper/NavButtonWrapper.tsx @@ -0,0 +1,28 @@ +import { css } from '@emotion/react'; +import { FC, ReactNode } from 'react'; +import LinkWrapper from '@/components/common/link/LinkWrapper'; + +interface NavButtonWrapperProps { + children: ReactNode, + linkUrl: string, + color: string, +} + +const NavButtonWrapper: FC = ({ children, linkUrl, color }) => ( + +
+ {children} +
+
+); + +export default NavButtonWrapper; + +const WrapperCSS = (color: string) => css` + display: flex; + flex-direction: column; + align-items: center; + font-size: 0.875rem; + gap: 0.25rem; + color: ${color}; +`; diff --git a/src/components/common/button/Button.test.tsx b/src/components/common/button/Button.test.tsx new file mode 100644 index 0000000..6a1f6df --- /dev/null +++ b/src/components/common/button/Button.test.tsx @@ -0,0 +1,32 @@ +import { render } from '@testing-library/react'; +import color from '@/styles/color'; +import Button from './Button'; +import '@testing-library/jest-dom/extend-expect'; + +describe('); + const buttonElement = getByText('Test Button'); + expect(buttonElement).toBeInTheDocument(); + }); + + it('button 렌더링 및 스타일 테스트 : green theme', () => { + const { getByText } = render(); + const buttonElement = getByText('Test Button'); + + expect(buttonElement).toHaveStyle({ + backgroundColor: color.darkGreen, + color: color.offWhite, + }); + }); + + it('button 렌더링 및 스타일 테스트 : white theme', () => { + const { getByText } = render(); + const buttonElement = getByText('Test Button'); + + expect(buttonElement).toHaveStyle({ + backgroundColor: color.offWhite, + color: color.greenGray, + }); + }); +}); diff --git a/src/components/common/button/Button.tsx b/src/components/common/button/Button.tsx new file mode 100644 index 0000000..441bb24 --- /dev/null +++ b/src/components/common/button/Button.tsx @@ -0,0 +1,41 @@ +import { FC, ReactNode } from 'react'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; + +type Theme = 'green' | 'white' + +interface ButtonProps { + children: ReactNode, + theme: Theme +} + +const themeTable = { + green: { + backgroundColor: color.darkGreen, + color: color.offWhite, + }, + white: { + backgroundColor: color.offWhite, + color: color.greenGray, + }, +}; + +const Button: FC = ({ children, theme }) => ( + +); + +export default Button; + +const ButtonCSS = (theme: Theme) => css` + width: 100%; + background-color: ${themeTable[theme].backgroundColor}; + border: none; + padding: 1rem; + border-radius: 1rem; + margin-top: 0.625rem; + font-size: 1rem; + font-weight: 400; + color: ${themeTable[theme].color}; +`; diff --git a/src/components/common/dialog/Dialog.test.tsx b/src/components/common/dialog/Dialog.test.tsx new file mode 100644 index 0000000..65b1d32 --- /dev/null +++ b/src/components/common/dialog/Dialog.test.tsx @@ -0,0 +1,38 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { render, fireEvent } from '@testing-library/react'; +import Dialog from './Dialog'; +import '@testing-library/jest-dom/extend-expect'; + +describe('', () => { + it('자식 컴포넌트가 랜더링 되는지', () => { + const { getByText } = render( {}}>Test Dialog); + const dialogElement = getByText('Test Dialog'); + expect(dialogElement).toBeInTheDocument(); + }); + + it('버튼을 누르면 모달이 닫히는지', () => { + const closeModalMock = jest.fn(); + const { getByText } = render(Test Dialog); + const closeButton = getByText('ENTER'); + + fireEvent.click(closeButton); + expect(closeModalMock).toHaveBeenCalled(); + }); + + it('Backdrop 클릭시 모달이 닫히는지', () => { + const closeModalMock = jest.fn(); + const { getByLabelText } = render(Test Dialog); + const backdrop = getByLabelText('close modal'); + + fireEvent.click(backdrop); + expect(closeModalMock).toHaveBeenCalled(); + }); + + // Focus 관리 검증 + it('모달창이 뜨면 Focus가 되는지', () => { + const { getByText } = render( {}}>Test Dialog); + const closeButton = getByText('ENTER'); + + expect(document.activeElement).toBe(closeButton); + }); +}); diff --git a/src/components/common/dialog/Dialog.tsx b/src/components/common/dialog/Dialog.tsx new file mode 100644 index 0000000..b7e6312 --- /dev/null +++ b/src/components/common/dialog/Dialog.tsx @@ -0,0 +1,112 @@ +import { css } from '@emotion/react'; +import { + FC, ReactNode, useEffect, useRef, +} from 'react'; +import color from '@/styles/color'; + +type Theme = 'green' | 'white' + +interface DialogProps { + closeModal: () => void, + theme: Theme, + children: ReactNode, +} + +const themeTable = { + green: { + backgroundColor: color.darkGreen, + color: color.offWhite, + subBackgroundColor: color.offWhite, + subColor: color.greenGray, + }, + white: { + backgroundColor: color.offWhite, + color: color.greenGray, + subBackgroundColor: color.darkGreen, + subColor: color.offWhite, + }, +}; + +const Dialog: FC = ({ closeModal, theme, children }) => { + const closeButtonRef = useRef(null); + + useEffect(() => { + if (closeButtonRef.current) { + closeButtonRef.current.focus(); + } + }, []); + + return ( +
+
+
{children}
+ +
+
+ ); +}; + +export default Dialog; + +const dialogCSS = css` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +`; + +const dialogContentsCSS = (theme: Theme) => css` + position: relative; + z-index: 2; + + border-radius: 1rem; + padding: 1.25rem; + background-color: ${themeTable[theme].backgroundColor}; + color: ${themeTable[theme].color}; + + text-align: center; +`; + +const dialogTextCSS = css` + font-size: 0.875rem; + padding: 0.625rem; + padding-bottom: 1.25rem; +`; + +const dialogBackdropCSS = css` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + font-size: 1.25rem; + background: rgba(0, 0, 0, 0.5); + z-index: 1; +`; + +const buttonCSS = (theme: Theme) => css` + width: 80%; + background-color: ${themeTable[theme].subBackgroundColor}; + border: none; + padding: 0.625rem; + margin-top: 1.875rem; + border-radius: 0.75rem; + margin-top: 0.625rem; + font-size: 0.75rem; + font-weight: 400; + color: ${themeTable[theme].subColor}; + + &:focus { + outline: none; + } +`; diff --git a/src/components/common/dialog/Loading.tsx b/src/components/common/dialog/Loading.tsx new file mode 100644 index 0000000..42459f5 --- /dev/null +++ b/src/components/common/dialog/Loading.tsx @@ -0,0 +1,46 @@ +import { css } from '@emotion/react'; +import Image from 'next/image'; + +const Loading = () => ( +
+
+ 로딩 +
+
+
+); + +export default Loading; + +const loadingCSS = css` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +`; + +const loadingContentsCSS = () => css` + position: relative; + z-index: 2; + text-align: center; +`; + +const dialogBackdropCSS = css` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + font-size: 1.25rem; + z-index: 1; +`; diff --git a/src/components/common/divideLine/DivideLine.tsx b/src/components/common/divideLine/DivideLine.tsx new file mode 100644 index 0000000..9e38f1a --- /dev/null +++ b/src/components/common/divideLine/DivideLine.tsx @@ -0,0 +1,28 @@ +import { css } from '@emotion/react'; +import color from '@/styles/color'; + +const DivideLine = () => ( +
+
+
+); + +export default DivideLine; + +const lineContainerCSS = css` + display: flex; + justify-content: center; + align-items: center; + width: 100%; + color: ${color.lightGray}; + font-size: 0.875rem; +`; + +const lineCSS = css` + display: block; + margin: 0.625rem; + margin-bottom: 2.5rem; + height: 1px; + background: ${color.lightGray}; + width: 100%; +`; diff --git a/src/components/common/divideLine/DivideLineText.tsx b/src/components/common/divideLine/DivideLineText.tsx new file mode 100644 index 0000000..f7ebb1d --- /dev/null +++ b/src/components/common/divideLine/DivideLineText.tsx @@ -0,0 +1,35 @@ +import { css } from '@emotion/react'; +import { FC, ReactNode } from 'react'; +import color from '@/styles/color'; + +interface DivideLineTextProps { + children: ReactNode +} + +const DivideLineText:FC = ({ children }) => ( +
+
+
{children}
+
+
+); + +export default DivideLineText; + +const lineContainerCSS = css` + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 1.875rem; + width: 100%; + color: ${color.lightGray}; + font-size: 0.875rem; +`; + +const lineCSS = css` + display: block; + margin: 0.625rem; + height: 1px; + background: ${color.lightGray}; + width: 100%; +`; diff --git a/src/components/common/head/GA.tsx b/src/components/common/head/GA.tsx new file mode 100644 index 0000000..04c2e26 --- /dev/null +++ b/src/components/common/head/GA.tsx @@ -0,0 +1,19 @@ +import Script from 'next/script'; + +const GA = () => ( + <> + {/* Google tag (gtag.js) */} + + +); + +export default GA; diff --git a/src/components/common/head/SEO.tsx b/src/components/common/head/SEO.tsx new file mode 100644 index 0000000..feae303 --- /dev/null +++ b/src/components/common/head/SEO.tsx @@ -0,0 +1,16 @@ +import Head from 'next/head'; +import { FC } from 'react'; + +interface SEOProps { + title: string, +} + +const SEO: FC = ({ title }) => ( + + + {`ToonChat | ${title}`} + + +); + +export default SEO; diff --git a/src/components/common/input/Input.tsx b/src/components/common/input/Input.tsx new file mode 100644 index 0000000..dafc2a9 --- /dev/null +++ b/src/components/common/input/Input.tsx @@ -0,0 +1,45 @@ +import { css } from '@emotion/react'; +import { + ChangeEvent, Dispatch, FC, SetStateAction, +} from 'react'; +import color from '@/styles/color'; + +interface InputProps { + text: string, inputType: 'email' | 'text', + value: string, setState: Dispatch> +} + +// TODO: 회원가입, 로그인시 닉네임 길이 등 제약이 필요함 +const Input: FC = ({ + text, inputType, value, setState, +}) => { + const changeHandler = (e: ChangeEvent) => { + setState(e.target.value); + }; + + return ( + <> +
{text}
+ + + ); +}; +export default Input; + +const textCSS = css` + font-size: 0.875rem; + color: ${color.lightGreen}; +`; + +const inputTagCSS = css` + display: block; + width: 100%; + margin-top: 0.875rem; + margin-bottom: 0.875rem; + padding: 0.25rem; + font-size: 1rem; + outline: none; + background: none; + border: none; + border-bottom: 1px solid ${color.lightGray}; +`; diff --git a/src/components/common/input/PasswordInput.tsx b/src/components/common/input/PasswordInput.tsx new file mode 100644 index 0000000..b3a0aa5 --- /dev/null +++ b/src/components/common/input/PasswordInput.tsx @@ -0,0 +1,49 @@ +import { css } from '@emotion/react'; +import { + FC, ChangeEvent, Dispatch, SetStateAction, +} from 'react'; +import color from '@/styles/color'; + +interface PasswordInputProps { + text: string, value: string, setState: Dispatch>, +} + +// TODO: 회원가입, 로그인시 비밀번호 길이 제약이 필요함 +const PasswordInput: FC = ({ text, value, setState }) => { + const handleInputChange = (e: ChangeEvent) => { + setState(e.target.value); + }; + + return ( + <> +
{text}
+ + + ); +}; + +export default PasswordInput; + +const textCSS = css` + font-size: 0.875rem; + color: ${color.lightGreen}; +`; + +const inputTagCSS = css` + display: block; + width: 100%; + margin-top: 0.875rem; + margin-bottom: 0.875rem; + padding: 0.25rem; + font-size: 1rem; + outline: none; + background: none; + border: none; + border-bottom: 1px solid ${color.lightGray}; +`; diff --git a/src/components/common/link/LinkWrapper.tsx b/src/components/common/link/LinkWrapper.tsx new file mode 100644 index 0000000..183cde1 --- /dev/null +++ b/src/components/common/link/LinkWrapper.tsx @@ -0,0 +1,28 @@ +import color from '@/styles/color'; +import styled from '@emotion/styled'; +import Link from 'next/link'; +import { FC, ReactNode } from 'react'; + +interface LinkWrapperProps { + children: ReactNode, + linkUrl: string, +} + +const LinkWrapper: FC = ({ linkUrl, children }) => ( + + + {children} + + +); + +export default LinkWrapper; + +const ResetLink = styled.a` + font-size: 0.75rem; + text-decoration: none; + color: ${color.black}; + &:active { + text-decoration: none; + } +`; diff --git a/src/components/common/pageText/PageDescribe.tsx b/src/components/common/pageText/PageDescribe.tsx new file mode 100644 index 0000000..803961b --- /dev/null +++ b/src/components/common/pageText/PageDescribe.tsx @@ -0,0 +1,24 @@ +import { FC, ReactNode } from 'react'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; + +type pageDescribeProps = { + children: ReactNode +} + +const PageDescribe: FC = ({ children }) => ( +
{children}
+); + +export default PageDescribe; + +const textCSS = css` + width: 80%; + padding: 1rem; + margin: auto; + border-radius: 1rem; + text-align: center; + font-size: 0.875rem; + font-weight: 700; + color: ${color.greenGray}; +`; diff --git a/src/components/common/pageText/PageTitle.tsx b/src/components/common/pageText/PageTitle.tsx new file mode 100644 index 0000000..cb8e3a5 --- /dev/null +++ b/src/components/common/pageText/PageTitle.tsx @@ -0,0 +1,22 @@ +import { FC, ReactNode } from 'react'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; + +type pageTitleProps = { + children: ReactNode +} + +const PageTitle: FC = ({ children }) => ( +
{children}
+); + +export default PageTitle; + +const textCSS = css` + margin: auto; + border-radius: 1rem; + text-align: center; + font-size: 1.25rem; + font-weight: 700; + color: ${color.black}; +`; diff --git a/src/components/common/searchBar/SearchBar.tsx b/src/components/common/searchBar/SearchBar.tsx new file mode 100644 index 0000000..f52f9c2 --- /dev/null +++ b/src/components/common/searchBar/SearchBar.tsx @@ -0,0 +1,98 @@ +import { css } from '@emotion/react'; +import { + ChangeEvent, KeyboardEvent, MouseEvent, useState, +} from 'react'; +import color from '@/styles/color'; +import Toast from '@/components/common/toast/Toast'; +import SearchIcon from '@/components/icons/SearchIcon'; + +interface ToastMessage { + key: number; + message: string; +} + +const SearchBar = () => { + const [searchText, setSearchText] = useState(''); + const [toastMessages, setToastMessages] = useState([]); + const [toastKey, setToastKey] = useState(0); + + const handleToastClose = (key: number) => { + setToastMessages(toastMessages.filter((toast) => toast.key !== key)); + }; + + const changeHandler = (e: ChangeEvent) => { + e.preventDefault(); + setSearchText(e.target.value); + }; + + // TODO: 검색을 호출할 API가 필요함. + const keyPressHandler = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + if (!searchText) { + return; + } + console.log(searchText); + setToastKey(toastKey + 1); + setToastMessages([ + ...toastMessages, + { key: toastKey, message: '검색 기능은 추후에 지원될 예정입니다.' }, + ]); + setSearchText(''); + } + }; + + const clickHandler = (e: MouseEvent) => { + e.preventDefault(); + // TODO: 검색창을 누르면 호출할 API가 필요합니다. + if (!searchText) { + return; + } + console.log(searchText); + setToastKey(toastKey + 1); + setToastMessages([ + ...toastMessages, + { key: toastKey, message: '검색 기능은 추후에 지원될 예정입니다.' }, + ]); + setSearchText(''); + }; + + return ( +
+ + + {toastMessages.map((toast) => ( + handleToastClose(toast.key)} + /> + ))} +
+ ); +}; + +export default SearchBar; + +const searchBarCSS = css` + border-radius: 0.8rem; + background-color: ${color.whiteGray}; + padding: 0.3rem; + font-size: 0.75rem; + font-weight: 400; + color: ${color.darkGreen}; +`; + +const inputCSS = css` + outline: none; + border: none; + background: none; + padding-left: 0.75rem; + width: 4rem; +`; + +const buttonCSS = css` + border: none; + background: none; +`; diff --git a/src/components/common/sectionLine/SectionLine.tsx b/src/components/common/sectionLine/SectionLine.tsx new file mode 100644 index 0000000..8fc893f --- /dev/null +++ b/src/components/common/sectionLine/SectionLine.tsx @@ -0,0 +1,15 @@ +import { css } from '@emotion/react'; +import color from '@/styles/color'; + +const SectionLine = () => ( +
+); + +export default SectionLine; + +const lineCSS = css` + height: 1px; + margin: 0.625rem auto; + background: ${color.lightGray}; + width: 40%; +`; diff --git a/src/components/common/sectionTitle/SectionTitle.tsx b/src/components/common/sectionTitle/SectionTitle.tsx new file mode 100644 index 0000000..7b9c9c6 --- /dev/null +++ b/src/components/common/sectionTitle/SectionTitle.tsx @@ -0,0 +1,19 @@ +import { css } from '@emotion/react'; +import { FC, ReactNode } from 'react'; +import color from '@/styles/color'; + +interface SectionTitleProps { + children: ReactNode +} + +const SectionTitle:FC = ({ children }) => ( +
{children}
+); + +export default SectionTitle; + +const titleCSS = css` + font-size: 0.9rem; + font-weight: bold; + color: ${color.greenGray}; +`; diff --git a/src/components/common/skeleton/RecommendBoxSkeleton.tsx b/src/components/common/skeleton/RecommendBoxSkeleton.tsx new file mode 100644 index 0000000..987cc5a --- /dev/null +++ b/src/components/common/skeleton/RecommendBoxSkeleton.tsx @@ -0,0 +1,72 @@ +import { loadingCSS } from '@/styles/GlobalStyles'; +import color from '@/styles/color'; +import { css } from '@emotion/react'; + +const RecommendBoxSkeleton = () => ( +
+ + +
+); + +export default RecommendBoxSkeleton; + +const SingleSkeleton = () => ( +
+
+
+
+
+
+); + +const recommendsCSS = css` + width:100%; + display:grid; + grid-template-columns: repeat(2, 1fr); +`; + +const boxCSS = css` + margin: 0.5rem; + padding: 1rem; + height: 12.5rem; + background-color: ${color.whiteGray}; + border-radius: 1rem; + display: flex; + flex-direction: column; + align-items: center; +`; + +const imageWrapperCSS = css` + width: 3rem; + height: 3rem; + position: relative; + margin: 0.25rem; + border-radius: 50%; + ${loadingCSS} +`; + +const characterNameCSS = css` + width: 5rem; + height: 1rem; + border-radius: 1rem; + ${loadingCSS}; +`; + +const hashTagCSS = css` + height: 0.75rem; + margin: 0.375rem; + width: 8rem; + border-radius: 0.75rem; + ${loadingCSS} +`; + +const statusMessageCSS = css` + color: ${color.greenGray}; + height: 0.875rem; + width: 8rem; + margin: 0.375rem; + border-radius: 0.875rem; + + ${loadingCSS} +`; diff --git a/src/components/common/skeleton/SkeletonList.tsx b/src/components/common/skeleton/SkeletonList.tsx new file mode 100644 index 0000000..fd0d490 --- /dev/null +++ b/src/components/common/skeleton/SkeletonList.tsx @@ -0,0 +1,59 @@ +import { loadingCSS } from '@/styles/GlobalStyles'; +import { css } from '@emotion/react'; + +const SkeletonList = () => ( + <> + + + +); + +export default SkeletonList; + +const SingleSkeleton = () => ( +
+
+
+
+
+
+
+); + +const friendCSS = css` + display:flex; + flex-direction:row; + align-items:center; +`; + +const imageWrapperCSS = css` + width: 3rem; + height: 3rem; + position: relative; + margin: 0.375rem; + border-radius: 50%; + ${loadingCSS}; +`; + +const textWrapperCSS = css` + display:flex; + flex-direction:column; + align-items:start; + justify-content:center; + gap: 0.25rem; +`; + +const characterNameCSS = css` + padding-bottom: 0.25rem; + width: 5rem; + height: 1rem; + border-radius: 1rem; + ${loadingCSS}; +`; + +const messageCSS = css` + width: 15rem; + height: 0.75rem; + border-radius: 0.75rem; + ${loadingCSS}; +`; diff --git a/src/components/common/textUnderLineDeco/UnderLineText.tsx b/src/components/common/textUnderLineDeco/UnderLineText.tsx new file mode 100644 index 0000000..8609f2e --- /dev/null +++ b/src/components/common/textUnderLineDeco/UnderLineText.tsx @@ -0,0 +1,17 @@ +import { FC, ReactNode } from 'react'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; + +type underLineTextProps = { + children: ReactNode +} + +const UnderLineText: FC = ({ children }) => ( + { children } +); + +export default UnderLineText; + +const underLineCSS = css` + background: linear-gradient(to top, ${color.whiteGreen} 50%, transparent 50%) +`; diff --git a/src/components/common/timeStamp/TimeStamp.tsx b/src/components/common/timeStamp/TimeStamp.tsx new file mode 100644 index 0000000..c90d3f3 --- /dev/null +++ b/src/components/common/timeStamp/TimeStamp.tsx @@ -0,0 +1,32 @@ +import type { FC } from 'react'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; + +interface TimestampProps { + timestamp: number +} + +const TimeStamp: FC = ({ timestamp }) => ( + {makeDate(timestamp)} +); + +export default TimeStamp; + +const makeDate = (timestamp : number) => { + const dateObject = new Date(timestamp); + let hour = dateObject.getHours(); + const ampm = hour > 12 ? 'PM' : 'AM'; + if (hour > 12) { + hour -= 12; + } else if (hour === 0) { + hour += 12; + } + const minute = dateObject.getMinutes(); + return `${hour}:${minute.toString().padStart(2, '0')} ${ampm}`; +}; + +const timestampCSS = css` + color: ${color.greenGray}; + font-size: 0.625rem; + padding: 0.25rem; +`; diff --git a/src/components/common/toast/Toast.tsx b/src/components/common/toast/Toast.tsx new file mode 100644 index 0000000..e1fa484 --- /dev/null +++ b/src/components/common/toast/Toast.tsx @@ -0,0 +1,67 @@ +import { css, keyframes } from '@emotion/react'; +import { + useState, useEffect, FC, KeyboardEvent, +} from 'react'; +import color from '@/styles/color'; + +interface ToastProps { + message: string; + handleClose: () => void; +} + +const Toast: FC = ({ message, handleClose }) => { + const [startAnimation, setStartAnimation] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => { + setStartAnimation(true); + handleClose(); + }, 3000); + return () => clearTimeout(timer); + }, []); + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + handleClose(); + } + }; + + return ( +
+ {message} +
+ ); +}; + +const slide = keyframes` + 0% { + opacity: 0.5; + } + 100% { + opacity: 0; + } +`; + +const toastCSS = (startAnimation : boolean) => css` + position: fixed; + z-index: 3; + bottom: 50%; + right: 50%; + transform: translateX(50%); + background: ${color.black}; + opacity: 0.5; + color: ${color.white}; + padding: 1rem; + border-radius: 1.875rem; + animation: ${startAnimation ? css`${slide} 1s ease forwards` : 'none'}; + display: flex; + align-items: center; +`; + +export default Toast; diff --git a/src/components/community/BoardList.tsx b/src/components/community/BoardList.tsx new file mode 100644 index 0000000..432261d --- /dev/null +++ b/src/components/community/BoardList.tsx @@ -0,0 +1,62 @@ +import { css } from '@emotion/react'; +import { useEffect, useState } from 'react'; +import { CharacterInfo } from '@/types/characterInfo'; +import { findAllCharacters } from '@/utils/api/character'; +import Loading from '../common/dialog/Loading'; +import FriendInfo from '../friends/friend/FriendInfo'; +import FriendWrapper from '../friends/friend/FriendWrapper'; +import SkeletonList from '../common/skeleton/SkeletonList'; + +const BoardList = () => { + const [characterInfoList, setCharacterInfoList] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + findAllCharacters() + .then((data) => { + setCharacterInfoList(data); + setLoading(false); + }) + .catch((error) => { + console.error('Error fetching post:', error); + }); + }, []); + if (loading) { + return ( +
+ +
+ ); + } + + return ( +
+ {characterInfoList ? ( + characterInfoList.map((characterInfo) => ( +
+ + + +
+ )) + ) : } +
+ ); +}; + +export default BoardList; + +const boardListWrapperCSS = css` + display: flex; + flex-direction: column; + word-break: keep-all; + padding: 0.375rem; + padding-top: 1.25rem; +`; diff --git a/src/components/community/CommunityHeader.tsx b/src/components/community/CommunityHeader.tsx new file mode 100644 index 0000000..ed7b030 --- /dev/null +++ b/src/components/community/CommunityHeader.tsx @@ -0,0 +1,68 @@ +import { useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; +import { useRouter } from 'next/router'; +import { findCharacterById } from '@/utils/api/character'; +import { CharacterInfo as CharacterInfoType } from '@/types/characterInfo'; +import SearchBar from '@/components/common/searchBar/SearchBar'; +import CharacterInfo from '@/components/chat/characterHeader/CharacterInfo'; +import Loading from '../common/dialog/Loading'; + +const CommunityHeader = () => { + // TODO: 친밀도를 API로 받아와야 작업이 가능함! + const router = useRouter(); + const { character_id: characterId } = router.query; + const [characterInfo, setCharacterInfo] = useState(); + useEffect(() => { + if (characterId && typeof characterId === 'string') { + findCharacterById(characterId) + .then((data) => setCharacterInfo(data)) + .catch((error) => { + console.error('Error fetching post:', error); + }); + } + }, [characterId]); + return ( +
+ {/* TODO: 캐릭터의 정보, 이미지가 필요합니다. */} + + { + characterInfo + ? ( + + ) + : + } + + +
+ ); +}; + +export default CommunityHeader; + +const headerCSS = css` + position: sticky; + top: 0; + z-index: 100; // 채팅보다 위에 존재해야하기 때문에 필요함 + width: 100%; + max-width: 400px; + padding-left: 1.25rem; + padding-right: 0.6rem; + padding-top: 0.6rem; display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + background-color: ${color.white}; +`; + +const characterInfoCSS = css` + display: flex; + flex-direction: row; + align-items: center; +`; diff --git a/src/components/community/PostHeader.tsx b/src/components/community/PostHeader.tsx new file mode 100644 index 0000000..0ce6c29 --- /dev/null +++ b/src/components/community/PostHeader.tsx @@ -0,0 +1,64 @@ +import { useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; +import { useRouter } from 'next/router'; +import { findCharacterById } from '@/utils/api/character'; +import { CharacterInfo as CharacterInfoType } from '@/types/characterInfo'; +import CharacterInfo from '@/components/chat/characterHeader/CharacterInfo'; +import Loading from '../common/dialog/Loading'; + +const PostHeader = () => { + const router = useRouter(); + const { character_id: characterId } = router.query; + const [characterInfo, setCharacterInfo] = useState(); + useEffect(() => { + if (characterId && typeof characterId === 'string') { + findCharacterById(characterId) + .then((data) => setCharacterInfo(data)) + .catch((error) => { + console.error('Error fetching post:', error); + }); + } + }, [characterId]); + return ( +
+ {/* TODO: 캐릭터의 정보, 이미지가 필요합니다. */} + + { + characterInfo + ? ( + + ) + : + } + +
+ ); +}; + +export default PostHeader; + +const headerCSS = css` + position: sticky; + top: 0; + z-index: 100; // 채팅보다 위에 존재해야하기 때문에 필요함 + width: 100%; + max-width: 400px; + padding: 1rem 1rem; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + background-color: ${color.white}; +`; + +const characterInfoCSS = css` + display: flex; + flex-direction: row; + align-items: center; +`; diff --git a/src/components/community/PostList.tsx b/src/components/community/PostList.tsx new file mode 100644 index 0000000..f2fd8a1 --- /dev/null +++ b/src/components/community/PostList.tsx @@ -0,0 +1,47 @@ +import { css } from '@emotion/react'; +import { useEffect, useState } from 'react'; +import { PostData } from '@/types/post'; +import { useRouter } from 'next/router'; +import { findBoardById } from '@/utils/api/boards'; +import Loading from '@/components/common/dialog/Loading'; +import Post from './postList/Post'; + +const PostList = () => { + const router = useRouter(); + const [postList, setPostList] = useState([]); + const { character_id: characterId } = router.query; + useEffect(() => { + if (characterId && typeof characterId === 'string') { + findBoardById(characterId).then((data) => { + setPostList(data); + console.log(data); + }).catch((error) => { + console.error('Error fetching boards:', error); + }); + } + }, [characterId]); + + return ( +
+ {postList ? ( + postList.map((post) => ( + // 문자열 타입만 전달하도록 확인 + typeof characterId === 'string' ? ( + + ) : null + )) + ) : } +
+ ); +}; + +export default PostList; + +const boardListWrapperCSS = css` + display: flex; + flex-direction: column; + word-break: keep-all; + padding: 2rem 0.5rem; + padding-top: 1.25rem; + width: 100%; +`; diff --git a/src/components/community/WriteButton.tsx b/src/components/community/WriteButton.tsx new file mode 100644 index 0000000..d6be9ec --- /dev/null +++ b/src/components/community/WriteButton.tsx @@ -0,0 +1,55 @@ +import color from '@/styles/color'; +import PostWriteIcon from '@/components/icons/PostWriteIcon'; +import { css } from '@emotion/react'; +import { useRouter } from 'next/router'; + +const WriteButton = () => { + const router = useRouter(); + const { character_id: characterId } = router.query; + + const buttonHandler = () => { + if (characterId && typeof characterId === 'string') { + router.push(`/community/${characterId}/edit`); + } + }; + + return ( + + ); +}; + +export default WriteButton; + +const buttonCSS = css` + position: fixed; + top: 80%; + left: 50%; + transform: translate(-50%, -50%); + width: 10rem; + z-index: 5; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 1rem; + + padding: 0.5rem; + border: none; + border-radius: 1rem; + + background-color: ${color.offWhite}; + color: ${color.black}; + + &:active, &:hover { + background-color: ${color.lightGray}; + } + +`; + +const textCSS = css` + font-size: 1rem; +`; diff --git a/src/components/community/postDetail/PostStatus.tsx b/src/components/community/postDetail/PostStatus.tsx new file mode 100644 index 0000000..7ed829f --- /dev/null +++ b/src/components/community/postDetail/PostStatus.tsx @@ -0,0 +1,45 @@ +import { css } from '@emotion/react'; +import HeartIcon from '@/components/icons/HeartIcon'; +import CommentIcon from '@/components/icons/CommentIcon'; +import ReportIcon from '@/components/icons/ReportIcon'; +import color from '@/styles/color'; + +const PostStatus = () => ( +
    +
  • + +
    1
    +
  • +
  • + +
    3
    +
  • +
  • + +
    신고
    +
  • +
+); + +export default PostStatus; + +const ulCSS = css` + display: flex; + flex-direction: row; + align-items: center; + gap: 0.25rem; + padding-top: 1rem; +`; + +const liCSS = css` + display: flex; + flex-direction: row; + align-items: center; + gap: 0.25rem; +`; + +const statusCSS = css` + font-size: 0.75rem; + font-weight: bold; + color: ${color.black}; +`; diff --git a/src/components/community/postList/Post.tsx b/src/components/community/postList/Post.tsx new file mode 100644 index 0000000..225c0cb --- /dev/null +++ b/src/components/community/postList/Post.tsx @@ -0,0 +1,80 @@ +import { postDateParse } from '@/utils/services/date'; +import FriendWrapper from '@/components/friends/friend/FriendWrapper'; +import HeartIcon from '@/components/icons/HeartIcon'; +import CommentIcon from '@/components/icons/CommentIcon'; +import color from '@/styles/color'; +import { css } from '@emotion/react'; +import { FC } from 'react'; +import { PostData } from '@/types/post'; + +interface PostStates { + characterId: string, + post: PostData +} + +const Post: FC = ({ characterId, post }) => ( + <> + +
+
{post.title}
+
{`${postDateParse(post.createdAt)} | ${post.writerName}`}
+
+ {/* TODO: 댓글, 좋아요를 받아와야합니다! */} +
    +
  • + +
    1
    +
  • +
  • + +
    3
    +
  • +
+
+
+ +); + +export default Post; + +const postCSS = css` + padding: 0.2rem 0 0.2rem 0.5rem; + width: 100%; +`; + +const titleCSS = css` + font-size: 1rem; + font-weight:bold; + color:${color.black}; + padding-bottom: 0.3rem; +`; + +const contentCSS = css` + font-size: 0.75rem; + color:${color.gray}; +`; + +const liCSS = css` + display: flex; + flex-direction: row; + align-items: center; + gap: 0.25rem; + padding-right: 0.3rem; +`; + +const statusCSS = css` + font-size: 0.625rem; + font-weight: bold; + color: ${color.black}; +`; + +const lineCSS = css` + display: block; + height: 1px; + width: 100%; + background: ${color.lightGray}; + margin: 0.2rem 0; +`; diff --git a/src/components/friends/ChatLogs.tsx b/src/components/friends/ChatLogs.tsx new file mode 100644 index 0000000..272475a --- /dev/null +++ b/src/components/friends/ChatLogs.tsx @@ -0,0 +1,79 @@ +import { css } from '@emotion/react'; +import TimeStamp from '@/components/common/timeStamp/TimeStamp'; +import { useEffect, useState } from 'react'; +import { recentChatAPI } from '@/utils/api/chats'; +import { CharacterInfo } from '@/types/characterInfo'; +import FriendWrapper from './friend/FriendWrapper'; +import FriendInfo from './friend/FriendInfo'; +import ChatBadge from './friend/ChatBadge'; +import SkeletonList from '../common/skeleton/SkeletonList'; + +interface recentMessageWithCharacterInfo { + characterInfo: CharacterInfo; + lastMessage: { + content: string; + createdAt: number; + fromUser: boolean; + }; +} + +const ChatLogs = () => { + const [recentChatList, setRecentChatList] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + recentChatAPI() + .then((recentChatData) => { + setRecentChatList([...recentChatData]); + setLoading(false); + }) + .catch((error) => { + console.error('Error fetching post:', error); + }); + }, []); + if (loading) { + return ( +
+ +
+ ); + } + + return ( +
+ {recentChatList.map(({ characterInfo, lastMessage }) => ( + + +
+ + +
+
+ ))} +
+ ); +}; + +export default ChatLogs; + +const chatLogsWrapperCSS = css` + display: flex; + flex-direction: column; + word-break: keep-all; + padding: 0.375rem; + padding-top: 1.25rem; +`; + +const subInfoWrapperCSS = css` + display:flex; + flex-direction:column; + align-items: flex-end; + margin-right: 0.625rem; +`; diff --git a/src/components/friends/Friends.tsx b/src/components/friends/Friends.tsx new file mode 100644 index 0000000..584fe84 --- /dev/null +++ b/src/components/friends/Friends.tsx @@ -0,0 +1,57 @@ +import { css } from '@emotion/react'; +import { useEffect, useState } from 'react'; +import { CharacterInfo } from '@/types/characterInfo'; +import { findAllCharacters } from '@/utils/api/character'; +import FriendWrapper from './friend/FriendWrapper'; +import FriendHashTag from './friend/FriendHashTag'; +import FriendInfo from './friend/FriendInfo'; +import SkeletonList from '../common/skeleton/SkeletonList'; + +const Friends = () => { + const [characterInfoList, setCharacterInfoList] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + findAllCharacters() + .then((data) => { + setCharacterInfoList(data); + setLoading(false); + }) + .catch((error) => { + console.error('Error fetching post:', error); + }); + }, []); + if (loading) { + return ( +
+ +
+ ); + } + return ( +
+ {characterInfoList.map((characterInfo) => ( + + + + + ))} +
+ ); +}; + +export default Friends; + +const friendsWrapperCSS = css` + display: flex; + flex-direction: column; + word-break: keep-all; + padding: 0.375rem; +`; diff --git a/src/components/friends/Recommends.tsx b/src/components/friends/Recommends.tsx new file mode 100644 index 0000000..115e37f --- /dev/null +++ b/src/components/friends/Recommends.tsx @@ -0,0 +1,50 @@ +import { css } from '@emotion/react'; +import { findAllCharacters } from '@/utils/api/character'; +import { CharacterInfo } from '@/types/characterInfo'; +import { useEffect, useState } from 'react'; +import RecommendBox from './recommend/RecommendBox'; +import RecommendBoxSkeleton from '../common/skeleton/RecommendBoxSkeleton'; + +const Recommends = () => { + const [characterInfoList, setCharacterInfoList] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + findAllCharacters() + .then((data) => { + setCharacterInfoList(data); + setLoading(false); + }) + .catch((error) => { + console.error('Error fetching post:', error); + }); + }, []); + + if (loading) { + return ( + + ); + } + + return ( +
+ {characterInfoList.map((characterInfo) => ( + + ))} +
+ ); +}; +export default Recommends; + +const recommendsCSS = css` + width:100%; + display:grid; + grid-template-columns: repeat(2, 1fr); +`; diff --git a/src/components/friends/friend/ChatBadge.tsx b/src/components/friends/friend/ChatBadge.tsx new file mode 100644 index 0000000..f6c5b15 --- /dev/null +++ b/src/components/friends/friend/ChatBadge.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react'; +import { css } from '@emotion/react'; +import color from '@/styles/color'; + +interface ChatBadgeProps { + unreadCount: boolean +} + +const ChatBadge: FC = ({ unreadCount }) => ( +
{unreadCount ? '0' : '1'}
+); + +export default ChatBadge; + +const countCSS = (unreadCount : boolean) => css` + visibility : ${unreadCount ? 'visible' : 'hidden'}; + background-color: #F04A4C; + border-radius: 50%; + height: 1rem; + width: 1rem; + color: ${color.white}; + font-size: 0.75rem; + text-align: center; + margin: 0.25rem; + + display: flex; + align-items: center; + justify-content: center; +`; diff --git a/src/components/friends/friend/FriendHashTag.tsx b/src/components/friends/friend/FriendHashTag.tsx new file mode 100644 index 0000000..691e72e --- /dev/null +++ b/src/components/friends/friend/FriendHashTag.tsx @@ -0,0 +1,20 @@ +import { css } from '@emotion/react'; +import { FC } from 'react'; +import color from '@/styles/color'; + +interface FriendHashTagProps { + hashTag: string +} + +const FriendHashTag:FC = ({ hashTag }) => ( +
{hashTag}
+); + +export default FriendHashTag; + +const hashTagCSS = css` + font-size: 0.75rem; + color:${color.greenGray}; + width: 6.25rem; + margin-right:0.625rem; +`; diff --git a/src/components/friends/friend/FriendInfo.tsx b/src/components/friends/friend/FriendInfo.tsx new file mode 100644 index 0000000..a7faf99 --- /dev/null +++ b/src/components/friends/friend/FriendInfo.tsx @@ -0,0 +1,65 @@ +import Image from 'next/image'; +import { css } from '@emotion/react'; +import { FC } from 'react'; +import color from '@/styles/color'; + +interface FriendInfoProps { + characterName: string, + message: string, + profileImageUrl: string, +} + +const FriendInfo: FC = ({ characterName, message, profileImageUrl }) => ( +
+
+ {characterName} +
+
+
{characterName}
+
{message}
+
+
+); + +export default FriendInfo; + +const imageWrapperCSS = css` + width: 3rem; + height: 3rem; + position: relative; + margin: 0.375rem; +`; + +const imageCSS = css` + border-radius: 50%; +`; + +const textWrapperCSS = css` + display:flex; + flex-direction:column; + align-items:start; + justify-content:center; + gap: 0.25rem; +`; + +const characterNameCSS = css` + font-size: 1rem; + font-weight:bold; + padding-bottom: 0.25rem; + color:${color.black}; +`; + +const messageCSS = css` + font-size: 0.75rem; + color:${color.greenGray}; + max-width: 15rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; diff --git a/src/components/friends/friend/FriendWrapper.tsx b/src/components/friends/friend/FriendWrapper.tsx new file mode 100644 index 0000000..5b1e004 --- /dev/null +++ b/src/components/friends/friend/FriendWrapper.tsx @@ -0,0 +1,28 @@ +import { css } from '@emotion/react'; +import { FC, ReactNode } from 'react'; +import LinkWrapper from '@/components/common/link/LinkWrapper'; + +interface FriendProps { + children: ReactNode, + linkUrl: string, +} + +const FriendWrapper: FC = ({ + children, linkUrl, +}) => ( + +
+ {children} +
+
+); + +export default FriendWrapper; + +const friendCSS = css` + display:flex; + flex-direction:row; + align-items:center; + justify-content:space-between; + padding: 0.25rem 0; +`; diff --git a/src/components/friends/recommend/RecommendBox.tsx b/src/components/friends/recommend/RecommendBox.tsx new file mode 100644 index 0000000..71bf23b --- /dev/null +++ b/src/components/friends/recommend/RecommendBox.tsx @@ -0,0 +1,91 @@ +import { css } from '@emotion/react'; +import { FC } from 'react'; +import Image from 'next/image'; +import Link from 'next/link'; +import styled from '@emotion/styled'; +import color from '@/styles/color'; +import { RecommendCharacterProps } from '@/types/characterInfo'; + +const RecommendBox:FC = ({ + characterName, characterId, hashTag, statusMessage, profileImageUrl: imageUrl, +}) => ( + + +
+
+ {characterName} +
+
{characterName}
+
{hashTag}
+
{statusMessage}
+
+
+ +); + +export default RecommendBox; + +const ResetLink = styled.a` + text-decoration: none; + + &:active { + text-decoration: none; + } +`; + +const boxCSS = css` + margin: 0.5rem; + padding: 1rem; + height: 12.5rem; + background-color: ${color.whiteGray}; + border-radius: 1rem; + display: flex; + flex-direction: column; + align-items: center; + word-break: keep-all; + + &:hover { + background-color: ${color.offWhite}; + } + + &:active { + background-color: ${color.offWhite}; + } +`; + +const imageWrapperCSS = css` + width: 3rem; + height: 3rem; + position: relative; + margin: 0.25rem; +`; + +const imageCSS = css` + border-radius: 50%; +`; + +const characterNameCSS = css` + color: ${color.black}; + font-size: 1rem; + font-weight: 700; +`; + +const hashTagCSS = css` + color: ${color.greenGray}; + font-size: 0.75rem; + padding: 0.375rem; + text-align: center; +`; + +const statusMessageCSS = css` + color: ${color.greenGray}; + font-size: 0.875rem; + padding: 0.375rem; + text-align: center; +`; diff --git a/src/components/icons/BackwordIcon.tsx b/src/components/icons/BackwordIcon.tsx new file mode 100644 index 0000000..5f0630e --- /dev/null +++ b/src/components/icons/BackwordIcon.tsx @@ -0,0 +1,10 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const BackwordIcon: FC = ({ color }) => ( + + + +); + +export default BackwordIcon; diff --git a/src/components/icons/ChatIcon.tsx b/src/components/icons/ChatIcon.tsx new file mode 100644 index 0000000..7926709 --- /dev/null +++ b/src/components/icons/ChatIcon.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const ChatIcon: FC = ({ color }) => ( + + + + + + + + + + +); + +export default ChatIcon; diff --git a/src/components/icons/CommentIcon.tsx b/src/components/icons/CommentIcon.tsx new file mode 100644 index 0000000..6d34c14 --- /dev/null +++ b/src/components/icons/CommentIcon.tsx @@ -0,0 +1,16 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const CommentIcon: FC = ({ color }) => ( + + + + + + + + + +); + +export default CommentIcon; diff --git a/src/components/icons/CommunityIcon.tsx b/src/components/icons/CommunityIcon.tsx new file mode 100644 index 0000000..3a575e2 --- /dev/null +++ b/src/components/icons/CommunityIcon.tsx @@ -0,0 +1,16 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const CommunityIcon: FC = ({ color }) => ( + + + + + + + + + +); + +export default CommunityIcon; diff --git a/src/components/icons/HeartIcon.tsx b/src/components/icons/HeartIcon.tsx new file mode 100644 index 0000000..eb8b258 --- /dev/null +++ b/src/components/icons/HeartIcon.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const HeartIcon: FC = ({ color }) => ( + + + + + + +); + +export default HeartIcon; diff --git a/src/components/icons/HomeIcon.tsx b/src/components/icons/HomeIcon.tsx new file mode 100644 index 0000000..b901110 --- /dev/null +++ b/src/components/icons/HomeIcon.tsx @@ -0,0 +1,11 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const HomeIcon: FC = ({ color }) => ( + + + + +); + +export default HomeIcon; diff --git a/src/components/icons/LogoutIcon.tsx b/src/components/icons/LogoutIcon.tsx new file mode 100644 index 0000000..fa95cf7 --- /dev/null +++ b/src/components/icons/LogoutIcon.tsx @@ -0,0 +1,11 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const LogOutIcon: FC = ({ color }) => ( + + + + +); + +export default LogOutIcon; diff --git a/src/components/icons/PostWriteIcon.tsx b/src/components/icons/PostWriteIcon.tsx new file mode 100644 index 0000000..0c510a1 --- /dev/null +++ b/src/components/icons/PostWriteIcon.tsx @@ -0,0 +1,11 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const PostWriteIcon: FC = ({ color }) => ( + + + + +); + +export default PostWriteIcon; diff --git a/src/components/icons/ProfileIcon.tsx b/src/components/icons/ProfileIcon.tsx new file mode 100644 index 0000000..3ab672c --- /dev/null +++ b/src/components/icons/ProfileIcon.tsx @@ -0,0 +1,15 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const ProfileIcon: FC = ({ color }) => ( + + + + + + + + +); + +export default ProfileIcon; diff --git a/src/components/icons/ProfileSettingIcon.tsx b/src/components/icons/ProfileSettingIcon.tsx new file mode 100644 index 0000000..3602414 --- /dev/null +++ b/src/components/icons/ProfileSettingIcon.tsx @@ -0,0 +1,11 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const ProfileSettingIcon: FC = ({ color }) => ( + + + + +); + +export default ProfileSettingIcon; diff --git a/src/components/icons/ReportIcon.tsx b/src/components/icons/ReportIcon.tsx new file mode 100644 index 0000000..312d69b --- /dev/null +++ b/src/components/icons/ReportIcon.tsx @@ -0,0 +1,10 @@ +import { IconProps } from '@/types/icon'; +import { FC } from 'react'; + +const ReportIcon: FC = ({ color }) => ( + + + +); + +export default ReportIcon; diff --git a/src/components/icons/SearchIcon.tsx b/src/components/icons/SearchIcon.tsx new file mode 100644 index 0000000..acb4b4a --- /dev/null +++ b/src/components/icons/SearchIcon.tsx @@ -0,0 +1,11 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const SearchIcon: FC = ({ color }) => ( + + + + +); + +export default SearchIcon; diff --git a/src/components/icons/SettingIcon.tsx b/src/components/icons/SettingIcon.tsx new file mode 100644 index 0000000..e95d225 --- /dev/null +++ b/src/components/icons/SettingIcon.tsx @@ -0,0 +1,10 @@ +import { FC } from 'react'; +import { IconProps } from '@/types/icon'; + +const SettingIcon: FC = ({ color }) => ( + + + +); + +export default SettingIcon; diff --git a/src/components/profile/CharacterProfileInfo.tsx b/src/components/profile/CharacterProfileInfo.tsx new file mode 100644 index 0000000..03c4bcd --- /dev/null +++ b/src/components/profile/CharacterProfileInfo.tsx @@ -0,0 +1,47 @@ +import { css } from '@emotion/react'; +import Image from 'next/image'; +import { FC } from 'react'; +import color from '@/styles/color'; + +interface CharacterProfileInfoProps { + characterName: string, + hashTag: string, + profileImageUrl: string, + statusMessage: string, +} + +const CharacterProfileInfo: FC = ({ + characterName, hashTag, profileImageUrl, statusMessage, +}) => ( +
+
+ {`/${characterName}`} +
+
{characterName}
+
{hashTag}
+
{statusMessage}
+
+); + +export default CharacterProfileInfo; + +const profileInfoCSS = css` + display:flex; + flex-direction:column; + align-items: center; +`; + +const imageWrapperCSS = css` + width: 5.5rem; + height: 5.5rem; + position: relative; +`; + +const imageCSS = css` + border-radius: 50%; +`; diff --git a/src/components/profile/ProfileFriendShip.tsx b/src/components/profile/ProfileFriendShip.tsx new file mode 100644 index 0000000..29cc2ee --- /dev/null +++ b/src/components/profile/ProfileFriendShip.tsx @@ -0,0 +1,18 @@ +import { css } from '@emotion/react'; +import color from '@/styles/color'; + +// TODO: 친밀도, 친구된 지 며칠됐는 지 알려주는 API를 만들어주세요~! +const ProfileFriendShip = () => ( +
+
+
조금씩 친해지는 사이
+
Lv. 0
+
+
+
우리가 친구 된 지
+
1 일째
+
+
+); + +export default ProfileFriendShip; diff --git a/src/components/profile/ProfileRouteButtons.tsx b/src/components/profile/ProfileRouteButtons.tsx new file mode 100644 index 0000000..307c233 --- /dev/null +++ b/src/components/profile/ProfileRouteButtons.tsx @@ -0,0 +1,34 @@ +import { css } from '@emotion/react'; +import { FC } from 'react'; +import ChatIcon from '@/components/icons/ChatIcon'; +import CommunityIcon from '@/components/icons/CommunityIcon'; +import color from '@/styles/color'; +import ProfileRouteWrapper from './routeButtons/ProfileRouteWrapper'; + +interface RouteProps { + characterId: number +} + +const ProfileRouteButtons:FC = ({ characterId }) => ( +
+ + + chat + + + + Community + +
+); + +export default ProfileRouteButtons; + +const ButtonsWrapperCSS = css` + width:100%; + padding-top:3rem; + display: grid; + grid-template-columns: repeat(2, 1fr); + justify-items: center; + justify-content: center; +`; diff --git a/src/components/profile/UserRouteButtons.tsx b/src/components/profile/UserRouteButtons.tsx new file mode 100644 index 0000000..de55516 --- /dev/null +++ b/src/components/profile/UserRouteButtons.tsx @@ -0,0 +1,69 @@ +import { css } from '@emotion/react'; +import color from '@/styles/color'; +import { signOut } from 'next-auth/react'; +import Toast from '@/components/common/toast/Toast'; +import { useState } from 'react'; +import LogOutIcon from '../icons/LogoutIcon'; +import ProfileSettingIcon from '../icons/ProfileSettingIcon'; + +const ProfileRouteButtons = () => { + const [toastMessage, setToastMessage] = useState(''); + + // TODO: 미들웨어로 로그인 여부를 컨트롤할 예정 + // TODO: 백엔드 로그아웃 로직이 변경되고 있는 중이라 아직 쓸 수 없는 코드가 있음 + // const { data: session }: any = useSession(); + const signOutHandler = () => { + // logOutAPI(session?.accessToken); + signOut({ callbackUrl: '/' }); + }; + + const profileEditHandler = () => { + setToastMessage('개인 프로필 수정은 추후에 제공될 예정입니다. :)'); + }; + + const handleToastClose = () => { + setToastMessage(''); + }; + + return ( + <> +
+ + +
+ { + toastMessage + ? + : null + } + + ); +}; + +export default ProfileRouteButtons; + +const ButtonsWrapperCSS = css` + width:100%; + padding-top:3rem; + display: grid; + grid-template-columns: repeat(2, 1fr); + justify-items: center; + justify-content: center; +`; + +const buttonCSS = css` + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; + color: ${color.black}; + background-color: ${color.white}; + border: none; + +`; diff --git a/src/components/profile/routeButtons/ProfileRouteWrapper.tsx b/src/components/profile/routeButtons/ProfileRouteWrapper.tsx new file mode 100644 index 0000000..5a788d2 --- /dev/null +++ b/src/components/profile/routeButtons/ProfileRouteWrapper.tsx @@ -0,0 +1,27 @@ +import { css } from '@emotion/react'; +import { FC, ReactNode } from 'react'; +import LinkWrapper from '@/components/common/link/LinkWrapper'; + +interface ProfileRouteWrapperProps { + children: ReactNode, + linkUrl: string, + color: string, +} + +const ProfileRouteWrapper: FC = ({ children, linkUrl, color }) => ( + +
+ {children} +
+
+); + +export default ProfileRouteWrapper; + +const WrapperCSS = (color: string) => css` + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; + color: ${color}; +`; diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..93a4332 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,53 @@ +/* eslint-disable consistent-return */ +import { getToken } from 'next-auth/jwt'; +import { NextRequest, NextResponse } from 'next/server'; + +const withAuthList = [ + '/chats', '/chats/:path*', + '/friends', '/friends/:path*', + '/community', '/community/:path*', + '/profile', '/profile/:path*', +]; +const withOutAuthList = ['/login', '/signup']; + +export async function middleware(req: NextRequest) { + const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); + + const { pathname } = req.nextUrl; + const isWithAuth = pathMatch(pathname, withAuthList); + const isWithOutAuth = pathMatch(pathname, withOutAuthList); + + if (isWithAuth) return withAuth(req, !!token); // 로그인 여부에 따라 redirect 하는 함수 + if (isWithOutAuth) return withOutAuth(req, !!token); // 로그인 여부에 따라 redirect 하는 함수 +} + +export const config = { + mathcher: [...withAuthList, ...withOutAuthList], +}; + +const withAuth = (req: NextRequest, token: boolean) => { + const url = req.nextUrl.clone(); + + if (!token) { + url.pathname = '/login'; + return NextResponse.redirect(url); + } + return null; +}; + +const withOutAuth = async (req:NextRequest, token: boolean) => { + const url = req.nextUrl.clone(); + + if (token) { + url.pathname = '/'; + + return NextResponse.redirect(url); + } +}; + +function pathMatch(path: string, patternList: string[]): boolean { + return patternList.some((pattern) => { + const regex = new RegExp(`^${pattern.replace(/:\w+\*/g, '\\w+').replace(/\*/g, '.*')}$`); + return regex.test(path); + }); +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx new file mode 100644 index 0000000..6bc73f2 --- /dev/null +++ b/src/pages/_app.tsx @@ -0,0 +1,35 @@ +import type { AppProps } from 'next/app'; +import GlobalStyles from '@/styles/GlobalStyles'; +import { SessionProvider } from 'next-auth/react'; +import GA from '@/components/common/head/GA'; +import { datadogRum } from '@datadog/browser-rum'; + +datadogRum.init({ + applicationId: '2e271a0d-bf2a-4465-8ef1-59bd1c2eceb7', + clientToken: 'pub30feb69c70c140b8f84ae56e3fea50f7', + site: 'ap1.datadoghq.com', + service: 'front', + env: 'staging-1', + // Specify a version number to identify the deployed version of your application in Datadog + // version: '1.0.0', + sessionSampleRate: 100, + sessionReplaySampleRate: 100, + trackUserInteractions: true, + trackResources: true, + trackLongTasks: true, + defaultPrivacyLevel: 'mask-user-input', +}); + +datadogRum.startSessionReplayRecording(); + +const App = ({ Component, pageProps: { session, ...pageProps } }: AppProps) => ( + <> + + + + + + +); + +export default App; diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx new file mode 100644 index 0000000..67f9e17 --- /dev/null +++ b/src/pages/_document.tsx @@ -0,0 +1,41 @@ +import { + Html, Head, Main, NextScript, +} from 'next/document'; + +const Document = () => ( + + + + + + + + + + + + {/* PWA */} + + + {/* OG */} + + + + + + + + {/* 트위터 */} + + + + + + +
+ + + +); + +export default Document; diff --git a/src/pages/api/auth/[...nextauth].ts b/src/pages/api/auth/[...nextauth].ts new file mode 100644 index 0000000..1c09521 --- /dev/null +++ b/src/pages/api/auth/[...nextauth].ts @@ -0,0 +1,138 @@ +/* eslint-disable no-param-reassign */ +import NextAuth, { NextAuthOptions, Session } from 'next-auth'; +import { JWT } from 'next-auth/jwt'; +import GoogleProvider from 'next-auth/providers/google'; +import NaverProvider from 'next-auth/providers/naver'; +import KakaoProvider from 'next-auth/providers/kakao'; +import CredentialsProvider from 'next-auth/providers/credentials'; +import { credentialsLoginAPI, refreshAccessToken, socialLoginAPI } from '@/utils/api/accounts'; +import { isTokenExpired } from '@/utils/services/auth'; + +interface CustomSession extends Session { + accessToken: string | null; + refreshToken: string | null; +} + +interface SessionCallback { + session: CustomSession; token: JWT +} + +export const authOptions: NextAuthOptions = { + providers: [ + GoogleProvider({ + clientId: process.env.GOOGLE_CLIENT_ID as string, + clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, + }), + + NaverProvider({ + clientId: process.env.NAVER_CLIENT_ID as string, + clientSecret: process.env.NAVER_CLIENT_SECRET as string, + }), + + KakaoProvider({ + clientId: process.env.KAKAO_CLIENT_ID as string, + clientSecret: process.env.KAKAO_CLIENT_SECRET as string, + }), + + CredentialsProvider({ + name: 'Credentials', + credentials: { + email: { label: 'email', type: 'text', placeholder: 'test-email' }, + password: { label: 'Password', type: 'password' }, + }, + + async authorize(credentials) { + const data = await credentialsLoginAPI({ + email: credentials?.email || '', + password: credentials?.password || '', + provider: 'credential', + }); + + if (data.nickname && data.accessToken) { + const user = { + accessToken: data.accessToken, + refreshToken: data.refreshToken, + name: data.nickname, + image: data.profileUrl, + email: credentials?.email, + id: data.memberId, + }; + return user; + } + return null; + }, + }), + ], + session: { + strategy: 'jwt', + maxAge: 7 * 24 * 60 * 60, // 7 days + }, + jwt: { + secret: process.env.NEXTAUTH_SECRET, + }, + pages: { + signIn: '/login', + }, + callbacks: { + async signIn({ user, account }) { + if (account?.type === 'credentials') return true; + const data = await socialLoginAPI({ + email: user.email || '', + name: user.name || '', + provider: account?.provider || '', + password: null, + }); + + if (data?.accessToken && data?.refreshToken) { + user.accessToken = data.accessToken; + user.refreshToken = data.refreshToken; + return true; + } + + return false; + }, + + async jwt({ token, user }) { + if (user?.accessToken) { + token.accessToken = user.accessToken; + } + if (user?.refreshToken) { + token.refreshToken = user.refreshToken; + } + + // accessToken 만료를 검사합니다. + if (token.accessToken && isTokenExpired(token.accessToken as string)) { + // 만료된 경우 refreshToken으로 새 accessToken을 발급 + const newToken = await refreshAccessToken(token.refreshToken as string); + + if (newToken) { + token.accessToken = newToken.accessToken; // 새로운 accessToken으로 업데이트 + token.refreshToken = newToken.refreshToken; // 새로운 accessToken으로 업데이트 + } else { + // TODO: refresh token 만료시 추가 처리 + // refreshToken도 만료되었거나 문제가 있을 경우 + // 필요한 추가 처리 (로그아웃)를 여기에다가 작성 + } + } + + return token; + }, + + async session({ session, token }: SessionCallback) { + (session as CustomSession).accessToken = token.accessToken as string | null; + (session as CustomSession).refreshToken = token.refreshToken as string | null; + return session; + }, + + async redirect({ url, baseUrl }) { + if (url.startsWith('/')) { + return `${baseUrl}${url}`; + } if (new URL(url).origin === baseUrl) { + return `${url}`; + } + return baseUrl; + }, + }, +}; + +export default NextAuth(authOptions); diff --git a/src/pages/api/userStatus/[character_id].tsx b/src/pages/api/userStatus/[character_id].tsx new file mode 100644 index 0000000..006838a --- /dev/null +++ b/src/pages/api/userStatus/[character_id].tsx @@ -0,0 +1,25 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from 'next'; + +interface NextApiRequestWithId extends NextApiRequest { + query: { + character_id: string; + }; +} + +type Data = { + friendShipExp: number, + maxFriendShipExp: number, + friendShipLv: number +} + +export default function handler( + req: NextApiRequestWithId, + res: NextApiResponse, +) { + res.status(200).json({ + friendShipExp: Math.random() * 200, + maxFriendShipExp: 200, + friendShipLv: 0, + }); +} diff --git a/src/pages/api/users/signup.ts b/src/pages/api/users/signup.ts new file mode 100644 index 0000000..c0e4830 --- /dev/null +++ b/src/pages/api/users/signup.ts @@ -0,0 +1,52 @@ +import { credentialsSignupAPI } from '@/utils/api/accounts'; +import type { NextApiRequest, NextApiResponse } from 'next'; + +interface ExtendedNextApiRequest extends NextApiRequest { + body: { + email: string, + username: string, + password: string, + confirmPassword: string, + }; +} + +export default async function handler(req: ExtendedNextApiRequest, res: NextApiResponse) { + const allowedOrigins = ['https://www.webtoonchat.com', 'https://webtoonchat.com']; + const isLocal = process.env.NEXT_PUBLIC_ENV === 'local'; + if (isLocal) allowedOrigins.push('http://localhost:3000'); + + const { origin } = req.headers; + + if (allowedOrigins.includes(origin || '')) { + res.setHeader('Access-Control-Allow-Origin', origin || ''); + } + + res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + // Handle OPTIONS request + if (req.method === 'OPTIONS') { + return res.status(200).end(); + } + + if (req.method !== 'POST') { + return res.status(405).end(); + } + const { + email, username, password, confirmPassword, + } = req.body; + + // TODO: 여기에서 유효성 검사를 하면 좋을 것 같음 + if (!email || !username || !password || password !== confirmPassword) { + return res.status(401).end(); + } + + const sendSignupData = { + email, + name: username, + password, + }; + + const result = await credentialsSignupAPI(sendSignupData); + return res.json(result); +} diff --git a/src/pages/chats/[character].tsx b/src/pages/chats/[character].tsx new file mode 100644 index 0000000..03ead00 --- /dev/null +++ b/src/pages/chats/[character].tsx @@ -0,0 +1,73 @@ +import { css } from '@emotion/react'; +import MessageInput from '@/components/chat/MessageInput'; +import Header from '@/components/chat/Header'; +import Main from '@/components/chat/Main'; +import SEO from '@/components/common/head/SEO'; +import { useEffect } from 'react'; +import useChatStore from '@/store/chat'; +import { CharacterInfo } from '@/types/characterInfo'; +import { ssrFindAllCharacters, ssrFindCharacterById } from '@/utils/api/character'; +import { GetStaticPaths, GetStaticProps } from 'next'; + +const Character = ({ characterInfo }: { characterInfo: CharacterInfo }) => { + const { setChatInfo } = useChatStore(); + + useEffect(() => { + setChatInfo(characterInfo.characterName, characterInfo.characterId); + }, []); + + return ( + <> + +
+
+
+
+
+ +
+ + ); +}; +export default Character; + +export const getStaticPaths: GetStaticPaths = async () => { + const characterIds = await ssrFindAllCharacters(); + + return { + paths: characterIds.map((data) => ({ params: { character: data.characterId.toString() } })), + fallback: 'blocking', // 존재하지 않는 경로의 경우, 서버에서 렌더링하도록 설정 + }; +}; + +export const getStaticProps: GetStaticProps = async (context) => { + const { character } = context.params!; + + try { + const characterInfo = await ssrFindCharacterById(character as string); + return { props: { characterInfo } }; + } catch (error) { + return { notFound: true }; + } +}; + +const pageCSS = css` + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + padding: 0 0.625rem; +`; diff --git a/src/pages/chats/index.tsx b/src/pages/chats/index.tsx new file mode 100644 index 0000000..fcb5627 --- /dev/null +++ b/src/pages/chats/index.tsx @@ -0,0 +1,57 @@ +import { css } from '@emotion/react'; +import SEO from '@/components/common/head/SEO'; +import SearchBar from '@/components/common/searchBar/SearchBar'; +import ChatLogs from '@/components/friends/ChatLogs'; +import SectionTitle from '@/components/common/sectionTitle/SectionTitle'; +import BottomNavBar from '@/components/common/bottomNavBar/BottomNavBar'; + +const ChatMain = () => ( + <> + +
+
+
+
+ Chats + +
+
+
+ +
+
+ +
+ +); + +export default ChatMain; + +const pageCSS = css` + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + padding: 0.625rem; + padding-bottom: 0; +`; + +const contentsCSS = css` + width: 100%; + overflow-y: auto; + ::-webkit-scrollbar { + display: none; + } + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +`; + +const titleSectionCSS = css` + width:100%; + display:flex; + justify-content:space-between; + align-items: center; + padding-left: 1.25rem; + padding-right: 0.6rem; + padding-top: 0.6rem;`; diff --git a/src/pages/community/[character_id]/[post_id]/index.tsx b/src/pages/community/[character_id]/[post_id]/index.tsx new file mode 100644 index 0000000..1222f43 --- /dev/null +++ b/src/pages/community/[character_id]/[post_id]/index.tsx @@ -0,0 +1,109 @@ +import { css } from '@emotion/react'; +import BottomNavBar from '@/components/common/bottomNavBar/BottomNavBar'; +import SEO from '@/components/common/head/SEO'; +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/router'; +import { findPostById } from '@/utils/api/boards'; +import Loading from '@/components/common/dialog/Loading'; +import color from '@/styles/color'; +import { postDetailDateParse } from '@/utils/services/date'; +import { PostData } from '@/types/post'; +import PostHeader from '@/components/community/PostHeader'; +import Image from 'next/image'; +import PostStatus from '@/components/community/postDetail/PostStatus'; +import DivideLine from '@/components/common/divideLine/DivideLine'; + +const Post = () => { + const router = useRouter(); + const { character_id: characterId, post_id: postId } = router.query; + const [post, setPost] = useState(); + useEffect(() => { + if (typeof characterId === 'string' && typeof postId === 'string') { + findPostById(characterId, postId).then((data) => { + setPost(data); + }).catch((error) => { + console.error('Error fetching post:', error); + }); + } + }, [characterId, postId]); + + return ( + <> + + +
+
+ {post ? ( +
+ {/* TODO: 게시글 쓴 사람의 이미지가 필요함 */} +
+ user-profile +
+
{post.writerName}
+
{postDetailDateParse(post.createdAt)}
+
+
+
+
{post.title}
+
{post.content}
+
+ +
+ ) + : } +
+ +
덧글은 추후에 제공될 예정입니다 :)
+
+ + + ); +}; +export default Post; + +const pageCSS = css` + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + padding: 0 3rem; +`; + +const postCSS = css` + width: 100%; + padding: 1rem 0; +`; + +const titleCSS = css` + font-size: 1rem; + font-weight: bold; + color:${color.black}; + + padding-bottom: 1rem; +`; + +const contentCSS = css` + font-size: 0.75rem; + color:${color.gray}; +`; + +const postInfoCSS = css` + padding: 0.2rem 0 0.2rem 0; + display: flex; + flex-direction: row; +`; + +const writerNameCSS = css` + font-size: 0.9rem; + color:${color.black}; + padding-bottom: 0.2rem; +`; + +const dateCSS = css` + font-size: 0.6rem; + color:${color.gray}; +`; + +const postMainCSS = css` + padding: 1.25rem 0 0.2rem 0; +`; diff --git a/src/pages/community/[character_id]/edit.tsx b/src/pages/community/[character_id]/edit.tsx new file mode 100644 index 0000000..61c03f5 --- /dev/null +++ b/src/pages/community/[character_id]/edit.tsx @@ -0,0 +1,119 @@ +import { css } from '@emotion/react'; +import SEO from '@/components/common/head/SEO'; +import { FormEvent, useState } from 'react'; +import PostHeader from '@/components/community/PostHeader'; +import DivideLine from '@/components/common/divideLine/DivideLine'; +import Button from '@/components/common/button/Button'; +import { createPost } from '@/utils/api/boards'; +import { useRouter } from 'next/router'; +import Toast from '@/components/common/toast/Toast'; + +const Post = () => { + const [title, setTitle] = useState(''); + const [content, setContent] = useState(''); + const [toastMessage, setToastMessage] = useState(''); + const router = useRouter(); + const { character_id: characterId } = router.query; + + const handleToastClose = () => { + setToastMessage(''); + }; + const messageHandler = (message: string) => { + setToastMessage(message); + }; + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + if (characterId && typeof characterId === 'string') { + if (title === '' || title.length > 30) { + messageHandler('제목을 30자 이내로 작성해주세요! :)'); + return; + } + + if (content === '' || content.length > 3000) { + messageHandler('내용은 3000자 이내로 작성해주세요! :)'); + return; + } + + const result = await createPost(characterId, title, content); + + if (result.status === 201) { + router.push({ + pathname: `/community/${characterId}`, + }); + return; + } + messageHandler('게시글 작성에 실패했습니다 :('); + } + router.push({ + pathname: '/community', + }); + }; + + return ( + <> + +
+ +
+ setTitle(e.target.value)} required /> +