diff --git "a/.github/ISSUE_TEMPLATE/bug-report-\353\262\204\352\267\270-\354\210\230\354\240\225-\355\205\234\355\224\214\353\246\277-.md" "b/.github/ISSUE_TEMPLATE/bug-report-\353\262\204\352\267\270-\354\210\230\354\240\225-\355\205\234\355\224\214\353\246\277-.md" new file mode 100644 index 000000000..b9a662105 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/bug-report-\353\262\204\352\267\270-\354\210\230\354\240\225-\355\205\234\355\224\214\353\246\277-.md" @@ -0,0 +1,22 @@ +--- +name: Bug report(버그 수정 템플릿) +about: 버그 제보 관련 템플릿 +title: "[BUG]" +labels: "\U0001F41B 버그" +assignees: '' + +--- + +### 버그 사항 +해당 버그를 **자세하게** 적어주세요 😊 + + +### 버그 simulation +버그를 발견하게 된 상황을 단계별로 적어주세요 😊 + + +### 원하던 상황 +원했던 상황을 자세하게 적어주세요 😊 + + +### **Screenshots** diff --git "a/.github/ISSUE_TEMPLATE/feature-request-\352\270\260\353\212\245-\352\260\234\353\260\234-\355\205\234\355\224\214\353\246\277-.md" "b/.github/ISSUE_TEMPLATE/feature-request-\352\270\260\353\212\245-\352\260\234\353\260\234-\355\205\234\355\224\214\353\246\277-.md" new file mode 100644 index 000000000..b5a4b2a48 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/feature-request-\352\270\260\353\212\245-\352\260\234\353\260\234-\355\205\234\355\224\214\353\246\277-.md" @@ -0,0 +1,27 @@ +--- +name: Feature request(기능 개발 템플릿) +about: 기능 이슈 관련 템플릿 +title: '' +labels: '' +assignees: '' +--- + +## ✨ 추가 기능 + + + +## 📆 일정 추정 + + + +## 📃 세부 사항 + + + +## ✅ 투두리스트 + +- [ ] item +- [ ] item + +## 🔗 참고 자료 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..caeee0e96 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +## 🔥 연관 이슈 + +close: # + +## 📝 작업 요약 + +수행할 작업을 1~2줄 사이로 요약해주세요. + +## ⏰ 소요 시간 + +기능 구현에 소요된 시간을 적어주세요. (추정했던 시간과 다르다면 이유도 함께) + +## 🔎 작업 상세 설명 + +주요 기능 및 로직에 관해 설명해주세요. + +## 🌟 논의 사항 + +크루들과 이야기 해보고 싶은 부분을 적어주세요. diff --git a/.github/workflows/backend-pr-comment.yml b/.github/workflows/backend-pr-comment.yml new file mode 100644 index 000000000..f1a50e52d --- /dev/null +++ b/.github/workflows/backend-pr-comment.yml @@ -0,0 +1,56 @@ +name: Pull request comment (BE) +on: + issue_comment: + types: [created, edited, deleted] + +defaults: + run: + working-directory: backend + +jobs: + pull_request_comment: + # This job only runs for pull request comments + if: ${{ github.event.issue.pull_request }} + runs-on: ubuntu-latest + steps: + - name: Send Slack notification When Review Completed + if: contains(github.event.comment.body, 'be-리뷰완') # check the comment if it contains the keywords + uses: slackapi/slack-github-action@v1.24.0 + with: + channel-id: ${{ secrets.SLACK_BE_CHANNEL }} # Slack 채널 ID + payload: | + { + "text": "", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "리뷰 완료했습니다👍\n<${{ github.event.comment.html_url }}|리뷰어의 코멘트 확인하러 가기>" + } + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} # Slack 토큰 + + - name: Send Slack notification When Re-review Requested + if: contains(github.event.comment.body, 'be-리뷰요청') + uses: slackapi/slack-github-action@v1.24.0 + with: + channel-id: ${{ secrets.SLACK_BE_CHANNEL }} + payload: | + { + "text": "", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "리뷰 반영 최종 완료!✅ 확인 부탁드립니다😃\n<${{ github.event.comment.html_url }}|피드백 반영 확인하러 가기>" + } + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} diff --git a/.github/workflows/backend-pr-deadline-slack-bot.yml b/.github/workflows/backend-pr-deadline-slack-bot.yml new file mode 100644 index 000000000..a6f25a39d --- /dev/null +++ b/.github/workflows/backend-pr-deadline-slack-bot.yml @@ -0,0 +1,58 @@ +name: Notify Pull Request Deadline (BE) + +on: + pull_request: + types: + - opened + branches: ['dev'] + paths: + - 'backend/**' + +jobs: + pull_request_open: + runs-on: ubuntu-latest + name: New pr to repo + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Remove "https://" from PR URL + id: remove_https + run: | + PR_URL="${{ github.event.pull_request.html_url }}" + PR_URL="http://${PR_URL#https://}" + echo "::set-output name=pr_url::$PR_URL" + + - name: Set environment variable + run: echo "PR_CREATED_AT_UTC=${{ github.event.pull_request.created_at }}" >> $GITHUB_ENV + - name: Convert UTC to KST + run: | + UTC_TIME=$PR_CREATED_AT_UTC + KST_TIME=$(date -u -d "$UTC_TIME 9 hour" "+%Y-%m-%dT%H:%M:%SZ") + echo "PR_CREATED_AT_KST=$KST_TIME" >> $GITHUB_ENV + + - name: Calculate deadline + id: deadline + run: node .github/workflows/scripts/calculatePRDeadline.js + env: + PR_CREATED_AT_KST: ${{ env.PR_CREATED_AT_KST }} + + - name: Send Slack notification When BE PR + uses: slackapi/slack-github-action@v1.24.0 + with: + channel-id: ${{ secrets.SLACK_BE_CHANNEL }} # Slack 채널 ID + payload: | + { + "text": "", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "<${{ steps.remove_https.outputs.pr_url }}|${{ github.event.pull_request.title }}>\n코드리뷰 마감시간: ${{ steps.deadline.outputs.DEADLINE }}" + } + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} # Slack 토큰 diff --git a/.github/workflows/backend-pr-test.yml b/.github/workflows/backend-pr-test.yml new file mode 100644 index 000000000..a9921f454 --- /dev/null +++ b/.github/workflows/backend-pr-test.yml @@ -0,0 +1,71 @@ +name: Backend Test When Pull Request + +on: + pull_request: + branches: + - main + - dev + paths: + - 'backend/**' + - '.github/**' + +defaults: + run: + working-directory: backend + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 10 + + permissions: + checks: write + pull-requests: write + + steps: + - name: 레포지토리 체크아웃 + uses: actions/checkout@v3 + + - name: JDK 17 환경 설정 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: temurin + + - name: Gradle 캐시 + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Gradle 실행 권한 부여 + run: chmod +x gradlew + + - name: Gradle 테스트 실행 + run: ./gradlew --info test + + - name: 테스트 결과 PR 코멘트 등록 + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: '**/build/test-results/test/TEST-*.xml' + + - name: 테스트 실패 시 해당 코드 라인에 Check 등록 + uses: mikepenz/action-junit-report@v3 + if: always() + with: + report_paths: '**/build/test-results/test/TEST-*.xml' + + - name: 테스트 실패 시 Slack 알림 + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + author_name: 백엔드 테스트 실패 알림 + fields: repo, message, commit, author, action, eventName, ref, workflow, job, took + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + if: failure() diff --git a/.github/workflows/frontend-lighthouse-ci.yml b/.github/workflows/frontend-lighthouse-ci.yml new file mode 100644 index 000000000..e510a9063 --- /dev/null +++ b/.github/workflows/frontend-lighthouse-ci.yml @@ -0,0 +1,112 @@ +name: Run Lighthouse CI When PR + +on: + pull_request: + branches: ['dev'] + paths: + - 'frontend/**' + +defaults: + run: + working-directory: frontend + +jobs: + lhci: + permissions: write-all + name: Lighthouse CI + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install packages + run: npm install + + - name: Set environment variable + run: | + touch .env + echo VOTOGETHER_BASE_URL=${{ secrets.VOTOGETHER_BASE_URL }} >> .env + echo VOTOGETHER_MOCKING_URL=${{ secrets.VOTOGETHER_MOCKING_URL }} >> .env + echo VOTOGETHER_REST_API_KEY=${{ secrets.VOTOGETHER_REST_API_KEY }} >> .env + echo VOTOGETHER_SERVER_REDIRECT_URL=${{ secrets.VOTOGETHER_SERVER_REDIRECT_URL }} >> .env + echo VOTOGETHER_CHANNEL_TALK_KEY=${{ secrets.VOTOGETHER_CHANNEL_TALK_KEY }} >> .env + echo VOTOGETHER_GOOGLE_TAG_ID=${{ secrets.VOTOGETHER_GOOGLE_TAG_ID }} >> .env + cat .env + + - name: Build + run: npm run build + env: + VOTOGETHER_BASE_URL: ${{ env.VOTOGETHER_BASE_URL }} + VOTOGETHER_MOCKING_URL: ${{ env.VOTOGETHER_MOCKING_URL }} + VOTOGETHER_REST_API_KEY: ${{ env.VOTOGETHER_REST_API_KEY }} + VOTOGETHER_SERVER_REDIRECT_URL: ${{ env.VOTOGETHER_SERVER_REDIRECT_URL}} + VOTOGETHER_CHANNEL_TALK_KEY: ${{ env.VOTOGETHER_CHANNEL_TALK_KEY }} + VOTOGETHER_GOOGLE_TAG_ID: ${{ env.VOTOGETHER_GOOGLE_TAG_ID }} + + - name: Run Lighthouse CI + env: + LHCI_GITHUB_APP_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + npm install -g @lhci/cli + lhci autorun || echo "Fail to Run Lighthouse CI!" + + - name: Format lighthouse score + id: format_lighthouse_score + uses: actions/github-script@v3 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const fs = require('fs'); + + const results = JSON.parse(fs.readFileSync('./frontend/lhci_reports/manifest.json')); + let comments = ''; + + results.forEach((result) => { + const { summary, jsonPath } = result; + + const details = JSON.parse(fs.readFileSync(jsonPath)); + const { audits } = details; + + const formatResult = (res) => Math.round(res * 100); + + Object.keys(summary).forEach( + (key) => (summary[key] = formatResult(summary[key])) + ); + + const score = (res) => (res >= 90 ? '🟢' : res >= 50 ? '🟠' : '🔴'); + + const comment = [ + `⚡️ Lighthouse report!`, + `| Category | Score |`, + `| --- | --- |`, + `| ${score(summary.performance)} Performance | ${summary.performance} |`, + `| ${score(summary.accessibility)} Accessibilty | ${summary.accessibility} |`, + `| ${score(summary.seo)} SEO | ${summary.seo} |`, + `| ${score(summary.pwa)} PWA | ${summary.pwa} |`, + ].join('\n'); + + const detail = [ + `| Category | Score |`, + `| --- | --- |`, + `| ${score(audits['first-contentful-paint'].score * 100)} First Contentful Paint | ${audits['first-contentful-paint'].displayValue} |`, + `| ${score(audits['largest-contentful-paint'].score * 100)} Largest Contentful Paint | ${audits['largest-contentful-paint'].displayValue} |`, + `| ${score(audits['total-blocking-time'].score * 100)} Total Blocking Time | ${audits['total-blocking-time'].displayValue} |`, + `| ${score(audits['cumulative-layout-shift'].score * 100)} Cumulative Layout Shift | ${audits['cumulative-layout-shift'].displayValue} |`, + `| ${score(audits['speed-index'].score * 100)} Speed Index | ${audits['speed-index'].displayValue} |`, + ].join('\n'); + + comments += comment + '\n\n' + detail + '\n'; + }); + + core.setOutput('comments', comments) + + - name: comment PR + uses: marocchino/sticky-pull-request-comment@v2 + with: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + message: ${{ steps.format_lighthouse_score.outputs.comments}} diff --git a/.github/workflows/frontend-pr-comment.yml b/.github/workflows/frontend-pr-comment.yml new file mode 100644 index 000000000..ccc0928bc --- /dev/null +++ b/.github/workflows/frontend-pr-comment.yml @@ -0,0 +1,56 @@ +name: Pull request comment (FE) +on: + issue_comment: + types: [created, edited, deleted] + +defaults: + run: + working-directory: frontend + +jobs: + pull_request_comment: + # This job only runs for pull request comments + if: ${{ github.event.issue.pull_request }} + runs-on: ubuntu-latest + steps: + - name: Send Slack notification When Review Completed + if: contains(github.event.comment.body, 'fe-리뷰완') # check the comment if it contains the keywords + uses: slackapi/slack-github-action@v1.24.0 + with: + channel-id: ${{ secrets.SLACK_FE_CHANNEL }} # Slack 채널 ID + payload: | + { + "text": "", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "리뷰 완료했습니다👍\n<${{ github.event.comment.html_url }}|리뷰어의 코멘트 확인하러 가기>" + } + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} # Slack 토큰 + + - name: Send Slack notification When Re-review Requested + if: contains(github.event.comment.body, 'fe-리뷰요청') # check the comment if it contains the keywords + uses: slackapi/slack-github-action@v1.24.0 + with: + channel-id: ${{ secrets.SLACK_FE_CHANNEL }} # Slack 채널 ID + payload: | + { + "text": "", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "리뷰 반영 최종 완료!✅ 확인 부탁드립니다😃\n<${{ github.event.comment.html_url }}|피드백 반영 확인하러 가기>" + } + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} # Slack 토큰 diff --git a/.github/workflows/frontend-pr-deadline-slack-bot.yml b/.github/workflows/frontend-pr-deadline-slack-bot.yml new file mode 100644 index 000000000..9dfd6e087 --- /dev/null +++ b/.github/workflows/frontend-pr-deadline-slack-bot.yml @@ -0,0 +1,58 @@ +name: Notify Pull Request Deadline (FE) + +on: + pull_request: + types: + - opened + branches: ['dev'] + paths: + - 'frontend/**' + +jobs: + pull_request_open: + runs-on: ubuntu-latest + name: New pr to repo + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Remove "https://" from PR URL + id: remove_https + run: | + PR_URL="${{ github.event.pull_request.html_url }}" + PR_URL="http://${PR_URL#https://}" + echo "::set-output name=pr_url::$PR_URL" + + - name: Set environment variable + run: echo "PR_CREATED_AT_UTC=${{ github.event.pull_request.created_at }}" >> $GITHUB_ENV + - name: Convert UTC to KST + run: | + UTC_TIME=$PR_CREATED_AT_UTC + KST_TIME=$(date -u -d "$UTC_TIME 9 hour" "+%Y-%m-%dT%H:%M:%SZ") + echo "PR_CREATED_AT_KST=$KST_TIME" >> $GITHUB_ENV + + - name: Calculate deadline + id: deadline + run: node .github/workflows/scripts/calculatePRDeadline.js + env: + PR_CREATED_AT_KST: ${{ env.PR_CREATED_AT_KST }} + + - name: Send Slack notification When FE PR + uses: slackapi/slack-github-action@v1.24.0 + with: + channel-id: ${{ secrets.SLACK_FE_CHANNEL }} # Slack 채널 ID + payload: | + { + "text": "", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "<${{ steps.remove_https.outputs.pr_url }}|${{ github.event.pull_request.title }}>\n코드리뷰 마감시간: ${{ steps.deadline.outputs.DEADLINE }}" + } + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} # Slack 토큰 diff --git a/.github/workflows/frontend-storybook-deploy.yml b/.github/workflows/frontend-storybook-deploy.yml new file mode 100644 index 000000000..959063e4a --- /dev/null +++ b/.github/workflows/frontend-storybook-deploy.yml @@ -0,0 +1,60 @@ +name: Deploy + +on: + pull_request: + branches: ['dev'] + types: ['closed'] + paths: + - 'frontend/**' + - '.github/**' + +defaults: + run: + working-directory: frontend + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: write + concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + + steps: + - name: Use repository source + uses: actions/checkout@v3 + + - name: Use node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Cache node_modules + id: cache + uses: actions/cache@v3 + with: + path: '**/node_modules' + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install dependencies + run: npm ci + if: steps.cache.outputs.cache-hit != 'true' + + - name: Set PUBLIC_URL + run: | + PUBLIC_URL=$(echo $GITHUB_REPOSITORY | sed -r 's/^.+\/(.+)$/\/\1\//') + echo PUBLIC_URL=$PUBLIC_URL > .env + + - name: 스토리북을 빌드한다. + run: | + npm run build-storybook + + - name: storybook-deploy 브런치에 배포할 파일을 업데이트 한다. + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: storybook-deploy + publish_dir: ./frontend/storybook-static diff --git a/.github/workflows/scripts/calculatePRDeadline.js b/.github/workflows/scripts/calculatePRDeadline.js new file mode 100644 index 000000000..9dc14c362 --- /dev/null +++ b/.github/workflows/scripts/calculatePRDeadline.js @@ -0,0 +1,54 @@ +function calculatePRDeadline(prCreatedAtKST) { + const prCreatedAt = new Date(String(prCreatedAtKST)); + + const prCreatedMinute = prCreatedAt.getUTCMinutes(); + const prCreatedHour = prCreatedAt.getUTCHours(); + const prCreatedDate = prCreatedAt.getUTCDate(); + const prCreatedDay = prCreatedAt.getUTCDay(); + const prCreatedMonth = prCreatedAt.getUTCMonth() + 1; // getUTCMonth()는 0부터 시작하므로 1을 더함 + + const isFridayAfterTenPM = prCreatedDay === 5 && prCreatedHour >= 22; // 금요일 오후 10시 이후 (금요일: 5, 오후 12시: 12) + const isWeekend = prCreatedDay === 6 || prCreatedDay === 0; // 주말인 경우 + + // 주어진 근무시간(월요일 오전 10시~금요일 오후 10시) 내에 올린 pr인지 판별 + const isNotWorkingTime = isFridayAfterTenPM || isWeekend; + + let nextDay = new Date(prCreatedAt); + nextDay.setUTCDate(prCreatedDate + 1); // 다음 날의 날짜를 설정 + + const nextDayDate = nextDay.getUTCDate(); + const nextDayMonth = nextDay.getUTCMonth() + 1; + + let nextWeekMonday = new Date(prCreatedAt); + const daysUntilMonday = 8 - prCreatedDay; + nextWeekMonday.setUTCDate( + prCreatedDay === 0 ? prCreatedDate + 1 : prCreatedDate + daysUntilMonday + ); + const nextWeekMondayDate = nextWeekMonday.getUTCDate(); + const nextWeekMondayMonth = nextWeekMonday.getUTCMonth() + 1; + + const isFriday = prCreatedDay === 5; + + if (isNotWorkingTime) { + return `${nextWeekMondayMonth}월 ${nextWeekMondayDate}일 20시 00분`; + } + + if (prCreatedHour < 10 && prCreatedHour > 0) + return `${prCreatedMonth}월 ${prCreatedDate}일 20시 00분`; + else if (prCreatedHour === 22 || prCreatedHour === 23) + return `${nextDayMonth}월 ${nextDayDate}일 20시 00분`; + else if (prCreatedHour >= 12) + return `${isFriday ? nextWeekMondayMonth : nextDayMonth}월 ${ + isFriday ? nextWeekMondayDate : nextDayDate + }일 ${prCreatedHour - 2}시 ${prCreatedMinute}분`; + else + return `${prCreatedMonth}월 ${prCreatedDate}일 ${ + prCreatedHour + 10 + }시 ${prCreatedMinute}분`; +} + +console.log( + `::set-output name=DEADLINE::${calculatePRDeadline( + process.env.PR_CREATED_AT_KST + )}` +); diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..da76ca84f --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Mac +.DS_Store + +# IntelliJ +.idea + +# vscode +.vscode diff --git a/README.md b/README.md index fa5755065..e239f9406 100644 --- a/README.md +++ b/README.md @@ -1 +1,48 @@ # 2023-VoTogether + +### 다른 사람들의 의견이 궁금할 때가 있지 않나요? + +“운동 초보인데 어떤 운동으로 시작하면 좋을까요?“, “오늘의 점심 메뉴는 어떤 것이 좋을까요?” 등과 같이, 하루에도 수많은 선택의 상황에 있지 않으신가요? 다양한 의견을 듣고 싶지만, 친구나 가족만으로는 충분하지 않은 경우가 있습니다. 내 주변 사람들로부터 답을 들을 수 없는 경우도 있을 거예요. + +보투게더 팀 역시 혼자 사소한 고민을 하며 많은 시간과 에너지를 쏟아야 했고, 스트레스를 겪은 적이 있습니다. 그 때문에 사람들이 만족스럽게 고민을 해결할 방법에 대해 생각하게 되었습니다. +그 결과 다양한 고민에 대해 익명으로 사람들의 취향과 의견을 투표 방식으로 비교해 보는, ‘VoTogether(보투게더)’라는 서비스가 탄생하게 되었습니다. + +
+ +### 보투게더에서 당신의 이야기를 털어 놓아보세요 + +‘VoTogether(보투게더)’는 투표를 통해 고민을 해결하고 재미를 찾는 서비스입니다. +출퇴근 시 심심할 때 들어와 남들과 수다 떨 수 있는 공간입니다. 사람들이 낚시를 가서 최대 몇 센티미터의 물고기를 잡아봤는지, 3대 중량이 어느 정도인지, 라면 몇 개까지 먹는지 궁금하지 않으신가요. 음식의 기호, 취미생활과 같이 시시하지만 솔직한 이야기를 듣고 나눌 수 있습니다. +하지만 늘 가벼운 이야기만 나누지는 않습니다. 고민이 있는 사람은 자신의 고민을 글로 써 올리고, 다른 사람들은 글의 선택지에 투표하며 고민 해결을 돕습니다. + +예를 들어 20대 후반, 집과 직장만 오가는 일상에 맥주 한잔하고 싶지만 그럴 만한 친구가 없는 사람이 있습니다. 나만 이런 심심한 인생을 사는 건지 궁금하지만 물을 곳이 없습니다. 이같이 인생 상담, 직업 상담, 연애 상담처럼 남에게 털어놓기 어려울 때 의견을 편하게 구할 방법을 제시합니다. + +혹은 구체적인 결과가 궁금할 수도 있습니다. 창업을 준비하는 30대 친구가 있습니다. 20대 초반을 대상으로 하는 주점 창업을 준비하고 있는데, 요즘 친구들은 뭘 좋아하는지 모르겠습니다. 마라? 하이볼? 아니면 포장마차? 그럴 때 보투게더를 통해 사람들의 투표를 받아 나이별, 성별로 상세한 투표 통계 결과를 확인할 수 있습니다. 이 결과로 의사결정의 가이드를 받을 수 있고, 더불어 댓글로 더 좋은 의견과 선택지를 얻을 수도 있습니다. + +이처럼 보투게더는 투표라는 방식으로 사람들을 연결 짓고, 일상과 함께하면서 사람들이 각종 재미와 공감, 고민 해결의 실마리를 얻어가길 추구합니다. + +
+ +### 다 함께, 즐겁게, 심플하게! 보투게더를 이용해 보세요 + +고민이 있으신가요? 글을 써보세요! + +익명으로 글을 작성하며 나를 드러내지 않아도 쉽게 고민을 나눌 수 있습니다. 일정 시간이 지나면 자동으로 글은 마감되어 그 결과를 확인할 수 있습니다. 만약 구체적인 결과가 궁금하다면 글쓴이에게만 제공되는 통계 결과를 확인해 보세요! + +
+ +심심하신가요? 투표를 해보세요! + +마감 시간이 지나기 전에 글을 읽고 원하는 선택지에 투표할 수 있습니다. 투표를 한 후 다른 사람들의 선택을 확인하고, 댓글을 통해 자유로운 의견도 낼 수 있습니다. 다른 사람들이 무엇을 좋아하는지, 무엇을 고민하는지 알아가는 재미가 있을 거예요. + +
+ +궁금하신가요? 관심사를 탐색해 보세요! + +특정 소재에 대해 궁금증이 있다면 탐색할 수 있습니다. 관련된 카테고리에 들어가거나 키워드를 검색할 수 있습니다. 진행 중인 게시물에 들어가 나의 의견을 낼 수도 있고, 이미 마감된 게시물에 들어가 다른 사람들이 어떤 선택을 했는지 구경할 수도 있습니다. 여러 개 쌓인 게시물을 탐색하다 보면 고민 해결의 실마리를 찾을지도 몰라요. + +
+ +보투게더는 사람들의 다양한 주제로 질문하고 답변하면서, 사람들의 반응을 확인할 수 있다는 점에서 특별합니다. 이런 자유로운 분위기 속에서, 모든 사람이 즐겁게 서비스를 이용할 수 있도록 보투게더는 적절하지 않은 콘텐츠에 대해서는 신고를 통해 비공개 처리하고 있습니다. + +나의 이야기가 우리의 이야기가 되는 공간, 보투게더에서 우리 함께해요! 👍 diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 000000000..b0a520940 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,40 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### local environment test files ### +src/main/generated diff --git a/backend/build.gradle b/backend/build.gradle new file mode 100644 index 000000000..b0a03fbb0 --- /dev/null +++ b/backend/build.gradle @@ -0,0 +1,88 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.1.1' + id 'io.spring.dependency-management' version '1.1.0' +} + +group = 'com' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '17' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-log4j2' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + + modules { + module("org.springframework.boot:spring-boot-starter-logging") { + replacedBy("org.springframework.boot:spring-boot-starter-log4j2") + } + } + + //Start Querydsl + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + + //To response to java.lang.NoClassDefFoundError + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + //End Querydsl + + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + runtimeOnly 'com.h2database:h2' + runtimeOnly 'com.mysql:mysql-connector-j' + + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' + + testImplementation 'io.rest-assured:rest-assured' + testImplementation 'io.rest-assured:spring-mock-mvc' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'io.rest-assured:rest-assured' + testImplementation 'io.rest-assured:spring-mock-mvc' + testImplementation "org.testcontainers:testcontainers:1.19.0" +} + +tasks.named('test') { + useJUnitPlatform() +} + +//Start Querydsl +//Where To Create Querydsl Q Class +def generated = 'src/main/generated' + +//Set Querydsl Q class Generated Target +tasks.withType(JavaCompile).configureEach { + options.getGeneratedSourceOutputDirectory().set(file(generated)) +} + +//Add Q Class Target In Java Source Set +sourceSets { + main.java.srcDirs += [generated] +} + +//When Gradle Clean, Delete Q Class Directory +clean { + delete file(generated) +} +//End Querydsl diff --git a/backend/gradle/wrapper/gradle-wrapper.jar b/backend/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..c1962a79e Binary files /dev/null and b/backend/gradle/wrapper/gradle-wrapper.jar differ diff --git a/backend/gradle/wrapper/gradle-wrapper.properties b/backend/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..37aef8d3f --- /dev/null +++ b/backend/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/backend/gradlew b/backend/gradlew new file mode 100755 index 000000000..aeb74cbb4 --- /dev/null +++ b/backend/gradlew @@ -0,0 +1,245 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/backend/gradlew.bat b/backend/gradlew.bat new file mode 100644 index 000000000..6689b85be --- /dev/null +++ b/backend/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/backend/settings.gradle b/backend/settings.gradle new file mode 100644 index 000000000..6c2e75f41 --- /dev/null +++ b/backend/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'votogether' diff --git a/backend/src/main/java/com/votogether/VotogetherApplication.java b/backend/src/main/java/com/votogether/VotogetherApplication.java new file mode 100644 index 000000000..e2aa0e85d --- /dev/null +++ b/backend/src/main/java/com/votogether/VotogetherApplication.java @@ -0,0 +1,15 @@ +package com.votogether; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@ConfigurationPropertiesScan +@SpringBootApplication +public class VotogetherApplication { + + public static void main(String[] args) { + SpringApplication.run(VotogetherApplication.class, args); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/auth/controller/AuthController.java b/backend/src/main/java/com/votogether/domain/auth/controller/AuthController.java new file mode 100644 index 000000000..563ec6d79 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/controller/AuthController.java @@ -0,0 +1,95 @@ +package com.votogether.domain.auth.controller; + +import com.votogether.domain.auth.dto.request.AccessTokenRequest; +import com.votogether.domain.auth.dto.response.LoginResponse; +import com.votogether.domain.auth.dto.response.ReissuedAccessTokenResponse; +import com.votogether.domain.auth.exception.AuthExceptionType; +import com.votogether.domain.auth.service.AuthService; +import com.votogether.domain.auth.service.dto.LoginTokenDto; +import com.votogether.domain.auth.service.dto.ReissuedTokenDto; +import com.votogether.global.exception.BadRequestException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import java.util.Arrays; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/auth") +@RestController +public class AuthController implements AuthControllerDocs { + + private final AuthService authService; + + @GetMapping("/kakao/callback") + public ResponseEntity loginByKakao( + @RequestParam final String code, + final HttpServletResponse httpServletResponse + ) { + final LoginTokenDto loginTokenDto = authService.register(code); + + addRefreshTokenToCookie(httpServletResponse, loginTokenDto.refreshToken()); + final LoginResponse response = + new LoginResponse(loginTokenDto.accessToken(), loginTokenDto.hasEssentialInfo()); + return ResponseEntity.ok(response); + } + + @PostMapping("/silent-login") + public ResponseEntity reissueAccessToken( + @RequestBody @Valid final AccessTokenRequest request, + final HttpServletRequest httpServletRequest, + final HttpServletResponse httpServletResponse + ) { + final String refreshToken = getRefreshTokenFromCookie(httpServletRequest); + final ReissuedTokenDto reissuedTokenDto = authService.reissueAuthToken(request, refreshToken); + + addRefreshTokenToCookie(httpServletResponse, reissuedTokenDto.refreshToken()); + final ReissuedAccessTokenResponse response = new ReissuedAccessTokenResponse(reissuedTokenDto.accessToken()); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/logout") + public ResponseEntity logout( + final HttpServletRequest httpServletRequest, + final HttpServletResponse httpServletResponse + ) { + final String refreshToken = getRefreshTokenFromCookie(httpServletRequest); + authService.deleteRefreshToken(refreshToken); + + expireCookie(httpServletResponse, refreshToken); + return ResponseEntity.noContent().build(); + } + + private void addRefreshTokenToCookie(final HttpServletResponse httpServletResponse, final String refreshToken) { + final Cookie cookie = new Cookie("refreshToken", refreshToken); + cookie.setHttpOnly(true); + cookie.setSecure(true); + cookie.setPath("/auth"); + httpServletResponse.addCookie(cookie); + } + + private String getRefreshTokenFromCookie(final HttpServletRequest httpServletRequest) { + return Arrays.stream(httpServletRequest.getCookies()) + .filter(cookie -> cookie.getName().equals("refreshToken")) + .findAny() + .map(Cookie::getValue) + .orElseThrow(() -> new BadRequestException(AuthExceptionType.NONEXISTENT_REFRESH_TOKEN)); + } + + private void expireCookie(final HttpServletResponse httpServletResponse, final String refreshToken) { + final Cookie cookie = new Cookie("refreshToken", refreshToken); + cookie.setMaxAge(0); + httpServletResponse.addCookie(cookie); + } + +} + diff --git a/backend/src/main/java/com/votogether/domain/auth/controller/AuthControllerDocs.java b/backend/src/main/java/com/votogether/domain/auth/controller/AuthControllerDocs.java new file mode 100644 index 000000000..95599b3bd --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/controller/AuthControllerDocs.java @@ -0,0 +1,61 @@ +package com.votogether.domain.auth.controller; + +import com.votogether.domain.auth.dto.request.AccessTokenRequest; +import com.votogether.domain.auth.dto.response.LoginResponse; +import com.votogether.domain.auth.dto.response.ReissuedAccessTokenResponse; +import com.votogether.global.exception.ExceptionResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; + +@Tag(name = "인증", description = "인증 API") +public interface AuthControllerDocs { + + @Operation(summary = "카카오 로그인 하기", description = "카카오 계정으로 로그인 한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "카카오 로그인 성공"), + @ApiResponse( + responseCode = "400", + description = "올바르지 않은 요청", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ), + @ApiResponse( + responseCode = "401", + description = "올바르지 않은 인증코드", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity loginByKakao( + @Parameter(description = "카카오 인가코드", example = "abcdegf") final String code, + final HttpServletResponse httpServletResponse + ); + + @Operation(summary = "액세스 토큰 재발급 하기", description = "액세스 토큰을 재발급 받는다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "액세스 토큰 재발급 성공"), + @ApiResponse( + responseCode = "400", + description = "올바르지 않은 갱신 토큰", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "요청으로부터 찾을 수 없는 갱신 토큰", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity reissueAccessToken( + @RequestBody final AccessTokenRequest request, + final HttpServletRequest httpServletRequest, + final HttpServletResponse httpServletResponse + ); + +} diff --git a/backend/src/main/java/com/votogether/domain/auth/dto/request/AccessTokenRequest.java b/backend/src/main/java/com/votogether/domain/auth/dto/request/AccessTokenRequest.java new file mode 100644 index 000000000..e43d279df --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/dto/request/AccessTokenRequest.java @@ -0,0 +1,12 @@ +package com.votogether.domain.auth.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; + +@Schema(description = "인증 토큰 재발급 요청") +public record AccessTokenRequest( + @Schema(description = "인증 토큰", example = "abc.def.ghi") + @NotBlank(message = "인증 토큰은 빈 값이거나 null이 될 수 없습니다.") + String accessToken +) { +} diff --git a/backend/src/main/java/com/votogether/domain/auth/dto/response/KakaoMemberResponse.java b/backend/src/main/java/com/votogether/domain/auth/dto/response/KakaoMemberResponse.java new file mode 100644 index 000000000..4586f313b --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/dto/response/KakaoMemberResponse.java @@ -0,0 +1,25 @@ +package com.votogether.domain.auth.dto.response; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "카카오 서버로부터 받은 유저 정보") +@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) +public record KakaoMemberResponse( + @Schema(description = "카카오 소셜 ID", example = "1") + Long id, + + @Schema(description = "카카오 유저 정보") + KakaoAccount kakaoAccount +) { + + @Schema(description = "카카오 유저 정보") + @JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) + public record KakaoAccount( + @Schema(description = "이메일", example = "votogether@email.com") + String email + ) { + } + +} diff --git a/backend/src/main/java/com/votogether/domain/auth/dto/response/LoginResponse.java b/backend/src/main/java/com/votogether/domain/auth/dto/response/LoginResponse.java new file mode 100644 index 000000000..c3735520d --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/dto/response/LoginResponse.java @@ -0,0 +1,13 @@ +package com.votogether.domain.auth.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "로그인 응답") +public record LoginResponse( + @Schema(description = "인증 토큰", example = "abc.def.ghi") + String accessToken, + + @Schema(description = "성별, 나이대 정보를 가지고 있는지 여부", example = "true") + boolean hasEssentialInfo +) { +} diff --git a/backend/src/main/java/com/votogether/domain/auth/dto/response/OAuthAccessTokenResponse.java b/backend/src/main/java/com/votogether/domain/auth/dto/response/OAuthAccessTokenResponse.java new file mode 100644 index 000000000..9ee9761d8 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/dto/response/OAuthAccessTokenResponse.java @@ -0,0 +1,25 @@ +package com.votogether.domain.auth.dto.response; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "카카오 서버로부터 발급받는 토큰 정보") +@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) +public record OAuthAccessTokenResponse( + @Schema(description = "토큰 타입", example = "bearer") + String tokenType, + + @Schema(description = "인증 토큰", example = "abc.def.ghi") + String accessToken, + + @Schema(description = "인증 토큰 만료 시간", example = "10") + Integer expiresIn, + + @Schema(description = "갱신 토큰", example = "abc.def.ghi") + String refreshToken, + + @Schema(description = "갱신 토큰 만료 시간", example = "10") + Integer refreshTokenExpiresIn +) { +} diff --git a/backend/src/main/java/com/votogether/domain/auth/dto/response/ReissuedAccessTokenResponse.java b/backend/src/main/java/com/votogether/domain/auth/dto/response/ReissuedAccessTokenResponse.java new file mode 100644 index 000000000..5068a6ddf --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/dto/response/ReissuedAccessTokenResponse.java @@ -0,0 +1,10 @@ +package com.votogether.domain.auth.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "인증 토큰 재발급 응답") +public record ReissuedAccessTokenResponse( + @Schema(description = "인증 토큰", example = "abc.def.ghi") + String accessToken +) { +} diff --git a/backend/src/main/java/com/votogether/domain/auth/exception/AuthExceptionType.java b/backend/src/main/java/com/votogether/domain/auth/exception/AuthExceptionType.java new file mode 100644 index 000000000..37e1d584d --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/exception/AuthExceptionType.java @@ -0,0 +1,20 @@ +package com.votogether.domain.auth.exception; + +import com.votogether.global.exception.ExceptionType; +import lombok.Getter; + +@Getter +public enum AuthExceptionType implements ExceptionType { + + NONEXISTENT_REFRESH_TOKEN(300, "갱신 토큰이 존재하지 않습니다."), + UNMATCHED_INFORMATION_BETWEEN_TOKEN(301, "토큰 간의 정보가 일치하지 않습니다."); + + private final int code; + private final String message; + + AuthExceptionType(final int code, final String message) { + this.code = code; + this.message = message; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/auth/service/AuthService.java b/backend/src/main/java/com/votogether/domain/auth/service/AuthService.java new file mode 100644 index 000000000..58cc022e9 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/service/AuthService.java @@ -0,0 +1,88 @@ +package com.votogether.domain.auth.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.votogether.domain.auth.dto.request.AccessTokenRequest; +import com.votogether.domain.auth.dto.response.KakaoMemberResponse; +import com.votogether.domain.auth.exception.AuthExceptionType; +import com.votogether.domain.auth.service.dto.LoginTokenDto; +import com.votogether.domain.auth.service.dto.ReissuedTokenDto; +import com.votogether.domain.auth.service.dto.TokenPayloadDto; +import com.votogether.domain.auth.service.oauth.KakaoOAuthClient; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.service.MemberService; +import com.votogether.global.exception.BadRequestException; +import com.votogether.global.jwt.TokenPayload; +import com.votogether.global.jwt.TokenProcessor; +import com.votogether.global.jwt.exception.JsonException; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class AuthService { + + private final KakaoOAuthClient kakaoOAuthClient; + private final MemberService memberService; + private final TokenProcessor tokenProcessor; + private final RedisTemplate redisTemplate; + + @Transactional + public LoginTokenDto register(final String code) { + final String kakaoAccessToken = kakaoOAuthClient.getAccessToken(code); + final KakaoMemberResponse response = kakaoOAuthClient.getMemberInfo(kakaoAccessToken); + + final Member member = Member.from(response); + final Member registeredMember = memberService.register(member); + final String accessToken = tokenProcessor.generateAccessToken(registeredMember.getId()); + final String refreshToken = tokenProcessor.generateRefreshToken(registeredMember.getId()); + redisTemplate.opsForValue().set(refreshToken, registeredMember.getId()); + return new LoginTokenDto(accessToken, refreshToken, registeredMember.hasEssentialInfo()); + } + + @Transactional + public ReissuedTokenDto reissueAuthToken( + final AccessTokenRequest request, + final String refreshTokenByRequest + ) { + tokenProcessor.validateToken(refreshTokenByRequest); + + final TokenPayloadDto tokenPayloadDto = parseTokens(request.accessToken(), refreshTokenByRequest); + final TokenPayload accessTokenPayload = tokenPayloadDto.accessTokenPayload(); + final TokenPayload refreshTokenPayload = tokenPayloadDto.refreshTokenPayload(); + + redisTemplate.delete(refreshTokenByRequest); + validateTokenInfo(accessTokenPayload, refreshTokenPayload); + + final String newAccessToken = tokenProcessor.generateAccessToken(accessTokenPayload.memberId()); + final String newRefreshToken = tokenProcessor.generateRefreshToken(accessTokenPayload.memberId()); + redisTemplate.opsForValue().set(newRefreshToken, accessTokenPayload.memberId()); + return new ReissuedTokenDto(newAccessToken, newRefreshToken); + } + + private void validateTokenInfo(final TokenPayload accessTokenPayload, final TokenPayload refreshTokenPayload) { + if (!Objects.equals(accessTokenPayload.memberId(), refreshTokenPayload.memberId())) { + throw new BadRequestException(AuthExceptionType.UNMATCHED_INFORMATION_BETWEEN_TOKEN); + } + } + + private TokenPayloadDto parseTokens(final String accessToken, final String refreshToken) { + final TokenPayload accessTokenPayload; + final TokenPayload refreshTokenPayload; + try { + accessTokenPayload = tokenProcessor.parseToken(accessToken); + refreshTokenPayload = tokenProcessor.parseToken(refreshToken); + } catch (final JsonProcessingException e) { + throw new BadRequestException(JsonException.UNEXPECTED_EXCEPTION); + } + return new TokenPayloadDto(accessTokenPayload, refreshTokenPayload); + } + + @Transactional + public void deleteRefreshToken(final String refreshTokenByRequest) { + redisTemplate.delete(refreshTokenByRequest); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/auth/service/dto/LoginTokenDto.java b/backend/src/main/java/com/votogether/domain/auth/service/dto/LoginTokenDto.java new file mode 100644 index 000000000..55c0ee7e7 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/service/dto/LoginTokenDto.java @@ -0,0 +1,8 @@ +package com.votogether.domain.auth.service.dto; + +public record LoginTokenDto( + String accessToken, + String refreshToken, + boolean hasEssentialInfo +) { +} diff --git a/backend/src/main/java/com/votogether/domain/auth/service/dto/ReissuedTokenDto.java b/backend/src/main/java/com/votogether/domain/auth/service/dto/ReissuedTokenDto.java new file mode 100644 index 000000000..786483628 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/service/dto/ReissuedTokenDto.java @@ -0,0 +1,7 @@ +package com.votogether.domain.auth.service.dto; + +public record ReissuedTokenDto( + String accessToken, + String refreshToken +) { +} diff --git a/backend/src/main/java/com/votogether/domain/auth/service/dto/TokenPayloadDto.java b/backend/src/main/java/com/votogether/domain/auth/service/dto/TokenPayloadDto.java new file mode 100644 index 000000000..6e956282d --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/service/dto/TokenPayloadDto.java @@ -0,0 +1,9 @@ +package com.votogether.domain.auth.service.dto; + +import com.votogether.global.jwt.TokenPayload; + +public record TokenPayloadDto( + TokenPayload accessTokenPayload, + TokenPayload refreshTokenPayload +) { +} diff --git a/backend/src/main/java/com/votogether/domain/auth/service/oauth/KakaoOAuthClient.java b/backend/src/main/java/com/votogether/domain/auth/service/oauth/KakaoOAuthClient.java new file mode 100644 index 000000000..d1ff2b8eb --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/auth/service/oauth/KakaoOAuthClient.java @@ -0,0 +1,58 @@ +package com.votogether.domain.auth.service.oauth; + +import com.votogether.domain.auth.dto.response.KakaoMemberResponse; +import com.votogether.domain.auth.dto.response.OAuthAccessTokenResponse; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +@Getter +@ConfigurationProperties(prefix = "oauth.kakao") +@Component +public class KakaoOAuthClient { + + private static final RestTemplate restTemplate = new RestTemplate(); + + private final MultiValueMap info = new LinkedMultiValueMap<>(); + + public String getAccessToken(final String code) { + info.remove("code"); + info.add("code", code); + + final HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + final HttpEntity> httpEntity = new HttpEntity<>(info, headers); + + final OAuthAccessTokenResponse response = restTemplate.postForEntity( + "https://kauth.kakao.com/oauth/token", + httpEntity, + OAuthAccessTokenResponse.class + ).getBody(); + return response.accessToken(); + } + + public KakaoMemberResponse getMemberInfo(final String accessToken) { + final HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + final HttpEntity request = new HttpEntity<>(headers); + + final KakaoMemberResponse response = restTemplate.exchange( + "https://kapi.kakao.com/v2/user/me", + HttpMethod.GET, + request, + KakaoMemberResponse.class + ).getBody(); + return response; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/category/contorller/CategoryController.java b/backend/src/main/java/com/votogether/domain/category/contorller/CategoryController.java new file mode 100644 index 000000000..462d1e50e --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/category/contorller/CategoryController.java @@ -0,0 +1,49 @@ +package com.votogether.domain.category.contorller; + +import com.votogether.domain.category.dto.response.CategoryResponse; +import com.votogether.domain.category.service.CategoryService; +import com.votogether.domain.member.entity.Member; +import com.votogether.global.jwt.Auth; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/categories") +@RestController +public class CategoryController implements CategoryControllerDocs { + + private final CategoryService categoryService; + + @GetMapping("/guest") + public ResponseEntity> getAllCategories() { + final List categories = categoryService.getAllCategories(); + return ResponseEntity.status(HttpStatus.OK).body(categories); + } + + @GetMapping + public ResponseEntity> getAllCategories(@Auth final Member member) { + final List categories = categoryService.getAllCategories(member); + return ResponseEntity.status(HttpStatus.OK).body(categories); + } + + @PostMapping("/{categoryId}/like") + public ResponseEntity addFavoriteCategory(@PathVariable final Long categoryId, @Auth final Member member) { + categoryService.addFavoriteCategory(member, categoryId); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @DeleteMapping("/{categoryId}/like") + public ResponseEntity removeFavoriteCategory(@PathVariable final Long categoryId, @Auth final Member member) { + categoryService.removeFavoriteCategory(member, categoryId); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/category/contorller/CategoryControllerDocs.java b/backend/src/main/java/com/votogether/domain/category/contorller/CategoryControllerDocs.java new file mode 100644 index 000000000..1db511911 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/category/contorller/CategoryControllerDocs.java @@ -0,0 +1,66 @@ +package com.votogether.domain.category.contorller; + +import com.votogether.domain.category.dto.response.CategoryResponse; +import com.votogether.domain.member.entity.Member; +import com.votogether.global.exception.ExceptionResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import org.springframework.http.ResponseEntity; + +@Tag(name = "카테고리", description = "카테고리 API") +public interface CategoryControllerDocs { + + @Operation(summary = "[비회원] 전체 카테고리 목록 조회", description = "[비회원] 전체 카테고리 목록을 조회한다.") + @ApiResponse(responseCode = "200", description = "전체 카테고리 목록 조회 성공") + ResponseEntity> getAllCategories(); + + @Operation(summary = "[회원] 전체 카테고리 목록 조회", description = "[회원] 전체 카테고리 목록을 조회한다.") + @ApiResponse(responseCode = "200", description = "전체 카테고리 목록 조회 성공") + ResponseEntity> getAllCategories(final Member member); + + @Operation(summary = "선호 카테고리 추가", description = "선호 카테고리를 추가한다.") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "선호 카테고리 추가 성공"), + @ApiResponse( + responseCode = "400", + description = "중복된 선호 카테고리", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "존재하지 않는 카테고리", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ), + }) + ResponseEntity addFavoriteCategory( + @Parameter(description = "카테고리 ID", example = "1") final Long categoryId, + final Member member + ); + + @Operation(summary = "선호 카테고리 삭제", description = "선호하는 카테고리 삭제한다.") + @ApiResponses({ + @ApiResponse(responseCode = "204", description = "선호 카테고리 삭제 성공"), + @ApiResponse( + responseCode = "400", + description = "선호하지 않는 카테고리", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "존재하지 않는 카테고리", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity removeFavoriteCategory( + @Parameter(description = "카테고리 ID", example = "1") final Long categoryId, + final Member member + ); + +} + diff --git a/backend/src/main/java/com/votogether/domain/category/dto/response/CategoryResponse.java b/backend/src/main/java/com/votogether/domain/category/dto/response/CategoryResponse.java new file mode 100644 index 000000000..a685c499b --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/category/dto/response/CategoryResponse.java @@ -0,0 +1,27 @@ +package com.votogether.domain.category.dto.response; + +import com.votogether.domain.category.entity.Category; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +@Schema(description = "카테고리 응답") +public record CategoryResponse( + @Schema(description = "카테고리 ID", example = "1") + Long id, + + @Schema(description = "카테고리 이름", example = "개발") + String name, + + @Schema(description = "선호 여부", example = "true") + boolean isFavorite +) { + + public CategoryResponse(final Category category, final boolean isFavorite) { + this(category.getId(), category.getName(), isFavorite); + } + + public CategoryResponse(final Category category, final List favoriteCategories) { + this(category.getId(), category.getName(), favoriteCategories.contains(category)); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/category/entity/Category.java b/backend/src/main/java/com/votogether/domain/category/entity/Category.java new file mode 100644 index 000000000..2ac63a760 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/category/entity/Category.java @@ -0,0 +1,33 @@ +package com.votogether.domain.category.entity; + +import com.votogether.domain.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = {"id"}) +@Getter +public class Category extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 50, unique = true, nullable = false) + private String name; + + @Builder + private Category(final String name) { + this.name = name; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/category/repository/CategoryRepository.java b/backend/src/main/java/com/votogether/domain/category/repository/CategoryRepository.java new file mode 100644 index 000000000..63648d471 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/category/repository/CategoryRepository.java @@ -0,0 +1,7 @@ +package com.votogether.domain.category.repository; + +import com.votogether.domain.category.entity.Category; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CategoryRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/votogether/domain/category/service/CategoryService.java b/backend/src/main/java/com/votogether/domain/category/service/CategoryService.java new file mode 100644 index 000000000..ed0b5a84d --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/category/service/CategoryService.java @@ -0,0 +1,75 @@ +package com.votogether.domain.category.service; + +import com.votogether.domain.category.dto.response.CategoryResponse; +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.category.repository.CategoryRepository; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.MemberCategory; +import com.votogether.domain.member.repository.MemberCategoryRepository; +import java.util.Comparator; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class CategoryService { + + private final CategoryRepository categoryRepository; + private final MemberCategoryRepository memberCategoryRepository; + + @Transactional(readOnly = true) + public List getAllCategories() { + final List categories = categoryRepository.findAll(); + + return categories.stream() + .sorted(Comparator.comparing(Category::getName)) + .map(category -> new CategoryResponse(category, false)) + .toList(); + } + + @Transactional + public void addFavoriteCategory(final Member member, final Long categoryId) { + final Category category = categoryRepository.findById(categoryId) + .orElseThrow(() -> new IllegalArgumentException("해당 카테고리가 존재하지 않습니다.")); + + memberCategoryRepository.findByMemberAndCategory(member, category) + .ifPresent(ignore -> { + throw new IllegalStateException("이미 선호 카테고리에 등록되어 있습니다."); + }); + + final MemberCategory memberCategory = MemberCategory.builder() + .member(member) + .category(category) + .build(); + + memberCategoryRepository.save(memberCategory); + } + + @Transactional + public void removeFavoriteCategory(final Member member, final Long categoryId) { + final Category category = categoryRepository.findById(categoryId) + .orElseThrow(() -> new IllegalArgumentException("해당 카테고리가 존재하지 않습니다.")); + final MemberCategory memberCategory = memberCategoryRepository.findByMemberAndCategory(member, category) + .orElseThrow(() -> new IllegalArgumentException("해당 카테고리는 선호 카테고리가 아닙니다.")); + + memberCategoryRepository.delete(memberCategory); + } + + @Transactional(readOnly = true) + public List getAllCategories(final Member member) { + final List categories = categoryRepository.findAll(); + final List memberCategories = memberCategoryRepository.findAllByMember(member); + + final List favoriteCategories = memberCategories.stream() + .map(MemberCategory::getCategory) + .toList(); + + return categories.stream() + .sorted(Comparator.comparing(Category::getName)) + .map(category -> new CategoryResponse(category, favoriteCategories)) + .toList(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/common/BaseEntity.java b/backend/src/main/java/com/votogether/domain/common/BaseEntity.java new file mode 100644 index 000000000..ffb4f2a42 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/common/BaseEntity.java @@ -0,0 +1,25 @@ +package com.votogether.domain.common; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseEntity { + + @CreatedDate + @Column(columnDefinition = "datetime(6)", updatable = false, nullable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(columnDefinition = "datetime(6)", nullable = false) + private LocalDateTime updatedAt; + +} diff --git a/backend/src/main/java/com/votogether/domain/healthcheck/controller/HealthCheckController.java b/backend/src/main/java/com/votogether/domain/healthcheck/controller/HealthCheckController.java new file mode 100644 index 000000000..a155b4b12 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/healthcheck/controller/HealthCheckController.java @@ -0,0 +1,23 @@ +package com.votogether.domain.healthcheck.controller; + +import com.votogether.domain.healthcheck.service.HealthCheckService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/health-check") +@RestController +public class HealthCheckController implements HealthCheckControllerDocs { + + private final HealthCheckService healthCheckService; + + @GetMapping + public ResponseEntity check() { + final String response = healthCheckService.check(); + return ResponseEntity.ok(response); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/healthcheck/controller/HealthCheckControllerDocs.java b/backend/src/main/java/com/votogether/domain/healthcheck/controller/HealthCheckControllerDocs.java new file mode 100644 index 000000000..d39ecdedf --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/healthcheck/controller/HealthCheckControllerDocs.java @@ -0,0 +1,15 @@ +package com.votogether.domain.healthcheck.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +@Tag(name = "헬스 체크", description = "헬스 체크 API") +public interface HealthCheckControllerDocs { + + @Operation(summary = "헬스 체크", description = "헬스 체크를 한다.") + @ApiResponse(responseCode = "200", description = "헬스 체크 성공") + ResponseEntity check(); + +} diff --git a/backend/src/main/java/com/votogether/domain/healthcheck/service/HealthCheckService.java b/backend/src/main/java/com/votogether/domain/healthcheck/service/HealthCheckService.java new file mode 100644 index 000000000..0323f2875 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/healthcheck/service/HealthCheckService.java @@ -0,0 +1,12 @@ +package com.votogether.domain.healthcheck.service; + +import org.springframework.stereotype.Service; + +@Service +public class HealthCheckService { + + public String check() { + return "health-check"; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/member/controller/MemberController.java b/backend/src/main/java/com/votogether/domain/member/controller/MemberController.java new file mode 100644 index 000000000..86c84b39f --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/controller/MemberController.java @@ -0,0 +1,55 @@ +package com.votogether.domain.member.controller; + +import com.votogether.domain.member.dto.request.MemberDetailRequest; +import com.votogether.domain.member.dto.request.MemberNicknameUpdateRequest; +import com.votogether.domain.member.dto.response.MemberInfoResponse; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.service.MemberService; +import com.votogether.global.jwt.Auth; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/members") +@RestController +public class MemberController implements MemberControllerDocs { + + private final MemberService memberService; + + @GetMapping("/me") + public ResponseEntity findMemberInfo(@Auth final Member member) { + return ResponseEntity.ok(memberService.findMemberInfo(member)); + } + + @PatchMapping("/me/nickname") + public ResponseEntity changeNickname( + @Valid @RequestBody final MemberNicknameUpdateRequest request, + @Auth final Member member + ) { + memberService.changeNickname(member, request.nickname()); + return ResponseEntity.ok().build(); + } + + @PatchMapping("/me/detail") + public ResponseEntity updateDetails( + @Valid @RequestBody final MemberDetailRequest request, + @Auth final Member member + ) { + memberService.updateDetails(request, member); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/me/delete") + public ResponseEntity deleteMember(@Auth final Member member) { + memberService.deleteMember(member); + return ResponseEntity.noContent().build(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/member/controller/MemberControllerDocs.java b/backend/src/main/java/com/votogether/domain/member/controller/MemberControllerDocs.java new file mode 100644 index 000000000..082b424d0 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/controller/MemberControllerDocs.java @@ -0,0 +1,55 @@ +package com.votogether.domain.member.controller; + +import com.votogether.domain.member.dto.request.MemberDetailRequest; +import com.votogether.domain.member.dto.request.MemberNicknameUpdateRequest; +import com.votogether.domain.member.dto.response.MemberInfoResponse; +import com.votogether.domain.member.entity.Member; +import com.votogether.global.exception.ExceptionResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +@Tag(name = "회원", description = "회원 API") +public interface MemberControllerDocs { + + @Operation(summary = "회원 정보 조회", description = "회원 정보를 조회한다.") + @ApiResponse(responseCode = "200", description = "회원 정보 조회 성공") + ResponseEntity findMemberInfo(final Member member); + + @Operation(summary = "회원 닉네임 변경", description = "회원 닉네임을 변경한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "회원 닉네임 변경 성공"), + @ApiResponse( + responseCode = "400", + description = "잘못된 닉네임 형식", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity changeNickname( + final MemberNicknameUpdateRequest request, + final Member member + ); + + @Operation(summary = "회원 상세 정보 변경", description = "회원의 상세 정보를 변경한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "회원 상세 정보 변경 성공"), + @ApiResponse( + responseCode = "400", + description = "잘못된 상세 정보", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity updateDetails( + final MemberDetailRequest request, + final Member member + ); + + @Operation(summary = "회원 탈퇴", description = "회원 탈퇴한다.") + @ApiResponse(responseCode = "200", description = "회원 탈퇴 성공") + ResponseEntity deleteMember(final Member member); + +} diff --git a/backend/src/main/java/com/votogether/domain/member/dto/request/MemberDetailRequest.java b/backend/src/main/java/com/votogether/domain/member/dto/request/MemberDetailRequest.java new file mode 100644 index 000000000..a59ede0fd --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/dto/request/MemberDetailRequest.java @@ -0,0 +1,20 @@ +package com.votogether.domain.member.dto.request; + +import com.votogether.domain.member.entity.vo.Gender; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "회원 상세 정보 수정 요청") +public record MemberDetailRequest( + @Schema(description = "성별", example = "MALE") + Gender gender, + + @Schema(description = "출생년도", example = "2000") + @NotNull(message = "출생년도는 빈 값일 수 없습니다.") + @Min(value = 1800, message = "출생년도는 1800년 이상부터 가능합니다.") + @Max(value = 2100, message = "출생년도는 2100년 이하만 가능합니다.") + Integer birthYear +) { +} diff --git a/backend/src/main/java/com/votogether/domain/member/dto/request/MemberNicknameUpdateRequest.java b/backend/src/main/java/com/votogether/domain/member/dto/request/MemberNicknameUpdateRequest.java new file mode 100644 index 000000000..649623b34 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/dto/request/MemberNicknameUpdateRequest.java @@ -0,0 +1,12 @@ +package com.votogether.domain.member.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; + +@Schema(description = "닉네임 변경 요청") +public record MemberNicknameUpdateRequest( + @Schema(description = "변경할 닉네임", example = "jeomxon") + @NotBlank(message = "닉네임은 빈값 혹은 공백이 포함될 수 없습니다.") + String nickname +) { +} diff --git a/backend/src/main/java/com/votogether/domain/member/dto/response/MemberInfoResponse.java b/backend/src/main/java/com/votogether/domain/member/dto/response/MemberInfoResponse.java new file mode 100644 index 000000000..8c6247e70 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/dto/response/MemberInfoResponse.java @@ -0,0 +1,23 @@ +package com.votogether.domain.member.dto.response; + +import com.votogether.domain.member.entity.vo.Gender; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "회원 정보 응답") +public record MemberInfoResponse( + @Schema(description = "닉네임", example = "유저") + String nickname, + + @Schema(description = "성별", example = "남성") + Gender gender, + + @Schema(description = "출생년도", example = "2002") + Integer birthYear, + + @Schema(description = "작성한 게시글 수", example = "5") + int postCount, + + @Schema(description = "투표한 수", example = "10") + int voteCount +) { +} diff --git a/backend/src/main/java/com/votogether/domain/member/entity/Member.java b/backend/src/main/java/com/votogether/domain/member/entity/Member.java new file mode 100644 index 000000000..e9360903b --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/entity/Member.java @@ -0,0 +1,119 @@ +package com.votogether.domain.member.entity; + +import com.votogether.domain.auth.dto.response.KakaoMemberResponse; +import com.votogether.domain.common.BaseEntity; +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.entity.vo.Nickname; +import com.votogether.domain.member.entity.vo.SocialType; +import com.votogether.domain.member.exception.MemberExceptionType; +import com.votogether.global.exception.BadRequestException; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.apache.commons.lang3.RandomStringUtils; + +@Table(indexes = {@Index(columnList = "socialId, socialType")}) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = {"id"}) +@ToString +@Getter +@Entity +public class Member extends BaseEntity { + + private static final String INITIAL_NICKNAME_PREFIX = "익명의손님"; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Embedded + private Nickname nickname; + + @Enumerated(value = EnumType.STRING) + @Column(length = 20) + private Gender gender; + + @Column + private Integer birthYear; + + @Enumerated(value = EnumType.STRING) + @Column(length = 20, nullable = false) + private SocialType socialType; + + @Column(nullable = false) + private String socialId; + + @Builder + private Member( + final String nickname, + final Gender gender, + final Integer birthYear, + final SocialType socialType, + final String socialId + ) { + this.nickname = new Nickname(nickname); + this.gender = gender; + this.birthYear = birthYear; + this.socialType = socialType; + this.socialId = socialId; + } + + public static Member from(final KakaoMemberResponse response) { + return Member.builder() + .nickname(INITIAL_NICKNAME_PREFIX + RandomStringUtils.random(10, true, true)) + .socialType(SocialType.KAKAO) + .socialId(String.valueOf(response.id())) + .build(); + } + + public void changeNicknameByCycle(final String nickname, final Long days) { + if (nickname.startsWith(INITIAL_NICKNAME_PREFIX)) { + throw new BadRequestException(MemberExceptionType.NOT_ALLOWED_INITIAL_NICKNAME_PREFIX); + } + if (isNotPassedChangingCycle(days) && isNotInitialNickname()) { + throw new BadRequestException(MemberExceptionType.NOT_PASSED_NICKNAME_CHANGING_CYCLE); + } + this.nickname = new Nickname(nickname); + } + + private boolean isNotPassedChangingCycle(final Long days) { + return this.getUpdatedAt().isAfter(LocalDateTime.now().minusDays(days)); + } + + private boolean isNotInitialNickname() { + return this.nickname.nonStartsWith(INITIAL_NICKNAME_PREFIX); + } + + public void changeNicknameByReport() { + final String reportedNickname = "Pause1" + RandomStringUtils.random(9, true, true); + this.nickname = new Nickname(reportedNickname); + } + + public void updateDetails(final Gender gender, final Integer birthYear) { + this.gender = gender; + this.birthYear = birthYear; + } + + public boolean hasEssentialInfo() { + return (this.gender != null && this.birthYear != null); + } + + public String getNickname() { + return this.nickname.getValue(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/member/entity/MemberCategory.java b/backend/src/main/java/com/votogether/domain/member/entity/MemberCategory.java new file mode 100644 index 000000000..3cc1af94e --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/entity/MemberCategory.java @@ -0,0 +1,43 @@ +package com.votogether.domain.member.entity; + +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.common.BaseEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"member_id", "category_id"})}) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class MemberCategory extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id", nullable = false) + private Category category; + + @Builder + private MemberCategory(final Member member, final Category category) { + this.member = member; + this.category = category; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/member/entity/vo/AgeRange.java b/backend/src/main/java/com/votogether/domain/member/entity/vo/AgeRange.java new file mode 100644 index 000000000..7e5e80846 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/entity/vo/AgeRange.java @@ -0,0 +1,45 @@ +package com.votogether.domain.member.entity.vo; + +import com.votogether.domain.member.exception.MemberExceptionType; +import com.votogether.global.exception.BadRequestException; +import java.util.Arrays; +import lombok.Getter; + +@Getter +public enum AgeRange { + + UNDER_TEENS("10대 미만", 1, 9), + TEENS("10대", 10, 19), + TWENTIES("20대", 20, 29), + THIRTIES("30대", 30, 39), + FORTIES("40대", 40, 49), + FIFTIES("50대", 50, 59), + OVER_SIXTIES("60대 이상", 60, 999), + ; + + private final String name; + private final int startAge; + private final int endAge; + + AgeRange( + final String name, + final int startAge, + final int endAge + ) { + this.name = name; + this.startAge = startAge; + this.endAge = endAge; + } + + public static AgeRange from(final int age) { + return Arrays.stream(AgeRange.values()) + .filter(ageRange -> ageRange.isBelong(age)) + .findAny() + .orElseThrow(() -> new BadRequestException(MemberExceptionType.INVALID_AGE)); + } + + private boolean isBelong(final int age) { + return this.startAge <= age && this.endAge >= age; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/member/entity/vo/Gender.java b/backend/src/main/java/com/votogether/domain/member/entity/vo/Gender.java new file mode 100644 index 000000000..77e12a1a2 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/entity/vo/Gender.java @@ -0,0 +1,9 @@ +package com.votogether.domain.member.entity.vo; + +public enum Gender { + + MALE, + FEMALE, + ; + +} diff --git a/backend/src/main/java/com/votogether/domain/member/entity/vo/Nickname.java b/backend/src/main/java/com/votogether/domain/member/entity/vo/Nickname.java new file mode 100644 index 000000000..e4f397264 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/entity/vo/Nickname.java @@ -0,0 +1,41 @@ +package com.votogether.domain.member.entity.vo; + +import com.votogether.domain.member.exception.MemberExceptionType; +import com.votogether.global.exception.BadRequestException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.util.regex.Pattern; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Embeddable +public class Nickname { + + private static final int MINIMUM_NICKNAME_LENGTH = 2; + private static final int MAXIMUM_NICKNAME_LENGTH = 15; + + @Column(name = "nickname", length = 20, unique = true, nullable = false) + private String value; + + public Nickname(final String nickname) { + validateNickname(nickname); + this.value = nickname; + } + + private void validateNickname(final String nickname) { + if (nickname.length() < MINIMUM_NICKNAME_LENGTH || nickname.length() > MAXIMUM_NICKNAME_LENGTH) { + throw new BadRequestException(MemberExceptionType.INVALID_NICKNAME_LENGTH); + } + if (!Pattern.matches("^[가-힣a-zA-Z0-9]+$", nickname)) { + throw new BadRequestException(MemberExceptionType.INVALID_NICKNAME_LETTER); + } + } + + public boolean nonStartsWith(final String prefix) { + return !this.value.startsWith(prefix); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/member/entity/vo/SocialType.java b/backend/src/main/java/com/votogether/domain/member/entity/vo/SocialType.java new file mode 100644 index 000000000..35a42ab51 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/entity/vo/SocialType.java @@ -0,0 +1,8 @@ +package com.votogether.domain.member.entity.vo; + +public enum SocialType { + + KAKAO, + ; + +} diff --git a/backend/src/main/java/com/votogether/domain/member/exception/MemberExceptionType.java b/backend/src/main/java/com/votogether/domain/member/exception/MemberExceptionType.java new file mode 100644 index 000000000..67546d872 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/exception/MemberExceptionType.java @@ -0,0 +1,28 @@ +package com.votogether.domain.member.exception; + +import com.votogether.global.exception.ExceptionType; +import lombok.Getter; + +@Getter +public enum MemberExceptionType implements ExceptionType { + + INVALID_NICKNAME_LENGTH(800, "닉네임의 길이가 올바르지 않습니다."), + INVALID_NICKNAME_LETTER(801, "닉네임에 들어갈 수 없는 문자가 포함되어 있습니다."), + ALREADY_EXISTENT_NICKNAME(802, "이미 중복된 닉네임이 존재합니다."), + NONEXISTENT_MEMBER(803, "해당 회원이 존재하지 않습니다."), + INVALID_AGE(804, "존재할 수 없는 연령입니다."), + ALREADY_ASSIGNED_GENDER(805, "이미 성별이 할당되어 있습니다."), + ALREADY_ASSIGNED_BIRTH_YEAR(806, "이미 출생년도가 할당되어 있습니다."), + NOT_PASSED_NICKNAME_CHANGING_CYCLE(807, "최소 닉네임 변경주기가 지나지 않았습니다."), + NOT_ALLOWED_INITIAL_NICKNAME_PREFIX(808, "초기 닉네임에 포함된 접두어로 닉네임을 변경할 수 없습니다."), + ; + + private final int code; + private final String message; + + MemberExceptionType(final int code, final String message) { + this.code = code; + this.message = message; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/member/repository/MemberCategoryRepository.java b/backend/src/main/java/com/votogether/domain/member/repository/MemberCategoryRepository.java new file mode 100644 index 000000000..f2d11e12a --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/repository/MemberCategoryRepository.java @@ -0,0 +1,16 @@ +package com.votogether.domain.member.repository; + +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.MemberCategory; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberCategoryRepository extends JpaRepository { + + Optional findByMemberAndCategory(final Member member, final Category category); + + List findAllByMember(final Member member); + +} diff --git a/backend/src/main/java/com/votogether/domain/member/repository/MemberRepository.java b/backend/src/main/java/com/votogether/domain/member/repository/MemberRepository.java new file mode 100644 index 000000000..f4df26cd6 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/repository/MemberRepository.java @@ -0,0 +1,15 @@ +package com.votogether.domain.member.repository; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.vo.Nickname; +import com.votogether.domain.member.entity.vo.SocialType; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { + + Optional findBySocialIdAndSocialType(final String socialId, final SocialType socialType); + + boolean existsByNickname(final Nickname nickname); + +} diff --git a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java new file mode 100644 index 000000000..55705f9db --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java @@ -0,0 +1,191 @@ +package com.votogether.domain.member.service; + +import com.votogether.domain.member.dto.request.MemberDetailRequest; +import com.votogether.domain.member.dto.response.MemberInfoResponse; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.MemberCategory; +import com.votogether.domain.member.entity.vo.Nickname; +import com.votogether.domain.member.exception.MemberExceptionType; +import com.votogether.domain.member.repository.MemberCategoryRepository; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.comment.Comment; +import com.votogether.domain.post.repository.CommentRepository; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.domain.report.entity.Report; +import com.votogether.domain.report.entity.vo.ReportType; +import com.votogether.domain.report.repository.ReportRepository; +import com.votogether.domain.vote.entity.Vote; +import com.votogether.domain.vote.repository.VoteRepository; +import com.votogether.global.exception.BadRequestException; +import com.votogether.global.exception.NotFoundException; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class MemberService { + + private static final Long NICKNAME_CHANGING_CYCLE = 14L; + + private final MemberRepository memberRepository; + private final MemberCategoryRepository memberCategoryRepository; + private final PostRepository postRepository; + private final VoteRepository voteRepository; + private final ReportRepository reportRepository; + private final CommentRepository commentRepository; + + @Transactional + public Member register(final Member member) { + final Optional maybeMember = memberRepository.findBySocialIdAndSocialType( + member.getSocialId(), + member.getSocialType() + ); + return maybeMember.orElseGet(() -> memberRepository.save(member)); + } + + @Transactional(readOnly = true) + public Member findById(final Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new NotFoundException(MemberExceptionType.NONEXISTENT_MEMBER)); + } + + @Transactional(readOnly = true) + public MemberInfoResponse findMemberInfo(final Member member) { + final int numberOfPosts = postRepository.countByWriter(member); + final int numberOfVotes = voteRepository.countByMember(member); + + return new MemberInfoResponse( + member.getNickname(), + member.getGender(), + member.getBirthYear(), + numberOfPosts, + numberOfVotes + ); + } + + @Transactional + public void changeNickname(final Member member, final String nickname) { + validateExistentNickname(nickname); + member.changeNicknameByCycle(nickname, NICKNAME_CHANGING_CYCLE); + } + + private void validateExistentNickname(final String nickname) { + final boolean isExist = memberRepository.existsByNickname(new Nickname(nickname)); + if (isExist) { + throw new BadRequestException(MemberExceptionType.ALREADY_EXISTENT_NICKNAME); + } + } + + @Transactional + public void updateDetails(final MemberDetailRequest request, final Member member) { + validateExistentDetails(member); + member.updateDetails(request.gender(), request.birthYear()); + } + + private void validateExistentDetails(final Member member) { + if (member.getGender() != null) { + throw new BadRequestException(MemberExceptionType.ALREADY_ASSIGNED_GENDER); + } + if (member.getBirthYear() != null) { + throw new BadRequestException(MemberExceptionType.ALREADY_ASSIGNED_BIRTH_YEAR); + } + } + + @Transactional + public void deleteMember(final Member member) { + final List posts = deletePosts(member); + final List comments = deleteComments(member); + deleteVotes(member); + deleteMemberCategories(member); + deleteReports(member, posts, comments); + + memberRepository.delete(member); + } + + private List deletePosts(final Member member) { + final List posts = postRepository.findAllByWriter(member); + final List postIds = posts.stream() + .map(Post::getId) + .toList(); + postRepository.deleteAllById(postIds); + return posts; + } + + private List deleteComments(final Member member) { + final List comments = commentRepository.findAllByMember(member); + final List commentIds = comments.stream() + .map(Comment::getId) + .toList(); + commentRepository.deleteAllById(commentIds); + return comments; + } + + private void deleteVotes(final Member member) { + final List voteIds = voteRepository.findAllByMember(member) + .stream() + .map(Vote::getId) + .toList(); + voteRepository.deleteAllById(voteIds); + } + + private void deleteMemberCategories(final Member member) { + final List memberCategoryIds = memberCategoryRepository.findAllByMember(member) + .stream() + .map(MemberCategory::getId) + .toList(); + memberCategoryRepository.deleteAllById(memberCategoryIds); + } + + private void deleteReports( + final Member member, + final List posts, + final List comments + ) { + deleteReportByPost(posts); + deleteReportByComment(comments); + deleteReportByNickname(member); + deleteReportByMember(member); + } + + private void deleteReportByPost(final List posts) { + for (final Post post : posts) { + final List reportIds = reportRepository.findAllByReportTypeAndTargetId(ReportType.POST, post.getId()) + .stream() + .map(Report::getId) + .toList(); + reportRepository.deleteAllById(reportIds); + } + } + + private void deleteReportByComment(final List comments) { + for (final Comment comment : comments) { + final List reportIds = + reportRepository.findAllByReportTypeAndTargetId(ReportType.COMMENT, comment.getId()) + .stream() + .map(Report::getId) + .toList(); + reportRepository.deleteAllById(reportIds); + } + } + + private void deleteReportByNickname(final Member member) { + final List reportIdsByTargetId = + reportRepository.findAllByReportTypeAndTargetId(ReportType.NICKNAME, member.getId()) + .stream() + .map(Report::getId) + .toList(); + reportRepository.deleteAllById(reportIdsByTargetId); + } + + private void deleteReportByMember(final Member member) { + final List reportIdsByMember = reportRepository.findAllByMember(member) + .stream() + .map(Report::getId) + .toList(); + reportRepository.deleteAllById(reportIdsByMember); + } +} diff --git a/backend/src/main/java/com/votogether/domain/post/controller/PostCommentController.java b/backend/src/main/java/com/votogether/domain/post/controller/PostCommentController.java new file mode 100644 index 000000000..0d2ef6e6b --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/controller/PostCommentController.java @@ -0,0 +1,67 @@ +package com.votogether.domain.post.controller; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.dto.request.comment.CommentRegisterRequest; +import com.votogether.domain.post.dto.request.comment.CommentUpdateRequest; +import com.votogether.domain.post.dto.response.comment.CommentResponse; +import com.votogether.domain.post.service.PostCommentService; +import com.votogether.global.jwt.Auth; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/posts") +@RestController +public class PostCommentController implements PostCommentControllerDocs { + + private final PostCommentService postCommentService; + + @GetMapping("/{postId}/comments") + public ResponseEntity> getComments(@PathVariable final Long postId) { + final List response = postCommentService.getComments(postId); + return ResponseEntity.ok(response); + } + + @PostMapping("/{postId}/comments") + public ResponseEntity createComment( + @PathVariable final Long postId, + @Valid @RequestBody CommentRegisterRequest commentRegisterRequest, + @Auth final Member member + ) { + postCommentService.createComment(member, postId, commentRegisterRequest); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @PutMapping("/{postId}/comments/{commentId}") + public ResponseEntity updateComment( + @PathVariable final Long postId, + @PathVariable final Long commentId, + @RequestBody @Valid final CommentUpdateRequest commentUpdateRequest, + @Auth final Member member + ) { + postCommentService.updateComment(postId, commentId, commentUpdateRequest, member); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/{postId}/comments/{commentId}") + public ResponseEntity deleteComment( + @PathVariable final Long postId, + @PathVariable final Long commentId, + @Auth final Member member + ) { + postCommentService.deleteComment(postId, commentId, member); + return ResponseEntity.noContent().build(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/controller/PostCommentControllerDocs.java b/backend/src/main/java/com/votogether/domain/post/controller/PostCommentControllerDocs.java new file mode 100644 index 000000000..fecb9b3d8 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/controller/PostCommentControllerDocs.java @@ -0,0 +1,90 @@ +package com.votogether.domain.post.controller; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.dto.request.comment.CommentRegisterRequest; +import com.votogether.domain.post.dto.request.comment.CommentUpdateRequest; +import com.votogether.domain.post.dto.response.comment.CommentResponse; +import com.votogether.global.exception.ExceptionResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import org.springframework.http.ResponseEntity; + +@Tag(name = "게시글 댓글", description = "게시글 댓글 API") +public interface PostCommentControllerDocs { + + @Operation(summary = "게시글 댓글 목록 조회", description = "게시글 댓글 목록을 조회한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시글 댓글 목록 조회 성공"), + @ApiResponse( + responseCode = "404", + description = "존재하지 않는 게시글", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity> getComments( + @Parameter(description = "댓글 작성 게시글 ID", example = "1") final Long postId + ); + + @Operation(summary = "게시글 댓글 작성", description = "게시글 댓글을 작성한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시글 댓글 작성 성공"), + @ApiResponse( + responseCode = "404", + description = "존재하지 않는 게시글", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity createComment( + @Parameter(description = "댓글 작성 게시글 ID", example = "1") final Long postId, + final CommentRegisterRequest commentRegisterRequest, + final Member member + ); + + @Operation(summary = "게시글 댓글 수정", description = "게시글 댓글을 수정한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시글 댓글 수정 성공"), + @ApiResponse( + responseCode = "400", + description = "1.게시글에 속하지 않은 댓글\t\n2.올바르지 않은 댓글 작성자", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "1.존재하지 않는 게시글\t\n2.존재하지 않는 댓글", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity updateComment( + @Parameter(description = "게시글 ID", example = "1") final Long postId, + @Parameter(description = "댓글 ID", example = "1") final Long commentId, + final CommentUpdateRequest commentUpdateRequest, + final Member member + ); + + @Operation(summary = "게시글 댓글 삭제", description = "게시글 댓글을 삭제한다.") + @ApiResponses({ + @ApiResponse(responseCode = "204", description = "게시글 댓글 삭제 성공"), + @ApiResponse( + responseCode = "400", + description = "1.게시글에 속하지 않은 댓글\t\n2.올바르지 않은 댓글 작성자", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "1.존재하지 않는 게시글\t\n2.존재하지 않는 댓글", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity deleteComment( + @Parameter(description = "게시글 ID", example = "1") final Long postId, + @Parameter(description = "댓글 ID", example = "1") final Long commentId, + final Member member + ); + +} diff --git a/backend/src/main/java/com/votogether/domain/post/controller/PostController.java b/backend/src/main/java/com/votogether/domain/post/controller/PostController.java new file mode 100644 index 000000000..076fc86c0 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/controller/PostController.java @@ -0,0 +1,227 @@ +package com.votogether.domain.post.controller; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.dto.request.post.PostCreateRequest; +import com.votogether.domain.post.dto.request.post.PostUpdateRequest; +import com.votogether.domain.post.dto.response.post.PostDetailResponse; +import com.votogether.domain.post.dto.response.post.PostRankingResponse; +import com.votogether.domain.post.dto.response.post.PostResponse; +import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse; +import com.votogether.domain.post.entity.vo.PostClosingType; +import com.votogether.domain.post.entity.vo.PostSortType; +import com.votogether.domain.post.service.PostService; +import com.votogether.global.jwt.Auth; +import jakarta.validation.Valid; +import java.net.URI; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RequiredArgsConstructor +@RequestMapping("/posts") +@RestController +public class PostController implements PostControllerDocs { + + private final PostService postService; + + @GetMapping("/search") + public ResponseEntity> searchPostsWithKeyword( + @RequestParam final String keyword, + @RequestParam final int page, + @RequestParam final PostClosingType postClosingType, + @RequestParam final PostSortType postSortType, + @RequestParam(required = false, name = "category") final Long categoryId, + @Auth final Member member + ) { + final List responses = + postService.searchPostsWithKeyword(keyword, page, postClosingType, postSortType, categoryId, member); + return ResponseEntity.ok(responses); + } + + @GetMapping("/search/guest") + public ResponseEntity> searchPostsWithKeywordForGuest( + @RequestParam final String keyword, + @RequestParam final int page, + @RequestParam final PostClosingType postClosingType, + @RequestParam final PostSortType postSortType, + @RequestParam(required = false, name = "category") final Long categoryId + ) { + final List responses = + postService.searchPostsWithKeywordForGuest(keyword, page, postClosingType, postSortType, categoryId); + return ResponseEntity.ok(responses); + } + + @GetMapping("/me") + public ResponseEntity> getPostsByMe( + @RequestParam final int page, + @RequestParam final PostClosingType postClosingType, + @RequestParam final PostSortType postSortType, + @RequestParam(required = false, name = "category") final Long categoryId, + @Auth final Member member + ) { + final List responses = + postService.getPostsByWriter(page, postClosingType, postSortType, categoryId, member); + return ResponseEntity.ok(responses); + } + + @GetMapping + public ResponseEntity> getAllPost( + @RequestParam final int page, + @RequestParam final PostClosingType postClosingType, + @RequestParam final PostSortType postSortType, + @RequestParam(name = "category", required = false) final Long categoryId, + @Auth final Member member + ) { + final List responses = postService.getAllPostBySortTypeAndClosingTypeAndCategoryId( + page, + postClosingType, + postSortType, + categoryId, + member + ); + return ResponseEntity.ok(responses); + } + + @GetMapping("/guest") + public ResponseEntity> getPostsGuest( + @RequestParam final int page, + @RequestParam final PostClosingType postClosingType, + @RequestParam final PostSortType postSortType, + @RequestParam(required = false, name = "category") final Long categoryId + ) { + final List response = postService.getPostsGuest(page, postClosingType, postSortType, categoryId); + return ResponseEntity.ok(response); + } + + @GetMapping("{postId}") + public ResponseEntity getPost( + @PathVariable final Long postId, + @Auth final Member member + ) { + final PostDetailResponse response = postService.getPostById(postId, member); + return ResponseEntity.ok(response); + } + + @GetMapping("{postId}/guest") + public ResponseEntity getPostByGuest( + @PathVariable final Long postId + ) { + final PostDetailResponse response = postService.getPostById(postId, null); + return ResponseEntity.ok(response); + } + + @GetMapping("/{postId}/options") + public ResponseEntity getVoteStatistics( + @PathVariable final Long postId, + @Auth final Member member + ) { + final VoteOptionStatisticsResponse response = postService.getVoteStatistics(postId, member); + return ResponseEntity.ok(response); + } + + @GetMapping("/{postId}/options/{optionId}") + public ResponseEntity getVoteOptionStatistics( + @PathVariable final Long postId, + @PathVariable final Long optionId, + @Auth final Member member + ) { + final VoteOptionStatisticsResponse response = postService.getVoteOptionStatistics(postId, optionId, member); + return ResponseEntity.ok(response); + } + + @GetMapping("/votes/me") + public ResponseEntity> getPostsVotedByMe( + final int page, + final PostClosingType postClosingType, + final PostSortType postSortType, + @Auth final Member member + ) { + final List posts = postService.getPostsVotedByMember(page, postClosingType, postSortType, member); + return ResponseEntity.status(HttpStatus.OK).body(posts); + } + + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity save( + @RequestPart @Valid final PostCreateRequest request, + @RequestPart final List contentImages, + @RequestPart final List optionImages, + @Auth final Member member + ) { + System.out.println("PostController.save"); + + System.out.println("contentImages = " + contentImages); + if (contentImages != null && !contentImages.isEmpty()) { + System.out.println("contentImages = " + contentImages.get(0).getOriginalFilename()); + } + + System.out.println("optionImages = " + optionImages); + if (optionImages != null && !optionImages.isEmpty()) { + System.out.println("optionImages1 = " + optionImages.get(0).getOriginalFilename()); + System.out.println("optionImages2 = " + optionImages.get(1).getOriginalFilename()); + } + final Long postId = postService.save(request, member, contentImages, optionImages); + return ResponseEntity.created(URI.create("/posts/" + postId)).build(); + } + + @GetMapping("ranking/popular/guest") + public ResponseEntity> getRanking() { + final List responses = postService.getRanking(); + return ResponseEntity.ok(responses); + } + + @PutMapping(value = "/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity update( + @PathVariable final Long postId, + @RequestPart @Valid final PostUpdateRequest request, + @RequestPart final List contentImages, + @RequestPart final List optionImages, + @Auth final Member member + ) { + System.out.println("PostController.update"); + + System.out.println("contentImages = " + contentImages); + if (contentImages != null && !contentImages.isEmpty()) { + System.out.println("contentImages = " + contentImages.get(0).getOriginalFilename()); + } + + System.out.println("optionImages = " + optionImages); + if (optionImages != null && !optionImages.isEmpty()) { + System.out.println("optionImages1 = " + optionImages.get(0).getOriginalFilename()); + System.out.println("optionImages2 = " + optionImages.get(1).getOriginalFilename()); + } + + postService.update(postId, request, member, contentImages, optionImages); + return ResponseEntity.ok().build(); + } + + @PatchMapping("/{postId}/close") + public ResponseEntity closePostEarly( + @PathVariable final Long postId, + @Auth final Member loginMember + ) { + postService.closePostEarlyById(postId, loginMember); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/{postId}") + public ResponseEntity delete(@PathVariable final Long postId) { + postService.delete(postId); + return ResponseEntity.noContent().build(); + } + +} + + diff --git a/backend/src/main/java/com/votogether/domain/post/controller/PostControllerDocs.java b/backend/src/main/java/com/votogether/domain/post/controller/PostControllerDocs.java new file mode 100644 index 000000000..e87b40d0f --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/controller/PostControllerDocs.java @@ -0,0 +1,254 @@ +package com.votogether.domain.post.controller; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.dto.request.post.PostCreateRequest; +import com.votogether.domain.post.dto.request.post.PostUpdateRequest; +import com.votogether.domain.post.dto.response.post.PostDetailResponse; +import com.votogether.domain.post.dto.response.post.PostRankingResponse; +import com.votogether.domain.post.dto.response.post.PostResponse; +import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse; +import com.votogether.domain.post.entity.vo.PostClosingType; +import com.votogether.domain.post.entity.vo.PostSortType; +import com.votogether.global.exception.ExceptionResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import org.springframework.http.ResponseEntity; +import org.springframework.web.multipart.MultipartFile; + +@Tag(name = "게시글", description = "게시글 API") +public interface PostControllerDocs { + + @Operation(summary = "[회원] 전체 게시글 목록 조회", description = "[회원] 전체 게시글 목록을 조회한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "전체 게시글 목록 조회 성공"), + @ApiResponse( + responseCode = "400", + description = "잘못된 입력", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity> getAllPost( + @Parameter(description = "현재 페이지 위치", example = "0") final int page, + @Parameter(description = "게시글 마감 여부", example = "ALL") final PostClosingType postClosingType, + @Parameter(description = "게시글 정렬 기준", example = "HOT") final PostSortType postSortType, + @Parameter(description = "카테고리 ID", example = "1") final Long categoryId, + final Member member + ); + + @Operation(summary = "[비회원] 전체 게시글 목록 조회", description = "[비회원] 전체 게시글 목록을 조회한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "전체 게시글 목록 조회 성공"), + @ApiResponse( + responseCode = "400", + description = "잘못된 입력", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity> getPostsGuest( + @Parameter(description = "현재 페이지 위치", example = "0") final int page, + @Parameter(description = "게시글 마감 여부", example = "ALL") final PostClosingType postClosingType, + @Parameter(description = "게시글 정렬 기준", example = "HOT") final PostSortType postSortType, + @Parameter(description = "카테고리 ID", example = "1") final Long categoryId + ); + + @Operation(summary = "[회원] 게시글 상세 조회", description = "[회원] 게시글을 상세 조회 한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시글 상세 조회 성공"), + @ApiResponse( + responseCode = "404", + description = "존재하지 않는 게시글", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity getPost( + @Parameter(description = "게시글 ID", example = "1") final Long postId, + final Member member + ); + + @Operation(summary = "[비회원] 게시글 상세 조회", description = "[비회원] 게시글을 상세 조회 한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시글 상세 조회 성공"), + @ApiResponse( + responseCode = "404", + description = "존재하지 않는 게시글", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity getPostByGuest( + @Parameter(description = "게시글 ID", example = "1") final Long postId + ); + + @Operation(summary = "게시글 투표 통계 조회", description = "게시글 투표에 대한 전체 통계를 조회한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시글 투표 통계 조회 성공"), + @ApiResponse( + responseCode = "404", + description = "존재하지 않는 게시글", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity getVoteStatistics( + @Parameter(description = "게시글 ID", example = "1") final Long postId, + final Member member + ); + + @Operation(summary = "게시글 투표 선택지 통계 조회", description = "게시글 특정 투표 선택지에 대한 통계를 조회한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시글 투표 선택지 통계 조회 성공"), + @ApiResponse( + responseCode = "400", + description = "게시글에 속하지 않는 투표 옵션", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "1.존재하지 않는 게시글\t\n2.존재하지 않는 게시글 투표 옵션", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity getVoteOptionStatistics( + @Parameter(description = "게시글 ID", example = "1") final Long postId, + @Parameter(description = "게시글 선택지 ID", example = "2") final Long optionId, + final Member member + ); + + @Operation(summary = "투표한 게시글 목록 조회", description = "투표한 게시글 목록을 조회한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "투표한 게시글 목록 조회 성공"), + @ApiResponse( + responseCode = "400", + description = "잘못된 입력", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity> getPostsVotedByMe( + @Parameter(description = "현재 페이지 위치", example = "0") final int page, + @Parameter(description = "게시글 마감 여부", example = "ALL") final PostClosingType postClosingType, + @Parameter(description = "게시글 정렬 기준", example = "HOT") final PostSortType postSortType, + final Member member + ); + + + @Operation(summary = "[회원] 게시글 검색", description = "[회원] 키워드를 통해 게시글을 검색한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시글 검색 성공"), + @ApiResponse( + responseCode = "400", + description = "잘못된 입력", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity> searchPostsWithKeyword( + @Parameter(description = "검색 키워드", example = "취업") final String keyword, + @Parameter(description = "현재 페이지 위치", example = "0") final int page, + @Parameter(description = "게시글 마감 여부", example = "ALL") final PostClosingType postClosingType, + @Parameter(description = "게시글 정렬 기준", example = "HOT") final PostSortType postSortType, + @Parameter(description = "카테고리 ID", example = "1") final Long categoryId, + final Member member + ); + + @Operation(summary = "[비회원] 게시글 검색", description = "[비회원] 키워드를 통해 게시글을 검색한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시글 검색 성공"), + @ApiResponse( + responseCode = "400", + description = "잘못된 입력", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity> searchPostsWithKeywordForGuest( + @Parameter(description = "검색 키워드", example = "취업") final String keyword, + @Parameter(description = "현재 페이지 위치", example = "0") final int page, + @Parameter(description = "게시글 마감 여부", example = "ALL") final PostClosingType postClosingType, + @Parameter(description = "게시글 정렬 기준", example = "HOT") final PostSortType postSortType, + @Parameter(description = "카테고리 ID", example = "1") final Long categoryId + ); + + @Operation(summary = "작성한 게시글 목록 조회", description = "작성한 게시글 목록을 조회한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "작성한 게시글 목록 조회 성공"), + @ApiResponse( + responseCode = "400", + description = "잘못된 입력", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity> getPostsByMe( + @Parameter(description = "현재 페이지 위치", example = "0") final int page, + @Parameter(description = "게시글 마감 여부", example = "ALL") final PostClosingType postClosingType, + @Parameter(description = "게시글 정렬 기준", example = "HOT") final PostSortType postSortType, + @Parameter(description = "카테고리 ID", example = "1") final Long categoryId, + final Member member + ); + + @Operation(summary = "인기 게시글 랭킹 조회", description = "인기 게시글 랭킹을 조회한다.") + @ApiResponse(responseCode = "200", description = "인기 게시글 랭킹 조회 성공") + ResponseEntity> getRanking(); + + @Operation(summary = "게시글 작성", description = "게시글을 작성한다.") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "게시글 작성 성공"), + @ApiResponse( + responseCode = "400", + description = "잘못된 입력입니다.", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity save( + final PostCreateRequest request, + final List contentImages, + final List optionImages, + final Member member + ); + + @Operation(summary = "게시글 수정", description = "게시글을 수정한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시글 수정 성공"), + @ApiResponse( + responseCode = "400", + description = "잘못된 입력", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity update( + @Parameter(description = "게시글 ID", example = "1") final Long postId, + final PostUpdateRequest request, + final List contentImages, + final List optionImages, + final Member member + ); + + @Operation(summary = "게시글 조기 마감", description = "게시글을 조기 마감한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시물 조기 마감 성공."), + @ApiResponse( + responseCode = "400", + description = "잘못된 입력", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity closePostEarly( + @Parameter(description = "게시글 ID", example = "1") final Long postId, + final Member loginMember + ); + + @Operation(summary = "게시글 삭제", description = "게시글을 삭제한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "게시물 삭제 성공"), + @ApiResponse( + responseCode = "400", + description = "잘못된 입력", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity delete( + @Parameter(description = "게시글 ID", example = "1") final Long postId + ); + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/request/comment/CommentRegisterRequest.java b/backend/src/main/java/com/votogether/domain/post/dto/request/comment/CommentRegisterRequest.java new file mode 100644 index 000000000..ef6f641f4 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/request/comment/CommentRegisterRequest.java @@ -0,0 +1,12 @@ +package com.votogether.domain.post.dto.request.comment; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; + +@Schema(description = "댓글 작성 요청") +public record CommentRegisterRequest( + @Schema(description = "댓글 내용", example = "hello") + @NotBlank(message = "댓글 내용은 존재해야 합니다.") + String content +) { +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/request/comment/CommentUpdateRequest.java b/backend/src/main/java/com/votogether/domain/post/dto/request/comment/CommentUpdateRequest.java new file mode 100644 index 000000000..2c8724e57 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/request/comment/CommentUpdateRequest.java @@ -0,0 +1,12 @@ +package com.votogether.domain.post.dto.request.comment; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; + +@Schema(description = "댓글 수정 요청") +public record CommentUpdateRequest( + @Schema(description = "댓글 수정 내용", example = "content") + @NotBlank(message = "댓글 내용은 존재해야 합니다.") + String content +) { +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostCreateRequest.java b/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostCreateRequest.java new file mode 100644 index 000000000..4bfc21708 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostCreateRequest.java @@ -0,0 +1,44 @@ +package com.votogether.domain.post.dto.request.post; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; +import lombok.Builder; +import org.hibernate.validator.constraints.Length; + +@Schema(description = "게시글 작성 요청") +@Builder +public record PostCreateRequest( + @Schema(description = "카테고리의 여러 아이디", example = "[1, 3]") + @Size(min = 1, message = "게시글에 해당하는 카테고리는 최소 1개 이상이어야 합니다.") + List categoryIds, + + @Schema(description = "게시글 제목", example = "title") + @NotBlank(message = "제목을 입력해주세요.") + @Length(max = 100, message = "제목은 최대 100자까지 입력 가능합니다.") + String title, + + @Schema(description = "게시글 내용", example = "content") + @NotBlank(message = "내용을 입력해주세요.") + @Length(max = 1000, message = "내용은 최대 1000자까지 입력 가능합니다.") + String content, + + @Schema(description = "이미지 URL", example = "http://asdasdsadsad.com") + String imageUrl, + + @Schema(description = "게시글의 여러 선택지") + @Valid + @NotNull(message = "선택지는 최소 2개 이상 등록해야 합니다.") + @Size(min = 2, max = 5, message = "선택지는 최소 2개, 최대 5개까지 등록 가능합니다.") + List postOptions, + + @Schema(description = "마감 기한", example = "2023-08-01 15:30") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm") + LocalDateTime deadline +) { +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostOptionCreateRequest.java b/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostOptionCreateRequest.java new file mode 100644 index 000000000..f5c703126 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostOptionCreateRequest.java @@ -0,0 +1,19 @@ +package com.votogether.domain.post.dto.request.post; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import org.hibernate.validator.constraints.Length; + +@Schema(description = "게시글 선택지 요청") +@Builder +public record PostOptionCreateRequest( + @Schema(description = "선택지 내용", example = "content") + @NotBlank(message = "해당 선택지의 내용을 입력해주세요.") + @Length(max = 50, message = "선택지의 내용은 최대 50자까지 입력 가능합니다.") + String content, + + @Schema(description = "이미지 URL", example = "http://asdasdsadsad.com") + String imageUrl +) { +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostOptionUpdateRequest.java b/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostOptionUpdateRequest.java new file mode 100644 index 000000000..a321d2993 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostOptionUpdateRequest.java @@ -0,0 +1,19 @@ +package com.votogether.domain.post.dto.request.post; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import org.hibernate.validator.constraints.Length; + +@Schema(description = "게시글 선택지 수정 요청") +@Builder +public record PostOptionUpdateRequest( + @Schema(description = "선택지 내용", example = "content") + @NotBlank(message = "해당 선택지의 내용을 입력해주세요.") + @Length(max = 50, message = "선택지의 내용은 최대 50자까지 입력 가능합니다.") + String content, + + @Schema(description = "이미지 URL", example = "http://asdasdsadsad.com") + String imageUrl +) { +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostUpdateRequest.java b/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostUpdateRequest.java new file mode 100644 index 000000000..1dfed74c8 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/request/post/PostUpdateRequest.java @@ -0,0 +1,44 @@ +package com.votogether.domain.post.dto.request.post; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; +import lombok.Builder; +import org.hibernate.validator.constraints.Length; + +@Schema(description = "게시글 수정 요청") +@Builder +public record PostUpdateRequest( + @Schema(description = "카테고리의 여러 아이디", example = "[0, 2]") + @Size(min = 1, message = "게시글에 해당하는 카테고리는 최소 1개 이상이어야 합니다.") + List categoryIds, + + @Schema(description = "게시글 제목", example = "title") + @NotBlank(message = "제목을 입력해주세요.") + @Length(max = 100, message = "제목은 최대 100자까지 입력 가능합니다.") + String title, + + @Schema(description = "게시글 내용", example = "content") + @NotBlank(message = "내용을 입력해주세요.") + @Length(max = 1000, message = "내용은 최대 1000자까지 입력 가능합니다.") + String content, + + @Schema(description = "이미지 URL", example = "http://asdasdsadsad.com") + String imageUrl, + + @Schema(description = "게시글의 여러 선택지") + @Valid + @NotNull(message = "선택지는 최소 2개 이상 등록해야 합니다.") + @Size(min = 2, max = 5, message = "선택지는 최소 2개, 최대 5개까지 등록 가능합니다.") + List postOptions, + + @Schema(description = "마감 기한", example = "2023-08-01 15:30") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm") + LocalDateTime deadline +) { +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/comment/CommentResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/comment/CommentResponse.java new file mode 100644 index 000000000..a1e14588f --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/comment/CommentResponse.java @@ -0,0 +1,54 @@ +package com.votogether.domain.post.dto.response.comment; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.comment.Comment; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; + +@Schema(description = "댓글 응답") +public record CommentResponse( + @Schema(description = "댓글 ID", example = "1") + Long id, + + @Schema(description = "댓글 작성자 회원") + CommentMember member, + + @Schema(description = "댓글 내용", example = "재밌어요!") + String content, + + @Schema(description = "댓글 작성시각", example = "2023-08-01 10:56") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + LocalDateTime createdAt, + + @Schema(description = "댓글 수정시각", example = "2023-08-01 13:56") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + LocalDateTime updatedAt +) { + + public static CommentResponse from(final Comment comment) { + return new CommentResponse( + comment.getId(), + CommentMember.from(comment.getMember()), + comment.getContent(), + comment.getCreatedAt(), + comment.getUpdatedAt() + ); + } + + @Schema(description = "댓글 작성자 회원") + record CommentMember( + @Schema(description = "댓글 작성자 회원 ID", example = "1") + Long id, + + @Schema(description = "댓글 작성자 회원 닉네임", example = "votogether") + String nickname + ) { + + public static CommentMember from(final Member member) { + return new CommentMember(member.getId(), member.getNickname()); + } + + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/post/CategoryResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/post/CategoryResponse.java new file mode 100644 index 000000000..90635c851 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/post/CategoryResponse.java @@ -0,0 +1,19 @@ +package com.votogether.domain.post.dto.response.post; + +import com.votogether.domain.category.entity.Category; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "카테고리 응답") +public record CategoryResponse( + @Schema(description = "카테고리 ID", example = "1") + Long id, + + @Schema(description = "카테고리 이름", example = "개발") + String name +) { + + public static CategoryResponse of(Category category) { + return new CategoryResponse(category.getId(), category.getName()); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostDetailResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostDetailResponse.java new file mode 100644 index 000000000..5ace2220e --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostDetailResponse.java @@ -0,0 +1,97 @@ +package com.votogether.domain.post.dto.response.post; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.dto.response.vote.VoteDetailResponse; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.PostCategories; +import com.votogether.domain.post.entity.PostCategory; +import com.votogether.domain.post.entity.PostContentImage; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "게시글 상세 정보 응답") +public record PostDetailResponse( + @Schema(description = "게시글 ID", example = "1") + Long postId, + + @Schema(description = "작성자") + WriterResponse writer, + + @Schema(description = "게시글 제목", example = "이거 한번 투표해주세요") + String title, + + @Schema(description = "게시글 내용", example = "어떤게 더 맛있나요?") + String content, + + @Schema(description = "이미지 URL", example = "http://asdasdasd.com") + String imageUrl, + + @Schema(description = "카테고리 목록", example = "[1,2]") + List categories, + + @Schema(description = "게시글 생성시각", example = "2023-08-01 13:56") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + LocalDateTime createdAt, + + @Schema(description = "게시글 마감기한", example = "2023-08-01 13:56") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + LocalDateTime deadline, + + @Schema(description = "투표 통계 정보") + VoteDetailResponse voteInfo +) { + + public static PostDetailResponse of(final Post post, final Member loginMember) { + final Member writer = post.getWriter(); + final PostBody postBody = post.getPostBody(); + final List contentImages = postBody.getPostContentImages().getContentImages(); + final StringBuilder contentImageUrl = new StringBuilder(); + + if (!contentImages.isEmpty()) { + contentImageUrl.append(contentImages.get(0).getImageUrl()); + } + + final PostCategories postCategories = post.getPostCategories(); + return new PostDetailResponse( + post.getId(), + WriterResponse.of(writer.getId(), writer.getNickname()), + postBody.getTitle(), + postBody.getContent(), + contentImageUrl.toString(), + getCategories(postCategories.getPostCategories()), + post.getCreatedAt(), + post.getDeadline(), + VoteDetailResponse.of( + post.getSelectedOptionId(loginMember), + post.getFinalTotalVoteCount(loginMember), + getOptions(post, loginMember) + ) + ); + } + + private static List getCategories(final List postCategories) { + return postCategories.stream() + .map(PostCategory::getCategory) + .map(CategoryResponse::of) + .toList(); + } + + private static List getOptions( + final Post post, + final Member loginMember + ) { + return post.getPostOptions().getPostOptions().stream() + .map(postOption -> + PostOptionDetailResponse.of( + postOption, + post.isVisibleVoteResult(loginMember), + post.getFinalTotalVoteCount(loginMember) + ) + ) + .toList(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostOptionDetailResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostOptionDetailResponse.java new file mode 100644 index 000000000..8a4725bb8 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostOptionDetailResponse.java @@ -0,0 +1,38 @@ +package com.votogether.domain.post.dto.response.post; + +import com.votogether.domain.post.entity.PostOption; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "게시글 선택지 정보 응답") +public record PostOptionDetailResponse( + @Schema(description = "게시글 선택지 ID", example = "1") + Long optionId, + + @Schema(description = "게시글 선택지 내용", example = "짜장면") + String content, + + @Schema(description = "이미지 URL", example = "http://sdasdas.com") + String imageUrl, + + @Schema(description = "투표 개수", example = "4") + Integer voteCount, + + @Schema(description = "투표한 비율", example = "50.0") + Double votePercent +) { + + public static PostOptionDetailResponse of( + final PostOption postOption, + final Boolean isVisibleVoteResult, + final Long totalVoteCount + ) { + return new PostOptionDetailResponse( + postOption.getId(), + postOption.getContent(), + postOption.getImageUrl(), + postOption.getVoteCount(isVisibleVoteResult), + postOption.getVotePercent(totalVoteCount) + ); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostOptionResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostOptionResponse.java new file mode 100644 index 000000000..4212cf49a --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostOptionResponse.java @@ -0,0 +1,55 @@ +package com.votogether.domain.post.dto.response.post; + +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostOption; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "게시글 선택지 정보 응답") +public record PostOptionResponse( + @Schema(description = "게시글 선택지 ID", example = "1") + Long optionId, + + @Schema(description = "게시글 선택지 내용", example = "짜장면") + String content, + + @Schema(description = "이미지 URL", example = "http://sdasdas.com") + String imageUrl, + + @Schema(description = "투표 개수", example = "4") + Integer voteCount, + + @Schema(description = "투표한 비율", example = "50.0") + Double votePercent +) { + + private static final int HIDDEN_COUNT = -1; + + public static PostOptionResponse of(final Post post, final PostOption postOption) { + return new PostOptionResponse( + postOption.getId(), + postOption.getContent(), + convertImageUrl(postOption.getImageUrl()), + post.isClosed() ? postOption.getVoteCount() : HIDDEN_COUNT, + postOption.getVotePercent(post.getTotalVoteCount()) + ); + } + + private static String convertImageUrl(final String imageUrl) { + return imageUrl == null ? "" : imageUrl; + } + + public static PostOptionResponse of( + final PostOption postOption, + final boolean isVisibleVoteResult, + final Long totalVoteCount + ) { + return new PostOptionResponse( + postOption.getId(), + postOption.getContent(), + convertImageUrl(postOption.getImageUrl()), + postOption.getVoteCount(isVisibleVoteResult), + postOption.getVotePercent(totalVoteCount) + ); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostRankingResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostRankingResponse.java new file mode 100644 index 000000000..b8b250a18 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostRankingResponse.java @@ -0,0 +1,15 @@ +package com.votogether.domain.post.dto.response.post; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "게시글 랭킹 정보 응답") +public record PostRankingResponse( + @Schema(description = "게시글 랭킹", example = "1") + int ranking, + + @Schema(description = "게시글 정보") + @JsonProperty("post") + PostSummaryResponse postSummaryResponse +) { +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostResponse.java new file mode 100644 index 000000000..d17d677d9 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostResponse.java @@ -0,0 +1,125 @@ +package com.votogether.domain.post.dto.response.post; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.dto.response.vote.VoteResponse; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.PostCategory; +import com.votogether.domain.post.entity.PostContentImage; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "게시글에 관련한 데이터들입니다.") +public record PostResponse( + @Schema(description = "게시글 ID", example = "1") + Long postId, + + @Schema(description = "작성자") + WriterResponse writer, + + @Schema(description = "게시글 제목", example = "이거 한번 투표해주세요") + String title, + + @Schema(description = "게시글 내용", example = "어떤게 더 맛있나요?") + String content, + + @Schema(description = "이미지 URL", example = "http://asdasdasd.com") + String imageUrl, + + @Schema(description = "카테고리 목록", example = "[1,2]") + List categories, + + @Schema(description = "게시글 생성시각", example = "2023-08-01 13:56") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + LocalDateTime createdAt, + + @Schema(description = "게시글 마감기한", example = "2023-08-01 13:56") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + LocalDateTime deadline, + + @Schema(description = "투표 통계 정보") + VoteResponse voteInfo +) { + + public static PostResponse of(final Post post, final Member loginMember) { + final Member writer = post.getWriter(); + final PostBody postBody = post.getPostBody(); + final List contentImages = postBody.getPostContentImages().getContentImages(); + final StringBuilder contentImageUrl = new StringBuilder(); + + if (!contentImages.isEmpty()) { + contentImageUrl.append(contentImages.get(0).getImageUrl()); + } + + return new PostResponse( + post.getId(), + WriterResponse.of(writer.getId(), writer.getNickname()), + postBody.getTitle(), + postBody.getContent(), + convertImageUrl(contentImageUrl.toString()), + getCategories(post), + post.getCreatedAt(), + post.getDeadline(), + VoteResponse.of( + post.getSelectedOptionId(loginMember), + post.getFinalTotalVoteCount(loginMember), + getOptions(post, loginMember) + ) + ); + } + + private static String convertImageUrl(final String imageUrl) { + return imageUrl == null ? "" : imageUrl; + } + + private static List getCategories(final Post post) { + return post.getPostCategories() + .getPostCategories() + .stream() + .map(PostCategory::getCategory) + .map(CategoryResponse::of) + .toList(); + } + + private static List getOptions( + final Post post, + final Member loginMember + ) { + return post.getPostOptions() + .getPostOptions() + .stream() + .map(postOption -> + PostOptionResponse.of( + postOption, + post.isVisibleVoteResult(loginMember), + post.getFinalTotalVoteCount(loginMember) + ) + ) + .toList(); + } + + public static PostResponse forGuest(final Post post) { + final PostBody postBody = post.getPostBody(); + final List contentImages = postBody.getPostContentImages().getContentImages(); + final StringBuilder contentImageUrl = new StringBuilder(); + + if (!contentImages.isEmpty()) { + contentImageUrl.append(contentImages.get(0).getImageUrl()); + } + + return new PostResponse( + post.getId(), + WriterResponse.from(post.getWriter()), + post.getPostBody().getTitle(), + post.getPostBody().getContent(), + convertImageUrl(contentImageUrl.toString()), + getCategories(post), + post.getCreatedAt(), + post.getDeadline(), + VoteResponse.forGuest(post) + ); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostSummaryResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostSummaryResponse.java new file mode 100644 index 000000000..f90cb208d --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/post/PostSummaryResponse.java @@ -0,0 +1,30 @@ +package com.votogether.domain.post.dto.response.post; + +import com.votogether.domain.post.entity.Post; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "게시글 간략 정보 응답") +public record PostSummaryResponse( + @Schema(description = "게시글 ID", example = "1") + Long id, + + @Schema(description = "작성자", example = "익명의손님1") + String writer, + + @Schema(description = "게시글 제목", example = "제목") + String title, + + @Schema(description = "투표 수", example = "123") + long voteCount +) { + + public static PostSummaryResponse from(final Post post) { + return new PostSummaryResponse( + post.getId(), + post.getWriter().getNickname(), + post.getPostBody().getTitle(), + post.getTotalVoteCount() + ); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/post/WriterResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/post/WriterResponse.java new file mode 100644 index 000000000..985e4c872 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/post/WriterResponse.java @@ -0,0 +1,23 @@ +package com.votogether.domain.post.dto.response.post; + +import com.votogether.domain.member.entity.Member; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "작성자 응답") +public record WriterResponse( + @Schema(description = "작성자 ID", example = "1") + Long id, + + @Schema(description = "작성자 닉네임", example = "익명의닉네임2SDSDNKLNS") + String nickname +) { + + public static WriterResponse from(final Member member) { + return new WriterResponse(member.getId(), member.getNickname()); + } + + public static WriterResponse of(final Long id, final String nickname) { + return new WriterResponse(id, nickname); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteCountForAgeGroupResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteCountForAgeGroupResponse.java new file mode 100644 index 000000000..62be5c3c3 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteCountForAgeGroupResponse.java @@ -0,0 +1,29 @@ +package com.votogether.domain.post.dto.response.vote; + +import com.votogether.domain.member.entity.vo.Gender; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.Map; + +@Schema(description = "연령대별 투표 통계 응답") +public record VoteCountForAgeGroupResponse( + @Schema(description = "연령대", example = "20대") + String ageGroup, + + @Schema(description = "총 투표 수", example = "14") + int voteCount, + + @Schema(description = "남자 투표 수", example = "7") + int maleCount, + + @Schema(description = "여자 투표 수", example = "7") + int femaleCount +) { + public static VoteCountForAgeGroupResponse of(final String ageGroup, final Map genderGroup) { + final int maleCount = genderGroup.getOrDefault(Gender.MALE, 0L).intValue(); + final int femaleCount = genderGroup.getOrDefault(Gender.FEMALE, 0L).intValue(); + final int voteCount = maleCount + femaleCount; + + return new VoteCountForAgeGroupResponse(ageGroup, voteCount, maleCount, femaleCount); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteDetailResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteDetailResponse.java new file mode 100644 index 000000000..0eabf9522 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteDetailResponse.java @@ -0,0 +1,27 @@ +package com.votogether.domain.post.dto.response.vote; + +import com.votogether.domain.post.dto.response.post.PostOptionDetailResponse; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +@Schema(description = "투표 상세 응답") +public record VoteDetailResponse( + @Schema(description = "선택지 ID", example = "1") + Long selectedOptionId, + + @Schema(description = "총 투표 수", example = "2") + Long totalVoteCount, + + @Schema(description = "선택지 상세 응답") + List options +) { + + public static VoteDetailResponse of( + final long selectedOptionId, + final long finalTotalVoteCount, + final List options + ) { + return new VoteDetailResponse(selectedOptionId, finalTotalVoteCount, options); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteOptionStatisticsResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteOptionStatisticsResponse.java new file mode 100644 index 000000000..1b794e7f7 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteOptionStatisticsResponse.java @@ -0,0 +1,46 @@ +package com.votogether.domain.post.dto.response.vote; + +import com.votogether.domain.member.entity.vo.AgeRange; +import com.votogether.domain.member.entity.vo.Gender; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +@Schema(description = "투표 선택지 통계 응답") +public record VoteOptionStatisticsResponse( + @Schema(description = "총 투표 수", example = "14") + int totalVoteCount, + + @Schema(description = "총 남자 투표 수", example = "7") + int totalMaleCount, + + @Schema(description = "총 여자 투표 수", example = "7") + int totalFemaleCount, + + @Schema(description = "연령대별 투표 수 응답") + List ageGroup +) { + + public static VoteOptionStatisticsResponse from(final Map> voteStatusGroup) { + final List ageGroupStatistics = Arrays.stream(AgeRange.values()) + .map(AgeRange::getName) + .map(ageName -> + VoteCountForAgeGroupResponse.of( + ageName, + voteStatusGroup.computeIfAbsent(ageName, ignore -> new HashMap<>()) + ) + ) + .toList(); + + final int totalVoteCount = ageGroupStatistics.stream().mapToInt(VoteCountForAgeGroupResponse::voteCount).sum(); + final int totalMaleCount = ageGroupStatistics.stream().mapToInt(VoteCountForAgeGroupResponse::maleCount).sum(); + final int totalFemaleCount = + ageGroupStatistics.stream().mapToInt(VoteCountForAgeGroupResponse::femaleCount).sum(); + + return new VoteOptionStatisticsResponse(totalVoteCount, totalMaleCount, totalFemaleCount, ageGroupStatistics); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteResponse.java new file mode 100644 index 000000000..fac8d92de --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteResponse.java @@ -0,0 +1,56 @@ +package com.votogether.domain.post.dto.response.vote; + +import com.votogether.domain.post.dto.response.post.PostOptionResponse; +import com.votogether.domain.post.entity.Post; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + + +@Schema(description = "투표 응답") +public record VoteResponse( + @Schema(description = "선택지 ID", example = "1") + long selectedOptionId, + + @Schema(description = "총 투표 수", example = "7") + long totalVoteCount, + + @Schema(description = "선택지 옵션 응답") + List options +) { + + private static final int NOT_SELECTED = 0; + private static final int HIDDEN_COUNT = -1; + + public static VoteResponse of( + final long selectedOptionId, + final long finalTotalVoteCount, + final List options + ) { + return new VoteResponse(selectedOptionId, finalTotalVoteCount, options); + } + + public static VoteResponse forGuest(final Post post) { + return new VoteResponse( + NOT_SELECTED, + post.isClosed() ? post.getTotalVoteCount() : HIDDEN_COUNT, + listOfOptionsForGuest(post) + ); + } + + private static List listOfOptionsForGuest(final Post post) { + return post.getPostOptions().getPostOptions() + .stream() + .map(postOption -> PostOptionResponse.of(post, postOption)) + .toList(); + } + + @Override + public String toString() { + return "VoteInfoResponse{" + + "selectedOptionId=" + selectedOptionId + + ", totalVoteCount=" + totalVoteCount + + ", options=" + options + + '}'; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/Post.java b/backend/src/main/java/com/votogether/domain/post/entity/Post.java new file mode 100644 index 000000000..5e293b334 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/Post.java @@ -0,0 +1,273 @@ +package com.votogether.domain.post.entity; + +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.common.BaseEntity; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.comment.Comment; +import com.votogether.domain.post.exception.PostExceptionType; +import com.votogether.domain.report.exception.ReportExceptionType; +import com.votogether.domain.vote.entity.Vote; +import com.votogether.global.exception.BadRequestException; +import jakarta.persistence.Basic; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.IntStream; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Formula; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class Post extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member writer; + + @Embedded + private PostBody postBody; + + @Embedded + private PostCategories postCategories; + + @Embedded + private PostOptions postOptions; + + @Column(columnDefinition = "datetime(6)", nullable = false) + private LocalDateTime deadline; + + @Column(nullable = false) + private boolean isHidden; + + @Basic(fetch = FetchType.LAZY) + @Formula("(select count(v.id) from Vote v where v.post_option_id in " + + "(select po.id from Post_Option po where po.post_id = id)" + + ")") + private long totalVoteCount; + + @OneToMany(mappedBy = "post", cascade = CascadeType.PERSIST, orphanRemoval = true) + private List comments = new ArrayList<>(); + + @Builder + private Post( + final Member writer, + final PostBody postBody, + final LocalDateTime deadline + ) { + this.writer = writer; + this.postBody = postBody; + this.deadline = deadline; + this.postCategories = new PostCategories(); + this.postOptions = new PostOptions(); + this.isHidden = false; + } + + public void mapCategories(final List categories) { + this.postCategories.mapPostAndCategories(this, categories); + } + + public void mapPostOptionsByElements( + final List postOptionContents, + final List optionImageUrls + ) { + this.postOptions = PostOptions.of(this, postOptionContents, optionImageUrls); + } + + public void validateDeadlineNotExceedByMaximumDeadline(final int maximumDeadline) { + LocalDateTime maximumDeadlineFromNow = LocalDateTime.now().plusDays(maximumDeadline); + if (this.deadline.isAfter(maximumDeadlineFromNow)) { + throw new BadRequestException(PostExceptionType.DEADLINE_EXCEED_THREE_DAYS); + } + } + + public void validateWriter(final Member member) { + if (!Objects.equals(this.writer, member)) { + throw new BadRequestException(PostExceptionType.NOT_WRITER); + } + } + + public long getSelectedOptionId(final Member member) { + return this.postOptions.getSelectedOptionId(member); + } + + public Vote makeVote(final Member voter, final PostOption postOption) { + validateDeadLine(); + validateVoter(voter); + validatePostOption(postOption); + + final Vote vote = Vote.builder() + .member(voter) + .build(); + + postOption.addVote(vote); + return vote; + } + + public void validateDeadLine() { + if (isClosed()) { + throw new BadRequestException(PostExceptionType.POST_CLOSED); + } + } + + public boolean isClosed() { + return deadline.isBefore(LocalDateTime.now()); + } + + private void validateVoter(final Member voter) { + if (Objects.equals(this.writer.getId(), voter.getId())) { + throw new BadRequestException(PostExceptionType.NOT_VOTER); + } + } + + private void validatePostOption(final PostOption postOption) { + if (!hasPostOption(postOption)) { + throw new BadRequestException(PostExceptionType.POST_OPTION_NOT_FOUND); + } + } + + private boolean hasPostOption(final PostOption postOption) { + return postOptions.contains(postOption); + } + + public void closeEarly() { + this.deadline = LocalDateTime.now(); + } + + public void addContentImage(final String contentImageUrl) { + this.postBody.addContentImage(this, contentImageUrl); + } + + public long getFinalTotalVoteCount(final Member loginMember) { + if (isVisibleVoteResult(loginMember)) { + return this.totalVoteCount; + } + + return -1L; + } + + public boolean isVisibleVoteResult(final Member member) { + return this.postOptions.getSelectedOptionId(member) != 0 + || this.writer.equals(member) + || isClosed(); + } + + public void blind() { + this.isHidden = true; + } + + public void validateMine(final Member member) { + if (this.writer.equals(member)) { + throw new BadRequestException(ReportExceptionType.REPORT_MY_POST); + } + } + + public void validateHidden() { + if (this.isHidden) { + throw new BadRequestException(ReportExceptionType.ALREADY_HIDDEN_POST); + } + } + + public void addComment(final Comment comment) { + comments.add(comment); + comment.setPost(this); + } + + public void validatePossibleToDelete() { + if (this.totalVoteCount >= 20) { + throw new BadRequestException(PostExceptionType.CANNOT_DELETE_BECAUSE_MORE_THAN_TWENTY_VOTES); + } + } + + public void update( + final PostBody postBody, + final String oldContentImageUrl, + final List contentImageUrls, + final List categories, + final List postOptionContents, + final List oldPostOptionImageUrls, + final List postOptionImageUrls, + final LocalDateTime deadline + ) { + this.postBody.update(postBody, oldContentImageUrl, contentImageUrls); + this.postCategories.update(this, categories); + addAllPostOptions(postOptionContents, oldPostOptionImageUrls, postOptionImageUrls); + this.deadline = deadline; + } + + private void addAllPostOptions( + final List postOptionContents, + final List oldPostOptionImageUrls, + final List postOptionImageUrls + ) { + this.postOptions.addAll( + this, + postOptionContents, + getPostOptionImageUrls(oldPostOptionImageUrls, postOptionImageUrls) + ); + } + + public void postOptionsClear() { + this.postOptions.clear(); + } + + private List getPostOptionImageUrls( + final List oldPostOptionImageUrls, + final List postOptionImageUrls + ) { + return IntStream.range(0, postOptionImageUrls.size()) + .mapToObj(postOptionIndex -> + getPostOptionImageUrl( + postOptionIndex, + oldPostOptionImageUrls, + postOptionImageUrls + ) + ) + .toList(); + } + + private String getPostOptionImageUrl( + final int postOptionIndex, + final List oldPostOptionImageUrls, + final List postOptionImageUrls + ) { + final String postOptionImageUrl = postOptionImageUrls.get(postOptionIndex); + if (postOptionImageUrl.isEmpty()) { + return oldPostOptionImageUrls.get(postOptionIndex); + } + + return postOptionImageUrls.get(postOptionIndex); + } + + public void validateDeadLineToModify(final LocalDateTime deadlineToModify) { + if (getCreatedAt().plusDays(3).isBefore(deadlineToModify)) { + throw new BadRequestException(PostExceptionType.DEADLINE_EXCEED_THREE_DAYS); + } + } + + public void validateExistVote() { + if (totalVoteCount > 0) { + throw new BadRequestException(PostExceptionType.VOTING_PROGRESS_NOT_EDITABLE); + } + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/PostBody.java b/backend/src/main/java/com/votogether/domain/post/entity/PostBody.java new file mode 100644 index 000000000..9e5e00217 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/PostBody.java @@ -0,0 +1,58 @@ +package com.votogether.domain.post.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import java.util.List; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Embeddable +public class PostBody { + + @Column(length = 100, nullable = false) + private String title; + + @Column(length = 1000, nullable = false) + private String content; + + @Embedded + private PostContentImages postContentImages; + + @Builder + private PostBody(final String title, final String content) { + this.title = title; + this.content = content; + this.postContentImages = new PostContentImages(); + } + + public void update( + final PostBody postBody, + final String oldContentImageUrl, + final List contentImageUrls + ) { + this.title = postBody.getTitle(); + this.content = postBody.getContent(); + this.postContentImages.update(getContentImageUrl(oldContentImageUrl, contentImageUrls)); + } + + private String getContentImageUrl( + final String oldContentImageUrl, + final List contentImageUrls + ) { + if (contentImageUrls.isEmpty()) { + return oldContentImageUrl; + } + + return contentImageUrls.get(0); + } + + public void addContentImage(final Post post, final String contentImageUrl) { + this.postContentImages.addContentImage(post, contentImageUrl); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/PostCategories.java b/backend/src/main/java/com/votogether/domain/post/entity/PostCategories.java new file mode 100644 index 000000000..9f43068bb --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/PostCategories.java @@ -0,0 +1,46 @@ +package com.votogether.domain.post.entity; + +import com.votogether.domain.category.entity.Category; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Embeddable +public class PostCategories { + + @OneToMany(mappedBy = "post", cascade = CascadeType.PERSIST, orphanRemoval = true) + private List postCategories = new ArrayList<>(); + + public void mapPostAndCategories(final Post post, final List categories) { + categories.forEach(category -> postCategories.add(createPostCategory(post, category))); + } + + private PostCategory createPostCategory(final Post post, final Category category) { + return PostCategory.builder() + .post(post) + .category(category) + .build(); + } + + public void update(final Post post, final List categories) { + postCategories.removeIf(Predicate.not(postCategory -> categories.contains(postCategory.getCategory()))); + + categories.stream() + .filter(this::isCategoryNotPresent) + .forEach(category -> this.postCategories.add(createPostCategory(post, category))); + } + + private boolean isCategoryNotPresent(Category category) { + return this.postCategories.stream() + .noneMatch(postCategory -> postCategory.getCategory().equals(category)); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/PostCategory.java b/backend/src/main/java/com/votogether/domain/post/entity/PostCategory.java new file mode 100644 index 000000000..ef562207c --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/PostCategory.java @@ -0,0 +1,43 @@ +package com.votogether.domain.post.entity; + +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.common.BaseEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"post_id", "category_id"})}) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class PostCategory extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id", nullable = false) + private Category category; + + @Builder + private PostCategory(final Post post, final Category category) { + this.post = post; + this.category = category; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/PostContentImage.java b/backend/src/main/java/com/votogether/domain/post/entity/PostContentImage.java new file mode 100644 index 000000000..d6e5307b2 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/PostContentImage.java @@ -0,0 +1,43 @@ +package com.votogether.domain.post.entity; + +import com.votogether.domain.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class PostContentImage extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; + + @Column(nullable = false) + private String imageUrl; + + @Builder + public PostContentImage(final Post post, final String imageUrl) { + this.post = post; + this.imageUrl = imageUrl; + } + + public void updateImageUrl(final String imageUrl) { + this.imageUrl = imageUrl; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/PostContentImages.java b/backend/src/main/java/com/votogether/domain/post/entity/PostContentImages.java new file mode 100644 index 000000000..40701594f --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/PostContentImages.java @@ -0,0 +1,35 @@ +package com.votogether.domain.post.entity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Embeddable +public class PostContentImages { + + @OneToMany(mappedBy = "post", cascade = CascadeType.PERSIST, orphanRemoval = true) + private List contentImages = new ArrayList<>(); + + public void addContentImage(final Post post, final String contentImageUrl) { + this.contentImages.add(getPostContentImage(post, contentImageUrl)); + } + + private PostContentImage getPostContentImage(final Post post, final String contentImageUrl) { + return PostContentImage.builder() + .post(post) + .imageUrl(contentImageUrl) + .build(); + } + + public void update(final String imageUrl) { + this.contentImages.get(0).updateImageUrl(imageUrl); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/PostOption.java b/backend/src/main/java/com/votogether/domain/post/entity/PostOption.java new file mode 100644 index 000000000..8849c2e3f --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/PostOption.java @@ -0,0 +1,135 @@ +package com.votogether.domain.post.entity; + +import com.votogether.domain.common.BaseEntity; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.vote.entity.Vote; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"post_id", "sequence"})}) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class PostOption extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; + + @Column(nullable = false) + private int sequence; + + @Column(length = 50, nullable = false) + private String content; + + @Column + private String imageUrl; + + @OneToMany(mappedBy = "postOption", cascade = CascadeType.PERSIST, orphanRemoval = true) + private List votes = new ArrayList<>(); + + @Builder + private PostOption( + final Post post, + final int sequence, + final String content, + final String imageUrl + ) { + this.post = post; + this.sequence = sequence; + this.content = content; + this.imageUrl = imageUrl; + } + + public static PostOption of( + final String postOptionContent, + final Post post, + final int postOptionSequence, + final String optionImageUrl + ) { + if (!optionImageUrl.isEmpty()) { + return toPostOptionEntity(post, postOptionSequence, postOptionContent, optionImageUrl); + } + + return toPostOptionEntity(post, postOptionSequence, postOptionContent, ""); + } + + private static PostOption toPostOptionEntity( + final Post post, + final Integer postOptionSequence, + final String postOptionContent, + final String optionImageUrl + ) { + return PostOption.builder() + .post(post) + .sequence(postOptionSequence) + .content(postOptionContent) + .imageUrl(optionImageUrl) + .build(); + } + + public void addVote(final Vote vote) { + this.votes.add(vote); + vote.setPostOption(this); + } + + public boolean hasMemberVote(final Member member) { + return votes.stream() + .anyMatch(vote -> vote.isVoteByMember(member)); + } + + public boolean isBelongsTo(final Post post) { + return Objects.equals(this.post.getId(), post.getId()); + } + + public int getVoteCount(final boolean isPostVoteByMember) { + final int votesCount = votes.size(); + if (isPostVoteByMember) { + return votesCount; + } + + return -1; + } + + public double getVotePercent(final long totalVoteCount) { + if (isPostVoteByMember(totalVoteCount)) { + return calculateVotePercent(totalVoteCount); + } + + return totalVoteCount; + } + + private boolean isPostVoteByMember(final long totalVoteCount) { + return totalVoteCount > 0; + } + + private double calculateVotePercent(final Long totalVoteCount) { + return ((double) this.votes.size() / totalVoteCount) * 100; + } + + public int getVoteCount() { + return this.votes.size(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/PostOptions.java b/backend/src/main/java/com/votogether/domain/post/entity/PostOptions.java new file mode 100644 index 000000000..fb19477e6 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/PostOptions.java @@ -0,0 +1,85 @@ +package com.votogether.domain.post.entity; + +import com.votogether.domain.member.entity.Member; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Getter +@Embeddable +public class PostOptions { + + private static final Integer FIRST_OPTION_SEQUENCE = 1; + + @OneToMany(mappedBy = "post", cascade = CascadeType.PERSIST, orphanRemoval = true) + private List postOptions = new ArrayList<>(); + + public static PostOptions of( + final Post post, + final List postOptionContents, + final List optionImageUrls + ) { + final PostOptions newInstance = new PostOptions(); + final List postOptions = getPostOptions(post, postOptionContents, optionImageUrls); + + newInstance.postOptions.addAll(postOptions); + return newInstance; + } + + private static List getPostOptions( + final Post post, + final List postOptionContents, + final List optionImageUrls + ) { + return IntStream.rangeClosed(FIRST_OPTION_SEQUENCE, postOptionContents.size()) + .mapToObj(postOptionSequence -> + toPostOption(post, postOptionContents, optionImageUrls, postOptionSequence) + ) + .toList(); + } + + private static PostOption toPostOption( + final Post post, + final List postOptionContents, + final List optionImageUrls, + final int postOptionSequence + ) { + return PostOption.of( + postOptionContents.get(postOptionSequence - 1), + post, + postOptionSequence, + optionImageUrls.get(postOptionSequence - 1) + ); + } + + public Boolean contains(final PostOption postOption) { + return postOptions.contains(postOption); + } + + public Long getSelectedOptionId(final Member member) { + return postOptions.stream() + .filter(postOption -> postOption.hasMemberVote(member)) + .findAny() + .map(PostOption::getId) + .orElse(0L); + } + + public void addAll( + final Post post, + final List postOptionContents, + final List postOptionImageUrls + ) { + this.postOptions.addAll(getPostOptions(post, postOptionContents, postOptionImageUrls)); + } + + public void clear() { + this.postOptions.clear(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/comment/Comment.java b/backend/src/main/java/com/votogether/domain/post/entity/comment/Comment.java new file mode 100644 index 000000000..8f9250848 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/comment/Comment.java @@ -0,0 +1,97 @@ +package com.votogether.domain.post.entity.comment; + +import com.votogether.domain.common.BaseEntity; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.exception.CommentExceptionType; +import com.votogether.domain.report.exception.ReportExceptionType; +import com.votogether.global.exception.BadRequestException; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import java.util.Objects; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class Comment extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Setter + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + @Embedded + private Content content; + + @Column(nullable = false) + private boolean isHidden; + + @Builder + private Comment( + final Post post, + final Member member, + final String content + ) { + this.post = post; + this.member = member; + this.content = new Content(content); + this.isHidden = false; + } + + public void validateWriter(final Member member) { + if (!Objects.equals(this.member.getId(), member.getId())) { + throw new BadRequestException(CommentExceptionType.NOT_WRITER); + } + } + + public void validateBelong(final Post post) { + if (!Objects.equals(this.post.getId(), post.getId())) { + throw new BadRequestException(CommentExceptionType.NOT_BELONG_POST); + } + } + + public void blind() { + this.isHidden = true; + } + + public void validateMine(final Member reporter) { + if (this.member.equals(reporter)) { + throw new BadRequestException(ReportExceptionType.REPORT_MY_COMMENT); + } + } + + public void validateHidden() { + if (this.isHidden) { + throw new BadRequestException(ReportExceptionType.ALREADY_HIDDEN_COMMENT); + } + } + + public void updateContent(final String content) { + this.content = new Content(content); + } + + public String getContent() { + return content.getValue(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/comment/Content.java b/backend/src/main/java/com/votogether/domain/post/entity/comment/Content.java new file mode 100644 index 000000000..e6e0fd3b8 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/comment/Content.java @@ -0,0 +1,32 @@ +package com.votogether.domain.post.entity.comment; + +import com.votogether.domain.post.exception.CommentExceptionType; +import com.votogether.global.exception.BadRequestException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Embeddable +class Content { + + private static final int MAXIMUM_LENGTH = 500; + + @Column(name = "content", nullable = false, length = MAXIMUM_LENGTH) + private String value; + + public Content(final String value) { + validate(value); + this.value = value; + } + + private void validate(final String content) { + if (content.length() > MAXIMUM_LENGTH) { + throw new BadRequestException(CommentExceptionType.INVALID_CONTENT_LENGTH); + } + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/vo/PostClosingType.java b/backend/src/main/java/com/votogether/domain/post/entity/vo/PostClosingType.java new file mode 100644 index 000000000..d98d43b4a --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/vo/PostClosingType.java @@ -0,0 +1,9 @@ +package com.votogether.domain.post.entity.vo; + +public enum PostClosingType { + + ALL, + PROGRESS, + CLOSED + +} diff --git a/backend/src/main/java/com/votogether/domain/post/entity/vo/PostSortType.java b/backend/src/main/java/com/votogether/domain/post/entity/vo/PostSortType.java new file mode 100644 index 000000000..97abc0cf5 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/entity/vo/PostSortType.java @@ -0,0 +1,28 @@ +package com.votogether.domain.post.entity.vo; + +import lombok.Getter; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; + +@Getter +public enum PostSortType { + + LATEST( + Sort.by(Direction.DESC, "createdAt"), + Sort.by(Direction.DESC, "postOption.post.createdAt") + ), + + HOT( + Sort.by(Direction.DESC, "totalVoteCount"), + Sort.by(Direction.DESC, "postOption.post.totalVoteCount") + ); + + private final Sort postBaseSort; + private final Sort voteBaseSort; + + PostSortType(final Sort postBaseSort, final Sort voteBaseSort) { + this.postBaseSort = postBaseSort; + this.voteBaseSort = voteBaseSort; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/exception/CommentExceptionType.java b/backend/src/main/java/com/votogether/domain/post/exception/CommentExceptionType.java new file mode 100644 index 000000000..de350346f --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/exception/CommentExceptionType.java @@ -0,0 +1,22 @@ +package com.votogether.domain.post.exception; + +import com.votogether.global.exception.ExceptionType; +import lombok.Getter; + +@Getter +public enum CommentExceptionType implements ExceptionType { + + INVALID_CONTENT_LENGTH(2000, "유효하지 않은 댓글 길이입니다."), + COMMENT_NOT_FOUND(2001, "해당 댓글이 존재하지 않습니다."), + NOT_BELONG_POST(2002, "댓글의 게시글 정보와 일치하지 않습니다."), + NOT_WRITER(2003, "댓글 작성자가 아닙니다."); + + private final int code; + private final String message; + + CommentExceptionType(final int code, final String message) { + this.code = code; + this.message = message; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/exception/PostExceptionType.java b/backend/src/main/java/com/votogether/domain/post/exception/PostExceptionType.java new file mode 100644 index 000000000..7e09bd98e --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/exception/PostExceptionType.java @@ -0,0 +1,29 @@ +package com.votogether.domain.post.exception; + +import com.votogether.global.exception.ExceptionType; +import lombok.Getter; + +@Getter +public enum PostExceptionType implements ExceptionType { + + POST_NOT_FOUND(1000, "해당 게시글이 존재하지 않습니다."), + POST_OPTION_NOT_FOUND(1001, "해당 게시글 투표 옵션이 존재하지 않습니다."), + UNRELATED_POST_OPTION(1002, "게시글 투표 옵션이 게시글과 연관되어 있지 않습니다."), + NOT_WRITER(1003, "해당 게시글 작성자가 아닙니다."), + POST_CLOSED(1004, "게시글이 이미 마감되었습니다."), + POST_NOT_HALF_DEADLINE(1005, "게시글이 마감 시간까지 절반의 시간 이상이 지나지 않으면 조기마감을 할 수 없습니다."), + NOT_VOTER(1004, "해당 게시글 작성자는 투표할 수 없습니다."), + DEADLINE_EXCEED_THREE_DAYS(1005, "마감 기한은 생성 시간으로부터 3일을 초과할 수 없습니다."), + WRONG_IMAGE(1006, "이미지 저장에 실패했습니다. 다시 시도해주세요."), + CANNOT_DELETE_BECAUSE_MORE_THAN_TWENTY_VOTES(1007, "투표가 20개 이상이므로 해당 게시글을 삭제할 수 없습니다."), + VOTING_PROGRESS_NOT_EDITABLE(1008, "해당 게시글은 투표가 진행되어 수정할 수 없습니다."); + + private final int code; + private final String message; + + PostExceptionType(final int code, final String message) { + this.code = code; + this.message = message; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/repository/CommentRepository.java b/backend/src/main/java/com/votogether/domain/post/repository/CommentRepository.java new file mode 100644 index 000000000..e4010b24b --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/repository/CommentRepository.java @@ -0,0 +1,17 @@ +package com.votogether.domain.post.repository; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.comment.Comment; +import java.util.List; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentRepository extends JpaRepository { + + @EntityGraph(attributePaths = {"member"}) + List findAllByPostAndIsHiddenFalseOrderByCreatedAtAsc(final Post post); + + List findAllByMember(final Member member); + +} diff --git a/backend/src/main/java/com/votogether/domain/post/repository/PostCategoryRepository.java b/backend/src/main/java/com/votogether/domain/post/repository/PostCategoryRepository.java new file mode 100644 index 000000000..d6e61a244 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/repository/PostCategoryRepository.java @@ -0,0 +1,7 @@ +package com.votogether.domain.post.repository; + +import com.votogether.domain.post.entity.PostCategory; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostCategoryRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/votogether/domain/post/repository/PostContentImageRepository.java b/backend/src/main/java/com/votogether/domain/post/repository/PostContentImageRepository.java new file mode 100644 index 000000000..32bb8f3f8 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/repository/PostContentImageRepository.java @@ -0,0 +1,7 @@ +package com.votogether.domain.post.repository; + +import com.votogether.domain.post.entity.PostContentImage; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostContentImageRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/votogether/domain/post/repository/PostCustomRepository.java b/backend/src/main/java/com/votogether/domain/post/repository/PostCustomRepository.java new file mode 100644 index 000000000..18d0f022e --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/repository/PostCustomRepository.java @@ -0,0 +1,35 @@ +package com.votogether.domain.post.repository; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.vo.PostClosingType; +import com.votogether.domain.post.entity.vo.PostSortType; +import java.util.List; +import org.springframework.data.domain.Pageable; + +public interface PostCustomRepository { + + List findAllByClosingTypeAndSortTypeAndCategoryId( + final PostClosingType postClosingType, + final PostSortType postSortType, + final Long categoryId, + final Pageable pageable + ); + + List findAllWithKeyword( + final String keyword, + final PostClosingType postClosingType, + final PostSortType postSortType, + final Long categoryId, + final Pageable pageable + ); + + List findAllByWriterWithClosingTypeAndSortTypeAndCategoryId( + final Member writer, + final PostClosingType postClosingType, + final PostSortType postSortType, + final Long categoryId, + final Pageable pageable + ); + +} diff --git a/backend/src/main/java/com/votogether/domain/post/repository/PostCustomRepositoryImpl.java b/backend/src/main/java/com/votogether/domain/post/repository/PostCustomRepositoryImpl.java new file mode 100644 index 000000000..e271c4932 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/repository/PostCustomRepositoryImpl.java @@ -0,0 +1,130 @@ +package com.votogether.domain.post.repository; + +import static com.votogether.domain.post.entity.QPost.post; +import static com.votogether.domain.post.entity.QPostCategory.postCategory; + +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.vo.PostClosingType; +import com.votogether.domain.post.entity.vo.PostSortType; +import com.votogether.global.persistence.OrderByNull; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class PostCustomRepositoryImpl implements PostCustomRepository { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List findAllByClosingTypeAndSortTypeAndCategoryId( + final PostClosingType postClosingType, + final PostSortType postSortType, + final Long categoryId, + final Pageable pageable + ) { + return jpaQueryFactory + .selectFrom(post) + .distinct() + .join(post.writer).fetchJoin() + .leftJoin(post.postCategories.postCategories, postCategory) + .where( + categoryIdEq(categoryId), + deadlineEq(postClosingType), + post.isHidden.eq(false) + ) + .orderBy(orderBy(postSortType)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + + @Override + public List findAllByWriterWithClosingTypeAndSortTypeAndCategoryId( + final Member writer, + final PostClosingType postClosingType, + final PostSortType postSortType, + final Long categoryId, + final Pageable pageable + ) { + return jpaQueryFactory + .selectFrom(post) + .join(post.writer).fetchJoin() + .where( + categoryIdEq(categoryId), + deadlineEq(postClosingType), + post.writer.eq(writer), + post.isHidden.eq(false) + ) + .orderBy(orderBy(postSortType)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + + private BooleanExpression categoryIdEq(final Long categoryId) { + return categoryId == null ? null : postCategory.category.id.eq(categoryId); + } + + private BooleanExpression deadlineEq(final PostClosingType postClosingType) { + final LocalDateTime now = LocalDateTime.now(); + switch (postClosingType) { + case PROGRESS: + return post.deadline.after(now); + case CLOSED: + return post.deadline.before(now); + case ALL: + default: + return null; + } + } + + private OrderSpecifier orderBy(final PostSortType postSortType) { + switch (postSortType) { + case LATEST: + return post.createdAt.desc(); + case HOT: + return post.totalVoteCount.desc(); + default: + return OrderByNull.DEFAULT; + } + } + + @Override + public List findAllWithKeyword( + final String keyword, + final PostClosingType postClosingType, + final PostSortType postSortType, + final Long categoryId, + final Pageable pageable + ) { + return jpaQueryFactory + .selectFrom(post) + .distinct() + .join(post.writer).fetchJoin() + .leftJoin(post.postCategories.postCategories, postCategory) + .where( + containsKeywordInTitleOrContent(keyword), + categoryIdEq(categoryId), + deadlineEq(postClosingType), + post.isHidden.eq(false) + ) + .orderBy(orderBy(postSortType)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + + private BooleanExpression containsKeywordInTitleOrContent(final String keyword) { + return post.postBody.title.contains(keyword) + .or(post.postBody.content.contains(keyword)); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/repository/PostOptionRepository.java b/backend/src/main/java/com/votogether/domain/post/repository/PostOptionRepository.java new file mode 100644 index 000000000..e4507c0ec --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/repository/PostOptionRepository.java @@ -0,0 +1,8 @@ +package com.votogether.domain.post.repository; + +import com.votogether.domain.post.entity.PostOption; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostOptionRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/com/votogether/domain/post/repository/PostRepository.java b/backend/src/main/java/com/votogether/domain/post/repository/PostRepository.java new file mode 100644 index 000000000..15fb326d6 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/repository/PostRepository.java @@ -0,0 +1,41 @@ +package com.votogether.domain.post.repository; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.Post; +import java.time.LocalDateTime; +import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface PostRepository extends JpaRepository, PostCustomRepository { + + Slice findByDeadlineBefore(final LocalDateTime currentTime, final Pageable pageable); + + Slice findByDeadlineAfter(final LocalDateTime currentTime, final Pageable pageable); + + int countByWriter(final Member member); + + List findAllByWriter(final Member member); + + @Query("SELECT COUNT(p)" + + "FROM Member m " + + "LEFT JOIN Post p ON m.id = p.writer.id AND p.writer IN :members " + + "WHERE m IN :members " + + "GROUP BY m.id") + List findCountsByMembers(@Param("members") final List members); + + @Query("SELECT v.postOption.post FROM Vote v WHERE v.member = :member " + + "AND v.postOption.post.deadline < CURRENT_TIMESTAMP") + Slice findClosedPostsVotedByMember(@Param("member") final Member member, final Pageable pageable); + + @Query("SELECT v.postOption.post FROM Vote v WHERE v.member = :member " + + "AND v.postOption.post.deadline > CURRENT_TIMESTAMP") + Slice findOpenPostsVotedByMember(@Param("member") final Member member, final Pageable pageable); + + @Query("SELECT v.postOption.post FROM Vote v WHERE v.member = :member ") + Slice findPostsVotedByMember(@Param("member") final Member member, final Pageable pageable); + +} diff --git a/backend/src/main/java/com/votogether/domain/post/service/PostCommentService.java b/backend/src/main/java/com/votogether/domain/post/service/PostCommentService.java new file mode 100644 index 000000000..183d205e3 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/service/PostCommentService.java @@ -0,0 +1,85 @@ +package com.votogether.domain.post.service; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.dto.request.comment.CommentRegisterRequest; +import com.votogether.domain.post.dto.request.comment.CommentUpdateRequest; +import com.votogether.domain.post.dto.response.comment.CommentResponse; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.comment.Comment; +import com.votogether.domain.post.exception.CommentExceptionType; +import com.votogether.domain.post.exception.PostExceptionType; +import com.votogether.domain.post.repository.CommentRepository; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.global.exception.NotFoundException; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class PostCommentService { + + private final PostRepository postRepository; + private final CommentRepository commentRepository; + + @Transactional + public void createComment( + final Member member, + final Long postId, + final CommentRegisterRequest commentRegisterRequest + ) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND)); + + final Comment comment = Comment.builder() + .member(member) + .content(commentRegisterRequest.content()) + .build(); + + post.addComment(comment); + } + + @Transactional(readOnly = true) + public List getComments(final Long postId) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND)); + + return commentRepository.findAllByPostAndIsHiddenFalseOrderByCreatedAtAsc(post) + .stream() + .map(CommentResponse::from) + .toList(); + } + + @Transactional + public void updateComment( + final Long postId, + final Long commentId, + final CommentUpdateRequest commentUpdateRequest, + final Member member + ) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND)); + final Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new NotFoundException(CommentExceptionType.COMMENT_NOT_FOUND)); + + comment.validateBelong(post); + comment.validateWriter(member); + + comment.updateContent(commentUpdateRequest.content()); + } + + @Transactional + public void deleteComment(final Long postId, final Long commentId, final Member member) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND)); + final Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new NotFoundException(CommentExceptionType.COMMENT_NOT_FOUND)); + + comment.validateBelong(post); + comment.validateWriter(member); + + commentRepository.delete(comment); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/service/PostService.java b/backend/src/main/java/com/votogether/domain/post/service/PostService.java new file mode 100644 index 000000000..a658d0383 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/service/PostService.java @@ -0,0 +1,410 @@ +package com.votogether.domain.post.service; + +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.category.repository.CategoryRepository; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.vo.AgeRange; +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.exception.MemberExceptionType; +import com.votogether.domain.post.dto.request.post.PostCreateRequest; +import com.votogether.domain.post.dto.request.post.PostOptionCreateRequest; +import com.votogether.domain.post.dto.request.post.PostOptionUpdateRequest; +import com.votogether.domain.post.dto.request.post.PostUpdateRequest; +import com.votogether.domain.post.dto.response.post.PostDetailResponse; +import com.votogether.domain.post.dto.response.post.PostRankingResponse; +import com.votogether.domain.post.dto.response.post.PostResponse; +import com.votogether.domain.post.dto.response.post.PostSummaryResponse; +import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.PostOption; +import com.votogether.domain.post.entity.vo.PostClosingType; +import com.votogether.domain.post.entity.vo.PostSortType; +import com.votogether.domain.post.exception.PostExceptionType; +import com.votogether.domain.post.repository.PostOptionRepository; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.domain.vote.repository.VoteRepository; +import com.votogether.domain.vote.repository.dto.VoteStatus; +import com.votogether.global.exception.BadRequestException; +import com.votogether.global.exception.NotFoundException; +import com.votogether.global.util.ImageUploader; +import java.time.LocalDate; +import java.util.EnumMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Service +public class PostService { + + private static final int BASIC_PAGING_SIZE = 10; + private static final int MAXIMUM_DEADLINE = 3; + + private final Map>> postsVotedByMemberMapper; + private final PostRepository postRepository; + private final PostOptionRepository postOptionRepository; + private final CategoryRepository categoryRepository; + private final VoteRepository voteRepository; + + public PostService( + final PostRepository postRepository, + final PostOptionRepository postOptionRepository, + final CategoryRepository categoryRepository, + final VoteRepository voteRepository + ) { + this.postRepository = postRepository; + this.postOptionRepository = postOptionRepository; + this.categoryRepository = categoryRepository; + this.voteRepository = voteRepository; + this.postsVotedByMemberMapper = new EnumMap<>(PostClosingType.class); + initPostsVotedByMemberMapper(); + } + + private void initPostsVotedByMemberMapper() { + postsVotedByMemberMapper.put(PostClosingType.ALL, postRepository::findPostsVotedByMember); + postsVotedByMemberMapper.put(PostClosingType.PROGRESS, postRepository::findOpenPostsVotedByMember); + postsVotedByMemberMapper.put(PostClosingType.CLOSED, postRepository::findClosedPostsVotedByMember); + } + + @Transactional + public Long save( + final PostCreateRequest postCreateRequest, + final Member loginMember, + final List contentImages, + final List optionImages + ) { + final List categories = categoryRepository.findAllById(postCreateRequest.categoryIds()); + final Post post = toPostEntity(postCreateRequest, loginMember, contentImages, optionImages, categories); + post.validateDeadlineNotExceedByMaximumDeadline(MAXIMUM_DEADLINE); + + return postRepository.save(post).getId(); + } + + private Post toPostEntity( + final PostCreateRequest postCreateRequest, + final Member loginMember, + final List contentImages, + final List optionImages, + final List categories + ) { + final Post post = toPost(postCreateRequest, loginMember); + + post.mapPostOptionsByElements( + transformElements(postCreateRequest.postOptions(), PostOptionCreateRequest::content), + transformElements(optionImages, ImageUploader::upload) + ); + post.mapCategories(categories); + addContentImageIfPresent(post, contentImages); + + return post; + } + + private Post toPost( + final PostCreateRequest postCreateRequest, + final Member loginMember + ) { + return Post.builder() + .writer(loginMember) + .postBody(toPostBody(postCreateRequest.title(), postCreateRequest.content())) + .deadline(postCreateRequest.deadline()) + .build(); + } + + private PostBody toPostBody(final String title, final String content) { + return PostBody.builder() + .title(title) + .content(content) + .build(); + } + + private void addContentImageIfPresent(final Post post, final List contentImages) { + if (isContentImagesPresent(contentImages)) { + post.addContentImage(ImageUploader.upload(contentImages.get(0))); + } + } + + private boolean isContentImagesPresent(final List contentImages) { + return Objects.nonNull(contentImages) && !contentImages.isEmpty(); + } + + @Transactional(readOnly = true) + public List getAllPostBySortTypeAndClosingTypeAndCategoryId( + final int page, + final PostClosingType postClosingType, + final PostSortType postSortType, + final Long categoryId, + final Member loginMember + ) { + final Pageable pageable = PageRequest.of(page, BASIC_PAGING_SIZE, postSortType.getPostBaseSort()); + final List posts = postRepository.findAllByClosingTypeAndSortTypeAndCategoryId( + postClosingType, + postSortType, + categoryId, + pageable + ); + return posts.stream() + .map(post -> PostResponse.of(post, loginMember)) + .toList(); + } + + @Transactional(readOnly = true) + public List getPostsGuest( + final int page, + final PostClosingType postClosingType, + final PostSortType postSortType, + final Long categoryId + ) { + final Pageable pageable = PageRequest.of(page, BASIC_PAGING_SIZE); + final List posts = postRepository.findAllByClosingTypeAndSortTypeAndCategoryId( + postClosingType, + postSortType, + categoryId, + pageable + ); + + return transformElements(posts, PostResponse::forGuest); + } + + @Transactional(readOnly = true) + public PostDetailResponse getPostById(final Long postId, final Member loginMember) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND)); + + return PostDetailResponse.of(post, loginMember); + } + + @Transactional(readOnly = true) + public VoteOptionStatisticsResponse getVoteStatistics(final long postId, final Member member) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND)); + + post.validateWriter(member); + + final List voteStatuses = + voteRepository.findVoteCountByPostIdGroupByAgeRangeAndGender(post.getId()); + return VoteOptionStatisticsResponse.from(groupVoteStatus(voteStatuses)); + } + + @Transactional(readOnly = true) + public VoteOptionStatisticsResponse getVoteOptionStatistics( + final long postId, + final long optionId, + final Member member + ) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND)); + final PostOption postOption = postOptionRepository.findById(optionId) + .orElseThrow(() -> new NotFoundException(PostExceptionType.POST_OPTION_NOT_FOUND)); + + if (!postOption.isBelongsTo(post)) { + throw new BadRequestException(PostExceptionType.UNRELATED_POST_OPTION); + } + post.validateWriter(member); + + final List voteStatuses = + voteRepository.findVoteCountByPostOptionIdGroupByAgeRangeAndGender(postOption.getId()); + return VoteOptionStatisticsResponse.from(groupVoteStatus(voteStatuses)); + } + + private Map> groupVoteStatus(final List voteStatuses) { + return voteStatuses.stream() + .collect(Collectors.groupingBy( + status -> groupAgeRange(status.birthYear()), + LinkedHashMap::new, + Collectors.groupingBy( + VoteStatus::gender, + LinkedHashMap::new, + Collectors.summingLong(VoteStatus::count) + ) + )); + } + + private String groupAgeRange(final Integer birthYear) { + final int age = LocalDate.now().getYear() - birthYear + 1; + if (age <= 0) { + throw new BadRequestException(MemberExceptionType.INVALID_AGE); + } + return AgeRange.from(age).getName(); + } + + @Transactional(readOnly = true) + public List getPostsVotedByMember( + final int page, + final PostClosingType postClosingType, + final PostSortType postSortType, + final Member member + ) { + final Pageable pageable = PageRequest.of(page, BASIC_PAGING_SIZE, postSortType.getVoteBaseSort()); + + Slice posts = postsVotedByMemberMapper.get(postClosingType).apply(member, pageable); + + return posts.stream() + .map(post -> PostResponse.of(post, member)) + .toList(); + } + + @Transactional + public void closePostEarlyById(final Long id, final Member loginMember) { + final Post post = postRepository.findById(id) + .orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND)); + + post.validateWriter(loginMember); + post.validateDeadLine(); + post.closeEarly(); + } + + @Transactional(readOnly = true) + public List getPostsByWriter( + final int page, + final PostClosingType postClosingType, + final PostSortType postSortType, + final Long categoryId, + final Member member + ) { + final Pageable pageable = PageRequest.of(page, BASIC_PAGING_SIZE); + final List posts = postRepository.findAllByWriterWithClosingTypeAndSortTypeAndCategoryId( + member, + postClosingType, + postSortType, + categoryId, + pageable + ); + + return posts.stream() + .map(post -> PostResponse.of(post, member)) + .toList(); + } + + public List searchPostsWithKeyword( + final String keyword, + final int page, + final PostClosingType postClosingType, + final PostSortType postSortType, + final Long categoryId, + final Member member + ) { + final Pageable pageable = PageRequest.of(page, BASIC_PAGING_SIZE); + final List posts = + postRepository.findAllWithKeyword(keyword, postClosingType, postSortType, categoryId, pageable); + + return posts.stream() + .map(post -> PostResponse.of(post, member)) + .toList(); + } + + public List searchPostsWithKeywordForGuest( + final String keyword, + final int page, + final PostClosingType postClosingType, + final PostSortType postSortType, + final Long categoryId + ) { + final Pageable pageable = PageRequest.of(page, BASIC_PAGING_SIZE); + final List posts = + postRepository.findAllWithKeyword(keyword, postClosingType, postSortType, categoryId, pageable); + + return posts.stream() + .map(PostResponse::forGuest) + .toList(); + } + + @Transactional + public void delete(final Long postId) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new BadRequestException(PostExceptionType.POST_NOT_FOUND)); + post.validatePossibleToDelete(); + + postRepository.deleteById(postId); + } + + @Transactional + public void update( + final long postId, + final PostUpdateRequest request, + final Member member, + final List contentImages, + final List optionImages + ) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new BadRequestException(PostExceptionType.POST_NOT_FOUND)); + + post.validateExistVote(); + post.validateWriter(member); + post.validateDeadLine(); + post.validateDeadLineToModify(request.deadline()); + + postOptionsInit(post); + post.update( + toPostBody(request.title(), request.content()), + request.imageUrl(), + transformElements(contentImages, ImageUploader::upload), + categoryRepository.findAllById(request.categoryIds()), + transformElements(request.postOptions(), PostOptionUpdateRequest::content), + transformElements(request.postOptions(), PostOptionUpdateRequest::imageUrl), + transformElements(optionImages, ImageUploader::upload), + request.deadline() + ); + } + + private void postOptionsInit(final Post post) { + post.postOptionsClear(); + postRepository.flush(); + } + + private List transformElements(final List elements, final Function process) { + return elements.stream() + .map(process) + .toList(); + } + + @Transactional(readOnly = true) + public List getRanking() { + final List posts = postRepository.findAllByClosingTypeAndSortTypeAndCategoryId( + PostClosingType.ALL, + PostSortType.HOT, + null, + PageRequest.of(0, BASIC_PAGING_SIZE) + ); + + final Map rankings = calculateRanking(posts); + + return rankings.entrySet().stream() + .map(entry -> + new PostRankingResponse( + entry.getValue(), + PostSummaryResponse.from(entry.getKey()) + ) + ) + .toList(); + } + + private Map calculateRanking(final List posts) { + final Map rankings = new LinkedHashMap<>(); + + int currentRanking = 1; + int previousRanking = -1; + long previousVoteCount = -1; + for (Post post : posts) { + final long currentVoteCount = post.getTotalVoteCount(); + final int ranking = (currentVoteCount == previousVoteCount) ? previousRanking : currentRanking; + rankings.put(post, ranking); + + previousRanking = ranking; + previousVoteCount = currentVoteCount; + currentRanking++; + } + + return rankings; + } +} + diff --git a/backend/src/main/java/com/votogether/domain/ranking/controller/RankingController.java b/backend/src/main/java/com/votogether/domain/ranking/controller/RankingController.java new file mode 100644 index 000000000..56d832791 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/ranking/controller/RankingController.java @@ -0,0 +1,31 @@ +package com.votogether.domain.ranking.controller; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.ranking.dto.response.RankingResponse; +import com.votogether.domain.ranking.service.RankingService; +import com.votogether.global.jwt.Auth; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController +public class RankingController implements RankingControllerDocs { + + private final RankingService rankingService; + + @GetMapping("/members/me/ranking/passion") + public ResponseEntity getRanking(@Auth final Member member) { + final RankingResponse response = rankingService.getPassionRanking(member); + return ResponseEntity.ok(response); + } + + @GetMapping("/members/ranking/passion/guest") + public ResponseEntity> getPassionRankings() { + final List response = rankingService.getPassionRanking(); + return ResponseEntity.ok(response); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/ranking/controller/RankingControllerDocs.java b/backend/src/main/java/com/votogether/domain/ranking/controller/RankingControllerDocs.java new file mode 100644 index 000000000..007fae1d0 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/ranking/controller/RankingControllerDocs.java @@ -0,0 +1,22 @@ +package com.votogether.domain.ranking.controller; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.ranking.dto.response.RankingResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import org.springframework.http.ResponseEntity; + +@Tag(name = "랭킹", description = "랭킹 API") +public interface RankingControllerDocs { + + @Operation(summary = "나의 열정 랭킹 조회", description = "나의 열정 랭킹 정보를 조회한다.") + @ApiResponse(responseCode = "200", description = "나의 열정 랭킹 정보 조회 성공") + ResponseEntity getRanking(final Member member); + + @Operation(summary = "상위 10명의 열정 랭킹 조회", description = "상위 10명의 열정 랭킹 정보를 조회한다.") + @ApiResponse(responseCode = "200", description = "상위 10명의 열정 랭킹 조회 성공") + ResponseEntity> getPassionRankings(); + +} diff --git a/backend/src/main/java/com/votogether/domain/ranking/dto/response/RankingResponse.java b/backend/src/main/java/com/votogether/domain/ranking/dto/response/RankingResponse.java new file mode 100644 index 000000000..a33a61f92 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/ranking/dto/response/RankingResponse.java @@ -0,0 +1,34 @@ +package com.votogether.domain.ranking.dto.response; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.ranking.entity.PassionRankings; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "랭킹 정보 응답") +public record RankingResponse( + @Schema(description = "랭킹", example = "2") + int ranking, + + @Schema(description = "닉네임", example = "유저") + String nickname, + + @Schema(description = "게시글 수", example = "5") + int postCount, + + @Schema(description = "투표 수", example = "6") + int voteCount, + + @Schema(description = "점수", example = "31") + long score +) { + + public static RankingResponse of(final PassionRankings passionRankings, final Member member) { + return new RankingResponse( + passionRankings.getRanking(member), + member.getNickname(), + passionRankings.getPassionRecord(member).getPostCount(), + passionRankings.getPassionRecord(member).getVoteCount(), + passionRankings.getScore(member)); + } +} + diff --git a/backend/src/main/java/com/votogether/domain/ranking/entity/PassionRankings.java b/backend/src/main/java/com/votogether/domain/ranking/entity/PassionRankings.java new file mode 100644 index 000000000..823843e0b --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/ranking/entity/PassionRankings.java @@ -0,0 +1,59 @@ +package com.votogether.domain.ranking.entity; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.ranking.entity.vo.PassionRecord; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public class PassionRankings { + + private final Map rankings = new LinkedHashMap<>(); + private final Map passionBoard; + + public PassionRankings(final Map passionBoard) { + this.passionBoard = passionBoard; + calculateRanking(); + } + + private void calculateRanking() { + final List members = passionBoard.entrySet().stream() + .sorted(Comparator.comparingLong(entry -> -entry.getValue().calculateScore())) + .map(Entry::getKey) + .toList(); + + int currentRanking = 1; + int previousRanking = -1; + long previousScore = -1; + for (final Member member : members) { + long currentScore = passionBoard.get(member).calculateScore(); + int ranking = (currentScore == previousScore) ? previousRanking : currentRanking; + + this.rankings.put(member, ranking); + + previousRanking = ranking; + previousScore = currentScore; + currentRanking++; + } + } + + public List getTop10Members() { + return new ArrayList<>(rankings.keySet()) + .subList(0, Math.min(10, rankings.size())); + } + + public long getScore(final Member member) { + return passionBoard.get(member).calculateScore(); + } + + public int getRanking(final Member member) { + return rankings.get(member); + } + + public PassionRecord getPassionRecord(final Member member) { + return passionBoard.get(member); + } +} diff --git a/backend/src/main/java/com/votogether/domain/ranking/entity/vo/PassionRecord.java b/backend/src/main/java/com/votogether/domain/ranking/entity/vo/PassionRecord.java new file mode 100644 index 000000000..64f9784c5 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/ranking/entity/vo/PassionRecord.java @@ -0,0 +1,23 @@ +package com.votogether.domain.ranking.entity.vo; + +import lombok.Getter; + +@Getter +public class PassionRecord { + + private static final int POST_WEIGHT = 5; + private static final int VOTE_WEIGHT = 1; + + private final int postCount; + private final int voteCount; + + public PassionRecord(int postCount, int voteCount) { + this.postCount = postCount; + this.voteCount = voteCount; + } + + public long calculateScore() { + return postCount * POST_WEIGHT + voteCount * VOTE_WEIGHT; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/ranking/service/RankingService.java b/backend/src/main/java/com/votogether/domain/ranking/service/RankingService.java new file mode 100644 index 000000000..e811e6ede --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/ranking/service/RankingService.java @@ -0,0 +1,54 @@ +package com.votogether.domain.ranking.service; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.domain.ranking.dto.response.RankingResponse; +import com.votogether.domain.ranking.entity.PassionRankings; +import com.votogether.domain.ranking.entity.vo.PassionRecord; +import com.votogether.domain.vote.repository.VoteRepository; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class RankingService { + + private final MemberRepository memberRepository; + private final PostRepository postRepository; + private final VoteRepository voteRepository; + + @Transactional(readOnly = true) + public RankingResponse getPassionRanking(final Member member) { + final PassionRankings passionRankings = getPassionRankings(); + return RankingResponse.of(passionRankings, member); + } + + @Transactional(readOnly = true) + public List getPassionRanking() { + final PassionRankings passionRankings = getPassionRankings(); + return passionRankings.getTop10Members() + .stream() + .map(member -> RankingResponse.of(passionRankings, member) + ).toList(); + } + + private PassionRankings getPassionRankings() { + final List members = memberRepository.findAll(); + final List postCounts = postRepository.findCountsByMembers(members); + final List voteCounts = voteRepository.findCountsByMembers(members); + + final Map passionBoard = new LinkedHashMap<>(); + + for (int i = 0; i < members.size(); i++) { + passionBoard.put(members.get(i), new PassionRecord(postCounts.get(i), voteCounts.get(i))); + } + + return new PassionRankings(passionBoard); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/report/controller/ReportController.java b/backend/src/main/java/com/votogether/domain/report/controller/ReportController.java new file mode 100644 index 000000000..fc94dfd68 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/controller/ReportController.java @@ -0,0 +1,26 @@ +package com.votogether.domain.report.controller; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.report.dto.request.ReportRequest; +import com.votogether.domain.report.service.ReportService; +import com.votogether.global.jwt.Auth; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController +public class ReportController implements ReportControllerDocs { + + private final ReportService reportService; + + @PostMapping("/report") + public ResponseEntity report(@Valid @RequestBody final ReportRequest request, @Auth final Member member) { + reportService.report(member, request); + return ResponseEntity.ok().build(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/report/controller/ReportControllerDocs.java b/backend/src/main/java/com/votogether/domain/report/controller/ReportControllerDocs.java new file mode 100644 index 000000000..a07c32024 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/controller/ReportControllerDocs.java @@ -0,0 +1,33 @@ +package com.votogether.domain.report.controller; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.report.dto.request.ReportRequest; +import com.votogether.global.exception.ExceptionResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +@Tag(name = "신고", description = "신고 API") +public interface ReportControllerDocs { + + @Operation(summary = "신고", description = "게시글, 댓글, 닉네임을 신고한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "신고 성공"), + @ApiResponse( + responseCode = "400", + description = "올바르지 않은 요청", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "존재하지 않는 신고 대상", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity report(final ReportRequest request, final Member member); + +} diff --git a/backend/src/main/java/com/votogether/domain/report/dto/request/ReportRequest.java b/backend/src/main/java/com/votogether/domain/report/dto/request/ReportRequest.java new file mode 100644 index 000000000..d4749b914 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/dto/request/ReportRequest.java @@ -0,0 +1,21 @@ +package com.votogether.domain.report.dto.request; + +import com.votogether.domain.report.entity.vo.ReportType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "신고 요청") +public record ReportRequest( + @Schema(description = "신고 유형", example = "POST") + ReportType type, + + @Schema(description = "신고 대상 ID", example = "1") + @NotNull(message = "신고 대상 ID는 빈 값일 수 없습니다.") + Long id, + + @Schema(description = "신고 사유", example = "불건전한 닉네임") + @NotBlank(message = "신고 사유는 빈 값이거나 공백으로만 이루어질 수 없습니다.") + String reason +) { +} diff --git a/backend/src/main/java/com/votogether/domain/report/entity/Report.java b/backend/src/main/java/com/votogether/domain/report/entity/Report.java new file mode 100644 index 000000000..9d313c41f --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/entity/Report.java @@ -0,0 +1,64 @@ +package com.votogether.domain.report.entity; + +import com.votogether.domain.common.BaseEntity; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.report.entity.vo.ReportType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table( + uniqueConstraints = {@UniqueConstraint(columnNames = {"member_id", "reportType", "targetId"})}, + indexes = {@Index(columnList = "targetId, reportType")} +) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class Report extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + @Enumerated(value = EnumType.STRING) + @Column(length = 20, nullable = false) + private ReportType reportType; + + @Column(nullable = false) + private Long targetId; + + @Column(nullable = false, length = 50) + private String reason; + + @Builder + private Report( + final Member member, + final ReportType reportType, + final Long targetId, + final String reason + ) { + this.member = member; + this.reportType = reportType; + this.targetId = targetId; + this.reason = reason; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/report/entity/vo/ReportType.java b/backend/src/main/java/com/votogether/domain/report/entity/vo/ReportType.java new file mode 100644 index 000000000..3b38119fc --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/entity/vo/ReportType.java @@ -0,0 +1,10 @@ +package com.votogether.domain.report.entity.vo; + +public enum ReportType { + + POST, + COMMENT, + NICKNAME, + ; + +} diff --git a/backend/src/main/java/com/votogether/domain/report/exception/ReportExceptionType.java b/backend/src/main/java/com/votogether/domain/report/exception/ReportExceptionType.java new file mode 100644 index 000000000..ea38d6342 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/exception/ReportExceptionType.java @@ -0,0 +1,27 @@ +package com.votogether.domain.report.exception; + +import com.votogether.global.exception.ExceptionType; +import lombok.Getter; + +@Getter +public enum ReportExceptionType implements ExceptionType { + + REPORT_MY_POST(1200, "자신의 게시글은 신고할 수 없습니다."), + ALREADY_HIDDEN_POST(1201, "이미 블라인드 처리된 글입니다."), + DUPLICATE_POST_REPORT(1202, "하나의 글에 대해서 중복하여 신고할 수 없습니다."), + REPORT_MY_COMMENT(1203, "자신의 댓글은 신고할 수 없습니다."), + ALREADY_HIDDEN_COMMENT(1204, "이미 블라인드 처리된 댓글입니다."), + DUPLICATE_COMMENT_REPORT(1205, "하나의 댓글에 대해서 중복하여 신고할 수 없습니다."), + REPORT_MY_NICKNAME(1206, "자신의 닉네임은 신고할 수 없습니다."), + DUPLICATE_NICKNAME_REPORT(1207, "하나의 닉네임에 대해서 중복하여 신고할 수 없습니다."), + ; + + private final int code; + private final String message; + + ReportExceptionType(final int code, final String message) { + this.code = code; + this.message = message; + } + +} diff --git a/backend/src/main/java/com/votogether/domain/report/repository/ReportRepository.java b/backend/src/main/java/com/votogether/domain/report/repository/ReportRepository.java new file mode 100644 index 000000000..9f56a5d0b --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/repository/ReportRepository.java @@ -0,0 +1,24 @@ +package com.votogether.domain.report.repository; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.report.entity.Report; +import com.votogether.domain.report.entity.vo.ReportType; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReportRepository extends JpaRepository { + + int countByReportTypeAndTargetId(final ReportType reportType, final Long targetId); + + Optional findByMemberAndReportTypeAndTargetId( + final Member member, + final ReportType reportType, + final Long targetId + ); + + List findAllByMember(final Member member); + + List findAllByReportTypeAndTargetId(final ReportType reportType, final Long targetId); + +} diff --git a/backend/src/main/java/com/votogether/domain/report/service/ReportService.java b/backend/src/main/java/com/votogether/domain/report/service/ReportService.java new file mode 100644 index 000000000..5357b6c02 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/service/ReportService.java @@ -0,0 +1,164 @@ +package com.votogether.domain.report.service; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.exception.MemberExceptionType; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.comment.Comment; +import com.votogether.domain.post.exception.CommentExceptionType; +import com.votogether.domain.post.exception.PostExceptionType; +import com.votogether.domain.post.repository.CommentRepository; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.domain.report.dto.request.ReportRequest; +import com.votogether.domain.report.entity.Report; +import com.votogether.domain.report.entity.vo.ReportType; +import com.votogether.domain.report.exception.ReportExceptionType; +import com.votogether.domain.report.repository.ReportRepository; +import com.votogether.global.exception.BadRequestException; +import com.votogether.global.exception.NotFoundException; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class ReportService { + + private final ReportRepository reportRepository; + private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final MemberRepository memberRepository; + + @Transactional + public void report(final Member reporter, final ReportRequest request) { + if (request.type() == ReportType.POST) { + reportPost(reporter, request); + } + if (request.type() == ReportType.COMMENT) { + reportComment(reporter, request); + } + if (request.type() == ReportType.NICKNAME) { + reportNickname(reporter, request); + } + } + + private void reportPost( + final Member reporter, + final ReportRequest request + ) { + final Post reportedPost = postRepository.findById(request.id()) + .orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND)); + validatePost(reporter, reportedPost, request); + + saveReport(reporter, request); + blindPost(request, reportedPost); + } + + private void validatePost( + final Member reporter, + final Post reportedPost, + final ReportRequest request + ) { + reportedPost.validateMine(reporter); + reportedPost.validateHidden(); + validateDuplicatedReport(reporter, request, ReportExceptionType.DUPLICATE_POST_REPORT); + } + + private void validateDuplicatedReport( + final Member reporter, + final ReportRequest request, + final ReportExceptionType exceptionType + ) { + reportRepository.findByMemberAndReportTypeAndTargetId(reporter, request.type(), request.id()) + .ifPresent(report -> { + throw new BadRequestException(exceptionType); + }); + } + + private void saveReport(final Member reporter, final ReportRequest request) { + final Report report = Report.builder() + .member(reporter) + .reportType(request.type()) + .targetId(request.id()) + .reason(request.reason()) + .build(); + reportRepository.save(report); + } + + private void blindPost( + final ReportRequest request, + final Post reportedPost + ) { + final int reportCount = reportRepository.countByReportTypeAndTargetId(request.type(), request.id()); + if (reportCount >= 5) { + reportedPost.blind(); + } + } + + private void reportComment( + final Member reporter, + final ReportRequest request + ) { + final Comment reportedComment = commentRepository.findById(request.id()) + .orElseThrow(() -> new NotFoundException(CommentExceptionType.COMMENT_NOT_FOUND)); + validateComment(reporter, request, reportedComment); + + saveReport(reporter, request); + blindComment(request, reportedComment); + } + + private void validateComment( + final Member reporter, + final ReportRequest request, + final Comment reportedComment + ) { + reportedComment.validateMine(reporter); + reportedComment.validateHidden(); + validateDuplicatedReport(reporter, request, ReportExceptionType.DUPLICATE_COMMENT_REPORT); + } + + private void blindComment( + final ReportRequest request, + final Comment reportedComment + ) { + final int reportCount = reportRepository.countByReportTypeAndTargetId(request.type(), request.id()); + if (reportCount >= 5) { + reportedComment.blind(); + } + } + + private void reportNickname( + final Member reporter, + final ReportRequest request + ) { + final Member reportedMember = memberRepository.findById(request.id()) + .orElseThrow(() -> new NotFoundException(MemberExceptionType.NONEXISTENT_MEMBER)); + validateNickname(reporter, request); + + saveReport(reporter, request); + changeNicknameByReport(reportedMember, request.type()); + } + + private void validateNickname( + final Member reporter, + final ReportRequest request + ) { + validateMyNickname(reporter, request); + validateDuplicatedReport(reporter, request, ReportExceptionType.DUPLICATE_NICKNAME_REPORT); + } + + private void validateMyNickname(final Member reporter, final ReportRequest request) { + if (Objects.equals(reporter.getId(), request.id())) { + throw new BadRequestException(ReportExceptionType.REPORT_MY_NICKNAME); + } + } + + private void changeNicknameByReport(final Member reportedMember, final ReportType reportType) { + final int reportCount = reportRepository.countByReportTypeAndTargetId(reportType, reportedMember.getId()); + if (reportCount >= 3) { + reportedMember.changeNicknameByReport(); + } + } + +} diff --git a/backend/src/main/java/com/votogether/domain/vote/controller/VoteController.java b/backend/src/main/java/com/votogether/domain/vote/controller/VoteController.java new file mode 100644 index 000000000..7c4fbd5e2 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/vote/controller/VoteController.java @@ -0,0 +1,42 @@ +package com.votogether.domain.vote.controller; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.vote.service.VoteService; +import com.votogether.global.jwt.Auth; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class VoteController implements VoteControllerDocs { + + private final VoteService voteService; + + @PostMapping("/posts/{postId}/options/{optionId}") + public ResponseEntity vote( + @PathVariable final Long postId, + @PathVariable("optionId") final Long postOptionId, + @Auth final Member member + ) { + voteService.vote(member, postId, postOptionId); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @PatchMapping("/posts/{postId}/options") + public ResponseEntity changeVote( + @PathVariable final Long postId, + @RequestParam("source") final Long originPostOptionId, + @RequestParam("target") final Long newPostOptionId, + @Auth final Member member + ) { + voteService.changeVote(member, postId, originPostOptionId, newPostOptionId); + return ResponseEntity.status(HttpStatus.OK).build(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/vote/controller/VoteControllerDocs.java b/backend/src/main/java/com/votogether/domain/vote/controller/VoteControllerDocs.java new file mode 100644 index 000000000..ab92b3301 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/vote/controller/VoteControllerDocs.java @@ -0,0 +1,50 @@ +package com.votogether.domain.vote.controller; + +import com.votogether.domain.member.entity.Member; +import com.votogether.global.exception.ExceptionResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +@Tag(name = "투표", description = "투표 API") +public interface VoteControllerDocs { + + @Operation(summary = "투표", description = "게시글의 선택지에 투표를 한다.") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "투표 성공"), + @ApiResponse( + responseCode = "400", + description = "중복된 투표", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class))), + @ApiResponse( + responseCode = "404", + description = "1.존재하지 않는 게시글\t\n2.존재하지 않는 선택지", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class))) + }) + ResponseEntity vote( + @Parameter(description = "게시글 ID", example = "1") final Long postId, + @Parameter(description = "게시글 선택지 ID", example = "1") final Long postOptionId, + final Member member + ); + + @Operation(summary = "투표 수정", description = "게시글의 선택지의 투표를 수정한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "투표 수정 성공"), + @ApiResponse( + responseCode = "404", + description = "1.존재하지 않는 게시글\t\n2.존재하지 않는 선택지", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class))) + }) + ResponseEntity changeVote( + @Parameter(description = "게시글 ID", example = "1") final Long postId, + @Parameter(description = "기존 선택지 ID", example = "2") final Long originPostOptionId, + @Parameter(description = "바꿀 선택지 ID", example = "3") final Long newPostOptionId, + final Member member + ); + +} diff --git a/backend/src/main/java/com/votogether/domain/vote/entity/Vote.java b/backend/src/main/java/com/votogether/domain/vote/entity/Vote.java new file mode 100644 index 000000000..16088a3bc --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/vote/entity/Vote.java @@ -0,0 +1,51 @@ +package com.votogether.domain.vote.entity; + +import com.votogether.domain.common.BaseEntity; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.PostOption; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"member_id", "post_option_id"})}) +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Vote extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_option_id", nullable = false) + private PostOption postOption; + + @Builder + private Vote(final Member member, final PostOption postOption) { + this.member = member; + this.postOption = postOption; + } + + public boolean isVoteByMember(final Member member) { + return this.member.equals(member); + } + + public void setPostOption(final PostOption postOption) { + this.postOption = postOption; + } +} diff --git a/backend/src/main/java/com/votogether/domain/vote/repository/VoteRepository.java b/backend/src/main/java/com/votogether/domain/vote/repository/VoteRepository.java new file mode 100644 index 000000000..c508910dd --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/vote/repository/VoteRepository.java @@ -0,0 +1,51 @@ +package com.votogether.domain.vote.repository; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.PostOption; +import com.votogether.domain.vote.entity.Vote; +import com.votogether.domain.vote.repository.dto.VoteStatus; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface VoteRepository extends JpaRepository { + + @Query("SELECT new com.votogether.domain.vote.repository.dto.VoteStatus(m.birthYear, m.gender, COUNT(v))" + + " FROM Vote v" + + " JOIN v.member m" + + " JOIN v.postOption p" + + " WHERE p.post.id = :postId" + + " GROUP BY m.birthYear, m.gender" + + " ORDER BY m.birthYear DESC" + ) + List findVoteCountByPostIdGroupByAgeRangeAndGender(@Param("postId") final Long postId); + + @Query("SELECT new com.votogether.domain.vote.repository.dto.VoteStatus(m.birthYear, m.gender, COUNT(v))" + + " FROM Vote v" + + " JOIN v.member m" + + " WHERE v.postOption.id = :postOptionId" + + " GROUP BY m.birthYear, m.gender" + + " ORDER BY m.birthYear DESC" + ) + List findVoteCountByPostOptionIdGroupByAgeRangeAndGender( + @Param("postOptionId") final Long postOptionId + ); + + Optional findByMemberAndPostOption(final Member member, final PostOption postOption); + + List findAllByMemberAndPostOptionIn(final Member member, final List postOptions); + + int countByMember(final Member member); + + List findAllByMember(final Member member); + + @Query("SELECT COUNT(v)" + + "FROM Member m " + + "LEFT JOIN Vote v ON m.id = v.member.id AND v.member IN :members " + + "WHERE m IN :members " + + "GROUP BY m.id") + List findCountsByMembers(@Param("members") final List members); + +} diff --git a/backend/src/main/java/com/votogether/domain/vote/repository/dto/VoteStatus.java b/backend/src/main/java/com/votogether/domain/vote/repository/dto/VoteStatus.java new file mode 100644 index 000000000..47fe8407c --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/vote/repository/dto/VoteStatus.java @@ -0,0 +1,10 @@ +package com.votogether.domain.vote.repository.dto; + +import com.votogether.domain.member.entity.vo.Gender; + +public record VoteStatus( + Integer birthYear, + Gender gender, + long count +) { +} diff --git a/backend/src/main/java/com/votogether/domain/vote/service/VoteService.java b/backend/src/main/java/com/votogether/domain/vote/service/VoteService.java new file mode 100644 index 000000000..8273e0d7d --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/vote/service/VoteService.java @@ -0,0 +1,75 @@ +package com.votogether.domain.vote.service; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostOption; +import com.votogether.domain.post.entity.PostOptions; +import com.votogether.domain.post.repository.PostOptionRepository; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.domain.vote.entity.Vote; +import com.votogether.domain.vote.repository.VoteRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class VoteService { + + private final VoteRepository voteRepository; + private final PostRepository postRepository; + private final PostOptionRepository postOptionRepository; + + public void vote( + final Member member, + final Long postId, + final Long postOptionId + ) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다.")); + + validateAlreadyVoted(member, post); + + final PostOption postOption = postOptionRepository.findById(postOptionId) + .orElseThrow(() -> new IllegalArgumentException("해당 선택지가 존재 하지 않습니다.")); + + final Vote vote = post.makeVote(member, postOption); + voteRepository.save(vote); + } + + + private void validateAlreadyVoted(final Member member, final Post post) { + final PostOptions postOptions = post.getPostOptions(); + final List alreadyVoted = + voteRepository.findAllByMemberAndPostOptionIn(member, postOptions.getPostOptions()); + if (!alreadyVoted.isEmpty()) { + throw new IllegalStateException("해당 게시물에는 이미 투표하였습니다."); + } + } + + public void changeVote( + final Member member, + final Long postId, + final Long originPostOptionId, + final Long newPostOptionId + ) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다.")); + + final PostOption originPostOption = postOptionRepository.findById(originPostOptionId) + .orElseThrow(() -> new IllegalArgumentException("헤당 선택지가 존재하지 않습니다.")); + + final Vote originVote = voteRepository.findByMemberAndPostOption(member, originPostOption) + .orElseThrow(() -> new IllegalArgumentException("선택지에 해당되는 투표가 존재하지 않습니다.")); + + final PostOption newPostOption = postOptionRepository.findById(newPostOptionId) + .orElseThrow(() -> new IllegalArgumentException("헤당 선택지가 존재하지 않습니다.")); + + voteRepository.delete(originVote); + final Vote vote = post.makeVote(member, newPostOption); + voteRepository.save(vote); + } + +} diff --git a/backend/src/main/java/com/votogether/global/config/JpaConfig.java b/backend/src/main/java/com/votogether/global/config/JpaConfig.java new file mode 100644 index 000000000..d76a4555d --- /dev/null +++ b/backend/src/main/java/com/votogether/global/config/JpaConfig.java @@ -0,0 +1,18 @@ +package com.votogether.global.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Configuration +@EnableJpaAuditing +public class JpaConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper().registerModule(new JavaTimeModule()); + } + +} diff --git a/backend/src/main/java/com/votogether/global/config/OpenAPIConfig.java b/backend/src/main/java/com/votogether/global/config/OpenAPIConfig.java new file mode 100644 index 000000000..fb4d1e985 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/config/OpenAPIConfig.java @@ -0,0 +1,54 @@ +package com.votogether.global.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.security.SecurityScheme.In; +import io.swagger.v3.oas.models.security.SecurityScheme.Type; +import io.swagger.v3.oas.models.servers.Server; +import java.util.List; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OpenAPIConfig { + + private final String devUrl; + + public OpenAPIConfig(@Value("${votogether.openapi.dev-url}") final String devUrl) { + this.devUrl = devUrl; + } + + @Bean + public OpenAPI openAPI() { + final Server devServer = new Server(); + devServer.setUrl(devUrl); + devServer.description("개발 환경 서버 URL"); + + final Info info = new Info() + .title("VoTogether API") + .version("v1.0.0") + .description("보투게더 API"); + + final SecurityScheme securityScheme = new SecurityScheme() + .type(Type.HTTP) + .in(In.HEADER) + .name("Authorization") + .scheme("bearer") + .bearerFormat("JWT") + .description("Bearer JWT"); + + final SecurityRequirement securityRequirement = new SecurityRequirement() + .addList("bearerAuth"); + + return new OpenAPI() + .info(info) + .servers(List.of(devServer)) + .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) + .security(List.of(securityRequirement)); + } + +} diff --git a/backend/src/main/java/com/votogether/global/config/QuerydslConfig.java b/backend/src/main/java/com/votogether/global/config/QuerydslConfig.java new file mode 100644 index 000000000..650e9c741 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/config/QuerydslConfig.java @@ -0,0 +1,20 @@ +package com.votogether.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QuerydslConfig { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } + +} diff --git a/backend/src/main/java/com/votogether/global/config/RedisConfig.java b/backend/src/main/java/com/votogether/global/config/RedisConfig.java new file mode 100644 index 000000000..77163a656 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/config/RedisConfig.java @@ -0,0 +1,36 @@ +package com.votogether.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericToStringSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + @Bean + public RedisTemplate redisTemplate() { + final RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new GenericToStringSerializer<>(Long.class)); + redisTemplate.setEnableTransactionSupport(true); + return redisTemplate; + } + +} diff --git a/backend/src/main/java/com/votogether/global/config/SwaggerBeanConfig.java b/backend/src/main/java/com/votogether/global/config/SwaggerBeanConfig.java new file mode 100644 index 000000000..adeb5a654 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/config/SwaggerBeanConfig.java @@ -0,0 +1,18 @@ +package com.votogether.global.config; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; + +@Configuration +public class SwaggerBeanConfig { + + public SwaggerBeanConfig(final MappingJackson2HttpMessageConverter converter) { + final List supportedMediaTypes = new ArrayList<>(converter.getSupportedMediaTypes()); + supportedMediaTypes.add(new MediaType("application", "octet-stream")); + converter.setSupportedMediaTypes(supportedMediaTypes); + } + +} diff --git a/backend/src/main/java/com/votogether/global/config/WebMvcConfig.java b/backend/src/main/java/com/votogether/global/config/WebMvcConfig.java new file mode 100644 index 000000000..5262d23b0 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/config/WebMvcConfig.java @@ -0,0 +1,34 @@ +package com.votogether.global.config; + +import com.votogether.global.jwt.JwtAuthorizationArgumentResolver; +import java.util.List; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + private final JwtAuthorizationArgumentResolver jwtAuthorizationArgumentResolver; + + public WebMvcConfig(final JwtAuthorizationArgumentResolver jwtAuthorizationArgumentResolver) { + this.jwtAuthorizationArgumentResolver = jwtAuthorizationArgumentResolver; + } + + @Override + public void addArgumentResolvers(final List resolvers) { + resolvers.add(jwtAuthorizationArgumentResolver); + } + + @Override + public void addCorsMappings(final CorsRegistry registry) { + registry.addMapping("/**") + .allowedHeaders("*") + .allowedOrigins("*") + .allowedMethods("*") + .exposedHeaders(HttpHeaders.LOCATION); + } + +} diff --git a/backend/src/main/java/com/votogether/global/exception/BadRequestException.java b/backend/src/main/java/com/votogether/global/exception/BadRequestException.java new file mode 100644 index 000000000..b10c5343c --- /dev/null +++ b/backend/src/main/java/com/votogether/global/exception/BadRequestException.java @@ -0,0 +1,9 @@ +package com.votogether.global.exception; + +public class BadRequestException extends BaseException { + + public BadRequestException(final ExceptionType exceptionType) { + super(exceptionType); + } + +} diff --git a/backend/src/main/java/com/votogether/global/exception/BaseException.java b/backend/src/main/java/com/votogether/global/exception/BaseException.java new file mode 100644 index 000000000..80fe64e46 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/exception/BaseException.java @@ -0,0 +1,15 @@ +package com.votogether.global.exception; + +import lombok.Getter; + +@Getter +public class BaseException extends RuntimeException { + + private final ExceptionType exceptionType; + + public BaseException(final ExceptionType exceptionType) { + super(exceptionType.getMessage()); + this.exceptionType = exceptionType; + } + +} diff --git a/backend/src/main/java/com/votogether/global/exception/ExceptionResponse.java b/backend/src/main/java/com/votogether/global/exception/ExceptionResponse.java new file mode 100644 index 000000000..429791ebb --- /dev/null +++ b/backend/src/main/java/com/votogether/global/exception/ExceptionResponse.java @@ -0,0 +1,18 @@ +package com.votogether.global.exception; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "예외 발생 응답") +public record ExceptionResponse( + @Schema(description = "예외 코드", example = "-9999") + int code, + @Schema(description = "예외 메시지", example = "알 수 없는 서버 예외가 발생하였습니다.") + String message +) { + + public static ExceptionResponse from(final BaseException e) { + final ExceptionType exceptionType = e.getExceptionType(); + return new ExceptionResponse(exceptionType.getCode(), exceptionType.getMessage()); + } + +} diff --git a/backend/src/main/java/com/votogether/global/exception/ExceptionType.java b/backend/src/main/java/com/votogether/global/exception/ExceptionType.java new file mode 100644 index 000000000..09fe7c362 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/exception/ExceptionType.java @@ -0,0 +1,9 @@ +package com.votogether.global.exception; + +public interface ExceptionType { + + int getCode(); + + String getMessage(); + +} diff --git a/backend/src/main/java/com/votogether/global/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/votogether/global/exception/GlobalExceptionHandler.java new file mode 100644 index 000000000..cd2bbe06d --- /dev/null +++ b/backend/src/main/java/com/votogether/global/exception/GlobalExceptionHandler.java @@ -0,0 +1,93 @@ +package com.votogether.global.exception; + +import com.votogether.domain.post.exception.PostExceptionType; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MultipartException; +import org.springframework.web.multipart.support.MissingServletRequestPartException; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler + public ResponseEntity handleException(final Exception e) { + log.error("[" + e.getClass() + "] : " + e.getMessage()); + return ResponseEntity.internalServerError() + .body(new ExceptionResponse(-9999, "알 수 없는 서버 에러가 발생했습니다.")); + } + + @ExceptionHandler + public ResponseEntity handleMethodArgumentTypeMismatchException( + final MethodArgumentTypeMismatchException e + ) { + final String errorMessage = String.format( + "%s는 %s 타입이 필요합니다.", + e.getPropertyName(), + e.getRequiredType().getSimpleName() + ); + log.warn("[" + e.getClass() + "] : " + errorMessage); + return ResponseEntity.badRequest() + .body(new ExceptionResponse(-9998, errorMessage)); + } + + @ExceptionHandler + public ResponseEntity handleMethodArgumentNotValidException( + final MethodArgumentNotValidException e + ) { + final List errorMessages = e.getBindingResult() + .getAllErrors() + .stream() + .map(ObjectError::getDefaultMessage) + .toList(); + log.warn("[" + e.getClass() + "] : " + errorMessages); + return ResponseEntity.badRequest() + .body(new ExceptionResponse(-9997, errorMessages.toString())); + } + + @ExceptionHandler + public ResponseEntity handleBadRequestException(final BadRequestException e) { + log.warn("[" + e.getClass() + "] : " + e.getMessage()); + return ResponseEntity.badRequest() + .body(ExceptionResponse.from(e)); + } + + @ExceptionHandler + public ResponseEntity handleNotFoundException(final NotFoundException e) { + log.warn("[" + e.getClass() + "] : " + e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ExceptionResponse.from(e)); + } + + @ExceptionHandler + public ResponseEntity handleMultipartException(final MultipartException e) { + System.out.println("================================"); + System.out.println("GlobalExceptionHandler.handleMultipartException"); + e.printStackTrace(); + + log.warn("[" + e.getClass() + "] : " + e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ExceptionResponse.from(new BadRequestException(PostExceptionType.WRONG_IMAGE))); + } + + @ExceptionHandler + public ResponseEntity handleMissingServletRequestPartException( + final MissingServletRequestPartException e + ) { + System.out.println("================================"); + System.out.println("GlobalExceptionHandler.handleMissingServletRequestPartException"); + e.printStackTrace(); + + log.warn("[" + e.getClass() + "] : " + e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ExceptionResponse.from(new BadRequestException(PostExceptionType.WRONG_IMAGE))); + } + +} diff --git a/backend/src/main/java/com/votogether/global/exception/NotFoundException.java b/backend/src/main/java/com/votogether/global/exception/NotFoundException.java new file mode 100644 index 000000000..a6c9ec3e9 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/exception/NotFoundException.java @@ -0,0 +1,9 @@ +package com.votogether.global.exception; + +public class NotFoundException extends BaseException { + + public NotFoundException(final ExceptionType exceptionType) { + super(exceptionType); + } + +} diff --git a/backend/src/main/java/com/votogether/global/jwt/Auth.java b/backend/src/main/java/com/votogether/global/jwt/Auth.java new file mode 100644 index 000000000..ad622369c --- /dev/null +++ b/backend/src/main/java/com/votogether/global/jwt/Auth.java @@ -0,0 +1,13 @@ +package com.votogether.global.jwt; + +import io.swagger.v3.oas.annotations.Hidden; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Hidden +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface Auth { +} diff --git a/backend/src/main/java/com/votogether/global/jwt/JwtAuthenticationFilter.java b/backend/src/main/java/com/votogether/global/jwt/JwtAuthenticationFilter.java new file mode 100644 index 000000000..d004b9b7c --- /dev/null +++ b/backend/src/main/java/com/votogether/global/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,93 @@ +package com.votogether.global.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@RequiredArgsConstructor +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private static final List ALLOWED_URIS = List.of( + "/health-check", + "/categories/guest", + "/swagger-ui.html", + "/favicon.ico" + ); + + private static final List ALLOWED_START_URIS = List.of( + "/auth", + "/v3/api-docs", + "/swagger-ui", + "/h2-console" + ); + + private static final List ALLOWED_END_URIS = List.of( + "/guest" + ); + + private static final Map MATCH_URI_METHOD = new HashMap<>( + Map.ofEntries( + Map.entry("^/posts/.+/comments$", "GET") + ) + ); + + private final TokenProcessor tokenProcessor; + + @Override + protected void doFilterInternal( + final HttpServletRequest request, + final HttpServletResponse response, + final FilterChain filterChain + ) throws ServletException, IOException { + final String token = request.getHeader(HttpHeaders.AUTHORIZATION); + final String tokenWithoutType = tokenProcessor.resolveToken(token); + tokenProcessor.validateToken(tokenWithoutType); + filterChain.doFilter(request, response); + } + + @Override + protected boolean shouldNotFilter(final HttpServletRequest request) { + return containsAllowedUris(request) || startsWithAllowedStartUris(request) + || matchesGuestRequest(request) || matchesUriPattern(request); + } + + private boolean containsAllowedUris(final HttpServletRequest request) { + return ALLOWED_URIS.stream() + .anyMatch(url -> request.getRequestURI().contains(url)); + } + + private boolean startsWithAllowedStartUris(final HttpServletRequest request) { + return ALLOWED_START_URIS.stream() + .anyMatch(url -> request.getRequestURI().startsWith(url)); + } + + private boolean matchesGuestRequest(final HttpServletRequest request) { + return ALLOWED_END_URIS.stream() + .anyMatch(url -> request.getRequestURI().endsWith(url) && + Objects.equals(request.getMethod(), HttpMethod.GET.name())); + } + + private boolean matchesUriPattern(final HttpServletRequest request) { + return MATCH_URI_METHOD.keySet() + .stream() + .anyMatch(key -> isMatchUriAndMethod(request, key)); + } + + private boolean isMatchUriAndMethod(final HttpServletRequest request, final String uriPattern) { + return request.getRequestURI().matches(uriPattern) + && MATCH_URI_METHOD.get(uriPattern).equalsIgnoreCase(request.getMethod()); + } + +} diff --git a/backend/src/main/java/com/votogether/global/jwt/JwtAuthorizationArgumentResolver.java b/backend/src/main/java/com/votogether/global/jwt/JwtAuthorizationArgumentResolver.java new file mode 100644 index 000000000..7253f1634 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/jwt/JwtAuthorizationArgumentResolver.java @@ -0,0 +1,41 @@ +package com.votogether.global.jwt; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@RequiredArgsConstructor +@Component +public class JwtAuthorizationArgumentResolver implements HandlerMethodArgumentResolver { + + private final TokenProcessor tokenProcessor; + private final MemberService memberService; + + @Override + public boolean supportsParameter(final MethodParameter parameter) { + return parameter.withContainingClass(Member.class) + .hasParameterAnnotation(Auth.class); + } + + @Override + public Member resolveArgument( + final MethodParameter parameter, + final ModelAndViewContainer mavContainer, + final NativeWebRequest webRequest, + final WebDataBinderFactory binderFactory + ) throws JsonProcessingException { + final String token = webRequest.getHeader(HttpHeaders.AUTHORIZATION); + final String tokenWithoutType = tokenProcessor.resolveToken(token); + final TokenPayload tokenPayload = tokenProcessor.parseToken(tokenWithoutType); + return memberService.findById(tokenPayload.memberId()); + } + +} diff --git a/backend/src/main/java/com/votogether/global/jwt/TokenPayload.java b/backend/src/main/java/com/votogether/global/jwt/TokenPayload.java new file mode 100644 index 000000000..c4bf3fd99 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/jwt/TokenPayload.java @@ -0,0 +1,8 @@ +package com.votogether.global.jwt; + +public record TokenPayload( + Long memberId, + Long iat, + Long exp +) { +} diff --git a/backend/src/main/java/com/votogether/global/jwt/TokenProcessor.java b/backend/src/main/java/com/votogether/global/jwt/TokenProcessor.java new file mode 100644 index 000000000..0a3c824fc --- /dev/null +++ b/backend/src/main/java/com/votogether/global/jwt/TokenProcessor.java @@ -0,0 +1,110 @@ +package com.votogether.global.jwt; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SignatureException; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Date; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Slf4j +@Component +public class TokenProcessor { + + private static final String TOKEN_DELIMITER = "\\."; + private static final String BEARER_TOKEN_PREFIX = "Bearer "; + private static final String BLANK = " "; + + private final Key key; + private final int tokenExpirationTime; + private final int refreshTokenExpirationTime; + private final ObjectMapper objectMapper; + + public TokenProcessor( + @Value("${jwt.token.secret-key}") final String secretKey, + @Value("${jwt.token.access-expiration-time}") final int tokenExpirationTime, + @Value("${jwt.token.refresh-expiration-time}") final int refreshTokenExpirationTime, + final ObjectMapper objectMapper + ) { + this.key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + this.tokenExpirationTime = tokenExpirationTime; + this.refreshTokenExpirationTime = refreshTokenExpirationTime; + this.objectMapper = objectMapper; + } + + public String generateAccessToken(final Long memberId) { + final Date now = new Date(); + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .claim("memberId", memberId) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + tokenExpirationTime)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + public String generateRefreshToken(final Long memberId) { + final Date now = new Date(); + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .claim("memberId", memberId) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + refreshTokenExpirationTime)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + public String resolveToken(final String token) { + if (StringUtils.hasText(token) && token.startsWith(BEARER_TOKEN_PREFIX)) { + return token.split(BLANK)[1]; + } + throw new IllegalArgumentException("올바르지 않은 토큰입니다."); + } + + public TokenPayload parseToken(final String token) throws JsonProcessingException { + validateToken(token); + final String[] chunks = token.split(TOKEN_DELIMITER); + final String payload = new String(Decoders.BASE64.decode(chunks[1])); + return objectMapper.readValue(payload, TokenPayload.class); + } + + public void validateToken(final String token) { + try { + Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token); + } catch (final UnsupportedJwtException e) { + log.info("지원하지 않는 JWT입니다."); + throw new IllegalArgumentException("지원하지 않는 JWT입니다."); + } catch (final MalformedJwtException e) { + log.info("잘못된 JWT 서명입니다."); + throw new IllegalArgumentException("잘못된 JWT 서명입니다."); + } catch (final SignatureException e) { + log.info("토큰의 서명 유효성 검사가 실패했습니다."); + throw new IllegalArgumentException("토큰의 서명 유효성 검사가 실패했습니다."); + } catch (final ExpiredJwtException e) { + log.info("토큰의 유효기간이 만료되었습니다."); + throw new IllegalArgumentException("토큰의 유효기간이 만료되었습니다."); + } catch (final IllegalArgumentException e) { + log.info("토큰의 내용이 비어있습니다."); + throw new IllegalArgumentException("토큰의 내용이 비어있습니다."); + } catch (final Exception e) { + log.info("알 수 없는 토큰 유효성 문제가 발생했습니다."); + throw new IllegalArgumentException("알 수 없는 토큰 유효성 문제가 발생했습니다."); + } + } + +} diff --git a/backend/src/main/java/com/votogether/global/jwt/exception/JsonException.java b/backend/src/main/java/com/votogether/global/jwt/exception/JsonException.java new file mode 100644 index 000000000..4dfc0e574 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/jwt/exception/JsonException.java @@ -0,0 +1,20 @@ +package com.votogether.global.jwt.exception; + +import com.votogether.global.exception.ExceptionType; +import lombok.Getter; + +@Getter +public enum JsonException implements ExceptionType { + + UNEXPECTED_EXCEPTION(2000, "예상치 못한 Json관련 문제가 발생했습니다."), + ; + + private final int code; + private final String message; + + JsonException(final int code, final String message) { + this.code = code; + this.message = message; + } + +} diff --git a/backend/src/main/java/com/votogether/global/persistence/OrderByNull.java b/backend/src/main/java/com/votogether/global/persistence/OrderByNull.java new file mode 100644 index 000000000..c2edd6333 --- /dev/null +++ b/backend/src/main/java/com/votogether/global/persistence/OrderByNull.java @@ -0,0 +1,15 @@ +package com.votogether.global.persistence; + +import com.querydsl.core.types.NullExpression; +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; + +public class OrderByNull extends OrderSpecifier { + + public static final OrderByNull DEFAULT = new OrderByNull(); + + private OrderByNull() { + super(Order.ASC, NullExpression.DEFAULT, NullHandling.Default); + } + +} diff --git a/backend/src/main/java/com/votogether/global/util/ImageUploader.java b/backend/src/main/java/com/votogether/global/util/ImageUploader.java new file mode 100644 index 000000000..a381b648f --- /dev/null +++ b/backend/src/main/java/com/votogether/global/util/ImageUploader.java @@ -0,0 +1,46 @@ +package com.votogether.global.util; + +import com.votogether.domain.post.exception.PostExceptionType; +import com.votogether.global.exception.BadRequestException; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.web.multipart.MultipartFile; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ImageUploader { + + public static String upload(final MultipartFile image) { + if (image.getOriginalFilename().contains("없는사진")) { + return ""; + } + + final long milli = LocalDateTime.now().toInstant(ZoneOffset.UTC).toEpochMilli(); + + final String rootPath = new File("").getAbsolutePath() + File.separator; + final String imageDirPath = "static" + File.separator + "images" + File.separator; + final String imageFileName = milli + "_" + image.getOriginalFilename(); + + try { + File imageFolder = new File(rootPath + imageDirPath); + if (!imageFolder.exists()) { + imageFolder.mkdirs(); // Creates the directory if it does not exist + } + + Files.write(Paths.get(rootPath + imageDirPath + imageFileName), image.getBytes()); + } catch (IOException ignore) { + System.out.println("ImageUploader.upload"); + System.out.println("imageUrl = " + imageDirPath + imageFileName); + ignore.printStackTrace(); + throw new BadRequestException(PostExceptionType.WRONG_IMAGE); + } + + return imageDirPath + imageFileName; + } + +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 000000000..7cd6afcc3 --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,65 @@ +spring: + datasource: + driver-class-name: ${DATASOURCE_DRIVER_CLASS_NAME} + url: ${DATASOURCE_URL} + username: ${DATASOURCE_USERNAME} + password: ${DATASOURCE_PASSWORD} + + jpa: + database-platform: org.hibernate.dialect.MySQLDialect + show-sql: true + + properties: + hibernate: + format_sql: true + default_batch_fetch_size: 50 + + hibernate: + ddl-auto: ${HIBERNATE_DDL_AUTO} + + servlet: + multipart: + max-file-size: 5MB + max-request-size: 35MB + + h2: + console: + enabled: ${H2_CONSOLE_ENABLE} + path: /h2-console + + data: + redis: + host: ${REDIS_HOST} + port: ${REDIS_PORT} + +logging: + level: + org.hibernate.orm.jdbc.bind: trace + config: classpath:log4j2-${spring.profiles.active}.xml + +server: + forward-headers-strategy: FRAMEWORK + tomcat: + max-http-form-post-size: 35MB + +springdoc: + swagger-ui: + enabled: ${SWAGGER_ENABLE} + +votogether: + openapi: + dev-url: ${DEV_URL} + +oauth: + kakao: + info: + grant_type: ${GRANT_TYPE} + client_id: ${CLIENT_ID} + client_secret: ${CLIENT_SECRET} + redirect_uri: ${REDIRECT_URI} + +jwt: + token: + secret-key: ${SECRET_KEY} + access-expiration-time: ${ACCESS_EXPIRATION_TIME} + refresh-expiration-time: ${REFRESH_EXPIRATION_TIME} diff --git a/backend/src/main/resources/log4j2-dev.xml b/backend/src/main/resources/log4j2-dev.xml new file mode 100644 index 000000000..d80bc2e55 --- /dev/null +++ b/backend/src/main/resources/log4j2-dev.xml @@ -0,0 +1,49 @@ + + + + votogether + + [%d{yyyy-MM-dd 'T' HH:mm:ss.SSS}] %-5p %pid --- [%t] %c : %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/log4j2-local.xml b/backend/src/main/resources/log4j2-local.xml new file mode 100644 index 000000000..9f6d08bfa --- /dev/null +++ b/backend/src/main/resources/log4j2-local.xml @@ -0,0 +1,36 @@ + + + + + [%d{yyyy-MM-dd 'T' HH:mm:ss.SSS}] %highlight{%-5p} %highlight{%pid} --- [%t] %highlight{%c} : %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/log4j2-prod.xml b/backend/src/main/resources/log4j2-prod.xml new file mode 100644 index 000000000..9cbdabdba --- /dev/null +++ b/backend/src/main/resources/log4j2-prod.xml @@ -0,0 +1,41 @@ + + + + votogether + + [%d{yyyy-MM-dd 'T' HH:mm:ss.SSS}] %-5p %pid --- [%t] %c : %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/test/java/com/votogether/VotogetherApplicationTests.java b/backend/src/test/java/com/votogether/VotogetherApplicationTests.java new file mode 100644 index 000000000..0f0ab6c87 --- /dev/null +++ b/backend/src/test/java/com/votogether/VotogetherApplicationTests.java @@ -0,0 +1,13 @@ +package com.votogether; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class VotogetherApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/backend/src/test/java/com/votogether/domain/auth/controller/AuthControllerTest.java b/backend/src/test/java/com/votogether/domain/auth/controller/AuthControllerTest.java new file mode 100644 index 000000000..fe10b9b36 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/auth/controller/AuthControllerTest.java @@ -0,0 +1,155 @@ +package com.votogether.domain.auth.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; + +import com.votogether.domain.auth.dto.request.AccessTokenRequest; +import com.votogether.domain.auth.dto.response.ReissuedAccessTokenResponse; +import com.votogether.domain.auth.service.AuthService; +import com.votogether.domain.auth.service.dto.LoginTokenDto; +import com.votogether.domain.auth.service.dto.ReissuedTokenDto; +import com.votogether.domain.member.service.MemberService; +import com.votogether.global.jwt.TokenProcessor; +import io.restassured.http.Cookie; +import io.restassured.module.mockmvc.RestAssuredMockMvc; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +@WebMvcTest(controllers = AuthController.class) +class AuthControllerTest { + + @MockBean + AuthService authService; + + @MockBean + TokenProcessor tokenProcessor; + + @MockBean + MemberService memberService; + + @BeforeEach + void setUp() { + RestAssuredMockMvc.standaloneSetup(new AuthController(authService)); + } + + @Test + @DisplayName("카카오 로그인을 한다.") + void loginByKakao() { + // given + String accessToken = "abcdefg"; + String refreshToken = "adfdsfdsa"; + LoginTokenDto response = new LoginTokenDto(accessToken, refreshToken, false); + + given(authService.register(any())).willReturn(response); + + // when + String responseBody = RestAssuredMockMvc + .given().log().all() + .queryParam("code", "abc1234") + .when().get("/auth/kakao/callback") + .then().log().all() + .status(HttpStatus.OK) + .extract() + .asString(); + + // then + assertAll( + () -> assertThat(responseBody).contains("accessToken"), + () -> assertThat(responseBody).contains(accessToken), + () -> assertThat(responseBody).contains("hasEssentialInfo"), + () -> assertThat(responseBody).contains("false") + ); + } + + @Nested + class ReissueAccessToken { + + @Test + @DisplayName("인증 토큰을 재발급한다.") + void reissueAccessToken() { + // given + String accessToken = "abcdefg"; + String refreshToken = "adfdsfdsa"; + AccessTokenRequest request = new AccessTokenRequest(accessToken); + ReissuedTokenDto reissuedTokenDto = new ReissuedTokenDto(accessToken, refreshToken); + Cookie cookie = new Cookie.Builder("refreshToken", refreshToken).build(); + + given(authService.reissueAuthToken(any(AccessTokenRequest.class), anyString())).willReturn( + reissuedTokenDto); + + // when + ReissuedAccessTokenResponse response = RestAssuredMockMvc + .given().log().all() + .cookie(cookie) + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .when().post("/auth/silent-login") + .then().log().all() + .status(HttpStatus.OK) + .cookie("refreshToken", reissuedTokenDto.refreshToken()) + .extract() + .as(ReissuedAccessTokenResponse.class); + + // then + assertThat(response.accessToken()).isEqualTo(reissuedTokenDto.accessToken()); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("인증 토큰이 null 혹은 빈 값이면 400을 반환한다.") + void reissueAccessTokenWhenNotBlank(String accessToken) { + // given + String refreshToken = "adfdsfdsa"; + AccessTokenRequest request = new AccessTokenRequest(accessToken); + ReissuedTokenDto reissuedTokenDto = new ReissuedTokenDto(accessToken, refreshToken); + Cookie cookie = new Cookie.Builder("refreshToken", refreshToken).build(); + + given(authService.reissueAuthToken(any(AccessTokenRequest.class), anyString())).willReturn( + reissuedTokenDto); + + // when, then + RestAssuredMockMvc + .given().log().all() + .cookie(cookie) + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .when().post("/auth/silent-login") + .then().log().all() + .status(HttpStatus.BAD_REQUEST); + } + + } + + @Test + @DisplayName("로그아웃을 한다.") + void logout() { + // given + String refreshToken = "adfdsfdsa"; + final Cookie cookie = new Cookie.Builder("refreshToken", refreshToken).build(); + + willDoNothing().given(authService).deleteRefreshToken(refreshToken); + + // when + RestAssuredMockMvc + .given().log().all() + .cookie(cookie) + .when().delete("/auth/logout") + .then().log().all() + .header("Set-Cookie", containsString("Max-Age=0")) + .status(HttpStatus.NO_CONTENT); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/category/contorller/CategoryControllerTest.java b/backend/src/test/java/com/votogether/domain/category/contorller/CategoryControllerTest.java new file mode 100644 index 000000000..ce3b09add --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/category/contorller/CategoryControllerTest.java @@ -0,0 +1,113 @@ +package com.votogether.domain.category.contorller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; + +import com.votogether.domain.category.dto.response.CategoryResponse; +import com.votogether.domain.category.service.CategoryService; +import com.votogether.domain.member.service.MemberService; +import com.votogether.global.jwt.TokenProcessor; +import com.votogether.test.fixtures.CategoryFixtures; +import io.restassured.module.mockmvc.RestAssuredMockMvc; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +@WebMvcTest(CategoryController.class) +class CategoryControllerTest { + + @MockBean + CategoryService categoryService; + + @MockBean + TokenProcessor tokenProcessor; + + @MockBean + MemberService memberService; + + @BeforeEach + void setUp() { + RestAssuredMockMvc.standaloneSetup(new CategoryController(categoryService)); + } + + @Test + @DisplayName("전체 카테고리 목록을 조회한다.") + void getAllCategories() { + // given + given(categoryService.getAllCategories()) + .willReturn(List.of(new CategoryResponse(CategoryFixtures.DEVELOP.get(), false))); + + // when + RestAssuredMockMvc. + given().log().all() + .when().get("/categories/guest") + .then().log().all() + .status(HttpStatus.OK) + .body("[0].id", nullValue()) + .body("[0].name", equalTo("개발")) + .body("[0].isFavorite", equalTo(false)); + } + + @Test + @DisplayName("회원으로 전체 카테고리 목록을 조회한다.") + void getAllCategoriesFromMember() { + // given + List categoryResponses = List.of( + new CategoryResponse(CategoryFixtures.DEVELOP.get(), false), + new CategoryResponse(CategoryFixtures.FOOD.get(), true) + ); + + given(categoryService.getAllCategories(any())).willReturn(categoryResponses); + + // when + List results = RestAssuredMockMvc + .given().log().all() + .when().get("/categories") + .then().log().all() + .status(HttpStatus.OK) + .extract() + .as(new ParameterizedTypeReference>() { + }.getType()); + + // then + assertThat(results).usingRecursiveComparison().isEqualTo(categoryResponses); + } + + @Test + @DisplayName("선호하는 카테고리를 선호 카테고리 목록에 추가한다.") + void addFavoriteCategory() { + // given + doNothing().when(categoryService).addFavoriteCategory(any(), any()); + + // when & then + RestAssuredMockMvc. + given().log().all() + .when().post("/categories/{categoryId}/like", 1) + .then().log().all() + .status(HttpStatus.CREATED); + } + + @Test + @DisplayName("선호 카테고리를 삭제한다.") + void removeFavoriteCategory() { + // given + doNothing().when(categoryService).removeFavoriteCategory(any(), any()); + + // when & then + RestAssuredMockMvc. + given().log().all() + .when().delete("/categories/{categoryId}/like", 1) + .then().log().all() + .status(HttpStatus.NO_CONTENT); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/category/repository/CategoryRepositoryTest.java b/backend/src/test/java/com/votogether/domain/category/repository/CategoryRepositoryTest.java new file mode 100644 index 000000000..7c384b7a9 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/category/repository/CategoryRepositoryTest.java @@ -0,0 +1,90 @@ +package com.votogether.domain.category.repository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.votogether.domain.category.entity.Category; +import com.votogether.test.annotation.RepositoryTest; +import com.votogether.test.fixtures.CategoryFixtures; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; + +@RepositoryTest +class CategoryRepositoryTest { + + @Autowired + CategoryRepository categoryRepository; + + @Nested + @DisplayName("카테고리 저장") + class Saving { + + @Test + @DisplayName("카테고리를 저장한다.") + void save() { + // given + Category category = CategoryFixtures.DEVELOP.get(); + + // when + categoryRepository.save(category); + + // then + assertThat(category.getId()).isNotNull(); + } + + @Test + @DisplayName("같은 이름의 카테고리를 저장할 시 에러가 발생한다.") + void saveButException() { + // given + Category categoryA = CategoryFixtures.DEVELOP.get(); + Category categoryB = CategoryFixtures.DEVELOP.get(); + categoryRepository.save(categoryA); + + // when & then + assertThatThrownBy(() -> categoryRepository.save(categoryB)) + .isInstanceOf(DataIntegrityViolationException.class); + } + + } + + @Nested + @DisplayName("카테고리 조회") + class Finding { + + @Test + @DisplayName("모든 카테고리를 조회한다.") + void findAllCategories() { + // given + categoryRepository.save(CategoryFixtures.DEVELOP.get()); + categoryRepository.save(CategoryFixtures.FOOD.get()); + + // when + List categories = categoryRepository.findAll(); + + // then + assertAll( + () -> assertThat(categories).hasSize(2), + () -> assertThat(categories.get(0).getName()).isEqualTo("개발"), + () -> assertThat(categories.get(1).getName()).isEqualTo("음식")); + } + + @Test + @DisplayName("아이디를 통해 카테고리를 조회한다.") + void findById() { + // given + Category category = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + + // when + Category findCategory = categoryRepository.findById(category.getId()).get(); + + // then + assertThat(findCategory).isSameAs(category); + } + } + +} diff --git a/backend/src/test/java/com/votogether/domain/category/service/CategoryServiceTest.java b/backend/src/test/java/com/votogether/domain/category/service/CategoryServiceTest.java new file mode 100644 index 000000000..ae1403ec4 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/category/service/CategoryServiceTest.java @@ -0,0 +1,116 @@ +package com.votogether.domain.category.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.votogether.domain.category.dto.response.CategoryResponse; +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.category.repository.CategoryRepository; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.MemberCategory; +import com.votogether.domain.member.repository.MemberCategoryRepository; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.test.annotation.ServiceTest; +import com.votogether.test.fixtures.CategoryFixtures; +import com.votogether.test.fixtures.MemberFixtures; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@ServiceTest +class CategoryServiceTest { + + @Autowired + CategoryService categoryService; + + @Autowired + CategoryRepository categoryRepository; + + @Autowired + MemberCategoryRepository memberCategoryRepository; + + @Autowired + MemberRepository memberRepository; + + @Test + @DisplayName("모든 카테고리를 가져온다.") + void getAllCategories() { + // given + categoryRepository.save(CategoryFixtures.DEVELOP.get()); + + // when + List categories = categoryService.getAllCategories(); + + // then + assertAll( + () -> assertThat(categories.get(0).id()).isNotNull(), + () -> assertThat(categories.get(0).name()).isEqualTo("개발"), + () -> assertThat(categories.get(0).isFavorite()).isFalse() + ); + } + + @Test + @DisplayName("선호하는 카테고리를 선호 카테고리 목록에 추가한다.") + void addFavoriteCategory() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Category category = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + + // when + categoryService.addFavoriteCategory(member, category.getId()); + + // then + MemberCategory memberCategory = memberCategoryRepository.findByMemberAndCategory(member, category).get(); + + assertAll( + () -> assertThat(memberCategory.getMember()).isSameAs(member), + () -> assertThat(memberCategory.getCategory()).isSameAs(category) + ); + } + + @Nested + @DisplayName("카테고리 삭제") + class Deleting { + + @Test + @DisplayName("선호하는 카테고리를 선호 카테고리 목록에 삭제한다.") + void removeFavoriteCategory() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Category category = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + MemberCategory memberCategory = MemberCategory.builder() + .member(member) + .category(category) + .build(); + + memberCategoryRepository.save(memberCategory); + + // when + categoryService.removeFavoriteCategory(member, category.getId()); + + // then + Optional foundMemberCategory = + memberCategoryRepository.findByMemberAndCategory(member, category); + assertThat(foundMemberCategory).isEmpty(); + } + + @Test + @DisplayName("선호하는 카테고리에 없는 카테고리를 삭제하는 경우 예외가 발생한다.") + void removeFavoriteCategoryException() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Category category = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + + // when, then + assertThatThrownBy(() -> categoryService.removeFavoriteCategory(member, category.getId())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("해당 카테고리는 선호 카테고리가 아닙니다."); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/domain/member/controller/MemberControllerTest.java b/backend/src/test/java/com/votogether/domain/member/controller/MemberControllerTest.java new file mode 100644 index 000000000..40f45f43f --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/member/controller/MemberControllerTest.java @@ -0,0 +1,250 @@ +package com.votogether.domain.member.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; + +import com.votogether.domain.member.dto.request.MemberDetailRequest; +import com.votogether.domain.member.dto.request.MemberNicknameUpdateRequest; +import com.votogether.domain.member.dto.response.MemberInfoResponse; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.entity.vo.SocialType; +import com.votogether.domain.member.service.MemberService; +import com.votogether.global.jwt.TokenPayload; +import com.votogether.global.jwt.TokenProcessor; +import com.votogether.test.fixtures.MemberFixtures; +import io.restassured.http.ContentType; +import io.restassured.module.mockmvc.RestAssuredMockMvc; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.web.context.WebApplicationContext; + +@WebMvcTest(MemberController.class) +class MemberControllerTest { + + @MockBean + MemberService memberService; + + @MockBean + TokenProcessor tokenProcessor; + + @BeforeEach + void setUp(final WebApplicationContext webApplicationContext) { + RestAssuredMockMvc.standaloneSetup(new MemberController(memberService)); + RestAssuredMockMvc.webAppContextSetup(webApplicationContext); + } + + @Test + @DisplayName("회원 정보를 조회한다.") + void findMemberInfo() throws Exception { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(2000) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + MemberInfoResponse memberInfoResponse = new MemberInfoResponse( + "저문", + Gender.MALE, + 1988, + 0, + 0 + ); + + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(member); + given(memberService.findMemberInfo(any(Member.class))).willReturn(memberInfoResponse); + + // when + MemberInfoResponse response = RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().get("/members/me") + .then().log().all() + .extract() + .as(MemberInfoResponse.class); + + // then + assertAll( + () -> assertThat(response.nickname()).isEqualTo("저문"), + () -> assertThat(response.postCount()).isEqualTo(0), + () -> assertThat(response.voteCount()).isEqualTo(0) + ); + } + + @Nested + @DisplayName("회원의 닉네임이") + class ChangeNickname { + + @Test + @DisplayName("변경에 성공하면 200을 반환한다.") + void changeNicknameSuccess() throws Exception { + // given + String nicknameToChange = "jeomxon"; + MemberNicknameUpdateRequest memberNicknameUpdateRequest = new MemberNicknameUpdateRequest(nicknameToChange); + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + willDoNothing().given(memberService).changeNickname(any(Member.class), anyString()); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.JSON) + .body(memberNicknameUpdateRequest) + .when().patch("/members/me/nickname") + .then().log().all() + .statusCode(HttpStatus.OK.value()); + } + + @Test + @DisplayName("변경에 실패하면 400을 반환한다.") + void changeNicknameFail() throws Exception { + // given + String nicknameToChange = ""; + MemberNicknameUpdateRequest memberNicknameUpdateRequest = new MemberNicknameUpdateRequest(nicknameToChange); + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + willDoNothing().given(memberService).changeNickname(any(Member.class), anyString()); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.JSON) + .body(memberNicknameUpdateRequest) + .when().patch("/members/me/nickname") + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()); + } + + } + + @Nested + @DisplayName("회원 정보 수정을 할 때") + class UpdateDetails { + + @Test + @DisplayName("요청이 정상적이라면 성공한다.") + void updateDetails() throws Exception { + // given + Member member = MemberFixtures.MALE_20.get(); + MemberDetailRequest request = new MemberDetailRequest(Gender.MALE, 2000); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + willDoNothing().given(memberService).updateDetails(request, member); + + // when + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.JSON) + .body(request) + .when().patch("/members/me/detail") + .then().log().all() + .statusCode(HttpStatus.OK.value()); + } + + @ParameterizedTest + @ValueSource(ints = {1, 222, 55555}) + @DisplayName("유효하지 않은 출생년도라면 400을 반환한다.") + void invalidRangeOfBirthYear(int birthYear) throws Exception { + // given + Member member = MemberFixtures.MALE_20.get(); + MemberDetailRequest request = new MemberDetailRequest(Gender.MALE, birthYear); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + willDoNothing().given(memberService).updateDetails(request, member); + + // when + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.JSON) + .body(request) + .when().patch("/members/me/detail") + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()); + } + + @ParameterizedTest + @NullSource + @DisplayName("출생년도가 빈 값이면 400을 반환한다.") + void invalidNullOfBirthYear(Integer birthYear) throws Exception { + // given + Member member = MemberFixtures.MALE_20.get(); + MemberDetailRequest request = new MemberDetailRequest(Gender.MALE, birthYear); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + willDoNothing().given(memberService).updateDetails(request, member); + + // when + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.JSON) + .body(request) + .when().patch("/members/me/detail") + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()); + } + + } + + @Test + @DisplayName("회원 탈퇴에 성공하면 204를 반환한다.") + void deleteMember() throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + willDoNothing().given(memberService).deleteMember(MemberFixtures.FEMALE_20.get()); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().delete("/members/me/delete") + .then().log().all() + .statusCode(HttpStatus.NO_CONTENT.value()); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java b/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java new file mode 100644 index 000000000..b3eb1f33a --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java @@ -0,0 +1,115 @@ +package com.votogether.domain.member.entity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.entity.vo.SocialType; +import com.votogether.global.exception.BadRequestException; +import java.time.LocalDateTime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +class MemberTest { + + @Nested + @DisplayName("닉네임을 주기에 따라 변경하는 경우") + class ChangeNicknameByCycle { + + @Test + @DisplayName("15일전에 변경된 닉네임은 14일 주기로 변경할 때 성공적으로 변경된다.") + void success() { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + + LocalDateTime now = LocalDateTime.now().minusDays(15L); + ReflectionTestUtils.setField(member, "createdAt", now); + ReflectionTestUtils.setField(member, "updatedAt", now.plusHours(1L)); + + // when + member.changeNicknameByCycle("저라니", 14L); + + // then + assertThat(member.getNickname()).isEqualTo("저라니"); + } + + @Test + @DisplayName("13일 전에 변경된 닉네임은 14일 주기로 변경하더라도 한번도 닉네임을 변경하지 않았다면 성공적으로 변경된다.") + void successFirstChange() { + // given + Member member = Member.builder() + .nickname("익명의손님fFp4vAgX2d") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + + LocalDateTime now = LocalDateTime.now().minusDays(13L); + ReflectionTestUtils.setField(member, "createdAt", now); + ReflectionTestUtils.setField(member, "updatedAt", now.plusHours(1L)); + + // when + member.changeNicknameByCycle("저라니", 14L); + + // then + assertThat(member.getNickname()).isEqualTo("저라니"); + } + + @Test + @DisplayName("13일 전에 변경된 닉네임은 14일 주기로 변경할 때 변경에 실패한다.") + void fail() { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + + LocalDateTime createdTime = LocalDateTime.now().minusDays(20L); + LocalDateTime updatedTime = LocalDateTime.now().minusDays(13L); + ReflectionTestUtils.setField(member, "createdAt", createdTime); + ReflectionTestUtils.setField(member, "updatedAt", updatedTime); + + // when, then + assertThatThrownBy(() -> member.changeNicknameByCycle("저라니", 14L)) + .isInstanceOf(BadRequestException.class) + .hasMessage("최소 닉네임 변경주기가 지나지 않았습니다."); + } + + @Test + @DisplayName("초기 닉네임의 접두사가 포함되어 있으면 예외가 발생한다.") + void notAllowedChangeToInitialNicknamePrefix() { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + + LocalDateTime createdTime = LocalDateTime.now().minusDays(20L); + LocalDateTime updatedTime = LocalDateTime.now().minusDays(7L); + ReflectionTestUtils.setField(member, "createdAt", createdTime); + ReflectionTestUtils.setField(member, "updatedAt", updatedTime); + + // when, then + assertThatThrownBy(() -> member.changeNicknameByCycle("익명의손님저라니", 14L)) + .isInstanceOf(BadRequestException.class) + .hasMessage("초기 닉네임에 포함된 접두어로 닉네임을 변경할 수 없습니다."); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/domain/member/entity/PassionRankingsTest.java b/backend/src/test/java/com/votogether/domain/member/entity/PassionRankingsTest.java new file mode 100644 index 000000000..8bd2ef013 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/member/entity/PassionRankingsTest.java @@ -0,0 +1,85 @@ +package com.votogether.domain.member.entity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.votogether.domain.ranking.entity.PassionRankings; +import com.votogether.domain.ranking.entity.vo.PassionRecord; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +class PassionRankingsTest { + + @Nested + @DisplayName("랭킹") + class Ranking { + + @Test + @DisplayName("랭킹 순위와 점수를 계산한다.") + void calculateRank() { + //given + Member memberA = Member.builder().nickname("00").build(); + Member memberB = Member.builder().nickname("11").build(); + Member memberC = Member.builder().nickname("22").build(); + ReflectionTestUtils.setField(memberA, "id", 1L); + ReflectionTestUtils.setField(memberB, "id", 2L); + ReflectionTestUtils.setField(memberC, "id", 3L); + + Map board = new HashMap<>(); + board.put(memberA, new PassionRecord(1, 1)); + board.put(memberB, new PassionRecord(1, 2)); + board.put(memberC, new PassionRecord(1, 3)); + + PassionRankings passionRankings = new PassionRankings(board); + + //when, then + assertAll( + () -> assertThat(passionRankings.getScore(memberA)).isEqualTo(6), + () -> assertThat(passionRankings.getScore(memberB)).isEqualTo(7), + () -> assertThat(passionRankings.getScore(memberC)).isEqualTo(8), + () -> assertThat(passionRankings.getRanking(memberA)).isEqualTo(3), + () -> assertThat(passionRankings.getRanking(memberB)).isEqualTo(2), + () -> assertThat(passionRankings.getRanking(memberC)).isEqualTo(1) + ); + } + + @Test + @DisplayName("동순위가 존재하는 랭킹 순위와 점수를 계산한다.") + void calculateRank1() { + //given + Member memberA = Member.builder().nickname("00").build(); + Member memberB = Member.builder().nickname("11").build(); + Member memberC = Member.builder().nickname("22").build(); + Member memberD = Member.builder().nickname("33").build(); + ReflectionTestUtils.setField(memberA, "id", 1L); + ReflectionTestUtils.setField(memberB, "id", 2L); + ReflectionTestUtils.setField(memberC, "id", 3L); + ReflectionTestUtils.setField(memberD, "id", 4L); + + Map board = new HashMap<>(); + board.put(memberA, new PassionRecord(1, 1)); + board.put(memberB, new PassionRecord(1, 3)); + board.put(memberC, new PassionRecord(1, 3)); + board.put(memberD, new PassionRecord(1, 3)); + + PassionRankings passionRankings = new PassionRankings(board); + + //when, then + assertAll( + () -> assertThat(passionRankings.getScore(memberA)).isEqualTo(6), + () -> assertThat(passionRankings.getScore(memberB)).isEqualTo(8), + () -> assertThat(passionRankings.getScore(memberC)).isEqualTo(8), + () -> assertThat(passionRankings.getRanking(memberA)).isEqualTo(4), + () -> assertThat(passionRankings.getRanking(memberB)).isEqualTo(1), + () -> assertThat(passionRankings.getRanking(memberC)).isEqualTo(1), + () -> assertThat(passionRankings.getRanking(memberD)).isEqualTo(1) + ); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/domain/member/entity/vo/NicknameTest.java b/backend/src/test/java/com/votogether/domain/member/entity/vo/NicknameTest.java new file mode 100644 index 000000000..3338db3b7 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/member/entity/vo/NicknameTest.java @@ -0,0 +1,47 @@ +package com.votogether.domain.member.entity.vo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.votogether.global.exception.BadRequestException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class NicknameTest { + + @Test + @DisplayName("닉네임이 정상적으로 만들어진다.") + void create() { + // given + String value = "안녕"; + + // when + Nickname nickname = new Nickname(value); + + // then + assertThat(nickname.getValue()).isEqualTo(value); + } + + @ParameterizedTest + @ValueSource(strings = {"안", "", " ", "가나다라마바사아자차카타파하기니"}) + @DisplayName("닉네임의 길이가 맞지 않으면 예외가 발생한다.") + void validateLength(String value) { + // when, then + assertThatThrownBy(() -> new Nickname(value)) + .isInstanceOf(BadRequestException.class) + .hasMessage("닉네임의 길이가 올바르지 않습니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"저문@", "다%즐", ".루쿠", "아 벨"}) + @DisplayName("한글, 영어, 숫자가 이외의 글자가 포함되어 있으면 예외를 던진다.") + void validateInvalidLetter(String value) { + // when, then + assertThatThrownBy(() -> new Nickname(value)) + .isInstanceOf(BadRequestException.class) + .hasMessage("닉네임에 들어갈 수 없는 문자가 포함되어 있습니다."); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/member/repository/MemberCategoryRepositoryTest.java b/backend/src/test/java/com/votogether/domain/member/repository/MemberCategoryRepositoryTest.java new file mode 100644 index 000000000..a3d345b3d --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/member/repository/MemberCategoryRepositoryTest.java @@ -0,0 +1,102 @@ +package com.votogether.domain.member.repository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.category.repository.CategoryRepository; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.MemberCategory; +import com.votogether.test.annotation.RepositoryTest; +import com.votogether.test.fixtures.CategoryFixtures; +import com.votogether.test.fixtures.MemberFixtures; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@RepositoryTest +class MemberCategoryRepositoryTest { + + @Autowired + CategoryRepository categoryRepository; + + @Autowired + MemberRepository memberRepository; + + @Autowired + MemberCategoryRepository memberCategoryRepository; + + @Test + @DisplayName("멤버가 선호하는 카테고리를 저장한다.") + void save() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_10.get()); + Category category = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + + MemberCategory memberCategory = MemberCategory.builder() + .member(member) + .category(category) + .build(); + + // when + memberCategoryRepository.save(memberCategory); + + // then + assertThat(memberCategory.getId()).isNotNull(); + assertThat(memberCategory.getCategory().getName()).isEqualTo("개발"); + } + + @Test + @DisplayName("멤버와 카테고리를 통해 멤버 카테고리를 조회한다.") + void findByMemberAndCategory() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_10.get()); + Category category = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + + MemberCategory memberCategory = MemberCategory.builder() + .member(member) + .category(category) + .build(); + + memberCategoryRepository.save(memberCategory); + + // when + MemberCategory findMemberCategory = memberCategoryRepository.findByMemberAndCategory(member, category).get(); + + // then + assertThat(findMemberCategory).isSameAs(memberCategory); + } + + @Test + @DisplayName("멤버를 통해 멤버 카테고리 목록을 조회힌다.") + void findByMember() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Category categoryA = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Category categoryB = categoryRepository.save(CategoryFixtures.FOOD.get()); + + MemberCategory memberCategoryA = MemberCategory.builder() + .member(member) + .category(categoryA) + .build(); + + MemberCategory memberCategoryB = MemberCategory.builder() + .member(member) + .category(categoryB) + .build(); + + memberCategoryRepository.save(memberCategoryA); + memberCategoryRepository.save(memberCategoryB); + + // when + List memberCategories = memberCategoryRepository.findAllByMember(member); + + // then + assertAll( + () -> assertThat(memberCategories).hasSize(2), + () -> assertThat(memberCategories).contains(memberCategoryA, memberCategoryB) + ); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/member/repository/MemberRepositoryTest.java b/backend/src/test/java/com/votogether/domain/member/repository/MemberRepositoryTest.java new file mode 100644 index 000000000..6fecf8302 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/member/repository/MemberRepositoryTest.java @@ -0,0 +1,52 @@ +package com.votogether.domain.member.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.vo.Nickname; +import com.votogether.test.annotation.RepositoryTest; +import com.votogether.test.fixtures.MemberFixtures; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@RepositoryTest +class MemberRepositoryTest { + + @Autowired + MemberRepository memberRepository; + + @Nested + @DisplayName("회원 닉네임을 받아서 존재하는 회원이라면") + class existByNickname { + + @Test + @DisplayName("true를 반환한다.") + void ExistByNickname() { + // given + Member savedMember = memberRepository.save(MemberFixtures.MALE_20.get()); + + // when + boolean isExist = memberRepository.existsByNickname(new Nickname(savedMember.getNickname())); + + // then + assertThat(isExist).isTrue(); + } + + @Test + @DisplayName("false를 반환한다.") + void existsByNicknameFalse() { + // given + Nickname nonExistentNickname = new Nickname("jeomxon"); + + // when + boolean isExist = memberRepository.existsByNickname(nonExistentNickname); + + // then + assertThat(isExist).isFalse(); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java b/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java new file mode 100644 index 000000000..bba1e64e5 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java @@ -0,0 +1,490 @@ +package com.votogether.domain.member.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.category.repository.CategoryRepository; +import com.votogether.domain.member.dto.request.MemberDetailRequest; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.MemberCategory; +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.entity.vo.SocialType; +import com.votogether.domain.member.repository.MemberCategoryRepository; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.comment.Comment; +import com.votogether.domain.post.repository.CommentRepository; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.domain.report.entity.Report; +import com.votogether.domain.report.entity.vo.ReportType; +import com.votogether.domain.report.repository.ReportRepository; +import com.votogether.global.exception.BadRequestException; +import com.votogether.test.annotation.ServiceTest; +import com.votogether.test.fixtures.MemberFixtures; +import com.votogether.test.persister.MemberTestPersister; +import com.votogether.test.persister.PostTestPersister; +import com.votogether.test.persister.VoteTestPersister; +import jakarta.persistence.EntityManager; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; + +@ServiceTest +class MemberServiceTest { + + @Autowired + MemberService memberService; + + @Autowired + MemberRepository memberRepository; + + @Autowired + PostRepository postRepository; + + @Autowired + CategoryRepository categoryRepository; + + @Autowired + MemberCategoryRepository memberCategoryRepository; + + @Autowired + ReportRepository reportRepository; + + @Autowired + CommentRepository commentRepository; + + @Autowired + MemberTestPersister memberTestPersister; + + @Autowired + PostTestPersister postTestPersister; + + @Autowired + VoteTestPersister voteTestPersister; + + @Autowired + EntityManager em; + + @Test + @DisplayName("멤버가 존재하지 않으면 저장한다.") + void register() { + // given + Member member = MemberFixtures.FEMALE_20.get(); + + // when + Member registeredMember = memberService.register(member); + + // then + assertThat(registeredMember.getId()).isNotNull(); + } + + @Nested + @DisplayName("변경할 회원의 닉네임이") + class ChangeNickname { + + @Test + @DisplayName("한번도 변경되지 않았다면 닉네임 변경 주기에 상관없이 닉네임을 변경한다.") + void changeNickname() { + // given + Member member = Member.builder() + .nickname("익명의손님fFp4vAgX2d") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + String newNickname = "jeomxon"; + Member savedMember = memberRepository.save(member); + + // when + memberService.changeNickname(savedMember, newNickname); + + // then + assertThat(savedMember.getNickname()).isEqualTo(newNickname); + } + + @ParameterizedTest + @ValueSource(strings = {"j", "abcdefabcdefabcdeff", ""}) + @DisplayName("올바르지 않은 길이라면 예외가 발생한다.") + void changeNicknameThrowsExceptionInvalidLength(String newNickname) { + // given + Member member = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + // when, then + assertThatThrownBy(() -> memberService.changeNickname(member, newNickname)) + .isInstanceOf(BadRequestException.class) + .hasMessage("닉네임의 길이가 올바르지 않습니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"(((", "%%$%^^^", "12vvv^vvvd"}) + @DisplayName("한글, 영어, 숫자 이외의 문자가 포함되어있다면 예외가 발생한다.") + void changeNicknameThrowsExceptionInvalid(String newNickname) { + // given + Member member = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + // when, then + assertThatThrownBy(() -> memberService.changeNickname(member, newNickname)) + .isInstanceOf(BadRequestException.class) + .hasMessage("닉네임에 들어갈 수 없는 문자가 포함되어 있습니다."); + } + + @Test + @DisplayName("이미 존재하는 회원의 닉네임과 같다면 예외가 발생한다.") + void changeNicknameEqualToPrevious() { + // given + Member member1 = memberRepository.save(MemberFixtures.MALE_20.get()); + Member member2 = memberRepository.save(MemberFixtures.MALE_30.get()); + + // when, then + assertThatThrownBy(() -> memberService.changeNickname(member1, member2.getNickname())) + .isInstanceOf(BadRequestException.class) + .hasMessage("이미 중복된 닉네임이 존재합니다."); + } + + @Test + @DisplayName("최초 닉네임을 변경한 후 닉네임 변경 주기가 지나지 않았다면 예외가 발생한다.") + void changeNicknameThrowsExceptionNotPassedChangingCycle() { + // given + Member member = Member.builder() + .nickname("익명의손님fFp4vAgX2d") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + Member savedMember = memberRepository.save(member); + + memberService.changeNickname(savedMember, "저문"); + + // when, then + assertThatThrownBy(() -> memberService.changeNickname(savedMember, "저라니")) + .isInstanceOf(BadRequestException.class) + .hasMessage("최소 닉네임 변경주기가 지나지 않았습니다."); + } + + } + + @Nested + @DisplayName("회원의 상세 정보를 수정할 때") + class UpdateDetails { + + @Test + @DisplayName("성별과 출생년도가 null이면 정상적으로 성공한다.") + void updateDetailsSuccess() { + // given + Member unsavedMember = Member.builder() + .nickname("저문") + .socialType(SocialType.KAKAO) + .socialId("123123123") + .build(); + Member member = memberRepository.save(unsavedMember); + MemberDetailRequest request = new MemberDetailRequest(Gender.FEMALE, 2000); + + // when + memberService.updateDetails(request, member); + + // then + assertAll( + () -> assertThat(member.getGender()).isEqualTo(Gender.FEMALE), + () -> assertThat(member.getBirthYear()).isEqualTo(2000) + ); + } + + @Test + @DisplayName("기존 성별이 지정되어 있으면 예외가 발생한다.") + void updateDetailsSameGender() { + // given + Member unsavedMember = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .socialType(SocialType.KAKAO) + .socialId("123123123") + .build(); + Member member = memberRepository.save(unsavedMember); + MemberDetailRequest request = new MemberDetailRequest(Gender.FEMALE, 2000); + + // when, then + assertThatThrownBy(() -> memberService.updateDetails(request, member)) + .isInstanceOf(BadRequestException.class) + .hasMessage("이미 성별이 할당되어 있습니다."); + } + + @Test + @DisplayName("기존 출생년도가 지정되어 있으면 예외가 발생한다.") + void updateDetailsSameBirthYear() { + // given + Member unsavedMember = Member.builder() + .nickname("저문") + .birthYear(2000) + .socialType(SocialType.KAKAO) + .socialId("123123123") + .build(); + Member member = memberRepository.save(unsavedMember); + MemberDetailRequest request = new MemberDetailRequest(Gender.MALE, 1995); + + // when, then + assertThatThrownBy(() -> memberService.updateDetails(request, member)) + .isInstanceOf(BadRequestException.class) + .hasMessage("이미 출생년도가 할당되어 있습니다."); + } + + } + + @Nested + @DisplayName("회원 탈퇴를 할 때") + class DeleteMember { + + @Test + @DisplayName("회원만 존재하는 경우 정상적으로 성공한다.") + void success() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + // when + memberService.deleteMember(member); + + // then + assertThat(memberRepository.findAll()).isEmpty(); + } + + @Test + @DisplayName("회원과 게시글이 존재하는 경우 정상적으로 성공한다.") + void successWithPost() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(member) + .postBody(postBody) + .deadline(LocalDateTime.now().plusDays(3L).truncatedTo(ChronoUnit.MINUTES)) + .build(); + + postRepository.save(post); + + // when + memberService.deleteMember(member); + + // then + assertAll( + () -> assertThat(memberRepository.findAll()).isEmpty(), + () -> assertThat(postRepository.findAll()).isEmpty() + ); + } + + @Test + @DisplayName("회원과 타인의 게시글이 존재하는 경우 회원만 탈퇴된다.") + void deleteOnlyMember() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Member writer = memberRepository.save(MemberFixtures.FEMALE_20.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(LocalDateTime.now().plusDays(3L).truncatedTo(ChronoUnit.MINUTES)) + .build(); + + postRepository.save(post); + + // when + memberService.deleteMember(member); + + // then + assertAll( + () -> assertThat(memberRepository.findAll()).hasSize(1), + () -> assertThat(memberRepository.findById(writer.getId()).get()).isEqualTo(writer), + () -> assertThat(postRepository.findAll()).hasSize(1) + ); + } + + @Test + @DisplayName("회원과 회원의 즐겨찾는 카테고리가 존재하는 경우 모두 삭제된다.") + void deleteWithMemberCategory() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + Category category1 = Category.builder().name("음악").build(); + Category category2 = Category.builder().name("개발").build(); + Category category3 = Category.builder().name("연애").build(); + + categoryRepository.saveAll(List.of(category1, category2, category3)); + + MemberCategory memberCategory = MemberCategory.builder() + .member(member) + .category(category1) + .build(); + + memberCategoryRepository.save(memberCategory); + + // when + memberService.deleteMember(member); + + // then + assertAll( + () -> assertThat(memberRepository.findAll()).isEmpty(), + () -> assertThat(categoryRepository.findAll()).hasSize(3), + () -> assertThat(memberCategoryRepository.findAll()).isEmpty() + ); + } + + @Test + @DisplayName("회원과 회원의 신고가 존재하는 경우 모두 삭제된다.") + void deleteWithReportByOthers() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + Report report = Report.builder() + .member(member) + .reportType(ReportType.POST) + .targetId(1L) + .reason("불건전한 게시글") + .build(); + reportRepository.save(report); + + // when + memberService.deleteMember(member); + + // then + assertAll( + () -> assertThat(memberRepository.findAll()).isEmpty(), + () -> assertThat(reportRepository.findAll()).isEmpty() + ); + } + + @Test + @DisplayName("회원과 신고당한 회원의 게시글 기록 모두 삭제된다.") + void deleteWithReportedPost() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Member reporter = memberRepository.save(MemberFixtures.MALE_10.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(member) + .postBody(postBody) + .deadline(LocalDateTime.now().plusDays(3L).truncatedTo(ChronoUnit.MINUTES)) + .build(); + + postRepository.save(post); + + Report report = Report.builder() + .member(reporter) + .reportType(ReportType.POST) + .targetId(post.getId()) + .reason("불건전한 게시글") + .build(); + reportRepository.save(report); + + // when + memberService.deleteMember(member); + + // then + assertAll( + () -> assertThat(memberRepository.findAll()).hasSize(1), + () -> assertThat(postRepository.findAll()).isEmpty(), + () -> assertThat(reportRepository.findAll()).isEmpty() + ); + } + + @Test + @DisplayName("회원과 신고당한 회원의 댓글 기록 모두 삭제된다.") + void deleteWithReportedComment() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Member reporter = memberRepository.save(MemberFixtures.MALE_10.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(reporter) + .postBody(postBody) + .deadline(LocalDateTime.now().plusDays(3L).truncatedTo(ChronoUnit.MINUTES)) + .build(); + + Comment comment = Comment.builder() + .post(post) + .member(member) + .content("댓글입니다.") + .build(); + + post.addComment(comment); + + postRepository.save(post); + + Report report = Report.builder() + .member(reporter) + .reportType(ReportType.COMMENT) + .targetId(comment.getId()) + .reason("불건전한 댓글") + .build(); + reportRepository.save(report); + + em.flush(); + em.clear(); + + // when + memberService.deleteMember(member); + + // then + assertAll( + () -> assertThat(memberRepository.findAll()).hasSize(1), + () -> assertThat(commentRepository.findAll()).isEmpty(), + () -> assertThat(reportRepository.findAll()).isEmpty() + ); + } + + @Test + @DisplayName("회원과 신고당한 닉네임 기록 모두 삭제된다.") + void deleteWithReportedNickname() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Member reporter = memberRepository.save(MemberFixtures.MALE_10.get()); + + Report report = Report.builder() + .member(reporter) + .reportType(ReportType.NICKNAME) + .targetId(member.getId()) + .reason("불건전한 닉네임") + .build(); + reportRepository.save(report); + + // when + memberService.deleteMember(member); + + // then + assertAll( + () -> assertThat(memberRepository.findAll()).hasSize(1), + () -> assertThat(reportRepository.findAll()).isEmpty() + ); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/domain/post/controller/PostCommentControllerTest.java b/backend/src/test/java/com/votogether/domain/post/controller/PostCommentControllerTest.java new file mode 100644 index 000000000..3d51bb363 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/post/controller/PostCommentControllerTest.java @@ -0,0 +1,350 @@ +package com.votogether.domain.post.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.service.MemberService; +import com.votogether.domain.post.dto.request.comment.CommentRegisterRequest; +import com.votogether.domain.post.dto.request.comment.CommentUpdateRequest; +import com.votogether.domain.post.dto.response.comment.CommentResponse; +import com.votogether.domain.post.entity.comment.Comment; +import com.votogether.domain.post.service.PostCommentService; +import com.votogether.global.exception.GlobalExceptionHandler; +import com.votogether.global.jwt.JwtAuthenticationFilter; +import com.votogether.global.jwt.TokenPayload; +import com.votogether.global.jwt.TokenProcessor; +import com.votogether.test.fixtures.MemberFixtures; +import io.restassured.common.mapper.TypeRef; +import io.restassured.module.mockmvc.RestAssuredMockMvc; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@WebMvcTest(PostCommentController.class) +class PostCommentControllerTest { + + @MockBean + PostCommentService postCommentService; + + @MockBean + MemberService memberService; + + @MockBean + TokenProcessor tokenProcessor; + + @BeforeEach + void setUp() { + RestAssuredMockMvc.standaloneSetup( + MockMvcBuilders + .standaloneSetup(new PostCommentController(postCommentService)) + .setControllerAdvice(GlobalExceptionHandler.class) + .addFilters(new JwtAuthenticationFilter(tokenProcessor)) + ); + } + + @Nested + @DisplayName("게시글 댓글 등록 시 ") + class CreateComment { + + @ParameterizedTest + @ValueSource(strings = {"@", "a", "가"}) + @DisplayName("ID로 변환할 수 없는 타입이라면 400을 응답한다.") + void invalidIDType(String id) throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get()); + + // when, then + RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().post("/posts/{postId}/comments", id) + .then().log().all() + .status(HttpStatus.BAD_REQUEST) + .body("code", equalTo(-9998)) + .body("message", equalTo("postId는 Long 타입이 필요합니다.")); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("댓글 내용이 존재하지 않으면 400을 응답한다.") + void emptyContent(String content) throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get()); + + CommentRegisterRequest commentRegisterRequest = new CommentRegisterRequest(content); + + // when, then + RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(MediaType.APPLICATION_JSON) + .body(commentRegisterRequest) + .when().post("/posts/{postId}/comments", 1) + .then().log().all() + .status(HttpStatus.BAD_REQUEST) + .body("code", equalTo(-9997)) + .body("message", containsString("댓글 내용은 존재해야 합니다.")); + } + + @Test + @DisplayName("댓글을 정상적으로 등록하면 201을 응답한다.") + void createComment() throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get()); + + CommentRegisterRequest commentRegisterRequest = new CommentRegisterRequest("댓글입니다."); + willDoNothing().given(postCommentService) + .createComment(any(Member.class), anyLong(), any(CommentRegisterRequest.class)); + + // when, then + RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(MediaType.APPLICATION_JSON) + .body(commentRegisterRequest) + .when().post("/posts/{postId}/comments", 1) + .then().log().all() + .status(HttpStatus.CREATED); + } + + } + + @Nested + @DisplayName("게시글 댓글 목록 조회") + class GetComments { + + @ParameterizedTest + @ValueSource(strings = {"@", "a", "가"}) + @DisplayName("게시글 ID가 Long 타입으로 변환할 수 없는 값이라면 400을 응답한다.") + void invalidPostIDType(String postId) throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get()); + + // when, then + RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().get("/posts/{postId}/comments", postId) + .then().log().all() + .status(HttpStatus.BAD_REQUEST) + .body("code", equalTo(-9998)) + .body("message", containsString("postId는 Long 타입이 필요합니다.")); + } + + @Test + @DisplayName("정상적인 요청이라면 게시글 댓글 목록을 조회한다.") + void getComments() throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get()); + + Member memberA = MemberFixtures.MALE_20.get(); + Member memberB = MemberFixtures.FEMALE_20.get(); + ReflectionTestUtils.setField(memberA, "id", 1L); + ReflectionTestUtils.setField(memberB, "id", 2L); + + Comment commentA = Comment.builder() + .member(memberA) + .content("commentA") + .build(); + Comment commentB = Comment.builder() + .member(memberB) + .content("commentA") + .build(); + LocalDateTime now = LocalDateTime.now(); + ReflectionTestUtils.setField(commentA, "createdAt", now); + ReflectionTestUtils.setField(commentB, "createdAt", now); + + CommentResponse commentResponseA = CommentResponse.from(commentA); + CommentResponse commentResponseB = CommentResponse.from(commentB); + given(postCommentService.getComments(anyLong())).willReturn(List.of(commentResponseA, commentResponseB)); + + // when + List response = RestAssuredMockMvc.given().log().all() + //.headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().get("/posts/{postId}/comments", 1L) + .then().log().all() + .status(HttpStatus.OK) + .extract() + .as(new TypeRef>() { + }); + + // then + assertThat(response).usingRecursiveComparison() + .ignoringFieldsOfTypes(LocalDateTime.class) + .isEqualTo(List.of(commentResponseA, commentResponseB)); + } + + } + + @Nested + @DisplayName("게시글 댓글 수정") + class UpdateComment { + + @ParameterizedTest + @ValueSource(strings = {"@", "a", "가"}) + @DisplayName("게시글 ID가 Long 타입으로 변환할 수 없는 값이라면 400을 응답한다.") + void invalidPostIDType(String postId) throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get()); + CommentUpdateRequest request = new CommentUpdateRequest("hello"); + + // when, then + RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .when().put("/posts/{postId}/comments/{commentId}", postId, 1L) + .then().log().all() + .status(HttpStatus.BAD_REQUEST) + .body("code", equalTo(-9998)) + .body("message", containsString("postId는 Long 타입이 필요합니다.")); + } + + @ParameterizedTest + @ValueSource(strings = {"@", "a", "가"}) + @DisplayName("댓글 ID가 Long 타입으로 변환할 수 없는 값이라면 400을 응답한다.") + void invalidCommentIDType(String commentId) throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get()); + CommentUpdateRequest request = new CommentUpdateRequest("hello"); + + // when, then + RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .when().put("/posts/{postId}/comments/{commentId}", 1L, commentId) + .then().log().all() + .status(HttpStatus.BAD_REQUEST) + .body("code", equalTo(-9998)) + .body("message", containsString("commentId는 Long 타입이 필요합니다.")); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("수정할 댓글 내용이 비어있거나 존재하지 않으면 400을 응답한다.") + void nullAndEmptyCommentContent(String content) throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get()); + CommentUpdateRequest request = new CommentUpdateRequest(content); + + // when, then + RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .when().put("/posts/{postId}/comments/{commentId}", 1L, 1L) + .then().log().all() + .status(HttpStatus.BAD_REQUEST) + .body("code", equalTo(-9997)) + .body("message", containsString("댓글 내용은 존재해야 합니다.")); + } + + } + + @Nested + @DisplayName("게시글 댓글 삭제") + class DeleteComment { + + @ParameterizedTest + @ValueSource(strings = {"@", "a", "가"}) + @DisplayName("게시글 ID가 Long 타입으로 변환할 수 없는 값이라면 400을 응답한다.") + void invalidPostIDType(String postId) throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get()); + + // when, then + RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().delete("/posts/{postId}/comments/{commentId}", postId, 1L) + .then().log().all() + .status(HttpStatus.BAD_REQUEST) + .body("code", equalTo(-9998)) + .body("message", containsString("postId는 Long 타입이 필요합니다.")); + } + + @ParameterizedTest + @ValueSource(strings = {"@", "a", "가"}) + @DisplayName("댓글 ID가 Long 타입으로 변환할 수 없는 값이라면 400을 응답한다.") + void invalidCommentIDType(String commentId) throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get()); + + // when, then + RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().delete("/posts/{postId}/comments/{commentId}", 1L, commentId) + .then().log().all() + .status(HttpStatus.BAD_REQUEST) + .body("code", equalTo(-9998)) + .body("message", containsString("commentId는 Long 타입이 필요합니다.")); + } + + @Test + @DisplayName("댓글을 정상적으로 삭제하면 204를 응답한다.") + void deleteComment() throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get()); + + willDoNothing().given(postCommentService).deleteComment(anyLong(), anyLong(), any(Member.class)); + + // when, then + RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().delete("/posts/{postId}/comments/{commentId}", 1L, 1L) + .then().log().all() + .status(HttpStatus.NO_CONTENT); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/domain/post/controller/PostControllerTest.java b/backend/src/test/java/com/votogether/domain/post/controller/PostControllerTest.java new file mode 100644 index 000000000..ae3d1db20 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/post/controller/PostControllerTest.java @@ -0,0 +1,903 @@ +package com.votogether.domain.post.controller; + +import static com.votogether.test.fixtures.MemberFixtures.MALE_30; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.BDDMockito.given; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.service.MemberService; +import com.votogether.domain.post.dto.request.post.PostCreateRequest; +import com.votogether.domain.post.dto.request.post.PostOptionCreateRequest; +import com.votogether.domain.post.dto.request.post.PostOptionUpdateRequest; +import com.votogether.domain.post.dto.request.post.PostUpdateRequest; +import com.votogether.domain.post.dto.response.post.PostDetailResponse; +import com.votogether.domain.post.dto.response.post.PostRankingResponse; +import com.votogether.domain.post.dto.response.post.PostResponse; +import com.votogether.domain.post.dto.response.post.PostSummaryResponse; +import com.votogether.domain.post.dto.response.post.WriterResponse; +import com.votogether.domain.post.dto.response.vote.VoteCountForAgeGroupResponse; +import com.votogether.domain.post.dto.response.vote.VoteDetailResponse; +import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.vo.PostClosingType; +import com.votogether.domain.post.entity.vo.PostSortType; +import com.votogether.domain.post.service.PostService; +import com.votogether.global.exception.GlobalExceptionHandler; +import com.votogether.global.jwt.TokenPayload; +import com.votogether.global.jwt.TokenProcessor; +import com.votogether.test.fixtures.MemberFixtures; +import io.restassured.http.ContentType; +import io.restassured.module.mockmvc.RestAssuredMockMvc; +import io.restassured.module.mockmvc.response.MockMvcResponse; +import io.restassured.response.ExtractableResponse; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@WebMvcTest(PostController.class) +class PostControllerTest { + + @Autowired + ObjectMapper mapper; + + @MockBean + TokenProcessor tokenProcessor; + + @MockBean + MemberService memberService; + + @MockBean + PostService postService; + + @BeforeEach + void setUp(final WebApplicationContext webApplicationContext) { + RestAssuredMockMvc.standaloneSetup( + MockMvcBuilders + .standaloneSetup(new PostController(postService)) + .setControllerAdvice(GlobalExceptionHandler.class) + ); + RestAssuredMockMvc.webAppContextSetup(webApplicationContext); + } + + @Test + @DisplayName("게시글을 작성한다") + void save() throws IOException { + // given + PostOptionCreateRequest postOptionCreateRequest1 = PostOptionCreateRequest.builder() + .content("optionContent1") + .build(); + + PostOptionCreateRequest postOptionCreateRequest2 = PostOptionCreateRequest.builder() + .content("optionContent2") + .build(); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder() + .categoryIds(List.of(0L)) + .title("title") + .content("content") + .deadline(LocalDateTime.now().plusDays(2)) + .postOptions(List.of(postOptionCreateRequest1, postOptionCreateRequest2)) + .build(); + + String fileName1 = "testImage1.PNG"; + String resultFileName1 = "testResultImage1.PNG"; + String filePath1 = "src/test/resources/images/" + fileName1; + File file1 = new File(filePath1); + + String fileName2 = "testImage2.PNG"; + String resultFileName2 = "testResultImage2.PNG"; + String filePath2 = "src/test/resources/images/" + fileName2; + File file2 = new File(filePath2); + + String fileName3 = "testImage3.PNG"; + String resultFileName3 = "testResultImage3.PNG"; + String filePath3 = "src/test/resources/images/" + fileName3; + File file3 = new File(filePath3); + + String postRequestJson = mapper.writeValueAsString(postCreateRequest); + + long savedPostId = 1L; + given(postService.save(any(), any(), anyList(), anyList())).willReturn(savedPostId); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + // when, then + String locationStartsWith = "/posts/"; + ExtractableResponse response = RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.MULTIPART) + .multiPart("request", postRequestJson, "application/json") + .multiPart("contentImages", resultFileName3, new FileInputStream(file3), "image/png") + .multiPart("optionImages", resultFileName1, new FileInputStream(file1), "image/png") + .multiPart("optionImages", resultFileName2, new FileInputStream(file2), "image/png") + .when().post("/posts") + .then().log().all() + .status(HttpStatus.CREATED) + .header("Location", startsWith(locationStartsWith)) + .extract(); + + String postId = response.header("Location").substring(locationStartsWith.length()); + assertThat(Long.parseLong(postId)).isEqualTo(savedPostId); + } + + @Test + @DisplayName("게시글을 등록 시, 유효성 검증에 위배되는 데이터를 전달하면 예외를 던진다.") + void throwExceptionBlankTitle() throws IOException { + // given + PostOptionCreateRequest postOptionCreateRequest1 = PostOptionCreateRequest.builder() + .content("optionContent1") + .build(); + + PostOptionCreateRequest postOptionCreateRequest2 = PostOptionCreateRequest.builder() + .content("optionContent2") + .build(); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder() + .categoryIds(List.of(0L)) + .content("c".repeat(1001)) + .deadline(LocalDateTime.now().plusDays(2)) + .postOptions(List.of(postOptionCreateRequest1, postOptionCreateRequest2)) + .build(); + + String fileName1 = "testImage1.PNG"; + String resultFileName1 = "testResultImage1.PNG"; + String filePath1 = "src/test/resources/images/" + fileName1; + File file1 = new File(filePath1); + + String fileName2 = "testImage2.PNG"; + String resultFileName2 = "testResultImage2.PNG"; + String filePath2 = "src/test/resources/images/" + fileName2; + File file2 = new File(filePath2); + + String fileName3 = "testImage3.PNG"; + String resultFileName3 = "testResultImage3.PNG"; + String filePath3 = "src/test/resources/images/" + fileName3; + File file3 = new File(filePath3); + + String postRequestJson = mapper.writeValueAsString(postCreateRequest); + + long savedPostId = 1L; + given(postService.save(any(), any(), anyList(), anyList())).willReturn(savedPostId); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + // when + ExtractableResponse response = RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.MULTIPART) + .multiPart("request", postRequestJson, "application/json") + .multiPart("contentImages", resultFileName3, new FileInputStream(file3), "image/png") + .multiPart("optionImages", resultFileName1, new FileInputStream(file1), "image/png") + .multiPart("optionImages", resultFileName2, new FileInputStream(file2), "image/png") + .when().post("/posts") + .then().log().all() + .extract(); + + // then + final String message = response.body().jsonPath().get("message").toString(); + assertAll( + () -> assertThat(message).contains("제목을 입력해주세요.", "내용은 최대 1000자까지 입력 가능합니다."), + () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()) + ); + } + + @Nested + @DisplayName("전체 게시글 목록 조회에서") + class GetAllPost { + + @Test + @DisplayName("page에 숫자가 아닌 다른 값이 들어온 경우 400을 반환한다.") + void invalidPage() throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .param("page", "abc") + .param("postClosingType", PostClosingType.CLOSED) + .param("postSortType", PostSortType.LATEST) + .when().get("/posts") + .then().log().all() + .status(HttpStatus.BAD_REQUEST); + } + + @Test + @DisplayName("PageClosingType이 아닌 다른 값이 들어온 경우 400을 반환한다.") + void invalidPostClosingType() throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .param("page", 0) + .param("postClosingType", "abc") + .param("postSortType", PostSortType.LATEST) + .when().get("/posts") + .then().log().all() + .status(HttpStatus.BAD_REQUEST); + } + + @Test + @DisplayName("PostSortType이 아닌 다른 값이 들어온 경우 400을 반환한다.") + void invalidPostSortType() throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .param("page", 0) + .param("postClosingType", PostClosingType.CLOSED) + .param("postSortType", "abc") + .when().get("/posts") + .then().log().all() + .status(HttpStatus.BAD_REQUEST); + } + + @Test + @DisplayName("정렬 유형, 마감 유형, 카테고리로 모든 게시물 조회한다") + void getAllPostBySortTypeAndClosingTypeAndCategoryId() throws Exception { + // given + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(MALE_30.get()) + .postBody(postBody) + .deadline(LocalDateTime.now().plusDays(3L)) + .build(); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + given(postService.getAllPostBySortTypeAndClosingTypeAndCategoryId( + eq(0), + eq(PostClosingType.PROGRESS), + eq(PostSortType.LATEST), + anyLong(), + any(Member.class) + ) + ).willReturn(List.of(PostResponse.of(post, MALE_30.get()))); + + // when + List responses = RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .param("page", 0) + .param("postClosingType", PostClosingType.PROGRESS) + .param("postSortType", PostSortType.LATEST) + .param("category", 1L) + .when().get("/posts") + .then().log().all() + .contentType(ContentType.JSON) + .status(HttpStatus.OK) + .extract() + .as(new ParameterizedTypeReference>() { + }.getType()); + + // then + assertAll( + () -> assertThat(responses).isNotEmpty(), + () -> assertThat(responses).hasSize(1) + ); + } + + } + + + @Nested + @DisplayName("비회원 게시글 목록 조회") + class GetPostsGuest { + + @Test + @DisplayName("마감 시간 타입으로 변환할 수 없으면 400 상태를 응답한다.") + void invalidPostClosingType() { + RestAssuredMockMvc.given().log().all() + .param("page", 0) + .param("postClosingType", "hello") + .param("postSortType", PostSortType.LATEST) + .when().get("/posts/guest") + .then().log().all() + .contentType(ContentType.JSON) + .status(HttpStatus.BAD_REQUEST); + } + + @Test + @DisplayName("정렬 타입으로 변환할 수 없으면 400 상태를 반환한다.") + void invalidPostSortType() { + RestAssuredMockMvc.given().log().all() + .param("page", 0) + .param("postClosingType", PostClosingType.ALL) + .param("postSortType", "hello") + .when().get("/posts/guest") + .then().log().all() + .contentType(ContentType.JSON) + .status(HttpStatus.BAD_REQUEST); + } + + @Test + @DisplayName("카테고리가 없는 올바른 조회 요청이라면 게시글 목록 응답과 200 상태를 반환한다.") + void getPostsGuestWithoutCategory() { + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(MALE_30.get()) + .postBody(postBody) + .deadline(LocalDateTime.now().plusDays(3L)) + .build(); + + given(postService.getPostsGuest(anyInt(), any(PostClosingType.class), any(PostSortType.class), isNull())) + .willReturn(List.of(PostResponse.forGuest(post))); + + RestAssuredMockMvc.given().log().all() + .param("page", 0) + .param("postClosingType", PostClosingType.ALL) + .param("postSortType", PostSortType.LATEST) + .when().get("/posts/guest") + .then().log().all() + .contentType(ContentType.JSON) + .status(HttpStatus.OK); + } + + @Test + @DisplayName("카테고리가 있는 올바른 조회 요청이라면 게시글 목록 응답과 200 상태를 반환한다.") + void getPostsGuestWithCategory() { + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(MALE_30.get()) + .postBody(postBody) + .deadline(LocalDateTime.now().plusDays(3L)) + .build(); + + given(postService.getPostsGuest(anyInt(), any(PostClosingType.class), any(PostSortType.class), anyLong())) + .willReturn(List.of(PostResponse.forGuest(post))); + + RestAssuredMockMvc.given().log().all() + .param("page", 0) + .param("postClosingType", PostClosingType.ALL) + .param("postSortType", PostSortType.LATEST) + .param("category", 1L) + .when().get("/posts/guest") + .then().log().all() + .contentType(ContentType.JSON) + .status(HttpStatus.OK); + } + + } + + @Test + @DisplayName("한 게시글의 상세를 조회한다.") + void getPost() throws JsonProcessingException { + // given + long postId = 0L; + Member writer = MALE_30.get(); + LocalDateTime deadline = LocalDateTime.now().plusDays(3L); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(deadline) + .build(); + + Member member = MemberFixtures.MALE_20.get(); + given(postService.getPostById(postId, member)).willReturn(PostDetailResponse.of(post, member)); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + // when + String responseBody = RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().get("/posts/{postId}", postId) + .then().log().all() + .contentType(ContentType.JSON) + .status(HttpStatus.OK) + .extract().asString(); + + PostDetailResponse response = mapper.readValue(responseBody, new TypeReference<>() { + }); + + // then + WriterResponse writerResponse = response.writer(); + VoteDetailResponse voteDetailResponse = response.voteInfo(); + + assertAll( + () -> assertThat(response.title()).isEqualTo("title"), + () -> assertThat(response.content()).isEqualTo("content"), + () -> assertThat(response.deadline()).isEqualTo(deadline.truncatedTo(ChronoUnit.MINUTES)), + () -> assertThat(writerResponse.id()).isEqualTo(member.getId()), + () -> assertThat(writerResponse.nickname()).isEqualTo("user9"), + () -> assertThat(voteDetailResponse.totalVoteCount()).isZero() + ); + } + + @Test + @DisplayName("비회원이 한 게시글을 상세 조회한다.") + void getPostByGuest() { + // given + long postId = 0L; + Member writer = MALE_30.get(); + LocalDateTime deadline = LocalDateTime.now().plusDays(3L); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(deadline) + .build(); + + given(postService.getPostById(postId, null)).willReturn(PostDetailResponse.of(post, null)); + + // when + PostDetailResponse response = RestAssuredMockMvc.given().log().all() + .when().get("/posts/{postId}/guest", postId) + .then().log().all() + .contentType(ContentType.JSON) + .status(HttpStatus.OK) + .extract() + .as(PostDetailResponse.class); + + // then + WriterResponse writerResponse = response.writer(); + VoteDetailResponse voteDetailResponse = response.voteInfo(); + + assertAll( + () -> assertThat(response.title()).isEqualTo("title"), + () -> assertThat(response.content()).isEqualTo("content"), + () -> assertThat(response.deadline()).isEqualTo(deadline.truncatedTo(ChronoUnit.MINUTES)), + () -> assertThat(writerResponse.nickname()).isEqualTo("user9"), + () -> assertThat(voteDetailResponse.totalVoteCount()).isEqualTo(-1) + ); + } + + @Test + @DisplayName("게시글에 대한 전체 투표 통계를 조회한다.") + void getVoteStatistics() throws Exception { + // given + VoteOptionStatisticsResponse response = new VoteOptionStatisticsResponse( + 17, + 10, + 7, + List.of( + new VoteCountForAgeGroupResponse("10대 미만", 2, 1, 1), + new VoteCountForAgeGroupResponse("10대", 3, 1, 2), + new VoteCountForAgeGroupResponse("20대", 2, 2, 0), + new VoteCountForAgeGroupResponse("30대", 5, 3, 2), + new VoteCountForAgeGroupResponse("40대", 1, 1, 0), + new VoteCountForAgeGroupResponse("50대", 0, 0, 0), + new VoteCountForAgeGroupResponse("60대 이상", 4, 2, 2) + ) + ); + given(postService.getVoteStatistics(anyLong(), any(Member.class))).willReturn(response); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + // when + VoteOptionStatisticsResponse result = RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().get("/posts/{postId}/options", 1) + .then().log().all() + .status(HttpStatus.OK) + .extract() + .as(VoteOptionStatisticsResponse.class); + + // then + assertThat(result).usingRecursiveComparison().isEqualTo(response); + } + + @Test + @DisplayName("게시글 투표 옵션에 대한 투표 통계를 조회한다.") + void getVoteOptionStatistics() throws Exception { + // given + VoteOptionStatisticsResponse response = new VoteOptionStatisticsResponse( + 17, + 10, + 7, + List.of( + new VoteCountForAgeGroupResponse("10대 미만", 2, 1, 1), + new VoteCountForAgeGroupResponse("10대", 3, 1, 2), + new VoteCountForAgeGroupResponse("20대", 2, 2, 0), + new VoteCountForAgeGroupResponse("30대", 5, 3, 2), + new VoteCountForAgeGroupResponse("40대", 1, 1, 0), + new VoteCountForAgeGroupResponse("50대", 0, 0, 0), + new VoteCountForAgeGroupResponse("60대 이상", 4, 2, 2) + ) + ); + given(postService.getVoteOptionStatistics(anyLong(), anyLong(), any(Member.class))).willReturn(response); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + // when + VoteOptionStatisticsResponse result = RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().get("/posts/{postId}/options/{optionId}", 1, 1) + .then().log().all() + .status(HttpStatus.OK) + .extract() + .as(VoteOptionStatisticsResponse.class); + + // then + assertThat(result).usingRecursiveComparison().isEqualTo(response); + } + + @Test + @DisplayName("회원본인이 투표한 게시글 목록을 조회한다.") + void getPostsVotedByMember() throws Exception { + // given + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(MALE_30.get()) + .postBody(postBody) + .deadline(LocalDateTime.now().plusDays(3L).truncatedTo(ChronoUnit.MINUTES)) + .build(); + + PostResponse postResponse = PostResponse.of(post, MALE_30.get()); + + given(postService.getPostsVotedByMember(anyInt(), any(), any(), any(Member.class))) + .willReturn(List.of(postResponse)); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + // when + List result = RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .param("page", 0) + .param("postClosingType", PostClosingType.PROGRESS) + .param("postSortType", PostSortType.LATEST) + .when().get("/posts/votes/me") + .then().log().all() + .status(HttpStatus.OK) + .extract() + .as(new ParameterizedTypeReference>() { + }.getType()); + + // then + assertThat(result.get(0)).usingRecursiveComparison().isEqualTo(postResponse); + } + + @Test + @DisplayName("게시글을 조기 마감한다.") + void postClosedEarly() throws Exception { + // given + long postId = 1L; + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + // when + ExtractableResponse response = RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().patch("/posts/{postId}/close", postId) + .then().log().all() + .extract(); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); + } + + @Test + @DisplayName("회원본인이 작성한 게시글 목록을 조회한다.") + void getPostsByWriter() throws JsonProcessingException { + // given + long postId = 1L; + Member member = MemberFixtures.FEMALE_20.get(); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(member); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(member) + .postBody(postBody) + .deadline(LocalDateTime.now().plusDays(3L).truncatedTo(ChronoUnit.MINUTES)) + .build(); + + PostResponse postResponse = PostResponse.of(post, member); + + given(postService.getPostsByWriter( + anyInt(), + any(PostClosingType.class), + any(PostSortType.class), + anyLong(), + any(Member.class)) + ).willReturn(List.of(postResponse)); + + // when + List result = RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .param("page", 0) + .param("postClosingType", PostClosingType.PROGRESS) + .param("postSortType", PostSortType.LATEST) + .param("category", 1L) + .when().get("/posts/me") + .then().log().all() + .status(HttpStatus.OK) + .extract() + .as(new ParameterizedTypeReference>() { + }.getType()); + + // then + assertAll( + () -> assertThat(result).hasSize(1), + () -> assertThat(result.get(0)).usingRecursiveComparison().isEqualTo(postResponse) + ); + } + + @Test + @DisplayName("(회원) 키워드를 통해 게시글 목록을 조회한다.") + void searchPostsWithKeyword() throws JsonProcessingException { + // given + long postId = 1L; + Member member = MemberFixtures.FEMALE_20.get(); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(member); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(MALE_30.get()) + .postBody(postBody) + .deadline(LocalDateTime.now().plusDays(3L).truncatedTo(ChronoUnit.MINUTES)) + .build(); + + PostResponse postResponse = PostResponse.of(post, member); + + given(postService.searchPostsWithKeyword(anyString(), anyInt(), any(), any(), anyLong(), any(Member.class))) + .willReturn(List.of(postResponse)); + + // when + List result = RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .param("keyword", "하이") + .param("page", 0) + .param("postClosingType", PostClosingType.PROGRESS) + .param("postSortType", PostSortType.LATEST) + .param("category", 1L) + .when().get("/posts/search") + .then().log().all() + .status(HttpStatus.OK) + .extract() + .as(new ParameterizedTypeReference>() { + }.getType()); + + // then + assertThat(result.get(0)).usingRecursiveComparison().isEqualTo(postResponse); + } + + @Test + @DisplayName("(비회원) 키워드를 통해 게시글 목록을 조회한다.") + void searchPostsWithKeywordForGuest() { + // given + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(MALE_30.get()) + .postBody(postBody) + .deadline(LocalDateTime.now().plusDays(3L).truncatedTo(ChronoUnit.MINUTES)) + .build(); + + PostResponse postResponse = PostResponse.forGuest(post); + + given(postService.searchPostsWithKeywordForGuest( + anyString(), + anyInt(), + any(PostClosingType.class), + any(PostSortType.class), + anyLong()) + ).willReturn(List.of(postResponse)); + + // when + List result = RestAssuredMockMvc.given().log().all() + .param("keyword", "하이") + .param("page", 0) + .param("postClosingType", PostClosingType.PROGRESS) + .param("postSortType", PostSortType.LATEST) + .param("category", 1L) + .when().get("/posts/search/guest") + .then().log().all() + .status(HttpStatus.OK) + .extract() + .as(new ParameterizedTypeReference>() { + }.getType()); + + // then + assertThat(result.get(0)).usingRecursiveComparison().isEqualTo(postResponse); + } + + @Test + @DisplayName("게시글을 삭제한다.") + void delete() { + // given + long postId = 1L; + + // when, then + RestAssuredMockMvc.given().log().all() + .when().delete("/posts/{postId}", postId) + .then().log().all() + .assertThat() + .status(HttpStatus.NO_CONTENT); + } + + @Test + @DisplayName("게시글을 수정한다.") + void update() throws IOException { + // given + PostOptionUpdateRequest postOptionUpdateRequest1 = PostOptionUpdateRequest.builder() + .content("optionContent1") + .build(); + + PostOptionUpdateRequest postOptionUpdateRequest2 = PostOptionUpdateRequest.builder() + .content("optionContent2") + .build(); + + PostUpdateRequest postUpdateRequest = PostUpdateRequest.builder() + .categoryIds(List.of(0L)) + .title("title") + .content("content") + .deadline(LocalDateTime.now().plusDays(2)) + .postOptions(List.of(postOptionUpdateRequest1, postOptionUpdateRequest2)) + .build(); + + String fileName1 = "testImage1.PNG"; + String resultFileName1 = "testResultImage1.PNG"; + String filePath1 = "src/test/resources/images/" + fileName1; + File file1 = new File(filePath1); + + String fileName2 = "testImage2.PNG"; + String resultFileName2 = "testResultImage2.PNG"; + String filePath2 = "src/test/resources/images/" + fileName2; + File file2 = new File(filePath2); + + String fileName3 = "testImage3.PNG"; + String resultFileName3 = "testResultImage3.PNG"; + String filePath3 = "src/test/resources/images/" + fileName3; + File file3 = new File(filePath3); + + String postRequestJson = mapper.writeValueAsString(postUpdateRequest); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + // when, then + RestAssuredMockMvc.given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.MULTIPART) + .multiPart("request", postRequestJson, "application/json") + .multiPart("contentImages", resultFileName3, new FileInputStream(file3), "image/png") + .multiPart("optionImages", resultFileName1, new FileInputStream(file1), "image/png") + .multiPart("optionImages", resultFileName2, new FileInputStream(file2), "image/png") + .when().put("/posts/1", 1) + .then().log().all() + .status(HttpStatus.OK); + } + + @Test + @DisplayName("인기 게시물 랭킹을 불러온다.") + void getRanking() { + // given + Post post = Post.builder() + .postBody(PostBody.builder().title("제목").build()) + .writer(MemberFixtures.MALE_10.get()) + .build(); + ReflectionTestUtils.setField(post, "id", 1L); + + PostRankingResponse postRankingResponse = new PostRankingResponse(1, PostSummaryResponse.from(post)); + given(postService.getRanking()).willReturn(List.of(postRankingResponse)); + + // when, then + List result = RestAssuredMockMvc.given().log().all() + .when().get("/posts/ranking/popular/guest") + .then().log().all() + .status(HttpStatus.OK) + .extract() + .as(new ParameterizedTypeReference>() { + }.getType()); + + assertThat(result).isEqualTo(List.of(postRankingResponse)); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/post/entity/PostCategoriesTest.java b/backend/src/test/java/com/votogether/domain/post/entity/PostCategoriesTest.java new file mode 100644 index 000000000..271ebf660 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/post/entity/PostCategoriesTest.java @@ -0,0 +1,30 @@ +package com.votogether.domain.post.entity; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.votogether.domain.category.entity.Category; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PostCategoriesTest { + + @Test + @DisplayName("여러 Category를 전달하면 Post와 매핑되어 PostOptions를 생성한다") + void mapPostAndCategories() { + // given + PostCategories postCategories = new PostCategories(); + Post post = Post.builder().build(); + Category categoryA = Category.builder().build(); + Category categoryB = Category.builder().build(); + + List categories = List.of(categoryA, categoryB); + + // when + postCategories.mapPostAndCategories(post, categories); + + // then + assertThat(postCategories.getPostCategories()).hasSize(2); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/post/entity/PostOptionTest.java b/backend/src/test/java/com/votogether/domain/post/entity/PostOptionTest.java new file mode 100644 index 000000000..0eb9d854c --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/post/entity/PostOptionTest.java @@ -0,0 +1,28 @@ +package com.votogether.domain.post.entity; + +import static com.votogether.test.fixtures.MemberFixtures.MALE_30; +import static org.assertj.core.api.Assertions.assertThat; + +import com.votogether.domain.vote.entity.Vote; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PostOptionTest { + + @Test + @DisplayName("해당 선택지를 현재 회원이 투표한 건지 확인한다") + void isVoteByMember() { + // given + PostOption postOption = PostOption.builder().build(); + + Vote vote = Vote.builder().member(MALE_30.get()).build(); + postOption.getVotes().add(vote); + + // when + boolean isVoteByMember = postOption.hasMemberVote(MALE_30.get()); + + // then + assertThat(isVoteByMember).isTrue(); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/post/entity/PostTest.java b/backend/src/test/java/com/votogether/domain/post/entity/PostTest.java new file mode 100644 index 000000000..9b04243ed --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/post/entity/PostTest.java @@ -0,0 +1,205 @@ +package com.votogether.domain.post.entity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.exception.PostExceptionType; +import com.votogether.global.exception.BadRequestException; +import com.votogether.global.util.ImageUploader; +import com.votogether.test.fixtures.MemberFixtures; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; + +class PostTest { + + @Test + @DisplayName("여러 Category를 전달하면 Post와 매핑되어 PostOptions를 생성한다") + void mapCategories() { + // given + Post post = Post.builder().build(); + Category categoryA = Category.builder().build(); + Category categoryB = Category.builder().build(); + + List categories = List.of(categoryA, categoryB); + + // when + post.mapCategories(categories); + + // then + PostCategories actualPostCategories = post.getPostCategories(); + assertThat(actualPostCategories.getPostCategories()).hasSize(2); + } + + @Test + @DisplayName("PostOption의 내용을 전달하면 Post와 PostOption이 매핑된다") + void mapPostOptionsByElements() { + // given + Post post = Post.builder().build(); + + byte[] content = "Hello, World!".getBytes(StandardCharsets.UTF_8); + MockMultipartFile file1 = new MockMultipartFile( + "file1", + "hello1.txt", + MediaType.TEXT_PLAIN_VALUE, + content + ); + + MockMultipartFile file2 = new MockMultipartFile( + "file2", + "hello2.txt", + MediaType.TEXT_PLAIN_VALUE, + content + ); + + final List imageUrls = Stream.of(file1, file2) + .map(ImageUploader::upload) + .toList(); + + // when + post.mapPostOptionsByElements(List.of("content1", "content2"), imageUrls); + + // then + List postOptions = post.getPostOptions().getPostOptions(); + assertThat(postOptions).hasSize(2); + } + + @Test + @DisplayName("게시글 작성 시, 게시글의 마감 기한이 현재 시간보다 3일 초과 여부에 따라 예외를 던질 지 결정한다.") + void throwExceptionIsWriter() { + // given + final Member writer = MemberFixtures.MALE_30.get(); + ReflectionTestUtils.setField(writer, "id", 1L); + + Post post1 = Post.builder() + .writer(writer) + .deadline(LocalDateTime.now().plusDays(4)) + .build(); + + Post post2 = Post.builder() + .writer(writer) + .deadline(LocalDateTime.now().plusDays(2)) + .build(); + + // when, then + assertAll( + () -> assertThatThrownBy(() -> post1.validateDeadlineNotExceedByMaximumDeadline(3)) + .isInstanceOf(BadRequestException.class) + .hasMessage(PostExceptionType.DEADLINE_EXCEED_THREE_DAYS.getMessage()), + () -> assertThatNoException() + .isThrownBy(() -> post2.validateDeadlineNotExceedByMaximumDeadline(3)) + ); + } + + @Test + @DisplayName("게시글의 마감 여부에 따라 예외를 던질 지 결정한다.") + void throwExceptionIsDeadlinePassed() { + // given + final Member writer = MemberFixtures.MALE_30.get(); + ReflectionTestUtils.setField(writer, "id", 1L); + + Post post1 = Post.builder() + .writer(writer) + .deadline(LocalDateTime.of(2000, 1, 1, 1, 1)) + .build(); + + Post post2 = Post.builder() + .writer(writer) + .deadline(LocalDateTime.of(9999, 1, 1, 1, 1)) + .build(); + + // when, then + assertAll( + () -> assertThatThrownBy(post1::validateDeadLine) + .isInstanceOf(BadRequestException.class) + .hasMessage(PostExceptionType.POST_CLOSED.getMessage()), + () -> assertThatNoException() + .isThrownBy(post2::validateDeadLine) + ); + } + + @Test + @DisplayName("해당 게시글을 조기 마감 합니다.") + void closedEarly() { + // given + LocalDateTime deadline = LocalDateTime.of(2100, 1, 1, 0, 0); + Post post = Post.builder() + .deadline(deadline) + .build(); + + // when + post.closeEarly(); + + // then + assertThat(post.getDeadline()).isBefore(deadline); + } + + @Test + @DisplayName("게시글을 수정한다.") + void update() { + // given + final Member writer = MemberFixtures.MALE_30.get(); + final PostBody postBody1 = PostBody.builder() + .title("title1") + .content("content1") + .build(); + final Post post = Post.builder() + .writer(writer) + .postBody(postBody1) + .deadline(LocalDateTime.now().plusDays(3)) + .build(); + post.addContentImage("없는사진"); + + final PostBody postBody2 = PostBody.builder() + .title("title2") + .content("content2") + .build(); + + Category categoryA = Category.builder().name("category1").build(); + Category categoryB = Category.builder().name("category2").build(); + + List categories = List.of(categoryA, categoryB); + + // when + final LocalDateTime deadline = LocalDateTime.now().plusDays(2); + post.update( + postBody2, + "oldContentUrl", + List.of("newContentUrl2"), + categories, + List.of("option1", "option2"), + List.of("optionImage1", "optionImage2"), + List.of("newOptionImage1", "newOptionImage2"), + deadline + ); + + // then + final PostBody postBody = post.getPostBody(); + final PostContentImage postContentImage = postBody.getPostContentImages().getContentImages().get(0); + final List postOptions = post.getPostOptions().getPostOptions(); + final List postCategories = post.getPostCategories().getPostCategories(); + final LocalDateTime actualDeadline = post.getDeadline(); + assertAll( + () -> assertThat(postBody.getTitle()).isEqualTo("title2"), + () -> assertThat(postBody.getContent()).isEqualTo("content2"), + () -> assertThat(postContentImage.getImageUrl()).isEqualTo("newContentUrl2"), + () -> assertThat(postOptions.get(0).getContent()).isEqualTo("option1"), + () -> assertThat(postOptions.get(0).getImageUrl()).isEqualTo("newOptionImage1"), + () -> assertThat(postCategories.get(0).getCategory().getName()).isEqualTo("category1"), + () -> assertThat(actualDeadline).hasYear(deadline.getYear()), + () -> assertThat(actualDeadline).hasMonth(deadline.getMonth()), + () -> assertThat(actualDeadline).hasDayOfMonth(deadline.getDayOfMonth()) + ); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/post/entity/comment/CommentTest.java b/backend/src/test/java/com/votogether/domain/post/entity/comment/CommentTest.java new file mode 100644 index 000000000..8d3fc2e5e --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/post/entity/comment/CommentTest.java @@ -0,0 +1,134 @@ +package com.votogether.domain.post.entity.comment; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.global.exception.BadRequestException; +import com.votogether.test.fixtures.MemberFixtures; +import java.time.LocalDateTime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +class CommentTest { + + @Test + @DisplayName("댓글 내용이 최대 글자를 초과하면 예외를 던진다.") + void invalidContentLength() { + // given + String content = "a".repeat(501); + PostBody body = PostBody.builder() + .title("title") + .content("content") + .build(); + Post post = Post.builder() + .writer(MemberFixtures.FEMALE_20.get()) + .postBody(body) + .deadline(LocalDateTime.now()) + .build(); + + // when, then + assertThatThrownBy( + () -> Comment.builder() + .member(MemberFixtures.MALE_20.get()) + .post(post) + .content(content) + .build() + ) + .isInstanceOf(BadRequestException.class) + .hasMessage("유효하지 않은 댓글 길이입니다."); + } + + @Test + @DisplayName("댓글 작성자가 아니라면 예외를 던진다.") + void invalidWriter() { + // given + Member member = MemberFixtures.FEMALE_20.get(); + PostBody body = PostBody.builder() + .title("title") + .content("content") + .build(); + Post post = Post.builder() + .writer(member) + .postBody(body) + .deadline(LocalDateTime.now()) + .build(); + Comment comment = Comment.builder() + .member(member) + .post(post) + .content("content") + .build(); + + ReflectionTestUtils.setField(member, "id", 1L); + + // when, then + assertThatThrownBy(() -> comment.validateWriter(MemberFixtures.MALE_20.get())) + .isInstanceOf(BadRequestException.class) + .hasMessage("댓글 작성자가 아닙니다."); + } + + @Test + @DisplayName("작성되어 있는 게시글이 아니라면 예외를 던진다.") + void invalidPost() { + // given + Member member = MemberFixtures.FEMALE_20.get(); + PostBody bodyA = PostBody.builder() + .title("title") + .content("content") + .build(); + Post postA = Post.builder() + .writer(member) + .postBody(bodyA) + .deadline(LocalDateTime.now()) + .build(); + PostBody bodyB = PostBody.builder() + .title("title") + .content("content") + .build(); + Post postB = Post.builder() + .writer(member) + .postBody(bodyB) + .deadline(LocalDateTime.now()) + .build(); + Comment comment = Comment.builder() + .member(member) + .post(postA) + .content("content") + .build(); + + ReflectionTestUtils.setField(postA, "id", 1L); + + // when, then + assertThatThrownBy(() -> comment.validateBelong(postB)) + .isInstanceOf(BadRequestException.class) + .hasMessage("댓글의 게시글 정보와 일치하지 않습니다."); + } + + @Test + @DisplayName("댓글 수정 시 최대 글자를 초과하면 예외를 던진다.") + void updateContentWithInvalidContentLength() { + // given + PostBody body = PostBody.builder() + .title("title") + .content("content") + .build(); + Post post = Post.builder() + .writer(MemberFixtures.FEMALE_20.get()) + .postBody(body) + .deadline(LocalDateTime.now()) + .build(); + Comment comment = Comment.builder() + .member(MemberFixtures.MALE_20.get()) + .post(post) + .content("hello") + .build(); + + // when, then + assertThatThrownBy(() -> comment.updateContent("a".repeat(501))) + .isInstanceOf(BadRequestException.class) + .hasMessage("유효하지 않은 댓글 길이입니다."); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/post/repository/CommentRepositoryTest.java b/backend/src/test/java/com/votogether/domain/post/repository/CommentRepositoryTest.java new file mode 100644 index 000000000..c50d89c9d --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/post/repository/CommentRepositoryTest.java @@ -0,0 +1,64 @@ +package com.votogether.domain.post.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.comment.Comment; +import com.votogether.test.annotation.RepositoryTest; +import com.votogether.test.fixtures.MemberFixtures; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@RepositoryTest +class CommentRepositoryTest { + + @Autowired + CommentRepository commentRepository; + + @Autowired + MemberRepository memberRepository; + + @Autowired + PostRepository postRepository; + + @Test + @DisplayName("게시글의 댓글 목록을 조회한다.") + void findAllByPost() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Post post = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("titleA").content("contentA").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + Comment commentA = commentRepository.save( + Comment.builder() + .member(member) + .post(post) + .content("commentA") + .build() + ); + Comment commentB = commentRepository.save( + Comment.builder() + .member(member) + .post(post) + .content("commentB") + .build() + ); + + // when + List result = commentRepository.findAllByPostAndIsHiddenFalseOrderByCreatedAtAsc(post); + + // then + assertThat(result).containsExactly(commentA, commentB); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/post/repository/PostRepositoryTest.java b/backend/src/test/java/com/votogether/domain/post/repository/PostRepositoryTest.java new file mode 100644 index 000000000..331233c12 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/post/repository/PostRepositoryTest.java @@ -0,0 +1,866 @@ +package com.votogether.domain.post.repository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.category.repository.CategoryRepository; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.entity.vo.SocialType; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.PostCategory; +import com.votogether.domain.post.entity.PostOption; +import com.votogether.domain.post.entity.vo.PostClosingType; +import com.votogether.domain.post.entity.vo.PostSortType; +import com.votogether.domain.vote.entity.Vote; +import com.votogether.domain.vote.repository.VoteRepository; +import com.votogether.test.annotation.RepositoryTest; +import com.votogether.test.fixtures.MemberFixtures; +import com.votogether.test.persister.MemberTestPersister; +import com.votogether.test.persister.PostOptionTestPersister; +import com.votogether.test.persister.PostTestPersister; +import com.votogether.test.persister.VoteTestPersister; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +@RepositoryTest +class PostRepositoryTest { + + @Autowired + MemberRepository memberRepository; + + @Autowired + PostRepository postRepository; + + @Autowired + PostOptionRepository postOptionRepository; + + @Autowired + PostCategoryRepository postCategoryRepository; + + @Autowired + CategoryRepository categoryRepository; + + @Autowired + VoteRepository voteRepository; + + @Autowired + MemberTestPersister memberTestPersister; + + @Autowired + PostTestPersister postTestPersister; + + @Autowired + PostOptionTestPersister postOptionTestPersister; + + @Autowired + VoteTestPersister voteTestPersister; + + @Test + @DisplayName("Post를 저장한다") + void save() { + // given + final PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + final Member member = Member.builder() + .gender(Gender.MALE) + .socialType(SocialType.KAKAO) + .nickname("user1") + .birthYear(2000) + .socialId("kakao@gmail.com") + .build(); + + final Post post = Post.builder() + .writer(member) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + memberRepository.save(member); + + // when + final Post savedPost = postRepository.save(post); + + // then + assertThat(savedPost).isNotNull(); + } + + @Test + @DisplayName("해당 멤버가 작성한 글의 개수를 확인한다.") + void countByMember() { + // given + Member member = Member.builder() + .nickname("user1") + .gender(Gender.MALE) + .birthYear(2000) + .socialType(SocialType.KAKAO) + .socialId("kakao@gmail.com") + .build(); + + PostBody postBody1 = PostBody.builder() + .title("title1") + .content("content1") + .build(); + + PostBody postBody2 = PostBody.builder() + .title("title2") + .content("content2") + .build(); + + Post post1 = Post.builder() + .writer(member) + .postBody(postBody1) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + Post post2 = Post.builder() + .writer(member) + .postBody(postBody2) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + memberRepository.save(member); + postRepository.save(post1); + postRepository.save(post2); + + // when + int numberOfPosts = postRepository.countByWriter(member); + + // then + assertThat(numberOfPosts).isEqualTo(2); + } + + @Nested + @DisplayName("마감 여부와 정렬 기준으로 페이징 처리하여 게시글 목록을 조회한다.") + class FindAllByClosingTypeAndSortType { + + private List members; + private List posts; + private List categories; + + @BeforeEach + void setUp() { + members = new ArrayList<>(); + posts = new ArrayList<>(); + categories = new ArrayList<>(); + + for (int i = 0; i < 11; i++) { + members.add(memberTestPersister.builder().save()); + } + + Category categoryA = Category.builder() + .name("개발") + .build(); + + Category categoryB = Category.builder() + .name("연애") + .build(); + + categories.add(categoryRepository.save(categoryA)); + categories.add(categoryRepository.save(categoryB)); + + for (int i = 2; i > 0; i--) { + Post closedPost = postTestPersister.builder() + .writer(members.get(members.size() - 1)) + .deadline(LocalDateTime.of(2022, 12, 25, 0, 0)) + .save(); + Post notClosedPost = postTestPersister.builder() + .writer(members.get(members.size() - 1)) + .deadline(LocalDateTime.of(3022, 12, 25, 0, 0)) + .save(); + + posts.add(closedPost); + posts.add(notClosedPost); + + generatePostCategory(closedPost, categories.get(0)); + generatePostCategory(notClosedPost, categories.get(0)); + + generatePostOptionAndVote(closedPost, i + 5); + generatePostOptionAndVote(notClosedPost, i + 7); + } + } + + private void generatePostCategory(Post post, Category category) { + PostCategory postCategory = PostCategory.builder() + .post(post) + .category(category) + .build(); + postCategoryRepository.save(postCategory); + } + + private void generatePostOptionAndVote(Post post, int voteCount) { + for (int j = 0; j < 5; j++) { + PostOption postOption = postOptionTestPersister.builder().sequence(j + 1).post(post).save(); + for (int k = 0; k < voteCount; k++) { + voteTestPersister.builder().member(members.get(k)).postOption(postOption).save(); + } + } + } + + @Test + @DisplayName("마감 여부와 상관없이 게시글 목록을 최신순으로 조회한다.") + void getAllPostsOrderByLatest() { + // given + Pageable pageable = PageRequest.of(0, 2); + + // when + List result = postRepository.findAllByClosingTypeAndSortTypeAndCategoryId( + PostClosingType.ALL, + PostSortType.LATEST, + null, + pageable + ); + + // then + assertThat(result).containsExactly(posts.get(3), posts.get(2)); + } + + @Test + @DisplayName("마감 여부와 상관없이 게시글 목록을 인기순으로 조회한다.") + void getAllPostsOrderByHot() { + // given + Pageable pageable = PageRequest.of(0, 2); + + // when + List result = postRepository.findAllByClosingTypeAndSortTypeAndCategoryId( + PostClosingType.ALL, + PostSortType.HOT, + null, + pageable + ); + + // then + assertThat(result).containsExactly(posts.get(1), posts.get(3)); + } + + @Test + @DisplayName("진행중인 게시글 목록을 최신순으로 조회한다.") + void getProgressPostsOrderByLatest() { + // given + Pageable pageable = PageRequest.of(0, 2); + + // when + List result = postRepository.findAllByClosingTypeAndSortTypeAndCategoryId( + PostClosingType.PROGRESS, + PostSortType.LATEST, + null, + pageable + ); + + // then + assertThat(result).containsExactly(posts.get(3), posts.get(1)); + } + + @Test + @DisplayName("진행중인 게시글 목록을 인기순으로 조회한다.") + void getProgressPostsOrderByHot() { + // given + Pageable pageable = PageRequest.of(0, 2); + + // when + List result = postRepository.findAllByClosingTypeAndSortTypeAndCategoryId( + PostClosingType.PROGRESS, + PostSortType.HOT, + null, + pageable + ); + + // then + assertThat(result).containsExactly(posts.get(1), posts.get(3)); + } + + @Test + @DisplayName("마감된 게시글 목록을 최신순으로 조회한다.") + void getClosedPostsOrderByLatest() { + // given + Pageable pageable = PageRequest.of(0, 2); + + // when + List result = postRepository.findAllByClosingTypeAndSortTypeAndCategoryId( + PostClosingType.CLOSED, + PostSortType.LATEST, + null, + pageable + ); + + // then + assertThat(result).containsExactly(posts.get(2), posts.get(0)); + } + + @Test + @DisplayName("마감된 게시글 목록을 인기순으로 조회한다.") + void getClosedPostsOrderByHot() { + // given + Pageable pageable = PageRequest.of(0, 2); + + // when + List result = postRepository.findAllByClosingTypeAndSortTypeAndCategoryId( + PostClosingType.CLOSED, + PostSortType.HOT, + null, + pageable + ); + + // then + assertThat(result).containsExactly(posts.get(0), posts.get(2)); + } + + @Test + @DisplayName("특정 카테고리의 진행중인 게시글을 인기순으로 조회한다.") + void getClosedPostsOrderByHotWithCategory() { + // given + Pageable pageable = PageRequest.of(0, 5); + + // when + List result = postRepository.findAllByClosingTypeAndSortTypeAndCategoryId( + PostClosingType.PROGRESS, + PostSortType.HOT, + categories.get(0).getId(), + pageable + ); + + // then + assertThat(result).containsExactly(posts.get(1), posts.get(3)); + } + + } + + @Nested + @DisplayName("회원이 투표한 게시글 목록을 조회한다.") + class FindPostsVotedByMember { + + @Test + @DisplayName("마감된 게시글 목록을 최신순으로 가져온다.") + void findClosedPostsVotedByMember() { + // given + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + Member member = memberRepository.save(MemberFixtures.MALE_10.get()); + + Post openPost = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(3000, 7, 12, 0, 0)) + .build() + ); + PostOption postOption = postOptionRepository.save( + PostOption.builder() + .post(openPost) + .sequence(1) + .content("치킨") + .build() + ); + + Post closedPost = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(1000, 7, 12, 0, 0)) + .build()); + + PostOption postOption1 = postOptionRepository.save( + PostOption.builder() + .post(closedPost) + .sequence(1) + .content("치킨") + .build() + ); + + Post closedPost1 = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(1001, 7, 12, 0, 0)) + .build() + ); + PostOption postOption2 = postOptionRepository.save( + PostOption.builder() + .post(closedPost1) + .sequence(1) + .content("치킨") + .build() + ); + + voteRepository.save(Vote.builder().member(member).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(member).postOption(postOption1).build()); + voteRepository.save(Vote.builder().member(member).postOption(postOption2).build()); + + // when + PageRequest pageRequest = PageRequest.of(0, 10, PostSortType.LATEST.getVoteBaseSort()); + Slice posts = postRepository.findClosedPostsVotedByMember(member, pageRequest); + + // then + assertAll( + () -> assertThat(posts).hasSize(2), + () -> assertThat(posts.getContent().get(0)).usingRecursiveComparison().isEqualTo(closedPost1) + ); + } + + @Test + @DisplayName("마감되지 않은 게시글 목록을 투표순으로 가져온다.") + void findOpenPostsVotedByMember() { + // given + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + Member member = memberRepository.save(MemberFixtures.MALE_10.get()); + Member member1 = memberRepository.save(MemberFixtures.MALE_60.get()); + + Post openPost = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(3000, 7, 12, 0, 0)) + .build() + ); + PostOption postOption = postOptionRepository.save( + PostOption.builder() + .post(openPost) + .sequence(1) + .content("치킨") + .build() + ); + + Post openPost1 = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(3001, 7, 12, 0, 0)) + .build() + ); + PostOption postOption1 = postOptionRepository.save( + PostOption.builder() + .post(openPost1) + .sequence(1) + .content("치킨") + .build() + ); + + Post closedPost = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(1000, 7, 12, 0, 0)) + .build() + ); + PostOption postOption2 = postOptionRepository.save( + PostOption.builder() + .post(closedPost) + .sequence(1) + .content("치킨") + .build() + ); + + voteRepository.save(Vote.builder().member(member).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(member).postOption(postOption1).build()); + voteRepository.save(Vote.builder().member(member1).postOption(postOption1).build()); + voteRepository.save(Vote.builder().member(member).postOption(postOption2).build()); + + // when + PageRequest pageRequest = PageRequest.of(0, 10, PostSortType.HOT.getVoteBaseSort()); + Slice posts = postRepository.findOpenPostsVotedByMember(member, pageRequest); + + // then + assertAll( + () -> assertThat(posts).hasSize(2), + () -> assertThat(posts.getContent().get(0)).usingRecursiveComparison().isEqualTo(openPost1) + ); + } + + @Test + @DisplayName("모든 게시글 목록을 가져온다.") + void findPostsVotedByMember() { + // given + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + Member member = memberRepository.save(MemberFixtures.MALE_10.get()); + + Post openPost = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(3000, 7, 12, 0, 0)) + .build() + ); + PostOption postOption = postOptionRepository.save( + PostOption.builder() + .post(openPost) + .sequence(1) + .content("치킨") + .build() + ); + + Post closedPost = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(1000, 7, 12, 0, 0)) + .build() + ); + PostOption postOption1 = postOptionRepository.save( + PostOption.builder() + .post(closedPost) + .sequence(1) + .content("치킨") + .build() + ); + + voteRepository.save(Vote.builder().member(member).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(member).postOption(postOption1).build()); + + // when + PageRequest pageRequest = PageRequest.of(0, 10); + Slice posts = postRepository.findPostsVotedByMember(member, pageRequest); + + // then + assertThat(posts).hasSize(2); + } + + } + + @Nested + @DisplayName("키워드 검색을 통해 게시글 목록을 조회한다.") + class FindingPostsByKeyword { + + Category development; + Category love; + + Post devClosedPost; + Post devOpenPost; + Post loveClosedPost; + Post loveOpenPost; + + @BeforeEach + void setUp() { + development = categoryRepository.save(Category.builder().name("개발").build()); + love = categoryRepository.save(Category.builder().name("연애").build()); + + devClosedPost = postTestPersister.builder() + .postBody(PostBody.builder().title("자바제목").content("자바내용").build()) + .deadline(LocalDateTime.of(1000, 7, 12, 0, 0)) + .save(); + postCategoryRepository.save(PostCategory.builder().post(devClosedPost).category(development).build()); + + devOpenPost = postTestPersister.builder() + .postBody(PostBody.builder().title("자바제목1").content("자바내용1").build()) + .deadline(LocalDateTime.of(3000, 7, 12, 0, 0)) + .save(); + postCategoryRepository.save(PostCategory.builder().post(devOpenPost).category(development).build()); + + loveClosedPost = postTestPersister.builder() + .postBody(PostBody.builder().title("커플제목").content("커플내용").build()) + .deadline(LocalDateTime.of(1000, 7, 12, 0, 0)) + .save(); + postCategoryRepository.save(PostCategory.builder().post(loveClosedPost).category(love).build()); + + loveOpenPost = postTestPersister.builder() + .postBody(PostBody.builder().title("커플제목1").content("커플내용1").build()) + .deadline(LocalDateTime.of(3000, 7, 12, 0, 0)) + .save(); + postCategoryRepository.save(PostCategory.builder().post(loveOpenPost).category(love).build()); + + } + + @Test + @DisplayName("특정 카테고리에 속한 게시글 목록을 키워드를 통해 검색한다.") + void searchPostsWithCategory() { + // when + List posts = postRepository.findAllWithKeyword( + "자바", + PostClosingType.ALL, + PostSortType.LATEST, + development.getId(), + PageRequest.of(0, 10) + ); + + //then + assertAll( + () -> assertThat(posts).hasSize(2), + () -> assertThat(posts.get(0)).isEqualTo(devOpenPost), + () -> assertThat(posts.get(1)).isEqualTo(devClosedPost) + ); + } + + @Test + @DisplayName("게시글 목록을 키워드를 통해 검색한다.(제목)") + void searchPostsWithKeywordInTitle() { + // when + List posts = postRepository.findAllWithKeyword( + "제목1", + PostClosingType.ALL, + PostSortType.LATEST, + null, + PageRequest.of(0, 10) + ); + + //then + assertAll( + () -> assertThat(posts).hasSize(2), + () -> assertThat(posts.get(0)).isEqualTo(loveOpenPost), + () -> assertThat(posts.get(1)).isEqualTo(devOpenPost) + ); + } + + @Test + @DisplayName("게시글 목록을 키워드를 통해 검색한다.(내용)") + void searchPostsWithKeywordInContent() { + // when + List posts = postRepository.findAllWithKeyword( + "내용", + PostClosingType.ALL, + PostSortType.LATEST, + null, + PageRequest.of(0, 10) + ); + + //then + assertThat(posts).hasSize(4); + } + + @Test + @DisplayName("게시글 목록을 키워드를 통해 검색한다.(제목 + 내용)") + void searchPostsWithKeywordInTitleAndContent() { + // when + List posts = postRepository.findAllWithKeyword( + "1", + PostClosingType.ALL, + PostSortType.LATEST, + null, + PageRequest.of(0, 10) + ); + + //then + assertAll( + () -> assertThat(posts).hasSize(2), + () -> assertThat(posts.get(0)).isEqualTo(loveOpenPost), + () -> assertThat(posts.get(1)).isEqualTo(devOpenPost) + ); + } + + } + + @Nested + @DisplayName("회원이 작성한 게시글 목록을 조회한다.") + class findPostsByWriter { + + Member writer; + Member voter; + Member voter1; + + Post openPost_V2; + Post openPost1_V1; + Post closedPost_V1; + Post closedPost1_V0; + + @BeforeEach + void setUp() { + writer = memberRepository.save(MemberFixtures.MALE_20.get()); + voter = memberRepository.save(MemberFixtures.FEMALE_OVER_90.get()); + voter1 = memberRepository.save(MemberFixtures.MALE_60.get()); + + openPost_V2 = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(3000, 7, 12, 0, 0)) + .build() + ); + + PostOption postOption = postOptionRepository.save( + PostOption.builder() + .post(openPost_V2) + .sequence(1) + .content("치킨") + .build() + ); + voteRepository.save(Vote.builder().member(voter).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(voter1).postOption(postOption).build()); + + openPost1_V1 = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(3000, 7, 12, 0, 0)) + .build() + ); + + PostOption postOption3 = postOptionRepository.save( + PostOption.builder() + .post(openPost1_V1) + .sequence(1) + .content("치킨") + .build() + ); + voteRepository.save(Vote.builder().member(voter).postOption(postOption3).build()); + + closedPost_V1 = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(1000, 7, 12, 0, 0)) + .build()); + + PostOption postOption1 = postOptionRepository.save( + PostOption.builder() + .post(closedPost_V1) + .sequence(1) + .content("치킨") + .build() + ); + voteRepository.save(Vote.builder().member(voter).postOption(postOption1).build()); + + closedPost1_V0 = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(1001, 7, 12, 0, 0)) + .build() + ); + PostOption postOption2 = postOptionRepository.save( + PostOption.builder() + .post(closedPost1_V0) + .sequence(1) + .content("치킨") + .build() + ); + } + + @Test + @DisplayName("마감된 게시글을 최신순으로 가져온다.") + void findClosedPostsWithLatest() { + // when + List posts = postRepository.findAllByWriterWithClosingTypeAndSortTypeAndCategoryId( + writer, + PostClosingType.CLOSED, + PostSortType.LATEST, + null, + PageRequest.of(0, 10) + ); + + //then + assertAll( + () -> assertThat(posts).hasSize(2), + () -> assertThat(posts.get(0)).isEqualTo(closedPost1_V0), + () -> assertThat(posts.get(1)).isEqualTo(closedPost_V1) + ); + } + + @Test + @DisplayName("마감된 게시글을 투표순으로 가져온다.") + void findClosedPostsWithHot() { + // when + List posts = postRepository.findAllByWriterWithClosingTypeAndSortTypeAndCategoryId( + writer, + PostClosingType.CLOSED, + PostSortType.HOT, + null, + PageRequest.of(0, 10) + ); + + //then + assertAll( + () -> assertThat(posts).hasSize(2), + () -> assertThat(posts.get(0)).isEqualTo(closedPost_V1), + () -> assertThat(posts.get(1)).isEqualTo(closedPost1_V0) + ); + } + + @Test + @DisplayName("마감안된 게시글을 최신순으로 가져온다.") + void findOpenPostsWithLatest() { + // when + List posts = postRepository.findAllByWriterWithClosingTypeAndSortTypeAndCategoryId( + writer, + PostClosingType.PROGRESS, + PostSortType.LATEST, + null, + PageRequest.of(0, 10) + ); + + //then + assertAll( + () -> assertThat(posts).hasSize(2), + () -> assertThat(posts.get(0)).isEqualTo(openPost1_V1), + () -> assertThat(posts.get(1)).isEqualTo(openPost_V2) + ); + } + + @Test + @DisplayName("마감안된 게시글을 인기순으로 가져온다.") + void findOpenPostsWithHot() { + // when + List posts = postRepository.findAllByWriterWithClosingTypeAndSortTypeAndCategoryId( + writer, + PostClosingType.PROGRESS, + PostSortType.HOT, + null, + PageRequest.of(0, 10) + ); + + //then + assertAll( + () -> assertThat(posts).hasSize(2), + () -> assertThat(posts.get(0)).isEqualTo(openPost_V2), + () -> assertThat(posts.get(1)).isEqualTo(openPost1_V1) + ); + } + + @Test + @DisplayName("마감여부와 관계없이 게시글을 인기순으로 조회한다.") + void findPostsByHot() { + // when + List posts = postRepository.findAllByWriterWithClosingTypeAndSortTypeAndCategoryId( + writer, + PostClosingType.ALL, + PostSortType.HOT, + null, + PageRequest.of(0, 10) + ); + + //then + assertAll( + () -> assertThat(posts).hasSize(4), + () -> assertThat(posts.get(0)).isEqualTo(openPost_V2), + () -> assertThat(posts.get(3)).isEqualTo(closedPost1_V0) + ); + } + + } + + @Test + @DisplayName("모든 유저의 작성한 게시글 수를 가져온다.") + void findCountsByMembers() { + // given + Member member = memberTestPersister.builder().save(); + Member member1 = memberTestPersister.builder().save(); + Member member2 = memberTestPersister.builder().save(); + + postTestPersister.builder().writer(member).save(); + postTestPersister.builder().writer(member1).save(); + postTestPersister.builder().writer(member1).save(); + + // when + List postCounts = postRepository.findCountsByMembers(List.of(member, member1, member2)); + + // then + assertAll( + () -> assertThat(postCounts).hasSize(3), + () -> assertThat(postCounts.get(0)).isEqualTo(1), + () -> assertThat(postCounts.get(1)).isEqualTo(2), + () -> assertThat(postCounts.get(2)).isEqualTo(0) + ); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/post/service/PostCommentServiceTest.java b/backend/src/test/java/com/votogether/domain/post/service/PostCommentServiceTest.java new file mode 100644 index 000000000..749d74d3d --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/post/service/PostCommentServiceTest.java @@ -0,0 +1,385 @@ +package com.votogether.domain.post.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.post.dto.request.comment.CommentRegisterRequest; +import com.votogether.domain.post.dto.request.comment.CommentUpdateRequest; +import com.votogether.domain.post.dto.response.comment.CommentResponse; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.comment.Comment; +import com.votogether.domain.post.repository.CommentRepository; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.global.exception.BadRequestException; +import com.votogether.global.exception.NotFoundException; +import com.votogether.test.annotation.ServiceTest; +import com.votogether.test.fixtures.MemberFixtures; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@ServiceTest +class PostCommentServiceTest { + + @Autowired + PostCommentService postCommentService; + + @Autowired + MemberRepository memberRepository; + + @Autowired + PostRepository postRepository; + + @Autowired + CommentRepository commentRepository; + + @Nested + @DisplayName("게시글 댓글 등록") + class CreateComment { + + @Test + @DisplayName("존재하지 않는 게시글이라면 예외를 던진다.") + void emptyPost() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + CommentRegisterRequest commentRegisterRequest = new CommentRegisterRequest("hello"); + + // when, then + assertThatThrownBy(() -> postCommentService.createComment(member, -1L, commentRegisterRequest)) + .isInstanceOf(NotFoundException.class) + .hasMessage("해당 게시글이 존재하지 않습니다."); + } + + @Test + @DisplayName("게시글에 댓글을 등록한다.") + void createComment() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Post post = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + CommentRegisterRequest commentRegisterRequest = new CommentRegisterRequest("hello"); + + // when + postCommentService.createComment(member, post.getId(), commentRegisterRequest); + + // then + assertThat(commentRepository.findAll()).hasSize(1); + } + + } + + @Nested + @DisplayName("게시글 댓글 목록 조회") + class GetComments { + + @Test + @DisplayName("존재하지 않는 게시글이라면 예외를 던진다.") + void emptyPost() { + // given, when, then + assertThatThrownBy(() -> postCommentService.getComments(-1L)) + .isInstanceOf(NotFoundException.class) + .hasMessage("해당 게시글이 존재하지 않습니다."); + } + + @Test + @DisplayName("게시글 댓글 목록을 조회한다.") + void getComments() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Post post = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("titleA").content("contentA").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + Comment commentA = commentRepository.save( + Comment.builder() + .member(member) + .post(post) + .content("commentA") + .build() + ); + Comment commentB = commentRepository.save( + Comment.builder() + .member(member) + .post(post) + .content("commentB") + .build() + ); + + // when + List response = postCommentService.getComments(post.getId()); + + // then + assertThat(response).usingRecursiveComparison() + .isEqualTo(List.of(CommentResponse.from(commentA), CommentResponse.from(commentB))); + } + + } + + @Nested + @DisplayName("게시글 댓글 수정") + class UpdateComment { + + @Test + @DisplayName("존재하지 않는 게시글이라면 예외를 던진다.") + void emptyPost() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + CommentUpdateRequest request = new CommentUpdateRequest("hello"); + + // when, then + assertThatThrownBy(() -> postCommentService.updateComment(-1L, 1L, request, member)) + .isInstanceOf(NotFoundException.class) + .hasMessage("해당 게시글이 존재하지 않습니다."); + } + + @Test + @DisplayName("존재하지 않는 댓글이라면 예외를 던진다.") + void emptyComment() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Post post = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + CommentUpdateRequest request = new CommentUpdateRequest("hello"); + + // when, then + assertThatThrownBy(() -> postCommentService.updateComment(post.getId(), -1L, request, member)) + .isInstanceOf(NotFoundException.class) + .hasMessage("해당 댓글이 존재하지 않습니다."); + } + + @Test + @DisplayName("댓글의 게시글과 일치하지 않으면 예외를 던진다.") + void invalidBelongPost() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Post postA = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("titleA").content("contentA").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + Post postB = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("titleB").content("contentB").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + Comment comment = commentRepository.save( + Comment.builder() + .member(member) + .post(postA) + .content("comment") + .build() + ); + CommentUpdateRequest request = new CommentUpdateRequest("hello"); + + // when, then + assertThatThrownBy(() -> postCommentService.updateComment(postB.getId(), comment.getId(), request, member)) + .isInstanceOf(BadRequestException.class) + .hasMessage("댓글의 게시글 정보와 일치하지 않습니다."); + } + + @Test + @DisplayName("댓글의 작성자가 아니라면 예외를 던진다.") + void invalidWriter() { + // given + Member memberA = memberRepository.save(MemberFixtures.MALE_20.get()); + Member memberB = memberRepository.save(MemberFixtures.FEMALE_20.get()); + Post post = postRepository.save( + Post.builder() + .writer(memberA) + .postBody(PostBody.builder().title("titleA").content("contentA").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + Comment comment = commentRepository.save( + Comment.builder() + .member(memberB) + .post(post) + .content("comment") + .build() + ); + CommentUpdateRequest request = new CommentUpdateRequest("hello"); + + // when, then + assertThatThrownBy(() -> postCommentService.updateComment(post.getId(), comment.getId(), request, memberA)) + .isInstanceOf(BadRequestException.class) + .hasMessage("댓글 작성자가 아닙니다."); + } + + @Test + @DisplayName("게시글의 댓글을 수정한다.") + void deleteComment() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Post post = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("titleA").content("contentA").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + Comment comment = commentRepository.save( + Comment.builder() + .member(member) + .post(post) + .content("comment") + .build() + ); + CommentUpdateRequest request = new CommentUpdateRequest("hello"); + + // when + postCommentService.updateComment(post.getId(), comment.getId(), request, member); + + // then + assertThat(comment.getContent()).isEqualTo("hello"); + } + + } + + @Nested + @DisplayName("게시글 댓글 삭제") + class DeleteComment { + + @Test + @DisplayName("존재하지 않는 게시글이라면 예외를 던진다.") + void emptyPost() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + // when, then + assertThatThrownBy(() -> postCommentService.deleteComment(-1L, 1L, member)) + .isInstanceOf(NotFoundException.class) + .hasMessage("해당 게시글이 존재하지 않습니다."); + } + + @Test + @DisplayName("존재하지 않는 댓글이라면 예외를 던진다.") + void emptyComment() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Post post = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + + // when, then + assertThatThrownBy(() -> postCommentService.deleteComment(post.getId(), -1L, member)) + .isInstanceOf(NotFoundException.class) + .hasMessage("해당 댓글이 존재하지 않습니다."); + } + + @Test + @DisplayName("댓글의 게시글과 일치하지 않으면 예외를 던진다.") + void invalidBelongPost() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Post postA = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("titleA").content("contentA").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + Post postB = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("titleB").content("contentB").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + Comment comment = commentRepository.save( + Comment.builder() + .member(member) + .post(postA) + .content("comment") + .build() + ); + + // when, then + assertThatThrownBy(() -> postCommentService.deleteComment(postB.getId(), comment.getId(), member)) + .isInstanceOf(BadRequestException.class) + .hasMessage("댓글의 게시글 정보와 일치하지 않습니다."); + } + + @Test + @DisplayName("댓글의 작성자가 아니라면 예외를 던진다.") + void invalidWriter() { + // given + Member memberA = memberRepository.save(MemberFixtures.MALE_20.get()); + Member memberB = memberRepository.save(MemberFixtures.FEMALE_20.get()); + Post post = postRepository.save( + Post.builder() + .writer(memberA) + .postBody(PostBody.builder().title("titleA").content("contentA").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + Comment comment = commentRepository.save( + Comment.builder() + .member(memberB) + .post(post) + .content("comment") + .build() + ); + + // when, then + assertThatThrownBy(() -> postCommentService.deleteComment(post.getId(), comment.getId(), memberA)) + .isInstanceOf(BadRequestException.class) + .hasMessage("댓글 작성자가 아닙니다."); + } + + @Test + @DisplayName("게시글의 댓글을 삭제한다.") + void deleteComment() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Post post = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("titleA").content("contentA").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + Comment comment = commentRepository.save( + Comment.builder() + .member(member) + .post(post) + .content("comment") + .build() + ); + + // when + postCommentService.deleteComment(post.getId(), comment.getId(), member); + + // then + assertThat(commentRepository.findAll()).isEmpty(); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/domain/post/service/PostServiceTest.java b/backend/src/test/java/com/votogether/domain/post/service/PostServiceTest.java new file mode 100644 index 000000000..0bb486c11 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/post/service/PostServiceTest.java @@ -0,0 +1,1287 @@ +package com.votogether.domain.post.service; + +import static com.votogether.test.fixtures.MemberFixtures.FEMALE_10; +import static com.votogether.test.fixtures.MemberFixtures.FEMALE_70; +import static com.votogether.test.fixtures.MemberFixtures.FEMALE_80; +import static com.votogether.test.fixtures.MemberFixtures.MALE_10; +import static com.votogether.test.fixtures.MemberFixtures.MALE_20; +import static com.votogether.test.fixtures.MemberFixtures.MALE_30; +import static com.votogether.test.fixtures.MemberFixtures.MALE_60; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.votogether.domain.category.entity.Category; +import com.votogether.domain.category.repository.CategoryRepository; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.entity.vo.SocialType; +import com.votogether.domain.member.repository.MemberCategoryRepository; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.post.dto.request.comment.CommentRegisterRequest; +import com.votogether.domain.post.dto.request.post.PostCreateRequest; +import com.votogether.domain.post.dto.request.post.PostOptionCreateRequest; +import com.votogether.domain.post.dto.request.post.PostOptionUpdateRequest; +import com.votogether.domain.post.dto.request.post.PostUpdateRequest; +import com.votogether.domain.post.dto.response.post.CategoryResponse; +import com.votogether.domain.post.dto.response.post.PostDetailResponse; +import com.votogether.domain.post.dto.response.post.PostOptionDetailResponse; +import com.votogether.domain.post.dto.response.post.PostRankingResponse; +import com.votogether.domain.post.dto.response.post.PostResponse; +import com.votogether.domain.post.dto.response.post.WriterResponse; +import com.votogether.domain.post.dto.response.vote.VoteDetailResponse; +import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.PostCategory; +import com.votogether.domain.post.entity.PostContentImage; +import com.votogether.domain.post.entity.PostOption; +import com.votogether.domain.post.entity.PostOptions; +import com.votogether.domain.post.entity.comment.Comment; +import com.votogether.domain.post.entity.vo.PostClosingType; +import com.votogether.domain.post.entity.vo.PostSortType; +import com.votogether.domain.post.exception.PostExceptionType; +import com.votogether.domain.post.repository.CommentRepository; +import com.votogether.domain.post.repository.PostCategoryRepository; +import com.votogether.domain.post.repository.PostContentImageRepository; +import com.votogether.domain.post.repository.PostOptionRepository; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.domain.vote.entity.Vote; +import com.votogether.domain.vote.repository.VoteRepository; +import com.votogether.domain.vote.service.VoteService; +import com.votogether.global.exception.BadRequestException; +import com.votogether.global.exception.NotFoundException; +import com.votogether.test.annotation.ServiceTest; +import com.votogether.test.fixtures.CategoryFixtures; +import com.votogether.test.fixtures.MemberFixtures; +import com.votogether.test.persister.MemberTestPersister; +import com.votogether.test.persister.PostOptionTestPersister; +import com.votogether.test.persister.PostTestPersister; +import com.votogether.test.persister.VoteTestPersister; +import jakarta.persistence.EntityManager; +import java.io.FileInputStream; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +@ServiceTest +class PostServiceTest { + + @Autowired + EntityManager entityManager; + + @Autowired + MemberRepository memberRepository; + + @Autowired + MemberCategoryRepository memberCategoryRepository; + + @Autowired + PostRepository postRepository; + + @Autowired + PostOptionRepository postOptionRepository; + + @Autowired + CategoryRepository categoryRepository; + + @Autowired + VoteRepository voteRepository; + + @Autowired + PostService postService; + + @Autowired + VoteService voteService; + + @Autowired + MemberTestPersister memberTestPersister; + + @Autowired + PostTestPersister postTestPersister; + + @Autowired + PostOptionTestPersister postOptionTestPersister; + + @Autowired + VoteTestPersister voteTestPersister; + + @Autowired + PostCategoryRepository postCategoryRepository; + + @Autowired + PostContentImageRepository postContentImageRepository; + + @Autowired + CommentRepository commentRepository; + + @Autowired + PostCommentService postCommentService; + + @Test + @DisplayName("게시글을 등록한다") + void save() throws IOException { + // given + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image2", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file3 = new MockMultipartFile("image3", "test3.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder() + .categoryIds(List.of(category1.getId(), category2.getId())).title("title").content("content") + .postOptions(List.of(PostOptionCreateRequest.builder().content("option1").build(), + PostOptionCreateRequest.builder().content("option2").build())) + .deadline(LocalDateTime.now().plusDays(2)).build(); + + // when + Long savedPostId = postService.save(postCreateRequest, member, List.of(file3), List.of(file1, file2)); + + // then + assertThat(savedPostId).isNotNull(); + } + + @Nested + @DisplayName("게시글에 대한 투표 통계 조회 시 ") + class GetVoteStatistics { + + @Test + @DisplayName("게시글이 존재하지 않으면 예외를 던진다.") + void throwExceptionNonExistPost() { + // given, when, then + assertThatThrownBy(() -> postService.getVoteStatistics(-1L, MemberFixtures.MALE_20.get())).isInstanceOf( + NotFoundException.class).hasMessage("해당 게시글이 존재하지 않습니다."); + } + + @Test + @DisplayName("게시글 작성자가 아니라면 예외를 던진다.") + void throwExceptionNotWriter() { + // given + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + Member reader = memberRepository.save(MemberFixtures.FEMALE_20.get()); + Post post = postRepository.save( + Post.builder().writer(writer).postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)).build()); + + // when, then + assertThatThrownBy(() -> postService.getVoteStatistics(post.getId(), reader)).isInstanceOf( + BadRequestException.class).hasMessage("해당 게시글 작성자가 아닙니다."); + } + + @Test + @DisplayName("전체 투표 통계를 조회한다.") + void getVoteStatistics() { + // given + Member femaleEarly10 = memberRepository.save(MemberFixtures.FEMALE_10.get()); + Member male10 = memberRepository.save(MemberFixtures.MALE_10.get()); + Member male60 = memberRepository.save(MemberFixtures.MALE_60.get()); + Member female70 = memberRepository.save(MemberFixtures.FEMALE_70.get()); + Member female80 = memberRepository.save(MemberFixtures.FEMALE_80.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + Post post = postRepository.save( + Post.builder().writer(writer).postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)).build()); + PostOption postOptionA = postOptionRepository.save( + PostOption.builder().post(post).sequence(1).content("치킨").build()); + PostOption postOptionB = postOptionRepository.save( + PostOption.builder().post(post).sequence(2).content("피자").build()); + + voteRepository.save(Vote.builder().member(femaleEarly10).postOption(postOptionA).build()); + voteRepository.save(Vote.builder().member(male10).postOption(postOptionB).build()); + voteRepository.save(Vote.builder().member(male60).postOption(postOptionA).build()); + voteRepository.save(Vote.builder().member(female70).postOption(postOptionB).build()); + voteRepository.save(Vote.builder().member(female80).postOption(postOptionA).build()); + + // when + VoteOptionStatisticsResponse response = postService.getVoteStatistics(post.getId(), writer); + + // then + assertAll(() -> assertThat(response.totalVoteCount()).isEqualTo(5), + () -> assertThat(response.totalMaleCount()).isEqualTo(2), + () -> assertThat(response.totalFemaleCount()).isEqualTo(3), + () -> assertThat(response.ageGroup()).hasSize(7), + () -> assertThat(response.ageGroup().get(1).ageGroup()).isEqualTo("10대"), + () -> assertThat(response.ageGroup().get(1).voteCount()).isEqualTo(2), + () -> assertThat(response.ageGroup().get(1).maleCount()).isEqualTo(1), + () -> assertThat(response.ageGroup().get(1).femaleCount()).isEqualTo(1)); + } + + } + + @Nested + @DisplayName("게시글 투표 옵션에 대한 투표 통계 조회 시 ") + class GetVoteOptionStatistics { + + @Test + @DisplayName("게시글이 존재하지 않으면 예외를 던진다.") + void throwExceptionNonExistPost() { + // given, when, then + assertThatThrownBy( + () -> postService.getVoteOptionStatistics(-1L, 1L, MemberFixtures.MALE_20.get())).isInstanceOf( + NotFoundException.class).hasMessage("해당 게시글이 존재하지 않습니다."); + } + + @Test + @DisplayName("게시글 투표 옵션이 존재하지 않으면 예외를 던진다.") + void throwExceptionNonExistOption() { + // given + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + Post post = postRepository.save( + Post.builder().writer(writer).postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)).build()); + + // when, then + assertThatThrownBy(() -> postService.getVoteOptionStatistics(post.getId(), -1L, writer)).isInstanceOf( + NotFoundException.class).hasMessage("해당 게시글 투표 옵션이 존재하지 않습니다."); + } + + @Test + @DisplayName("게시글 투표 옵션이 게시글에 속하지 않으면 예외를 던진다.") + void throwExceptionNotBelongToPost() { + // given + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + Post post1 = postRepository.save( + Post.builder().writer(writer).postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)).build()); + Post post2 = postRepository.save( + Post.builder().writer(writer).postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)).build()); + PostOption postOption = postOptionRepository.save( + PostOption.builder().post(post2).sequence(1).content("치킨").build()); + + // when, then + assertThatThrownBy( + () -> postService.getVoteOptionStatistics(post1.getId(), postOption.getId(), writer)).isInstanceOf( + BadRequestException.class).hasMessage("게시글 투표 옵션이 게시글과 연관되어 있지 않습니다."); + } + + @Test + @DisplayName("게시글 작성자가 아니라면 예외를 던진다.") + void throwExceptionNotWriter() { + // given + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + Member reader = memberRepository.save(MemberFixtures.FEMALE_20.get()); + Post post = postRepository.save( + Post.builder().writer(writer).postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)).build()); + PostOption postOption = postOptionRepository.save( + PostOption.builder().post(post).sequence(1).content("치킨").build()); + + // when, then + assertThatThrownBy( + () -> postService.getVoteOptionStatistics(post.getId(), postOption.getId(), reader)).isInstanceOf( + BadRequestException.class).hasMessage("해당 게시글 작성자가 아닙니다."); + } + + @Test + @DisplayName("게시글 투표 옵션에 대한 투표 통계를 조회한다.") + void getVoteOptionStatistics() { + // given + Member female10 = memberRepository.save(FEMALE_10.get()); + Member male10 = memberRepository.save(MALE_10.get()); + Member male60 = memberRepository.save(MALE_60.get()); + Member female70 = memberRepository.save(FEMALE_70.get()); + Member female80 = memberRepository.save(FEMALE_80.get()); + Member writer = memberRepository.save(MALE_20.get()); + + Post post = postRepository.save( + Post.builder().writer(writer).postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)).build()); + PostOption postOption = postOptionRepository.save( + PostOption.builder().post(post).sequence(1).content("치킨").build()); + + voteRepository.save(Vote.builder().member(female10).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(male10).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(male60).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(female70).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(female80).postOption(postOption).build()); + + // when + VoteOptionStatisticsResponse response = postService.getVoteOptionStatistics(post.getId(), + postOption.getId(), writer); + + // then + assertAll(() -> assertThat(response.totalVoteCount()).isEqualTo(5), + () -> assertThat(response.totalMaleCount()).isEqualTo(2), + () -> assertThat(response.totalFemaleCount()).isEqualTo(3), + () -> assertThat(response.ageGroup()).hasSize(7), + () -> assertThat(response.ageGroup().get(1).ageGroup()).isEqualTo("10대"), + () -> assertThat(response.ageGroup().get(1).voteCount()).isEqualTo(2), + () -> assertThat(response.ageGroup().get(1).maleCount()).isEqualTo(1), + () -> assertThat(response.ageGroup().get(1).femaleCount()).isEqualTo(1)); + } + + } + + @Test + @DisplayName("해당 게시글을 조기 마감 합니다") + void postClosedEarlyById() { + // given + Member writer = memberRepository.save(MemberFixtures.MALE_30.get()); + LocalDateTime oldDeadline = LocalDateTime.now().plusDays(2); + Post post = postRepository.save( + Post.builder().writer(writer).postBody(PostBody.builder().title("title").content("content").build()) + .deadline(oldDeadline).build()); + + Post foundPost = postRepository.findById(post.getId()).get(); + + // when + postService.closePostEarlyById(post.getId(), writer); + + // then + assertAll(() -> assertThat(foundPost.getId()).isEqualTo(post.getId()), + () -> assertThat(foundPost.getDeadline()).isBefore(oldDeadline)); + } + + @Test + @DisplayName("해당 게시글을 조기 마감할 시, 작성자가 아니면 예외를 던진다.") + void throwExceptionNotWriterPostClosedEarly() { + // given + Member writer = memberRepository.save(MemberFixtures.MALE_30.get()); + LocalDateTime oldDeadline = LocalDateTime.of(2100, 7, 12, 0, 0); + Post post = postRepository.save( + Post.builder().writer(writer).postBody(PostBody.builder().title("title").content("content").build()) + .deadline(oldDeadline).build()); + + Post foundPost = postRepository.findById(post.getId()).get(); + + // when, then + assertThatThrownBy( + () -> postService.closePostEarlyById(foundPost.getId(), MemberFixtures.MALE_30.get())).isInstanceOf( + BadRequestException.class).hasMessage("해당 게시글 작성자가 아닙니다."); + } + + @Test + @DisplayName("해당 게시글을 조기 마감할 시, 마감이 된 게시글이면 예외를 던진다.") + void throwExceptionDeadLinePostClosedEarly() { + // given + Member writer = memberRepository.save(MemberFixtures.MALE_30.get()); + LocalDateTime oldDeadline = LocalDateTime.of(2000, 7, 12, 0, 0); + Post post = postRepository.save( + Post.builder().writer(writer).postBody(PostBody.builder().title("title").content("content").build()) + .deadline(oldDeadline).build()); + + Post foundPost = postRepository.findById(post.getId()).get(); + + // when, then + assertThatThrownBy(() -> postService.closePostEarlyById(foundPost.getId(), writer)).isInstanceOf( + BadRequestException.class).hasMessage("게시글이 이미 마감되었습니다."); + } + + @Test + @DisplayName("정렬 유형 및 마감 유형별로 모든 게시물 가져온다") + void getAllPostBySortTypeAndClosingType() { + // given + Member writer = MALE_30.get(); + memberRepository.save(writer); + + Member memberToAllPostVote = MALE_20.get(); + memberRepository.save(memberToAllPostVote); + + Category ca1 = Category.builder().name("ca1").build(); + Category ca2 = Category.builder().name("ca2").build(); + Category ca3 = Category.builder().name("ca3").build(); + + categoryRepository.saveAll(List.of(ca1, ca2, ca3)); + + MockMultipartFile file1 = new MockMultipartFile("file1", "hello1.txt", "text/plain", + "Hello, World!11".getBytes()); + + MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.txt", "text/plain", + "Hello, World!22".getBytes()); + + MockMultipartFile file3 = new MockMultipartFile("file3", "hello3.txt", "text/plain", + "Hello, World!33".getBytes()); + + for (int postSequence = 30; postSequence > 0; postSequence--) { + List options = new ArrayList<>() { + { + add(PostOptionCreateRequest.builder().content("option1").build()); + add(PostOptionCreateRequest.builder().content("option2").build()); + } + }; + + List optionImages = new ArrayList<>() { + { + add(file1); + add(file2); + } + }; + + List contentImages = new ArrayList<>() { + { + add(file3); + } + }; + + if (postSequence % 2 == 0) { + MockMultipartFile file4 = new MockMultipartFile("file4", "hello4.txt", "text/plain", + "Hello, World!44".getBytes()); + + optionImages.add(file4); + options.add(PostOptionCreateRequest.builder().content("option3").build()); + } + + PostCreateRequest postCreateRequest = PostCreateRequest.builder().categoryIds(List.of(0L, 2L)) + .title("title" + postSequence).content("content" + postSequence).postOptions(options) + .deadline(LocalDateTime.now().plusDays(2)).build(); + + Long savedPostId = postService.save(postCreateRequest, writer, contentImages, optionImages); + Post post = postRepository.findById(savedPostId).get(); + + List postOptions = post.getPostOptions().getPostOptions(); + Long postOptionId = postOptions.get(0).getId(); + voteService.vote(memberToAllPostVote, post.getId(), postOptionId); + + for (int voteCount = 0; voteCount <= postSequence; voteCount++) { + Member memberToVote = Member.builder().nickname("Abel" + postSequence + voteCount).gender(Gender.MALE) + .birthYear(2000).socialType(SocialType.KAKAO).socialId("Abel" + postSequence + voteCount) + .build(); + + memberRepository.save(memberToVote); + + PostOption perPostOption = postOptions.get(voteCount % postOptions.size()); + voteService.vote(memberToVote, savedPostId, perPostOption.getId()); + } + } + + entityManager.clear(); + + // when + List responses = postService.getAllPostBySortTypeAndClosingTypeAndCategoryId(0, + PostClosingType.PROGRESS, PostSortType.HOT, null, memberToAllPostVote); + + // then + PostResponse firstResponse = responses.get(0); + PostResponse secondResponse = responses.get(1); + assertAll(() -> assertThat(firstResponse.voteInfo().options()).hasSize(3), + () -> assertThat(firstResponse.voteInfo().totalVoteCount()).isEqualTo(32), + () -> assertThat(secondResponse.voteInfo().options()).hasSize(2), + () -> assertThat(secondResponse.voteInfo().totalVoteCount()).isEqualTo(31)); + } + + @Test + @DisplayName("비회원 게시글 목록 조회 시 마감된 게시글은 결과를 확인할 수 있고, 진행중인 게시글은 결과를 확인할 수 없다.") + void getPostsGuest() { + // given + List voters = new ArrayList<>(); + Member writer = memberTestPersister.builder().save(); + + for (int i = 0; i < 5; i++) { + voters.add(memberTestPersister.builder().save()); + } + + Post closedPost = postTestPersister.builder().writer(writer).deadline(LocalDateTime.of(2022, 12, 25, 0, 0)) + .save(); + + for (int j = 0; j < 2; j++) { + PostOption postOption = postOptionTestPersister.builder().sequence(j + 1).post(closedPost).save(); + for (int k = 0; k < 4; k++) { + voteTestPersister.builder().member(voters.get(k)).postOption(postOption).save(); + } + } + + Post notClosedPost = postTestPersister.builder().writer(writer).deadline(LocalDateTime.of(3022, 12, 25, 0, 0)) + .save(); + + for (int j = 0; j < 2; j++) { + PostOption postOption = postOptionTestPersister.builder().sequence(j + 1).post(notClosedPost).save(); + for (int k = 0; k < 4; k++) { + voteTestPersister.builder().member(voters.get(k)).postOption(postOption).save(); + } + } + + entityManager.flush(); + entityManager.clear(); + + // when + List result = postService.getPostsGuest(0, PostClosingType.ALL, PostSortType.LATEST, null); + + // then + assertAll(() -> assertThat(result).hasSize(2), + () -> assertThat(result.get(0).voteInfo().totalVoteCount()).isEqualTo(-1), + () -> assertThat(result.get(0).voteInfo().options().get(0).voteCount()).isEqualTo(-1), + () -> assertThat(result.get(0).voteInfo().options().get(1).voteCount()).isEqualTo(-1), + () -> assertThat(result.get(1).voteInfo().totalVoteCount()).isEqualTo(8), + () -> assertThat(result.get(1).voteInfo().options().get(0).voteCount()).isEqualTo(4), + () -> assertThat(result.get(1).voteInfo().options().get(1).voteCount()).isEqualTo(4)); + } + + @Test + @DisplayName("한 게시글의 상세를 조회할 시, 작성자면 투표 결과를 알 수 있다.") + void getPostByWriter() throws IOException { + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image1", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + LocalDateTime deadline = LocalDateTime.now().plusDays(3); + + PostOptionCreateRequest option1 = PostOptionCreateRequest.builder().content("option1").build(); + + PostOptionCreateRequest option2 = PostOptionCreateRequest.builder().content("option2").build(); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder() + .categoryIds(List.of(category1.getId(), category2.getId())).title("title").content("content") + .postOptions(List.of(option1, option2)).deadline(deadline).build(); + + Long savedPostId = postService.save(postCreateRequest, writer, List.of(), List.of(file1, file2)); + + // when + PostDetailResponse response = postService.getPostById(savedPostId, writer); + + // then + List categories = response.categories(); + WriterResponse writerResponse = response.writer(); + VoteDetailResponse voteDetailResponse = response.voteInfo(); + List options = voteDetailResponse.options(); + + assertAll(() -> assertThat(response.postId()).isEqualTo(savedPostId), + () -> assertThat(response.title()).isEqualTo("title"), + () -> assertThat(response.content()).isEqualTo("content"), () -> assertThat(categories).hasSize(2), + () -> assertThat(categories.get(0).name()).isEqualTo("개발"), + () -> assertThat(writerResponse.id()).isEqualTo(writer.getId()), + () -> assertThat(writerResponse.nickname()).isEqualTo("user7"), + () -> assertThat(voteDetailResponse.totalVoteCount()).isZero(), () -> assertThat(options).hasSize(2), + () -> assertThat(options.get(0).imageUrl()).contains("test1.png")); + } + + @Test + @DisplayName("한 게시글의 상세를 조회할 시, 해당 게시글의 투표자면 결과를 알 수 있다.") + void getPostByVoter() throws IOException { + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + Member voter = memberRepository.save(MemberFixtures.MALE_20.get()); + Member writer = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image1", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + LocalDateTime deadline = LocalDateTime.now().plusDays(3); + + PostOptionCreateRequest option1 = PostOptionCreateRequest.builder().content("option1").build(); + + PostOptionCreateRequest option2 = PostOptionCreateRequest.builder().content("option2").build(); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder() + .categoryIds(List.of(category1.getId(), category2.getId())).title("title").content("content") + .postOptions(List.of(option1, option2)).deadline(deadline).build(); + + Long savedPostId = postService.save(postCreateRequest, writer, List.of(), List.of(file1, file2)); + Post post = postRepository.findById(savedPostId).get(); + PostOptions postOptions = post.getPostOptions(); + PostOption postOption = postOptions.getPostOptions().get(0); + voteService.vote(voter, savedPostId, postOption.getId()); + + entityManager.clear(); + + // when + PostDetailResponse response = postService.getPostById(savedPostId, voter); + + // then + List categories = response.categories(); + WriterResponse writerResponse = response.writer(); + VoteDetailResponse voteDetailResponse = response.voteInfo(); + List options = voteDetailResponse.options(); + + assertAll(() -> assertThat(response.postId()).isEqualTo(savedPostId), + () -> assertThat(response.title()).isEqualTo("title"), + () -> assertThat(response.content()).isEqualTo("content"), () -> assertThat(categories).hasSize(2), + () -> assertThat(categories.get(0).name()).isEqualTo("개발"), + () -> assertThat(writerResponse.id()).isEqualTo(writer.getId()), + () -> assertThat(writerResponse.nickname()).isEqualTo("user10"), + () -> assertThat(voteDetailResponse.totalVoteCount()).isOne(), () -> assertThat(options).hasSize(2), + () -> assertThat(options.get(0).imageUrl()).contains("test1.png")); + } + + @Test + @DisplayName("한 게시글의 상세를 조회할 시, 마감된 게시글이면 투표 결과를 알 수 있다.") + void getClosedPost() throws IOException { + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + Member member = memberRepository.save(MemberFixtures.FEMALE_10.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image1", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + LocalDateTime deadline = LocalDateTime.now(); + + PostOptionCreateRequest option1 = PostOptionCreateRequest.builder().content("option1").build(); + + PostOptionCreateRequest option2 = PostOptionCreateRequest.builder().content("option2").build(); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder() + .categoryIds(List.of(category1.getId(), category2.getId())).title("title").content("content") + .postOptions(List.of(option1, option2)).deadline(deadline).build(); + + Long savedPostId = postService.save(postCreateRequest, writer, List.of(), List.of(file1, file2)); + + // when + PostDetailResponse response = postService.getPostById(savedPostId, member); + + // then + List categories = response.categories(); + WriterResponse writerResponse = response.writer(); + VoteDetailResponse voteDetailResponse = response.voteInfo(); + List options = voteDetailResponse.options(); + + assertAll(() -> assertThat(response.postId()).isEqualTo(savedPostId), + () -> assertThat(response.title()).isEqualTo("title"), + () -> assertThat(response.content()).isEqualTo("content"), () -> assertThat(categories).hasSize(2), + () -> assertThat(categories.get(0).name()).isEqualTo("개발"), + () -> assertThat(writerResponse.id()).isEqualTo(writer.getId()), + () -> assertThat(writerResponse.nickname()).isEqualTo("user7"), + () -> assertThat(voteDetailResponse.totalVoteCount()).isZero(), () -> assertThat(options).hasSize(2), + () -> assertThat(options.get(0).imageUrl()).contains("test1.png")); + } + + @Test + @DisplayName("한 게시글의 상세를 조회할 시, 작성자, 투표자, 마감된 게시글이 전부 아니면 투표 결과를 알 수 없다.") + void getPostInvisibleResult() throws IOException { + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + Member member = memberRepository.save(MemberFixtures.FEMALE_10.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image1", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + LocalDateTime deadline = LocalDateTime.now().plusDays(3); + + PostOptionCreateRequest option1 = PostOptionCreateRequest.builder().content("option1").build(); + + PostOptionCreateRequest option2 = PostOptionCreateRequest.builder().content("option2").build(); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder() + .categoryIds(List.of(category1.getId(), category2.getId())).title("title").content("content") + .postOptions(List.of(option1, option2)).deadline(deadline).build(); + + Long savedPostId = postService.save(postCreateRequest, writer, List.of(), List.of(file1, file2)); + + // when + PostDetailResponse response = postService.getPostById(savedPostId, member); + + // then + List categories = response.categories(); + WriterResponse writerResponse = response.writer(); + VoteDetailResponse voteDetailResponse = response.voteInfo(); + List options = voteDetailResponse.options(); + + assertAll(() -> assertThat(response.postId()).isEqualTo(savedPostId), + () -> assertThat(response.title()).isEqualTo("title"), + () -> assertThat(response.content()).isEqualTo("content"), () -> assertThat(categories).hasSize(2), + () -> assertThat(categories.get(0).name()).isEqualTo("개발"), + () -> assertThat(writerResponse.id()).isEqualTo(writer.getId()), + () -> assertThat(writerResponse.nickname()).isEqualTo("user7"), + () -> assertThat(voteDetailResponse.totalVoteCount()).isEqualTo(-1), + () -> assertThat(options).hasSize(2), + () -> assertThat(options.get(0).imageUrl()).contains("test1.png")); + } + + @Test + @DisplayName("존재하지 않은 게시글을 가져오려 할 시, 예외를 던진다.") + void throwExceptionNotFoundPost() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + // when, then + assertThatThrownBy(() -> postService.getPostById(1L, member)).isInstanceOf(NotFoundException.class) + .hasMessage(PostExceptionType.POST_NOT_FOUND.getMessage()); + } + + @Test + void delete() throws IOException { + // given + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image2", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file3 = new MockMultipartFile("image3", "test3.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder() + .categoryIds(List.of(category1.getId(), category2.getId())).title("title").content("content") + .postOptions(List.of(PostOptionCreateRequest.builder().content("option1").build(), + PostOptionCreateRequest.builder().content("option2").build())) + .deadline(LocalDateTime.now().plusDays(2)).build(); + + Long savedPostId = postService.save(postCreateRequest, writer, List.of(file3), List.of(file1, file2)); + final Post post = postRepository.findById(savedPostId).get(); + final List postOptions = post.getPostOptions().getPostOptions(); + + Member voter = MALE_30.get(); + memberRepository.save(voter); + PostOption perPostOption = postOptions.get(0); + voteService.vote(voter, savedPostId, perPostOption.getId()); + + CommentRegisterRequest commentRegisterRequest = new CommentRegisterRequest("hello"); + postCommentService.createComment(voter, post.getId(), commentRegisterRequest); + entityManager.flush(); + entityManager.clear(); + + final List postCategories = post.getPostCategories().getPostCategories(); + final PostBody postBody = post.getPostBody(); + final PostContentImage postContentImage = postBody.getPostContentImages().getContentImages().get(0); + final List votes = postOptions.stream().map(PostOption::getVotes).flatMap(Collection::stream).toList(); + final List comments = post.getComments(); + + // when, then + assertAll(() -> assertThatNoException().isThrownBy(() -> postService.delete(savedPostId)), + () -> assertThat(postCategoryRepository.findById(postCategories.get(0).getId())).isNotPresent(), + () -> assertThat(postContentImageRepository.findById(postContentImage.getId())).isNotPresent(), + () -> assertThat(postOptionRepository.findById(postOptions.get(0).getId())).isNotPresent(), + () -> assertThat(voteRepository.findById(votes.get(0).getId())).isNotPresent(), + () -> assertThat(commentRepository.findById(comments.get(0).getId())).isNotPresent()); + } + + @Test + @DisplayName("게시글을 삭제할 시, 투표가 20개 이상 진행된 게시글이면 예외를 던진다.") + void throwExceptionDeleteVoteOverTwenty() throws IOException { + // given + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image2", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file3 = new MockMultipartFile("image3", "test3.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder() + .categoryIds(List.of(category1.getId(), category2.getId())).title("title").content("content") + .postOptions(List.of(PostOptionCreateRequest.builder().content("option1").build(), + PostOptionCreateRequest.builder().content("option2").build())) + .deadline(LocalDateTime.now().plusDays(2)).build(); + + Long savedPostId = postService.save(postCreateRequest, member, List.of(file3), List.of(file1, file2)); + final Post post = postRepository.findById(savedPostId).get(); + + for (int voteCount = 0; voteCount < 20; voteCount++) { + Member memberToVote = Member.builder().nickname("Abel" + voteCount).gender(Gender.MALE).birthYear(2000) + .socialType(SocialType.KAKAO).socialId("Abel" + voteCount).build(); + + memberRepository.save(memberToVote); + + final List postOptions = post.getPostOptions().getPostOptions(); + PostOption perPostOption = postOptions.get(voteCount % postOptions.size()); + voteService.vote(memberToVote, savedPostId, perPostOption.getId()); + } + + entityManager.clear(); + + // when, then + assertThatThrownBy(() -> postService.delete(savedPostId)).isInstanceOf(BadRequestException.class) + .hasMessage(PostExceptionType.CANNOT_DELETE_BECAUSE_MORE_THAN_TWENTY_VOTES.getMessage()); + } + + @Test + @DisplayName("게시글을 수정한다") + void update() throws IOException { + // given + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image2", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file3 = new MockMultipartFile("image3", "test3.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder().categoryIds(List.of(category1.getId())) + .title("title").content("content").postOptions( + List.of(PostOptionCreateRequest.builder().content("option1").build(), + PostOptionCreateRequest.builder().content("option2").build())) + .deadline(LocalDateTime.now().plusDays(2)).build(); + + Long savedPostId = postService.save(postCreateRequest, writer, List.of(file3), List.of(file1, file2)); + + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + MockMultipartFile file4 = new MockMultipartFile("image4", "test4.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file5 = new MockMultipartFile("image5", "test5.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file6 = new MockMultipartFile("image6", "test6.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostUpdateRequest postUpdateRequest = PostUpdateRequest.builder().categoryIds(List.of(category2.getId())) + .title("title2").content("content2").postOptions( + List.of(PostOptionUpdateRequest.builder().content("option3").build(), + PostOptionUpdateRequest.builder().content("option4").build())) + .deadline(LocalDateTime.now().plusDays(1)).build(); + + // when + postService.update(savedPostId, postUpdateRequest, writer, List.of(file4), List.of(file5, file6)); + + // then + final PostDetailResponse postDetailResponse = postService.getPostById(savedPostId, writer); + final List options = postDetailResponse.voteInfo().options(); + final List categories = postDetailResponse.categories(); + assertAll(() -> assertThat(postDetailResponse.title()).isEqualTo("title2"), + () -> assertThat(postDetailResponse.content()).isEqualTo("content2"), + () -> assertThat(postDetailResponse.imageUrl()).contains("test4.png"), + () -> assertThat(options.get(0).content()).isEqualTo("option3"), + () -> assertThat(options.get(1).content()).isEqualTo("option4"), + () -> assertThat(options.get(0).imageUrl()).contains("test5.png"), + () -> assertThat(options.get(1).imageUrl()).contains("test6.png"), + () -> assertThat(categories.get(0).name()).isEqualTo("음식")); + } + + @Test + @DisplayName("게시글 수정 시, 작성자가 아니면 예외를 던진다.") + void throwExceptionUpdateNotWriter() throws IOException { + // given + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Member member = memberRepository.save(MemberFixtures.MALE_30.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image2", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file3 = new MockMultipartFile("image3", "test3.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder().categoryIds(List.of(category1.getId())) + .title("title").content("content").postOptions( + List.of(PostOptionCreateRequest.builder().content("option1").build(), + PostOptionCreateRequest.builder().content("option2").build())) + .deadline(LocalDateTime.now().plusDays(2)).build(); + + Long savedPostId = postService.save(postCreateRequest, writer, List.of(file3), List.of(file1, file2)); + + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + MockMultipartFile file4 = new MockMultipartFile("image4", "test4.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file5 = new MockMultipartFile("image5", "test5.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file6 = new MockMultipartFile("image6", "test6.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostUpdateRequest postUpdateRequest = PostUpdateRequest.builder().categoryIds(List.of(category2.getId())) + .title("title2").content("content2").postOptions( + List.of(PostOptionUpdateRequest.builder().content("option3").build(), + PostOptionUpdateRequest.builder().content("option4").build())) + .deadline(LocalDateTime.now().plusDays(1)).build(); + + // when, then + assertThatThrownBy(() -> postService.update(savedPostId, postUpdateRequest, member, List.of(file4), + List.of(file5, file6))).isInstanceOf(BadRequestException.class) + .hasMessage(PostExceptionType.NOT_WRITER.getMessage()); + } + + @Test + @DisplayName("게시글 수정 시, 마감된 게시글이면 예외를 던진다.") + void throwExceptionUpdateClosedPost() throws IOException { + // given + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image2", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file3 = new MockMultipartFile("image3", "test3.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder().categoryIds(List.of(category1.getId())) + .title("title").content("content").postOptions( + List.of(PostOptionCreateRequest.builder().content("option1").build(), + PostOptionCreateRequest.builder().content("option2").build())) + .deadline(LocalDateTime.now()).build(); + + Long savedPostId = postService.save(postCreateRequest, writer, List.of(file3), List.of(file1, file2)); + + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + MockMultipartFile file4 = new MockMultipartFile("image4", "test4.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file5 = new MockMultipartFile("image5", "test5.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file6 = new MockMultipartFile("image6", "test6.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostUpdateRequest postUpdateRequest = PostUpdateRequest.builder().categoryIds(List.of(category2.getId())) + .title("title2").content("content2").postOptions( + List.of(PostOptionUpdateRequest.builder().content("option3").build(), + PostOptionUpdateRequest.builder().content("option4").build())) + .deadline(LocalDateTime.now().plusDays(1)).build(); + + // when, then + assertThatThrownBy(() -> postService.update(savedPostId, postUpdateRequest, writer, List.of(file4), + List.of(file5, file6))).isInstanceOf(BadRequestException.class) + .hasMessage(PostExceptionType.POST_CLOSED.getMessage()); + } + + @Test + @DisplayName("게시글 수정 시, 수정할 마감 기한이 생성 날짜보다 3일 초과면 예외를 던진다.") + void throwExceptionUpdateDeadlineOver() throws IOException { + // given + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image2", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file3 = new MockMultipartFile("image3", "test3.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder().categoryIds(List.of(category1.getId())) + .title("title").content("content").postOptions( + List.of(PostOptionCreateRequest.builder().content("option1").build(), + PostOptionCreateRequest.builder().content("option2").build())) + .deadline(LocalDateTime.now().plusDays(3)).build(); + + Long savedPostId = postService.save(postCreateRequest, writer, List.of(file3), List.of(file1, file2)); + + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + MockMultipartFile file4 = new MockMultipartFile("image4", "test4.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file5 = new MockMultipartFile("image5", "test5.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file6 = new MockMultipartFile("image6", "test6.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostUpdateRequest postUpdateRequest = PostUpdateRequest.builder().categoryIds(List.of(category2.getId())) + .title("title2").content("content2").postOptions( + List.of(PostOptionUpdateRequest.builder().content("option3").build(), + PostOptionUpdateRequest.builder().content("option4").build())) + .deadline(LocalDateTime.now().plusDays(4)).build(); + + // when, then + assertThatThrownBy(() -> postService.update(savedPostId, postUpdateRequest, writer, List.of(file4), + List.of(file5, file6))).isInstanceOf(BadRequestException.class) + .hasMessage(PostExceptionType.DEADLINE_EXCEED_THREE_DAYS.getMessage()); + } + + @Test + @DisplayName("게시글을 수정할 시, 해당 게시글이 투표가 되어있으면 예외를 던진다.") + void throwExceptionUpdateVotingProgress() throws IOException { + // given + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile("image1", "test1.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file2 = new MockMultipartFile("image2", "test2.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file3 = new MockMultipartFile("image3", "test3.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder().categoryIds(List.of(category1.getId())) + .title("title").content("content").postOptions( + List.of(PostOptionCreateRequest.builder().content("option1").build(), + PostOptionCreateRequest.builder().content("option2").build())) + .deadline(LocalDateTime.now().plusDays(2)).build(); + + Long savedPostId = postService.save(postCreateRequest, writer, List.of(file3), List.of(file1, file2)); + + Member memberToVote = Member.builder().nickname("Abel").gender(Gender.MALE).birthYear(2000) + .socialType(SocialType.KAKAO).socialId("Abel").build(); + + memberRepository.save(memberToVote); + + final Post post = postRepository.findById(savedPostId).get(); + final List postOptions = post.getPostOptions().getPostOptions(); + PostOption perPostOption = postOptions.get(0); + voteService.vote(memberToVote, savedPostId, perPostOption.getId()); + entityManager.clear(); + + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + MockMultipartFile file4 = new MockMultipartFile("image4", "test4.png", "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG")); + MockMultipartFile file5 = new MockMultipartFile("image5", "test5.png", "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG")); + + MockMultipartFile file6 = new MockMultipartFile("image6", "test6.png", "image/png", + new FileInputStream("src/test/resources/images/testImage3.PNG")); + + PostUpdateRequest postUpdateRequest = PostUpdateRequest.builder().categoryIds(List.of(category2.getId())) + .title("title2").content("content2").postOptions( + List.of(PostOptionUpdateRequest.builder().content("option3").build(), + PostOptionUpdateRequest.builder().content("option4").build())) + .deadline(LocalDateTime.now().plusDays(1)).build(); + + // when, then + assertThatThrownBy(() -> postService.update(savedPostId, postUpdateRequest, writer, List.of(file4), + List.of(file5, file6))).isInstanceOf(BadRequestException.class) + .hasMessage(PostExceptionType.VOTING_PROGRESS_NOT_EDITABLE.getMessage()); + } + + @Test + @DisplayName("회원 본인이 작성한 게시글 목록을 가져온다.") + void getPostsByWriter() { + // given + Member writer = memberTestPersister.builder().save(); + Post post = postTestPersister.builder().writer(writer).save(); + PostOption postOption = postOptionTestPersister.builder().post(post).sequence(1).save(); + PostOption postOption1 = postOptionTestPersister.builder().post(post).sequence(2).save(); + voteTestPersister.builder().postOption(postOption).save(); + voteTestPersister.builder().postOption(postOption).save(); + voteTestPersister.builder().postOption(postOption1).save(); + + entityManager.flush(); + entityManager.clear(); + + // when + List responses = postService.getPostsByWriter(0, PostClosingType.ALL, PostSortType.LATEST, null, + writer); + + // then + assertAll(() -> assertThat(responses).hasSize(1), + () -> assertThat(responses.get(0).postId()).isEqualTo(post.getId()), + () -> assertThat(responses.get(0).writer().id()).isEqualTo(writer.getId()), + () -> assertThat(responses.get(0).voteInfo().totalVoteCount()).isEqualTo(3L)); + } + + @Test + @DisplayName("회원으로 키워드를 통해 게시글 목록을 검색한다.") + void getPostsByKeyword() { + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + Member member1 = memberRepository.save(MemberFixtures.MALE_30.get()); + + Post openPost = postTestPersister.builder().postBody(PostBody.builder().title("제목").content("키워요").build()) + .deadline(LocalDateTime.now().plusDays(3L)).save(); + Post openPost1 = postTestPersister.builder().postBody(PostBody.builder().title("키워드").content("안녕").build()) + .deadline(LocalDateTime.now().plusDays(3L)).save(); + + PostOption postOption = postOptionTestPersister.builder().post(openPost).save(); + PostOption postOption1 = postOptionTestPersister.builder().post(openPost1).save(); + voteTestPersister.builder().member(member).postOption(postOption).save(); + voteTestPersister.builder().member(member1).postOption(postOption1).save(); + + entityManager.flush(); + entityManager.clear(); + + // when + List responses = postService.searchPostsWithKeyword("키워", 0, PostClosingType.ALL, + PostSortType.LATEST, null, member); + + // then + assertAll(() -> assertThat(responses).hasSize(2), + () -> assertThat(responses.get(0).postId()).isEqualTo(openPost1.getId()), + () -> assertThat(responses.get(1).postId()).isEqualTo(openPost.getId()), + () -> assertThat(hasKeywordInPostResponse(responses.get(0), "키워")).isTrue(), + () -> assertThat(hasKeywordInPostResponse(responses.get(1), "키워")).isTrue(), + () -> assertThat(responses.get(0).voteInfo().totalVoteCount()).isEqualTo(-1L), + () -> assertThat(responses.get(1).voteInfo().totalVoteCount()).isEqualTo(1L)); + } + + private boolean hasKeywordInPostResponse(PostResponse postResponse, String keyword) { + return postResponse.title().contains(keyword) || postResponse.content().contains(keyword); + } + + @Test + @DisplayName("비회원으로 키워드를 통해 게시글 목록을 검색한다.") + void getPostsByKeywordForGuest() { + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + Post closedPost = postTestPersister.builder().postBody(PostBody.builder().title("제목").content("키워요").build()) + .deadline(LocalDateTime.now().minusDays(3L)).save(); + Post openPost1 = postTestPersister.builder().postBody(PostBody.builder().title("키워드").content("안녕").build()) + .deadline(LocalDateTime.now().plusDays(3L)).save(); + + PostOption postOption = postOptionTestPersister.builder().post(closedPost).save(); + PostOption postOption1 = postOptionTestPersister.builder().post(openPost1).save(); + voteTestPersister.builder().member(member).postOption(postOption).save(); + voteTestPersister.builder().member(member).postOption(postOption1).save(); + + entityManager.flush(); + entityManager.clear(); + + // when + List responses = postService.searchPostsWithKeywordForGuest("키워", 0, PostClosingType.ALL, + PostSortType.LATEST, null); + + // then + assertAll(() -> assertThat(responses).hasSize(2), + () -> assertThat(responses.get(0).postId()).isEqualTo(openPost1.getId()), + () -> assertThat(responses.get(1).postId()).isEqualTo(closedPost.getId()), + () -> assertThat(hasKeywordInPostResponse(responses.get(0), "키워")).isTrue(), + () -> assertThat(hasKeywordInPostResponse(responses.get(1), "키워")).isTrue(), + () -> assertThat(responses.get(0).voteInfo().totalVoteCount()).isEqualTo(-1L), + () -> assertThat(responses.get(1).voteInfo().totalVoteCount()).isEqualTo(1L)); + } + + + @Nested + @DisplayName("상위 10개 인기 게시물을 조회한다.") + class Ranking { + + @Test + @DisplayName("중복 순위가 있는 경우") + void getRanking() { + // given + List posts = new ArrayList<>(); + List postOptions = new ArrayList<>(); + + for (int i = 0; i < 11; i++) { + Post post = postTestPersister.builder().save(); + PostOption postOption = postOptionTestPersister.builder().post(post).save(); + posts.add(post); + postOptions.add(postOption); + } + + for (int i = 0; i < 10; i++) { + for (int j = 0; j < i + 1; j++) { + voteTestPersister.builder().postOption(postOptions.get(i)).save(); + } + } + + voteTestPersister.builder().postOption(postOptions.get(10)).save(); + voteTestPersister.builder().postOption(postOptions.get(10)).save(); + voteTestPersister.builder().postOption(postOptions.get(10)).save(); + voteTestPersister.builder().postOption(postOptions.get(7)).save(); + + /* + index |0| |1| |2| |3| |4| |5| |6| |7| |8| |9| |10| + voteCount |1| |2| |3| |4| |5| |6| |7| |9| |9| |10| |3| + ranking |11| |10| |8| |7| |6| |5| |4| |2| |2| |1| |8| + */ + + entityManager.clear(); + entityManager.flush(); + + // when + List rankings = postService.getRanking(); + + // then + assertAll( + () -> assertThat(rankings).hasSize(10), + () -> assertThat(rankings.get(0).ranking()).isEqualTo(1), + () -> assertThat(rankings.get(1).ranking()).isEqualTo(2), + () -> assertThat(rankings.get(2).ranking()).isEqualTo(2), + () -> assertThat(rankings.get(3).ranking()).isEqualTo(4), + () -> assertThat(rankings.get(4).ranking()).isEqualTo(5), + () -> assertThat(rankings.get(5).ranking()).isEqualTo(6), + () -> assertThat(rankings.get(6).ranking()).isEqualTo(7), + () -> assertThat(rankings.get(7).ranking()).isEqualTo(8), + () -> assertThat(rankings.get(8).ranking()).isEqualTo(8), + () -> assertThat(rankings.get(9).ranking()).isEqualTo(10), + + () -> assertThat(rankings.get(0).postSummaryResponse().id()).isEqualTo(posts.get(9).getId()), + () -> assertThat(rankings.get(3).postSummaryResponse().id()).isEqualTo(posts.get(6).getId()), + () -> assertThat(rankings.get(4).postSummaryResponse().id()).isEqualTo(posts.get(5).getId()), + () -> assertThat(rankings.get(5).postSummaryResponse().id()).isEqualTo(posts.get(4).getId()), + () -> assertThat(rankings.get(6).postSummaryResponse().id()).isEqualTo(posts.get(3).getId()), + () -> assertThat(rankings.get(9).postSummaryResponse().id()).isEqualTo(posts.get(1).getId()), + () -> assertThat( + List.of(rankings.get(7).postSummaryResponse().id(), + rankings.get(8).postSummaryResponse().id()) + .containsAll(List.of(posts.get(10).getId(), posts.get(2).getId()))), + () -> assertThat( + List.of(rankings.get(1).postSummaryResponse().id(), + rankings.get(2).postSummaryResponse().id()) + .containsAll(List.of(posts.get(7).getId(), + posts.get(8).getId()))) + ); + } + + @Test + @DisplayName("중복 순위가 없는 경우") + void getRanking1() { + // given + List posts = new ArrayList<>(); + List postOptions = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + Post post = postTestPersister.builder().save(); + PostOption postOption = postOptionTestPersister.builder().post(post).save(); + posts.add(post); + postOptions.add(postOption); + } + + for (int i = 0; i < 10; i++) { + for (int j = 0; j < i + 1; j++) { + voteTestPersister.builder().postOption(postOptions.get(i)).save(); + } + } + + /* + index |0| |1| |2| |3| |4| |5| |6| |7| |8| |9| + voteCount |1| |2| |3| |4| |5| |6| |7| |8| |9| |10| + ranking |10| |9| |8| |7| |6| |5| |4| |3| |2| |1| + */ + + entityManager.clear(); + entityManager.flush(); + + // when + List rankings = postService.getRanking(); + + // then + assertAll( + () -> assertThat(rankings).hasSize(10), + () -> assertThat(rankings.get(0).ranking()).isEqualTo(1), + () -> assertThat(rankings.get(1).ranking()).isEqualTo(2), + () -> assertThat(rankings.get(2).ranking()).isEqualTo(3), + () -> assertThat(rankings.get(3).ranking()).isEqualTo(4), + () -> assertThat(rankings.get(4).ranking()).isEqualTo(5), + () -> assertThat(rankings.get(5).ranking()).isEqualTo(6), + () -> assertThat(rankings.get(6).ranking()).isEqualTo(7), + () -> assertThat(rankings.get(7).ranking()).isEqualTo(8), + () -> assertThat(rankings.get(8).ranking()).isEqualTo(9), + () -> assertThat(rankings.get(9).ranking()).isEqualTo(10), + + () -> assertThat(rankings.get(0).postSummaryResponse().id()).isEqualTo(posts.get(9).getId()), + () -> assertThat(rankings.get(1).postSummaryResponse().id()).isEqualTo(posts.get(8).getId()), + () -> assertThat(rankings.get(2).postSummaryResponse().id()).isEqualTo(posts.get(7).getId()), + () -> assertThat(rankings.get(3).postSummaryResponse().id()).isEqualTo(posts.get(6).getId()), + () -> assertThat(rankings.get(4).postSummaryResponse().id()).isEqualTo(posts.get(5).getId()), + () -> assertThat(rankings.get(5).postSummaryResponse().id()).isEqualTo(posts.get(4).getId()), + () -> assertThat(rankings.get(6).postSummaryResponse().id()).isEqualTo(posts.get(3).getId()), + () -> assertThat(rankings.get(7).postSummaryResponse().id()).isEqualTo(posts.get(2).getId()), + () -> assertThat(rankings.get(8).postSummaryResponse().id()).isEqualTo(posts.get(1).getId()), + () -> assertThat(rankings.get(9).postSummaryResponse().id()).isEqualTo(posts.get(0).getId()) + ); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/domain/ranking/controller/RankingControllerTest.java b/backend/src/test/java/com/votogether/domain/ranking/controller/RankingControllerTest.java new file mode 100644 index 000000000..7bae813aa --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/ranking/controller/RankingControllerTest.java @@ -0,0 +1,115 @@ +package com.votogether.domain.ranking.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.service.MemberService; +import com.votogether.domain.ranking.dto.response.RankingResponse; +import com.votogether.domain.ranking.service.RankingService; +import com.votogether.global.jwt.TokenPayload; +import com.votogether.global.jwt.TokenProcessor; +import com.votogether.test.fixtures.MemberFixtures; +import io.restassured.common.mapper.TypeRef; +import io.restassured.module.mockmvc.RestAssuredMockMvc; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.context.WebApplicationContext; + +@WebMvcTest(RankingController.class) +class RankingControllerTest { + + @MockBean + RankingService rankingService; + + @MockBean + MemberService memberService; + + @MockBean + TokenProcessor tokenProcessor; + + @BeforeEach + void setUp(final WebApplicationContext webApplicationContext) { + RestAssuredMockMvc.standaloneSetup(new RankingController(rankingService)); + RestAssuredMockMvc.webAppContextSetup(webApplicationContext); + } + + @Test + @DisplayName("회원의 랭킹 순위를 가져온다.") + void getRanking() throws Exception { + // given + Member member = MemberFixtures.MALE_20.get(); + RankingResponse response = new RankingResponse(3, "익명의손님1", 1, 1, 6); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(member); + + given(rankingService.getPassionRanking(member)).willReturn(response); + + // when, then + RankingResponse result = RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().get("/members/me/ranking/passion") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract() + .as(RankingResponse.class); + + assertThat(result).isEqualTo(response); + } + + @Test + @DisplayName("상위 10명의 랭킹을 조회한다.") + void getPassionRankingTop10() { + // given + Member memberA = MemberFixtures.MALE_20.get(); + Member memberB = MemberFixtures.MALE_30.get(); + Member memberC = MemberFixtures.MALE_40.get(); + Member memberD = MemberFixtures.MALE_50.get(); + Member memberE = MemberFixtures.MALE_60.get(); + Member memberF = MemberFixtures.FEMALE_20.get(); + Member memberG = MemberFixtures.FEMALE_30.get(); + Member memberH = MemberFixtures.FEMALE_30.get(); + Member memberI = MemberFixtures.FEMALE_30.get(); + Member memberJ = MemberFixtures.FEMALE_30.get(); + + List members = List.of( + memberA, memberB, memberC, memberD, memberE, + memberF, memberG, memberH, memberI, memberJ + ); + List response = new ArrayList<>(); + for (Member member : members) { + response.add(new RankingResponse(1, member.getNickname(), 0, 0, 0)); + } + + given(rankingService.getPassionRanking()).willReturn(response); + + // when + List responses = RestAssuredMockMvc + .given().log().all() + .contentType(MediaType.APPLICATION_JSON) + .when().get("/members/ranking/passion/guest") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract() + .as(new TypeRef<>() { + }); + + // then + assertThat(responses).usingRecursiveComparison().isEqualTo(response); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/ranking/service/RankingServiceTest.java b/backend/src/test/java/com/votogether/domain/ranking/service/RankingServiceTest.java new file mode 100644 index 000000000..cfeea9c15 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/ranking/service/RankingServiceTest.java @@ -0,0 +1,109 @@ +package com.votogether.domain.ranking.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.ranking.dto.response.RankingResponse; +import com.votogether.test.annotation.ServiceTest; +import com.votogether.test.persister.MemberTestPersister; +import com.votogether.test.persister.PostOptionTestPersister; +import com.votogether.test.persister.PostTestPersister; +import com.votogether.test.persister.VoteTestPersister; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@ServiceTest +class RankingServiceTest { + + @Autowired + RankingService rankingService; + + @Autowired + MemberTestPersister memberTestPersister; + + @Autowired + PostTestPersister postTestPersister; + + @Autowired + PostOptionTestPersister postOptionTestPersister; + + @Autowired + VoteTestPersister voteTestPersister; + + @Test + @DisplayName("회원의 열정 랭킹을 조회한다.") + void getMemberPassionRanking() { + // given + Member memberA = memberTestPersister.builder().save(); + Member memberB = memberTestPersister.builder().save(); + Member memberC = memberTestPersister.builder().save(); + Member memberD = memberTestPersister.builder().save(); + Member memberE = memberTestPersister.builder().save(); + + postTestPersister.builder().writer(memberA).save(); + postTestPersister.builder().writer(memberB).save(); + postTestPersister.builder().writer(memberC).save(); + postTestPersister.builder().writer(memberD).save(); + + voteTestPersister.builder().member(memberA).save(); + voteTestPersister.builder().member(memberC).save(); + voteTestPersister.builder().member(memberD).save(); + voteTestPersister.builder().member(memberD).save(); + voteTestPersister.builder().member(memberE).save(); + + // when + RankingResponse response = rankingService.getPassionRanking(memberA); + + // then (score: 6,5,7,6,1) + assertAll( + () -> assertThat(response.ranking()).isEqualTo(2), + () -> assertThat(response.postCount()).isEqualTo(1), + () -> assertThat(response.voteCount()).isEqualTo(1), + () -> assertThat(response.score()).isEqualTo(6) + ); + } + + @Test + @DisplayName("상위10명의 열정 유저 랭킹을 조회한다.") + void getPassionRankingTop10() { + // given + Member memberA = memberTestPersister.builder().save(); + Member memberB = memberTestPersister.builder().save(); + memberTestPersister.builder().save(); + memberTestPersister.builder().save(); + memberTestPersister.builder().save(); + memberTestPersister.builder().save(); + memberTestPersister.builder().save(); + memberTestPersister.builder().save(); + memberTestPersister.builder().save(); + memberTestPersister.builder().save(); + + postTestPersister.builder().writer(memberA).save(); + postTestPersister.builder().writer(memberB).save(); + + // when + final List rankings = rankingService.getPassionRanking(); + + // then + assertAll( + () -> assertThat(rankings.get(0).ranking()).isEqualTo(1), + () -> assertThat(rankings.get(0).postCount()).isEqualTo(1), + () -> assertThat(rankings.get(0).score()).isEqualTo(5), + () -> assertThat(rankings.get(1).ranking()).isEqualTo(1), + () -> assertThat(rankings.get(1).postCount()).isEqualTo(1), + () -> assertThat(rankings.get(1).score()).isEqualTo(5), + () -> assertThat(rankings.get(2).ranking()).isEqualTo(3), + () -> assertThat(rankings.get(3).ranking()).isEqualTo(3), + () -> assertThat(rankings.get(4).ranking()).isEqualTo(3), + () -> assertThat(rankings.get(5).ranking()).isEqualTo(3), + () -> assertThat(rankings.get(6).ranking()).isEqualTo(3), + () -> assertThat(rankings.get(7).ranking()).isEqualTo(3), + () -> assertThat(rankings.get(8).ranking()).isEqualTo(3), + () -> assertThat(rankings.get(9).ranking()).isEqualTo(3) + ); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/report/controller/ReportControllerTest.java b/backend/src/test/java/com/votogether/domain/report/controller/ReportControllerTest.java new file mode 100644 index 000000000..699f90f96 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/report/controller/ReportControllerTest.java @@ -0,0 +1,213 @@ +package com.votogether.domain.report.controller; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.entity.vo.SocialType; +import com.votogether.domain.member.service.MemberService; +import com.votogether.domain.report.dto.request.ReportRequest; +import com.votogether.domain.report.entity.vo.ReportType; +import com.votogether.domain.report.service.ReportService; +import com.votogether.global.jwt.TokenPayload; +import com.votogether.global.jwt.TokenProcessor; +import io.restassured.http.ContentType; +import io.restassured.module.mockmvc.RestAssuredMockMvc; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.web.context.WebApplicationContext; + +@WebMvcTest(ReportController.class) +class ReportControllerTest { + + @MockBean + ReportService reportService; + + @MockBean + TokenProcessor tokenProcessor; + + @MockBean + MemberService memberService; + + @BeforeEach + void setUp(final WebApplicationContext webApplicationContext) { + RestAssuredMockMvc.standaloneSetup(new ReportController(reportService)); + RestAssuredMockMvc.webAppContextSetup(webApplicationContext); + } + + @Nested + @DisplayName("신고 기능은 ") + class Report { + + @Test + @DisplayName("게시글에 대해 정상적으로 작동한다.") + void reportPost() throws Exception { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(1977) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(member); + + ReportRequest request = new ReportRequest(ReportType.POST, 1L, "불건전한 게시글"); + willDoNothing().given(reportService).report(member, request); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.JSON) + .body(request) + .when().post("/report") + .then().log().all() + .statusCode(HttpStatus.OK.value()); + } + + @Test + @DisplayName("댓글에 대해 정상적으로 작동한다.") + void reportComment() throws Exception { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(member); + + ReportRequest request = new ReportRequest(ReportType.COMMENT, 1L, "불건전한 댓글"); + willDoNothing().given(reportService).report(member, request); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.JSON) + .body(request) + .when().post("/report") + .then().log().all() + .statusCode(HttpStatus.OK.value()); + } + + @Test + @DisplayName("닉네임에 대해 정상적으로 작동한다.") + void reportNickname() throws Exception { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(member); + + ReportRequest request = new ReportRequest(ReportType.NICKNAME, 1L, "불건전한 닉네임"); + willDoNothing().given(reportService).report(member, request); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.JSON) + .body(request) + .when().post("/report") + .then().log().all() + .statusCode(HttpStatus.OK.value()); + } + + } + + @ParameterizedTest + @NullSource + @DisplayName("신고 대상 Id가 빈 값인 경우 400을 반환한다.") + void report(Long id) throws Exception { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(member); + + ReportRequest request = new ReportRequest(ReportType.COMMENT, id, "불건전한 게시글"); + willDoNothing().given(reportService).report(member, request); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.JSON) + .body(request) + .when().post("/report") + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("신고 이유가 빈 값일 경우 400을 반환한다.") + void reportBadRequest(String reason) throws Exception { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(member); + + ReportRequest request = new ReportRequest(ReportType.POST, 1L, reason); + willDoNothing().given(reportService).report(member, request); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .contentType(ContentType.JSON) + .body(request) + .when().post("/report") + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/report/repository/ReportRepositoryTest.java b/backend/src/test/java/com/votogether/domain/report/repository/ReportRepositoryTest.java new file mode 100644 index 000000000..2c1422730 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/report/repository/ReportRepositoryTest.java @@ -0,0 +1,111 @@ +package com.votogether.domain.report.repository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.domain.report.entity.Report; +import com.votogether.domain.report.entity.vo.ReportType; +import com.votogether.test.annotation.RepositoryTest; +import com.votogether.test.fixtures.MemberFixtures; +import java.time.LocalDateTime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@RepositoryTest +class ReportRepositoryTest { + + @Autowired + ReportRepository reportRepository; + + @Autowired + MemberRepository memberRepository; + + @Autowired + PostRepository postRepository; + + @Test + @DisplayName("회원, 신고타입, 대상ID를 통해서 신고 횟수를 반환한다.") + void countByMemberAndReportTypeAndTargetId() { + // given + Member member = MemberFixtures.MALE_20.get(); + ReportType reportType = ReportType.POST; + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(member) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + memberRepository.save(member); + postRepository.save(post); + + Report report = Report.builder() + .member(member) + .reportType(reportType) + .targetId(post.getId()) + .reason("불건전한 게시글") + .build(); + reportRepository.save(report); + + // when + int reportCount = reportRepository.countByReportTypeAndTargetId(reportType, post.getId()); + + // then + assertThat(reportCount).isEqualTo(1); + } + + @Test + @DisplayName("회원, 신고유형, 신고대상ID를 통해 해당 신고정보를 반환한다.") + void findByMemberAndReportTypeAndTargetId() { + // given + Member member = MemberFixtures.FEMALE_30.get(); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(member) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + memberRepository.save(member); + postRepository.save(post); + + Report report = Report.builder() + .targetId(post.getId()) + .reportType(ReportType.POST) + .member(member) + .reason("불건전한 게시글") + .build(); + reportRepository.save(report); + + // when + Report actualReport = reportRepository.findByMemberAndReportTypeAndTargetId( + member, + ReportType.POST, + post.getId() + ).get(); + + // then + assertAll( + () -> assertThat(actualReport.getTargetId()).isEqualTo(post.getId()), + () -> assertThat(actualReport.getMember()).isEqualTo(member), + () -> assertThat(actualReport.getReportType()).isEqualTo(ReportType.POST) + ); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/report/service/ReportServiceTest.java b/backend/src/test/java/com/votogether/domain/report/service/ReportServiceTest.java new file mode 100644 index 000000000..74374aed9 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/report/service/ReportServiceTest.java @@ -0,0 +1,517 @@ +package com.votogether.domain.report.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.post.dto.response.post.PostResponse; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.comment.Comment; +import com.votogether.domain.post.entity.vo.PostClosingType; +import com.votogether.domain.post.entity.vo.PostSortType; +import com.votogether.domain.post.repository.CommentRepository; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.domain.post.service.PostCommentService; +import com.votogether.domain.post.service.PostService; +import com.votogether.domain.report.dto.request.ReportRequest; +import com.votogether.domain.report.entity.vo.ReportType; +import com.votogether.domain.report.repository.ReportRepository; +import com.votogether.global.exception.BadRequestException; +import com.votogether.global.exception.NotFoundException; +import com.votogether.test.annotation.ServiceTest; +import com.votogether.test.fixtures.MemberFixtures; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@ServiceTest +class ReportServiceTest { + + @Autowired + ReportService reportService; + + @Autowired + MemberRepository memberRepository; + + @Autowired + PostRepository postRepository; + + @Autowired + ReportRepository reportRepository; + + @Autowired + CommentRepository commentRepository; + + @Autowired + PostService postService; + + @Autowired + PostCommentService postCommentService; + + @Nested + @DisplayName("게시글 신고기능은") + class ReportPost { + + @Test + @DisplayName("정상적으로 동작한다.") + void reportPost() { + // given + Member reporter = memberRepository.save(MemberFixtures.FEMALE_60.get()); + Member writer = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + postRepository.save(post); + + ReportRequest request = new ReportRequest(ReportType.POST, post.getId(), "불건전한 게시글"); + + // when, then + assertDoesNotThrow(() -> reportService.report(reporter, request)); + } + + @Test + @DisplayName("없는 투표글을 신고하는 경우 예외가 발생한다.") + void reportNonExistPostThrowsException() { + // given + Member writer = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + ReportRequest request = new ReportRequest(ReportType.POST, -1L, "불건전한 게시글"); + + // when, then + assertThatThrownBy(() -> reportService.report(writer, request)) + .isInstanceOf(NotFoundException.class) + .hasMessage("해당 게시글이 존재하지 않습니다."); + } + + @Test + @DisplayName("자신의 투표글을 신고하는 경우 예외가 발생한다.") + void reportOwnPostThrowsException() { + // given + Member writer = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + postRepository.save(post); + + ReportRequest request = new ReportRequest(ReportType.POST, post.getId(), "불건전한 게시글"); + + // when, then + assertThatThrownBy(() -> reportService.report(writer, request)) + .isInstanceOf(BadRequestException.class) + .hasMessage("자신의 게시글은 신고할 수 없습니다."); + } + + @Test + @DisplayName("블라인드 처리된 투표글을 신고하는 경우 예외가 발생한다.") + void reportHiddenPost() { + // given + Member reporter = memberRepository.save(MemberFixtures.FEMALE_60.get()); + Member writer = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + post.blind(); + + postRepository.save(post); + + ReportRequest request = new ReportRequest(ReportType.POST, post.getId(), "불건전한 게시글"); + + // when, then + + assertThatThrownBy(() -> reportService.report(reporter, request)) + .isInstanceOf(BadRequestException.class) + .hasMessage("이미 블라인드 처리된 글입니다."); + } + + @Test + @DisplayName("하나의 회원이 투표글을 중복하여 신고하면 예외를 던진다.") + void reportDuplicated() { + // given + Member reporter = memberRepository.save(MemberFixtures.FEMALE_20.get()); + Member writer = memberRepository.save(MemberFixtures.FEMALE_10.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + postRepository.save(post); + + ReportRequest request = new ReportRequest(ReportType.POST, post.getId(), "불건전한 게시글"); + + // when + reportService.report(reporter, request); + + // then + assertThatThrownBy(() -> reportService.report(reporter, request)) + .isInstanceOf(BadRequestException.class) + .hasMessage("하나의 글에 대해서 중복하여 신고할 수 없습니다."); + } + + @Test + @DisplayName("투표글 신고가 5회가 되면 블라인드 처리가 된다.") + void reportAndBlind() { + // given + Member reporter1 = memberRepository.save(MemberFixtures.FEMALE_20.get()); + Member reporter2 = memberRepository.save(MemberFixtures.FEMALE_30.get()); + Member reporter3 = memberRepository.save(MemberFixtures.FEMALE_40.get()); + Member reporter4 = memberRepository.save(MemberFixtures.FEMALE_50.get()); + Member reporter5 = memberRepository.save(MemberFixtures.FEMALE_60.get()); + Member writer = memberRepository.save(MemberFixtures.FEMALE_10.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + postRepository.save(post); + + ReportRequest request = new ReportRequest(ReportType.POST, post.getId(), "불건전한 게시글"); + + // when + reportService.report(reporter1, request); + reportService.report(reporter2, request); + reportService.report(reporter3, request); + reportService.report(reporter4, request); + reportService.report(reporter5, request); + + // then + final List responses = postService.getPostsGuest( + 0, + PostClosingType.ALL, + PostSortType.HOT, + null + ); + + assertAll( + () -> assertThat(post.isHidden()).isTrue(), + () -> assertThat(responses).isEmpty() + ); + } + + } + + @Nested + @DisplayName("댓글 신고기능은") + class ReportComment { + + @Test + @DisplayName("정상적으로 동작한다.") + void reportComment() { + // given + Member reporter = memberRepository.save(MemberFixtures.FEMALE_60.get()); + Member writer = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + Comment comment = Comment.builder() + .post(post) + .member(writer) + .content("으어어어어") + .build(); + + postRepository.save(post); + commentRepository.save(comment); + + ReportRequest request = new ReportRequest(ReportType.COMMENT, comment.getId(), "불건전한 게시글"); + + // when, then + assertDoesNotThrow(() -> reportService.report(reporter, request)); + } + + @Test + @DisplayName("없는 댓글을 신고하는 경우 예외가 발생한다.") + void reportNonExistCommentThrowsException() { + // given + Member writer = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + ReportRequest request = new ReportRequest(ReportType.COMMENT, -1L, "불건전한 댓글"); + + // when, then + assertThatThrownBy(() -> reportService.report(writer, request)) + .isInstanceOf(NotFoundException.class) + .hasMessage("해당 댓글이 존재하지 않습니다."); + } + + @Test + @DisplayName("자신의 댓글을 신고하는 경우 예외가 발생한다.") + void reportOwnCommentThrowsException() { + // given + Member writer = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + Comment comment = Comment.builder() + .post(post) + .member(writer) + .content("으어어어어") + .build(); + + postRepository.save(post); + commentRepository.save(comment); + + ReportRequest request = new ReportRequest(ReportType.COMMENT, comment.getId(), "불건전한 댓글"); + + // when, then + assertThatThrownBy(() -> reportService.report(writer, request)) + .isInstanceOf(BadRequestException.class) + .hasMessage("자신의 댓글은 신고할 수 없습니다."); + } + + @Test + @DisplayName("블라인드 처리된 댓글을 신고하는 경우 예외가 발생한다.") + void reportHiddenComment() { + // given + Member reporter = memberRepository.save(MemberFixtures.FEMALE_60.get()); + Member writer = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + Comment comment = Comment.builder() + .post(post) + .member(writer) + .content("으어어어어") + .build(); + + postRepository.save(post); + commentRepository.save(comment); + + ReportRequest request = new ReportRequest(ReportType.COMMENT, comment.getId(), "불건전한 댓글"); + + // when, then + comment.blind(); + assertThatThrownBy(() -> reportService.report(reporter, request)) + .isInstanceOf(BadRequestException.class) + .hasMessage("이미 블라인드 처리된 댓글입니다."); + } + + @Test + @DisplayName("하나의 회원이 댓글을 중복하여 신고하면 예외를 던진다.") + void reportDuplicated() { + // given + Member reporter = memberRepository.save(MemberFixtures.FEMALE_20.get()); + Member writer = memberRepository.save(MemberFixtures.FEMALE_10.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + Comment comment = Comment.builder() + .post(post) + .member(writer) + .content("으어어어어") + .build(); + + postRepository.save(post); + commentRepository.save(comment); + + ReportRequest request = new ReportRequest(ReportType.COMMENT, comment.getId(), "불건전한 댓글"); + + // when + reportService.report(reporter, request); + + // then + assertThatThrownBy(() -> reportService.report(reporter, request)) + .isInstanceOf(BadRequestException.class) + .hasMessage("하나의 댓글에 대해서 중복하여 신고할 수 없습니다."); + } + + @Test + @DisplayName("댓글 신고가 5회가 되면 블라인드 처리가 된다.") + void reportAndBlind() { + // given + Member reporter1 = memberRepository.save(MemberFixtures.FEMALE_20.get()); + Member reporter2 = memberRepository.save(MemberFixtures.FEMALE_30.get()); + Member reporter3 = memberRepository.save(MemberFixtures.FEMALE_40.get()); + Member reporter4 = memberRepository.save(MemberFixtures.FEMALE_50.get()); + Member reporter5 = memberRepository.save(MemberFixtures.FEMALE_60.get()); + Member writer = memberRepository.save(MemberFixtures.FEMALE_10.get()); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build(); + + Comment comment = Comment.builder() + .post(post) + .member(writer) + .content("으어어어어") + .build(); + + postRepository.save(post); + commentRepository.save(comment); + + ReportRequest request = new ReportRequest(ReportType.COMMENT, comment.getId(), "불건전한 댓글"); + + // when + reportService.report(reporter1, request); + reportService.report(reporter2, request); + reportService.report(reporter3, request); + reportService.report(reporter4, request); + reportService.report(reporter5, request); + + // then + assertAll( + () -> assertThat(comment.isHidden()).isTrue(), + () -> assertThat(postCommentService.getComments(post.getId())).isEmpty() + ); + } + + } + + @Nested + @DisplayName("닉네임 신고기능은") + class ReportNickname { + + @Test + @DisplayName("정상적으로 동작한다.") + void reportNickname() { + // given + Member reporter = memberRepository.save(MemberFixtures.FEMALE_60.get()); + Member reported = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + ReportRequest request = new ReportRequest(ReportType.NICKNAME, reported.getId(), "불건전한 닉네임"); + + // when, then + assertDoesNotThrow(() -> reportService.report(reporter, request)); + } + + @Test + @DisplayName("자신의 닉네임을 신고하는 경우 예외가 발생한다.") + void reportOwnNicknameThrowsException() { + // given + Member reporter = memberRepository.save(MemberFixtures.FEMALE_30.get()); + + ReportRequest request = new ReportRequest(ReportType.NICKNAME, reporter.getId(), "불건전한 닉네임"); + + // when, then + assertThatThrownBy(() -> reportService.report(reporter, request)) + .isInstanceOf(BadRequestException.class) + .hasMessage("자신의 닉네임은 신고할 수 없습니다."); + } + + @Test + @DisplayName("하나의 회원이 다른 회원의 닉네임을 중복하여 신고하면 예외를 던진다.") + void reportDuplicated() { + // given + Member reporter = memberRepository.save(MemberFixtures.FEMALE_20.get()); + Member reported = memberRepository.save(MemberFixtures.FEMALE_10.get()); + + ReportRequest request = new ReportRequest(ReportType.NICKNAME, reported.getId(), "불건전한 닉네임"); + + // when + reportService.report(reporter, request); + + // then + assertThatThrownBy(() -> reportService.report(reporter, request)) + .isInstanceOf(BadRequestException.class) + .hasMessage("하나의 닉네임에 대해서 중복하여 신고할 수 없습니다."); + } + + @Test + @DisplayName("닉네임 신고가 3회가 되면 닉네임이 자동변경처리가 된다.") + void reportAndBlind() { + // given + Member reporter1 = memberRepository.save(MemberFixtures.FEMALE_20.get()); + Member reporter2 = memberRepository.save(MemberFixtures.FEMALE_30.get()); + Member reporter3 = memberRepository.save(MemberFixtures.FEMALE_40.get()); + Member reported = memberRepository.save(MemberFixtures.FEMALE_10.get()); + + ReportRequest request = new ReportRequest(ReportType.NICKNAME, reported.getId(), "불건전한 닉네임"); + + // when + reportService.report(reporter1, request); + reportService.report(reporter2, request); + reportService.report(reporter3, request); + + // then + assertThat(reported.getNickname()).contains("Pause1"); + } + } + +} diff --git a/backend/src/test/java/com/votogether/domain/vote/repository/VoteRepositoryTest.java b/backend/src/test/java/com/votogether/domain/vote/repository/VoteRepositoryTest.java new file mode 100644 index 000000000..476e83f5c --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/vote/repository/VoteRepositoryTest.java @@ -0,0 +1,322 @@ +package com.votogether.domain.vote.repository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.PostOption; +import com.votogether.domain.post.repository.PostOptionRepository; +import com.votogether.domain.post.repository.PostRepository; +import com.votogether.domain.vote.entity.Vote; +import com.votogether.domain.vote.repository.dto.VoteStatus; +import com.votogether.test.annotation.RepositoryTest; +import com.votogether.test.fixtures.MemberFixtures; +import com.votogether.test.persister.MemberTestPersister; +import com.votogether.test.persister.VoteTestPersister; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@RepositoryTest +class VoteRepositoryTest { + + @Autowired + MemberRepository memberRepository; + + @Autowired + PostRepository postRepository; + + @Autowired + VoteRepository voteRepository; + + @Autowired + PostOptionRepository postOptionRepository; + + @Autowired + VoteTestPersister voteTestPersister; + + @Autowired + MemberTestPersister memberTestPersister; + + @Test + @DisplayName("투표를 저장한다.") + void save() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + Post post = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + PostOption postOption = postOptionRepository.save( + PostOption.builder() + .post(post) + .sequence(1) + .content("치킨") + .build() + ); + + Vote vote = Vote.builder() + .postOption(postOption) + .member(member) + .build(); + + // when + voteRepository.save(vote); + + // then + assertThat(vote.getId()).isNotNull(); + } + + @Test + @DisplayName("멤버와 투표선택지를 통해 투표를 찾는다.") + void findByMemberAndPostOption() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + Post post = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + PostOption postOption = postOptionRepository.save( + PostOption.builder() + .post(post) + .sequence(1) + .content("치킨") + .build() + ); + + Vote vote = voteRepository.save(Vote.builder() + .postOption(postOption) + .member(member) + .build()); + + // when + Vote findVote = voteRepository.findByMemberAndPostOption(member, postOption).get(); + + // then + assertThat(findVote).isSameAs(vote); + } + + @Test + @DisplayName("멤버와 여러 투표선택지를 통해 투표를 찾는다.") + void findByMemberAndPostOptionIn() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + Post postA = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + PostOption postOptionA = postOptionRepository.save( + PostOption.builder() + .post(postA) + .sequence(1) + .content("치킨") + .build() + ); + Post postB = postRepository.save( + Post.builder() + .writer(member) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + PostOption postOptionB = postOptionRepository.save( + PostOption.builder() + .post(postB) + .sequence(1) + .content("치킨") + .build() + ); + + Vote voteA = Vote.builder() + .postOption(postOptionA) + .member(member) + .build(); + Vote voteB = Vote.builder() + .postOption(postOptionB) + .member(member) + .build(); + voteRepository.save(voteA); + voteRepository.save(voteB); + + // when + List votes = voteRepository.findAllByMemberAndPostOptionIn(member, List.of(postOptionA, postOptionB)); + + // then + assertThat(votes).hasSize(2); + } + + @Test + @DisplayName("게시글의 연령대와 성별로 그룹화된 투표 통계를 조회한다.") + void findVoteCountByPostIdGroupByAgeRangeAndGender() { + // given + Member femaleEarly10 = memberRepository.save(MemberFixtures.FEMALE_10.get()); + Member male10 = memberRepository.save(MemberFixtures.MALE_10.get()); + Member male60 = memberRepository.save(MemberFixtures.MALE_60.get()); + Member female70 = memberRepository.save(MemberFixtures.FEMALE_70.get()); + Member female80 = memberRepository.save(MemberFixtures.FEMALE_80.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + Post post = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + PostOption postOptionA = postOptionRepository.save( + PostOption.builder() + .post(post) + .sequence(1) + .content("치킨") + .build() + ); + PostOption postOptionB = postOptionRepository.save( + PostOption.builder() + .post(post) + .sequence(2) + .content("피자") + .build() + ); + + voteRepository.save(Vote.builder().member(femaleEarly10).postOption(postOptionA).build()); + voteRepository.save(Vote.builder().member(male10).postOption(postOptionB).build()); + voteRepository.save(Vote.builder().member(male60).postOption(postOptionA).build()); + voteRepository.save(Vote.builder().member(female70).postOption(postOptionB).build()); + voteRepository.save(Vote.builder().member(female80).postOption(postOptionA).build()); + + // when + List result = voteRepository.findVoteCountByPostIdGroupByAgeRangeAndGender(post.getId()); + + // then + assertThat(result).containsExactly( + new VoteStatus(2005, Gender.FEMALE, 1), + new VoteStatus(2005, Gender.MALE, 1), + new VoteStatus(1955, Gender.MALE, 1), + new VoteStatus(1945, Gender.FEMALE, 1), + new VoteStatus(1935, Gender.FEMALE, 1) + ); + } + + @Test + @DisplayName("게시글 투표 옵션의 연령대와 성별로 그룹화된 투표 통계를 조회한다.") + void findVoteCountByPostOptionIdGroupByAgeRangeAndGender() { + // given + Member femaleEarly10 = memberRepository.save(MemberFixtures.FEMALE_10.get()); + Member male10 = memberRepository.save(MemberFixtures.MALE_10.get()); + Member male60 = memberRepository.save(MemberFixtures.MALE_60.get()); + Member female70 = memberRepository.save(MemberFixtures.FEMALE_70.get()); + Member female80 = memberRepository.save(MemberFixtures.FEMALE_80.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + + Post post = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + PostOption postOption = postOptionRepository.save( + PostOption.builder() + .post(post) + .sequence(1) + .content("치킨") + .build() + ); + + voteRepository.save(Vote.builder().member(femaleEarly10).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(male10).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(male60).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(female70).postOption(postOption).build()); + voteRepository.save(Vote.builder().member(female80).postOption(postOption).build()); + + // when + List result = + voteRepository.findVoteCountByPostOptionIdGroupByAgeRangeAndGender(postOption.getId()); + + // then + assertThat(result).containsExactly( + new VoteStatus(2005, Gender.FEMALE, 1), + new VoteStatus(2005, Gender.MALE, 1), + new VoteStatus(1955, Gender.MALE, 1), + new VoteStatus(1945, Gender.FEMALE, 1), + new VoteStatus(1935, Gender.FEMALE, 1) + ); + } + + @Test + @DisplayName("해당 회원이 투표한 개수를 반환한다.") + void countByMember() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_10.get()); + Member writer = memberRepository.save(MemberFixtures.MALE_20.get()); + Post post = postRepository.save( + Post.builder() + .writer(writer) + .postBody(PostBody.builder().title("title").content("content").build()) + .deadline(LocalDateTime.of(2100, 7, 12, 0, 0)) + .build() + ); + PostOption postOption = postOptionRepository.save( + PostOption.builder() + .post(post) + .sequence(1) + .content("치킨") + .build() + ); + Vote vote = Vote.builder() + .member(member) + .postOption(postOption) + .build(); + + voteRepository.save(vote); + + // when + int numberOfVote = voteRepository.countByMember(member); + + // then + assertThat(numberOfVote).isEqualTo(1); + } + + @Test + @DisplayName("모든 멤버의 투표 개수를 가져온다.") + void findCountsByMembers() { + // given + Member member = memberTestPersister.builder().save(); + Member member1 = memberTestPersister.builder().save(); + Member member2 = memberTestPersister.builder().save(); + + voteTestPersister.builder().member(member).save(); + voteTestPersister.builder().member(member1).save(); + voteTestPersister.builder().member(member1).save(); + + // when + List voteCounts = voteRepository.findCountsByMembers(List.of(member, member1, member2)); + + // then + assertAll( + () -> assertThat(voteCounts).hasSize(3), + () -> assertThat(voteCounts.get(0)).isEqualTo(1), + () -> assertThat(voteCounts.get(1)).isEqualTo(2), + () -> assertThat(voteCounts.get(2)).isEqualTo(0) + ); + } + +} diff --git a/backend/src/test/java/com/votogether/global/jwt/TokenProcessorTest.java b/backend/src/test/java/com/votogether/global/jwt/TokenProcessorTest.java new file mode 100644 index 000000000..bbe8f8c07 --- /dev/null +++ b/backend/src/test/java/com/votogether/global/jwt/TokenProcessorTest.java @@ -0,0 +1,104 @@ +package com.votogether.global.jwt; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.entity.vo.SocialType; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.test.annotation.RepositoryTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; + +@Import(TokenProcessor.class) +@RepositoryTest +class TokenProcessorTest { + + @Autowired + MemberRepository memberRepository; + + @Autowired + TokenProcessor tokenProcessor; + + @Test + @DisplayName("토큰을 생성한다.") + void generateToken() throws Exception { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + + memberRepository.save(member); + + // when + String token = tokenProcessor.generateAccessToken(member.getId()); + + // then + TokenPayload tokenPayload = tokenProcessor.parseToken(token); + assertThat(tokenPayload.memberId()).isEqualTo(member.getId()); + } + + @Nested + @DisplayName("토큰의 prefix를 제외한 값을 추출할 때") + class ResolveToken { + + @Test + @DisplayName("Bearer가 prefix면 성공한다.") + void resolveTokenSuccess() throws JsonProcessingException { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + memberRepository.save(member); + + String token = tokenProcessor.generateAccessToken(member.getId()); + token = "Bearer " + token; + + // when + String resolvedToken = tokenProcessor.resolveToken(token); + + // then + TokenPayload tokenPayload = tokenProcessor.parseToken(resolvedToken); + assertThat(tokenPayload.memberId()).isEqualTo(member.getId()); + } + + @ParameterizedTest + @ValueSource(strings = {"Bear", "Barrier", "Baerer", "bearer"}) + @DisplayName("Bearer가 아닌 다른 prefix라면 예외를 발생시킨다.") + void resolveTokenFail(String prefix) { + // given + Member member = Member.builder() + .nickname("저문") + .gender(Gender.MALE) + .birthYear(1966) + .socialId("abc123") + .socialType(SocialType.KAKAO) + .build(); + memberRepository.save(member); + + String token = prefix + tokenProcessor.generateAccessToken(member.getId()); + + // when, then + assertThatThrownBy(() -> tokenProcessor.resolveToken(token)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("올바르지 않은 토큰입니다."); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/test/annotation/RepositoryTest.java b/backend/src/test/java/com/votogether/test/annotation/RepositoryTest.java new file mode 100644 index 000000000..84151b385 --- /dev/null +++ b/backend/src/test/java/com/votogether/test/annotation/RepositoryTest.java @@ -0,0 +1,22 @@ +package com.votogether.test.annotation; + +import com.votogether.global.config.JpaConfig; +import com.votogether.global.config.QuerydslConfig; +import com.votogether.test.persister.Persister; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.Import; + +@Target(value = ElementType.TYPE) +@Retention(value = RetentionPolicy.RUNTIME) +@Import({JpaConfig.class, QuerydslConfig.class}) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@DataJpaTest(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Persister.class)) +public @interface RepositoryTest { +} diff --git a/backend/src/test/java/com/votogether/test/annotation/ServiceTest.java b/backend/src/test/java/com/votogether/test/annotation/ServiceTest.java new file mode 100644 index 000000000..477e4b0e1 --- /dev/null +++ b/backend/src/test/java/com/votogether/test/annotation/ServiceTest.java @@ -0,0 +1,15 @@ +package com.votogether.test.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Target(value = ElementType.TYPE) +@Retention(value = RetentionPolicy.RUNTIME) +@Transactional +@SpringBootTest +public @interface ServiceTest { +} diff --git a/backend/src/test/java/com/votogether/test/config/RedisTestConfig.java b/backend/src/test/java/com/votogether/test/config/RedisTestConfig.java new file mode 100644 index 000000000..09fa3491f --- /dev/null +++ b/backend/src/test/java/com/votogether/test/config/RedisTestConfig.java @@ -0,0 +1,23 @@ +package com.votogether.test.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +@TestConfiguration +public class RedisTestConfig { + + private static final String REDIS_DOCKER_IMAGE = "redis:7.2.1-alpine"; + + static { + GenericContainer REDIS_CONTAINER = new GenericContainer<>(DockerImageName.parse(REDIS_DOCKER_IMAGE)) + .withExposedPorts(6379) + .withReuse(true); + + REDIS_CONTAINER.start(); + + System.setProperty("spring.data.redis.host", REDIS_CONTAINER.getHost()); + System.setProperty("spring.data.redis.port", REDIS_CONTAINER.getMappedPort(6379).toString()); + } + +} diff --git a/backend/src/test/java/com/votogether/test/fixtures/CategoryFixtures.java b/backend/src/test/java/com/votogether/test/fixtures/CategoryFixtures.java new file mode 100644 index 000000000..3ba4bf991 --- /dev/null +++ b/backend/src/test/java/com/votogether/test/fixtures/CategoryFixtures.java @@ -0,0 +1,23 @@ +package com.votogether.test.fixtures; + +import com.votogether.domain.category.entity.Category; + +public enum CategoryFixtures { + + DEVELOP("개발"), + FOOD("음식"), + ; + + private final String name; + + CategoryFixtures(final String name) { + this.name = name; + } + + public Category get() { + return Category.builder() + .name(name) + .build(); + } + +} diff --git a/backend/src/test/java/com/votogether/test/fixtures/MemberFixtures.java b/backend/src/test/java/com/votogether/test/fixtures/MemberFixtures.java new file mode 100644 index 000000000..a37c71fb2 --- /dev/null +++ b/backend/src/test/java/com/votogether/test/fixtures/MemberFixtures.java @@ -0,0 +1,53 @@ +package com.votogether.test.fixtures; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.entity.vo.SocialType; + +public enum MemberFixtures { + + MALE_UNDER_10("user1", Gender.MALE, 2015, "user1"), + FEMALE_UNDER_10("user2", Gender.FEMALE, 2015, "user2"), + MALE_10("user3", Gender.MALE, 2005, "user3"), + FEMALE_10("user4", Gender.FEMALE, 2005, "user4"), + MALE_20("user7", Gender.MALE, 1995, "user7"), + FEMALE_20("user8", Gender.FEMALE, 1995, "user8"), + MALE_30("user9", Gender.MALE, 1985, "user9"), + FEMALE_30("user10", Gender.FEMALE, 1985, "user10"), + MALE_40("user11", Gender.MALE, 1975, "user11"), + FEMALE_40("user12", Gender.FEMALE, 1975, "user12"), + MALE_50("user13", Gender.MALE, 1965, "user13"), + FEMALE_50("user14", Gender.FEMALE, 1965, "user14"), + MALE_60("user15", Gender.MALE, 1955, "user15"), + FEMALE_60("user16", Gender.FEMALE, 1955, "user16"), + MALE_70("user17", Gender.MALE, 1945, "user17"), + FEMALE_70("user18", Gender.FEMALE, 1945, "user18"), + MALE_80("user19", Gender.MALE, 1935, "user19"), + FEMALE_80("user20", Gender.FEMALE, 1935, "user20"), + MALE_OVER_90("user21", Gender.MALE, 1925, "user21"), + FEMALE_OVER_90("user22", Gender.FEMALE, 1925, "user22"), + ; + + private final String nickname; + private final Gender gender; + private final Integer birthYear; + private final String socialId; + + MemberFixtures(final String nickname, final Gender gender, final Integer birthYear, final String socialId) { + this.nickname = nickname; + this.gender = gender; + this.birthYear = birthYear; + this.socialId = socialId; + } + + public Member get() { + return Member.builder() + .nickname(nickname) + .gender(gender) + .birthYear(birthYear) + .socialType(SocialType.KAKAO) + .socialId(socialId) + .build(); + } + +} diff --git a/backend/src/test/java/com/votogether/test/persister/MemberTestPersister.java b/backend/src/test/java/com/votogether/test/persister/MemberTestPersister.java new file mode 100644 index 000000000..4e111c4bc --- /dev/null +++ b/backend/src/test/java/com/votogether/test/persister/MemberTestPersister.java @@ -0,0 +1,66 @@ +package com.votogether.test.persister; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.entity.vo.Gender; +import com.votogether.domain.member.entity.vo.SocialType; +import com.votogether.domain.member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.RandomStringUtils; + +@RequiredArgsConstructor +@Persister +public class MemberTestPersister { + + private final MemberRepository memberRepository; + + public MemberBuilder builder() { + return new MemberBuilder(); + } + + public final class MemberBuilder { + + private String nickname; + private Gender gender; + private Integer birthYear; + private SocialType socialType; + private String socialId; + + public MemberBuilder nickname(String nickname) { + this.nickname = nickname; + return this; + } + + public MemberBuilder gender(Gender gender) { + this.gender = gender; + return this; + } + + public MemberBuilder birthday(Integer birthday) { + this.birthYear = birthday; + return this; + } + + public MemberBuilder socialType(SocialType socialType) { + this.socialType = socialType; + return this; + } + + public MemberBuilder socialId(String socialId) { + this.socialId = socialId; + return this; + } + + public Member save() { + Member member = Member.builder() + .nickname(nickname == null ? RandomStringUtils.random(10, true, true) : nickname) + .gender(gender == null ? Gender.MALE : gender) + .birthYear(birthYear == null ? 1995 : birthYear) + .socialType(socialType == null ? SocialType.KAKAO : socialType) + .socialId(socialId == null ? "id" : socialId) + .build(); + return memberRepository.save(member); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/test/persister/Persister.java b/backend/src/test/java/com/votogether/test/persister/Persister.java new file mode 100644 index 000000000..cabfadc34 --- /dev/null +++ b/backend/src/test/java/com/votogether/test/persister/Persister.java @@ -0,0 +1,13 @@ +package com.votogether.test.persister; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.stereotype.Component; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Component +public @interface Persister { +} diff --git a/backend/src/test/java/com/votogether/test/persister/PostOptionTestPersister.java b/backend/src/test/java/com/votogether/test/persister/PostOptionTestPersister.java new file mode 100644 index 000000000..f2eaa8347 --- /dev/null +++ b/backend/src/test/java/com/votogether/test/persister/PostOptionTestPersister.java @@ -0,0 +1,59 @@ +package com.votogether.test.persister; + +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostOption; +import com.votogether.domain.post.repository.PostOptionRepository; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.RandomStringUtils; + +@RequiredArgsConstructor +@Persister +public class PostOptionTestPersister { + + private final PostTestPersister postTestPersister; + private final PostOptionRepository postOptionRepository; + + public PostOptionBuilder builder() { + return new PostOptionBuilder(); + } + + public final class PostOptionBuilder { + + private Post post; + private int sequence; + private String content; + private String imageUrl; + + public PostOptionBuilder post(Post post) { + this.post = post; + return this; + } + + public PostOptionBuilder sequence(int sequence) { + this.sequence = sequence; + return this; + } + + public PostOptionBuilder content(String content) { + this.content = content; + return this; + } + + public PostOptionBuilder imageUrl(String imageUrl) { + this.imageUrl = imageUrl; + return this; + } + + public PostOption save() { + PostOption postOption = PostOption.builder() + .post(post == null ? postTestPersister.builder().save() : post) + .sequence(sequence) + .content(content == null ? RandomStringUtils.random(10, true, true) : content) + .imageUrl(imageUrl) + .build(); + return postOptionRepository.save(postOption); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/test/persister/PostTestPersister.java b/backend/src/test/java/com/votogether/test/persister/PostTestPersister.java new file mode 100644 index 000000000..7f2e3cfb0 --- /dev/null +++ b/backend/src/test/java/com/votogether/test/persister/PostTestPersister.java @@ -0,0 +1,60 @@ +package com.votogether.test.persister; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.repository.PostRepository; +import java.time.LocalDateTime; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Persister +public class PostTestPersister { + + private final MemberTestPersister memberTestPersister; + private final PostRepository postRepository; + + public PostBuilder builder() { + return new PostBuilder(); + } + + public final class PostBuilder { + + private Member writer; + private PostBody postBody; + private LocalDateTime deadline; + + public PostBuilder writer(Member writer) { + this.writer = writer; + return this; + } + + public PostBuilder postBody(PostBody postBody) { + this.postBody = postBody; + return this; + } + + public PostBuilder deadline(LocalDateTime deadline) { + this.deadline = deadline; + return this; + } + + public Post save() { + Post post = Post.builder() + .writer(writer == null ? memberTestPersister.builder().save() : writer) + .postBody(postBody == null ? generatePostBody() : postBody) + .deadline(deadline == null ? LocalDateTime.of(2100, 12, 25, 0, 0) : deadline) + .build(); + return postRepository.save(post); + } + + private PostBody generatePostBody() { + return PostBody.builder() + .title("title") + .content("content") + .build(); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/test/persister/VoteTestPersister.java b/backend/src/test/java/com/votogether/test/persister/VoteTestPersister.java new file mode 100644 index 000000000..19d1af3fe --- /dev/null +++ b/backend/src/test/java/com/votogether/test/persister/VoteTestPersister.java @@ -0,0 +1,46 @@ +package com.votogether.test.persister; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.entity.PostOption; +import com.votogether.domain.vote.entity.Vote; +import com.votogether.domain.vote.repository.VoteRepository; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Persister +public class VoteTestPersister { + + private final MemberTestPersister memberTestPersister; + private final PostOptionTestPersister postOptionTestPersister; + private final VoteRepository voteRepository; + + public VoteBuilder builder() { + return new VoteBuilder(); + } + + public final class VoteBuilder { + + private Member member; + private PostOption postOption; + + public VoteBuilder member(Member member) { + this.member = member; + return this; + } + + public VoteBuilder postOption(PostOption postOption) { + this.postOption = postOption; + return this; + } + + public Vote save() { + Vote vote = Vote.builder() + .member(member == null ? memberTestPersister.builder().save() : member) + .postOption(postOption == null ? postOptionTestPersister.builder().save() : postOption) + .build(); + return voteRepository.save(vote); + } + + } + +} diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml new file mode 100644 index 000000000..f0314148f --- /dev/null +++ b/backend/src/test/resources/application.yml @@ -0,0 +1,62 @@ +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:test;MODE=MySQL + username: sa + password: + + jpa: + database-platform: org.hibernate.dialect.MySQLDialect + show-sql: true + + properties: + hibernate: + format_sql: true + default_batch_fetch_size: 50 + + hibernate: + ddl-auto: create + + servlet: + multipart: + max-file-size: 30MB + max-request-size: 30MB + + h2: + console: + enabled: false + path: /h2-console + + data: + redis: + host: localhost + port: 6379 + +logging: + level: + org.hibernate.orm.jdbc.bind: trace + +server: + forward-headers-strategy: FRAMEWORK + +springdoc: + swagger-ui: + enabled: false + +votogether: + openapi: + dev-url: http://localhost:8080 + +oauth: + kakao: + info: + grant_type: aaaaaaaaaaaaaaaa + client_id: bbbbbbbbbbbbbbbbbbbbbbbbbbbb + client_secret: cccccccccccccccccccccccccccccccccccccc + redirect_uri: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +jwt: + token: + secret-key: abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabc + access-expiration-time: 100000 + refresh-expiration-time: 222222 diff --git a/backend/src/test/resources/images/testImage1.PNG b/backend/src/test/resources/images/testImage1.PNG new file mode 100644 index 000000000..9297c169e Binary files /dev/null and b/backend/src/test/resources/images/testImage1.PNG differ diff --git a/backend/src/test/resources/images/testImage2.PNG b/backend/src/test/resources/images/testImage2.PNG new file mode 100644 index 000000000..2cd1fb916 Binary files /dev/null and b/backend/src/test/resources/images/testImage2.PNG differ diff --git a/backend/src/test/resources/images/testImage3.PNG b/backend/src/test/resources/images/testImage3.PNG new file mode 100644 index 000000000..0fba0748f Binary files /dev/null and b/backend/src/test/resources/images/testImage3.PNG differ diff --git a/frontend/.babelrc.json b/frontend/.babelrc.json new file mode 100644 index 000000000..00ca841a4 --- /dev/null +++ b/frontend/.babelrc.json @@ -0,0 +1,16 @@ +{ + "sourceType": "unambiguous", + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "chrome": 100 + } + } + ], + "@babel/preset-typescript", + "@babel/preset-react" + ], + "plugins": [] +} diff --git a/frontend/.env.test b/frontend/.env.test new file mode 100644 index 000000000..c7f936bf7 --- /dev/null +++ b/frontend/.env.test @@ -0,0 +1,2 @@ +VOTOGETHER_BASE_URL='' +VOTOGETHER_MOCKING_URL='' diff --git a/frontend/.eslintrc b/frontend/.eslintrc new file mode 100644 index 000000000..d748fc6ef --- /dev/null +++ b/frontend/.eslintrc @@ -0,0 +1,104 @@ +{ + "extends": ["react-app", "eslint:recommended"], + "rules": { + "no-var": "error", // var 금지 + "no-multiple-empty-lines": "error", // 여러 줄 공백 금지 + "no-console": ["error", { "allow": ["warn", "error", "info"] }], + "eqeqeq": "error", // 일치 연산자 사용 필수 + "dot-notation": "error", // 가능하다면 dot notation 사용 + "no-unused-vars": "error", // 사용하지 않는 변수 금지 + "import/order": [ + "error", + { + "groups": [ + "type", + "builtin", + "external", + "internal", + "parent", + "sibling", + "index", + "unknown" + ], + "newlines-between": "always", + "pathGroups": [ + { + "pattern": "react*", + "group": "external", + "position": "before" + }, + { + "pattern": "@type/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@hooks/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@atoms/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@selectors/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@routes/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@api/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@pages/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@components/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@constants/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@utils/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@styles/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@assets/**", + "group": "internal", + "position": "after" + }, + { + "pattern": "@mocks/**", + "group": "internal", + "position": "after" + } + ], + "alphabetize": { + "caseInsensitive": true, + "order": "asc" + }, + "pathGroupsExcludedImportTypes": [] + } + ] + } +} diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 000000000..1a4f00cb9 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,3 @@ +/node_modules +.env +/dist diff --git a/frontend/.husky/pre-commit b/frontend/.husky/pre-commit new file mode 100755 index 000000000..9d20d3dbc --- /dev/null +++ b/frontend/.husky/pre-commit @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +cd frontend +npx lint-staged diff --git a/frontend/.husky/pre-push b/frontend/.husky/pre-push new file mode 100755 index 000000000..5e9565991 --- /dev/null +++ b/frontend/.husky/pre-push @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +cd frontend +npm run test diff --git a/frontend/.lighthouserc.js b/frontend/.lighthouserc.js new file mode 100644 index 000000000..2c3d07b7a --- /dev/null +++ b/frontend/.lighthouserc.js @@ -0,0 +1,24 @@ +module.exports = { + ci: { + collect: { + staticDistDir: 'dist', + url: ['http://localhost:3000'], + numberOfRuns: 1, + }, + assert: { + assertions: { + // performance 카테고리 점수가 50점 미만이면 warning + 'categories:performance': ['warn', { minScore: 0.5 }], + // accessibility 카테고리 점수가 70점 미만이면 error 발생, build 실패시키기 + 'categories:accessibility': ['error', { minScore: 0.7 }], + 'categories:best-practices': ['warn', { minScore: 0.8 }], + 'categories:seo': ['warn', { minScore: 0.9 }], + }, + }, + upload: { + target: 'filesystem', + outputDir: './lhci_reports', + reportFilenamePattern: '%%PATHNAME%%-%%DATETIME%%-report.%%EXTENSION%%', + }, + }, +}; diff --git a/frontend/.prettierrc.js b/frontend/.prettierrc.js new file mode 100644 index 000000000..83073a6c8 --- /dev/null +++ b/frontend/.prettierrc.js @@ -0,0 +1,5 @@ +module.exports = { + printWidth: 100, // 한줄당 문자 100개로 제한 + singleQuote: true, // "" => '' + arrowParens: 'avoid', // arrow function parameter가 하나일 경우 괄호 생략 +}; diff --git a/frontend/.storybook/main.ts b/frontend/.storybook/main.ts new file mode 100644 index 000000000..ac95e4da7 --- /dev/null +++ b/frontend/.storybook/main.ts @@ -0,0 +1,43 @@ +import path from 'path'; +import type { StorybookConfig } from '@storybook/react-webpack5'; + +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); + +const config: StorybookConfig = { + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + ], + framework: { + name: '@storybook/react-webpack5', + options: {}, + }, + docs: { + autodocs: 'tag', + }, + webpackFinal: async config => { + if (!config.resolve) { + config.resolve = {}; + } + + if (!config.resolve.plugins) { + config.resolve.plugins = []; + } + + config.resolve.plugins.push( + new TsconfigPathsPlugin({ + configFile: path.resolve(__dirname, '../tsconfig.json'), + }) + ); + return config; + }, + staticDirs: ['./public'], + env: config => ({ + ...config, + VOTOGETHER_BASE_URL: '', + VOTOGETHER_MOCKING_URL: '', + }), +}; +export default config; diff --git a/frontend/.storybook/preview.tsx b/frontend/.storybook/preview.tsx new file mode 100644 index 000000000..e858a3aa1 --- /dev/null +++ b/frontend/.storybook/preview.tsx @@ -0,0 +1,49 @@ +import React from 'react'; + +import type { Preview } from '@storybook/react'; + +import { initialize, mswDecorator } from 'msw-storybook-addon'; + +import { GlobalStyle } from '../src/styles/globalStyle'; + +import { BrowserRouter } from 'react-router-dom'; + +import { AuthProvider } from '../../frontend/src/hooks/context/auth'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { handlers } from '../src/mocks/handlers'; + +const queryClient = new QueryClient(); +initialize(); + +const preview: Preview = { + parameters: { + msw: handlers, + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + }, + decorators: [ + mswDecorator, + Story => ( + + + + + + + + + ), + ], +}; + +if (typeof global.process === 'undefined') { + const { worker } = require('../src/mocks/worker'); + worker.start(); +} + +export default preview; diff --git a/frontend/.storybook/public/mockServiceWorker.js b/frontend/.storybook/public/mockServiceWorker.js new file mode 100644 index 000000000..8ee70b3e4 --- /dev/null +++ b/frontend/.storybook/public/mockServiceWorker.js @@ -0,0 +1,303 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (1.2.2). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70' +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + const accept = request.headers.get('accept') || '' + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return + } + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = Math.random().toString(16).slice(2) + + event.respondWith( + handleRequest(event, requestId).catch((error) => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url, + ) + return + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}`, + ) + }), + ) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const clonedResponse = response.clone() + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: + clonedResponse.body === null ? null : await clonedResponse.text(), + headers: Object.fromEntries(clonedResponse.headers.entries()), + redirected: clonedResponse.redirected, + }, + }) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + const clonedRequest = request.clone() + + function passthrough() { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const headers = Object.fromEntries(clonedRequest.headers.entries()) + + // Remove MSW-specific request headers so the bypassed requests + // comply with the server's CORS preflight check. + // Operate with the headers as an object because request "Headers" + // are immutable. + delete headers['x-msw-bypass'] + + return fetch(clonedRequest, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Bypass requests with the explicit bypass header. + // Such requests can be issued by "ctx.fetch()". + if (request.headers.get('x-msw-bypass') === 'true') { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.text(), + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'MOCK_NOT_FOUND': { + return passthrough() + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.data + const networkError = new Error(message) + networkError.name = name + + // Rejecting a "respondWith" promise emulates a network error. + throw networkError + } + } + + return passthrough() +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [channel.port2]) + }) +} + +function sleep(timeMs) { + return new Promise((resolve) => { + setTimeout(resolve, timeMs) + }) +} + +async function respondWithMock(response) { + await sleep(response.delay) + return new Response(response.body, response) +} diff --git a/frontend/.storybook/webpack.config.js b/frontend/.storybook/webpack.config.js new file mode 100644 index 000000000..7eca295d9 --- /dev/null +++ b/frontend/.storybook/webpack.config.js @@ -0,0 +1,17 @@ +const path = require('path'); +const Dotenv = require('dotenv-webpack'); + +const envPath = + process.env.NODE_ENV === 'development' + ? path.join(__dirname, '../webpack/.env.development') + : path.join(__dirname, '../webpack/.env.production'); + +module.exports = ({ config }) => { + config.plugins.push( + new Dotenv({ + path: envPath, + }) + ); + + return config; +}; diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/__test__/api/categoryList.test.ts b/frontend/__test__/api/categoryList.test.ts new file mode 100644 index 000000000..58a757f2a --- /dev/null +++ b/frontend/__test__/api/categoryList.test.ts @@ -0,0 +1,55 @@ +import { + addFavoriteCategory, + getGuestCategoryList, + getUserCategoryList, + removeFavoriteCategory, + transformCategoryListResponse, +} from '@api/categoryList'; + +import { MOCK_CATEGORY_LIST, MOCK_GUEST_CATEGORY_LIST } from '@mocks/mockData/categoryList'; + +describe('카테고리에 대한 통신(카테고리 목록 조회, 즐겨찾기 추가, 제거)이 올바르게 작동하는 지 확인한다.', () => { + test('회원의 카테고리 정보를 불러올 수 있다.', async () => { + const data = await getUserCategoryList(); + + const expectResult = transformCategoryListResponse(MOCK_CATEGORY_LIST); + + expect(data).toEqual(expectResult); + }); + + test('비회원의 카테고리 정보를 불러올 수 있다.', async () => { + const data = await getGuestCategoryList(); + + const expectResult = transformCategoryListResponse(MOCK_GUEST_CATEGORY_LIST); + + expect(data).toEqual(expectResult); + }); + + test('회원이 카테고리 즐겨찾기를 할 수 있다.', async () => { + MOCK_CATEGORY_LIST[1].isFavorite = false; + + await addFavoriteCategory(MOCK_CATEGORY_LIST[1].id); + + const data = await getUserCategoryList(); + + expect(data[1].isFavorite).toBe(true); + }); + + test('회원이 카테고리 즐겨찾기를 해제할 수 있다.', async () => { + MOCK_CATEGORY_LIST[0].isFavorite = true; + + await removeFavoriteCategory(MOCK_CATEGORY_LIST[0].id); + + const data = await getUserCategoryList(); + + expect(data[0].isFavorite).toBe(false); + }); + + test('클라이언트에서 사용하는 API 명세가 [id, name, isFavorite]으로 존재해야한다.', async () => { + const data = await getGuestCategoryList(); + + const categoryKeys = Object.keys(data[0]); + + expect(categoryKeys).toEqual(['id', 'name', 'isFavorite']); + }); +}); diff --git a/frontend/__test__/api/commentList.test.ts b/frontend/__test__/api/commentList.test.ts new file mode 100644 index 000000000..c62815f10 --- /dev/null +++ b/frontend/__test__/api/commentList.test.ts @@ -0,0 +1,19 @@ +import { getCommentList, transformCommentListResponse } from '@api/comment'; + +import { MOCK_COMMENT_LIST } from '@mocks/mockData/comment'; + +describe('서버와 통신하여 댓글 목록을 불러오는지 확인한다.', () => { + test('댓글 목록을 불러온다', async () => { + const data = await getCommentList(1); + + expect(data).toEqual(transformCommentListResponse(MOCK_COMMENT_LIST)); + }); + + test('댓글 목록을 프론트엔드에서 가공해서 사용한다. 오브젝트의 키 값으로는 [id, content, createdAt, member, isEdit] 이 존재한다.', async () => { + const data = await getCommentList(1); + + const commentKeys = Object.keys(data[0]); + + expect(commentKeys).toEqual(['id', 'content', 'createdAt', 'member', 'isEdit']); + }); +}); diff --git a/frontend/__test__/api/postList.test.ts b/frontend/__test__/api/postList.test.ts new file mode 100644 index 000000000..88883999d --- /dev/null +++ b/frontend/__test__/api/postList.test.ts @@ -0,0 +1,187 @@ +import { getPostList } from '@api/postList'; + +import { POST_TYPE, SORTING, STATUS } from '@constants/post'; + +import { MOCK_TRANSFORM_GUEST_POST_LIST, MOCK_TRANSFORM_POST_LIST } from '@mocks/mockData/post'; + +describe('서버와 통신하여 전체 게시글 목록을 불러오는지 확인한다.', () => { + test('게시글 목록의 개수는 10개씩 불러온다.', async () => { + const data = await getPostList( + { + postStatus: STATUS.ALL, + postSorting: SORTING.POPULAR, + pageNumber: 0, + postType: POST_TYPE.ALL, + isLoggedIn: false, + }, + { + categoryId: 0, + keyword: '', + } + ); + + expect(data.postList.length).toBe(10); + }); + + test('(회원)게시글 목록을 불러온다.', async () => { + const data = await getPostList( + { + postStatus: STATUS.CLOSED, + postSorting: SORTING.POPULAR, + pageNumber: 0, + postType: POST_TYPE.ALL, + isLoggedIn: true, + }, + { + categoryId: 0, + keyword: '', + } + ); + + expect(data.postList).toEqual(MOCK_TRANSFORM_POST_LIST); + }); + + test('(비회원)전체 게시글 목록을 불러온다.', async () => { + const data = await getPostList( + { + postStatus: STATUS.CLOSED, + postSorting: SORTING.POPULAR, + pageNumber: 0, + postType: POST_TYPE.SEARCH, + isLoggedIn: false, + }, + { + categoryId: 0, + keyword: '갤럭시', + } + ); + + expect(data.postList).toEqual(MOCK_TRANSFORM_GUEST_POST_LIST); + }); + + test('게시글 페이지의 정보를 불러온다.', async () => { + const data = await getPostList( + { + postStatus: STATUS.CLOSED, + postSorting: SORTING.POPULAR, + pageNumber: 3, + postType: POST_TYPE.ALL, + isLoggedIn: false, + }, + { + categoryId: 0, + keyword: '', + } + ); + + expect(data.pageNumber).toEqual(3); + }); + + test('(회원)카테고리별 게시글 페이지의 정보를 불러온다.', async () => { + const data = await getPostList( + { + postStatus: STATUS.CLOSED, + postSorting: SORTING.POPULAR, + pageNumber: 0, + postType: POST_TYPE.CATEGORY, + isLoggedIn: true, + }, + { + categoryId: 1, + keyword: '', + } + ); + + expect(data.postList).toEqual(MOCK_TRANSFORM_POST_LIST); + }); + + test('(비회원)카테고리별 게시글 페이지의 정보를 불러온다.', async () => { + const data = await getPostList( + { + postStatus: STATUS.CLOSED, + postSorting: SORTING.POPULAR, + pageNumber: 0, + postType: POST_TYPE.CATEGORY, + isLoggedIn: false, + }, + { + categoryId: 1, + keyword: '', + } + ); + + expect(data.postList).toEqual(MOCK_TRANSFORM_GUEST_POST_LIST); + }); + + test('(회원만 가능)내가 작성한 게시글 페이지의 정보를 불러온다.', async () => { + const data = await getPostList( + { + postStatus: STATUS.CLOSED, + postSorting: SORTING.POPULAR, + pageNumber: 0, + postType: POST_TYPE.MY_POST, + isLoggedIn: true, + }, + { + categoryId: 0, + keyword: '', + } + ); + + expect(data.postList).toEqual(MOCK_TRANSFORM_POST_LIST); + }); + + test('(회원만 가능)내가 투표한 게시글 페이지의 정보를 불러온다.', async () => { + const data = await getPostList( + { + postStatus: STATUS.CLOSED, + postSorting: SORTING.POPULAR, + pageNumber: 0, + postType: POST_TYPE.MY_VOTE, + isLoggedIn: true, + }, + { + categoryId: 0, + keyword: '', + } + ); + + expect(data.postList).toEqual(MOCK_TRANSFORM_POST_LIST); + }); + + test('(회원)내가 검색한 게시글 페이지의 정보를 불러온다.', async () => { + const data = await getPostList( + { + postStatus: STATUS.CLOSED, + postSorting: SORTING.POPULAR, + pageNumber: 0, + postType: POST_TYPE.SEARCH, + isLoggedIn: true, + }, + { + categoryId: 0, + keyword: '갤럭시', + } + ); + + expect(data.postList).toEqual(MOCK_TRANSFORM_POST_LIST); + }); + + test('(비회원)내가 검색한 게시글 페이지의 정보를 불러온다.', async () => { + const data = await getPostList( + { + postStatus: STATUS.CLOSED, + postSorting: SORTING.POPULAR, + pageNumber: 0, + postType: POST_TYPE.SEARCH, + isLoggedIn: false, + }, + { + categoryId: 0, + keyword: '갤럭시', + } + ); + + expect(data.postList).toEqual(MOCK_TRANSFORM_GUEST_POST_LIST); + }); +}); diff --git a/frontend/__test__/api/postTokens.test.ts b/frontend/__test__/api/postTokens.test.ts new file mode 100644 index 000000000..7bf0b2ba8 --- /dev/null +++ b/frontend/__test__/api/postTokens.test.ts @@ -0,0 +1,10 @@ +import { postTokens } from '@api/token'; + +import { MOCK_TOKEN } from '@mocks/mockData/token'; + +test('액세스 토큰을 보내서 엑세스 토큰과 리프레시 토큰을 재발급 받는다.', async () => { + const accessToken = 'ge21ieg21gqwg'; + const tokens = await postTokens(accessToken); + + expect(tokens).toEqual(MOCK_TOKEN); +}); diff --git a/frontend/__test__/api/user.test.ts b/frontend/__test__/api/user.test.ts new file mode 100644 index 000000000..03aed922d --- /dev/null +++ b/frontend/__test__/api/user.test.ts @@ -0,0 +1,60 @@ +import { + withdrawalMembership, + getUserInfo, + modifyNickname, + transformUserInfoResponse, + logoutUser, +} from '@api/userInfo'; + +import { REFRESH_EXPIRATION_TIME } from '@constants/token'; + +import { getCookie, setCookie } from '@utils/cookie'; + +import { MOCK_USER_INFO } from '@mocks/mockData/user'; + +describe('서버와 통신하여 유저의 정보를 불러올 수 있어야 한다.', () => { + const isLoggedIn = true; + const isNotLoggedIn = false; + + test('비회원일때는 null을 반환한다.', async () => { + const data = await getUserInfo(isNotLoggedIn); + + expect(data).toEqual(null); + }); + + test('유저의 정보를 불러온다', async () => { + const data = await getUserInfo(isLoggedIn); + + expect(data).toEqual(transformUserInfoResponse(MOCK_USER_INFO)); + }); + + test('클라이언트에서 사용하는 유저 정보 API 명세가 [nickname, gender, birthYear, postCount, voteCount]으로 존재해야한다', async () => { + const data = await getUserInfo(isLoggedIn); + + const userInfoKeys = Object.keys(data ?? {}); + + expect(userInfoKeys).toEqual(['nickname', 'gender', 'birthYear', 'postCount', 'voteCount']); + }); + + test('유저의 닉네임을 수정한다', async () => { + await modifyNickname('wood'); + + expect(MOCK_USER_INFO.nickname).toBe('wood'); + }); + + test('유저가 회원 탈퇴를 한다', async () => { + await withdrawalMembership(); + + expect(MOCK_USER_INFO.nickname).toBe('cancel'); + }); + + test('유저가 로그아웃을 한다', async () => { + setCookie({ key: 'hasEssentialInfo', maxAge: REFRESH_EXPIRATION_TIME, value: 'REFRESH!!' }); + + await logoutUser(); + + const result = getCookie().hasEssentialInfo; + + expect(result).toBe(undefined); + }); +}); diff --git a/frontend/__test__/api/vote.test.ts b/frontend/__test__/api/vote.test.ts new file mode 100644 index 000000000..a64a544ed --- /dev/null +++ b/frontend/__test__/api/vote.test.ts @@ -0,0 +1,14 @@ +import { votePost } from '@api/post'; + +import { MOCK_POST_INFO } from '@mocks/mockData/post'; + +describe('서버와 통신하여 유저의 정보를 불러올 수 있어야 한다.', () => { + test('유저의 정보를 불러온다', async () => { + const postId = 1; + const optionId = 2; + + await votePost(postId, optionId); + + expect(MOCK_POST_INFO.voteInfo.selectedOptionId).toBe(999); + }); +}); diff --git a/frontend/__test__/calculateDeadlineTime.test.ts b/frontend/__test__/calculateDeadlineTime.test.ts new file mode 100644 index 000000000..f7f4b29c7 --- /dev/null +++ b/frontend/__test__/calculateDeadlineTime.test.ts @@ -0,0 +1,94 @@ +import { calculateDeadlineTime } from '@utils/post/calculateDeadlineTime'; + +describe('calculateDeadlineTime 함수를 이용해서 시작시간과 마감시간으로 몇일, 몇시간, 몇분을 설정했는지 구한다.', () => { + test('시작 시간: 2023-07-12 12:40, 마감 시간: 2023-07-13 12:40 일 때 하루를 반환한다.', () => { + const createdAt = '2023-07-12 12:40'; + const deadline = '2023-07-13 12:40'; + + const result = calculateDeadlineTime(createdAt, deadline); + + expect(result).toEqual({ + day: 1, + hour: 0, + minute: 0, + }); + }); + + test('시작 시간: 2023-07-12 12:40, 마감 시간: 2023-07-13 18:40 일 때 1일 6시간을 반환한다.', () => { + const createdAt = '2023-07-12 12:40'; + const deadline = '2023-07-13 18:40'; + + const result = calculateDeadlineTime(createdAt, deadline); + + expect(result).toEqual({ + day: 1, + hour: 6, + minute: 0, + }); + }); + + test('시작 시간: 2023-07-12 12:40, 마감 시간: 2023-07-13 18:20 일 때 1일 5시간 40분을 반환한다.', () => { + const createdAt = '2023-07-12 12:40'; + const deadline = '2023-07-13 18:20'; + + const result = calculateDeadlineTime(createdAt, deadline); + + expect(result).toEqual({ + day: 1, + hour: 5, + minute: 40, + }); + }); + + test('시작 시간: 2023-07-12 12:40, 마감 시간: 2023-07-12 12:50 일 때 10분을 반환한다.', () => { + const createdAt = '2023-07-12 12:40'; + const deadline = '2023-07-12 12:50'; + + const result = calculateDeadlineTime(createdAt, deadline); + + expect(result).toEqual({ + day: 0, + hour: 0, + minute: 10, + }); + }); + + test('시작 시간: 2023-07-12 00:00, 마감 시간: 2023-07-14 23:59 일 때 2일 23시간 59분을 반환한다.', () => { + const createdAt = '2023-07-12 00:00'; + const deadline = '2023-07-14 23:59'; + + const result = calculateDeadlineTime(createdAt, deadline); + + expect(result).toEqual({ + day: 2, + hour: 23, + minute: 59, + }); + }); + + test('시작 시간이 undefined, 마감 시간: 2023-07-14 23:59 일 때 0일 0시간 0분을 반환한다.', () => { + const createdAt = undefined; + const deadline = '2023-07-14 23:59'; + + const result = calculateDeadlineTime(createdAt, deadline); + + expect(result).toEqual({ + day: 0, + hour: 0, + minute: 0, + }); + }); + + test('시작 시간이 2023-07-14 23:59, 마감 시간: undefined 일 때 0일 0시간 0분을 반환한다.', () => { + const createdAt = '2023-07-14 23:59'; + const deadline = undefined; + + const result = calculateDeadlineTime(createdAt, deadline); + + expect(result).toEqual({ + day: 0, + hour: 0, + minute: 0, + }); + }); +}); diff --git a/frontend/__test__/calculatePRDeadline.test.ts b/frontend/__test__/calculatePRDeadline.test.ts new file mode 100644 index 000000000..58a97a801 --- /dev/null +++ b/frontend/__test__/calculatePRDeadline.test.ts @@ -0,0 +1,209 @@ +describe('calculatePRDeadline 함수를 이용해서 PR 생성시간에 따른 코드리뷰 마감시간이 올바른지 테스트한다.', () => { + test('PR 생성시간(한국 기준)이 2023-09-04T01:30:55Z (오늘 9월 4일 월요일 새벽 1시 반) 이면 마감시간은 오늘 20시이다.', () => { + const prCreatedAt = '2023-09-04T01:30:55Z'; + const prReviewDeadline = '9월 4일 20시 00분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-04T11:30:55Z (오늘 9월 4일 월요일 오전 11시 반) 이면 마감시간은 오늘 21시 30분이다.', () => { + const prCreatedAt = '2023-09-04T11:30:55Z'; + const prReviewDeadline = '9월 4일 21시 30분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-05T01:30:55Z (오늘 9월 5일 화요일 새벽 1시 반) 이면 마감시간은 오늘 20시이다.', () => { + const prCreatedAt = '2023-09-05T01:30:55Z'; + const prReviewDeadline = '9월 5일 20시 00분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-05T09:30:55Z (오늘 9월 5일 화요일 오전 9시 반) 이면 마감시간은 오늘 20시이다.', () => { + const prCreatedAt = '2023-09-05T09:30:55Z'; + const prReviewDeadline = '9월 5일 20시 00분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-05T22:30:55Z (오늘 9월 5일 화요일 오후 10시 반) 이면 마감시간은 내일 오후 8시이다.', () => { + const prCreatedAt = '2023-09-05T22:30:55Z'; + const prReviewDeadline = '9월 6일 20시 00분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-05T11:30:55Z (오늘 9월 5일 화요일 오전 11시 반) 이면 마감시간은 오늘 21시 30분이다.', () => { + const prCreatedAt = '2023-09-05T11:30:55Z'; + const prReviewDeadline = '9월 5일 21시 30분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-05T13:30:55Z (오늘 9월 5일 화요일 오후 1시 반) 이면 마감시간은 내일 오전 11시 반이다.', () => { + const prCreatedAt = '2023-09-05T13:30:55Z'; + const prReviewDeadline = '9월 6일 11시 30분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-05T17:30:55Z (오늘 9월 5일 화요일 오후 5시 반) 이면 마감시간은 내일 오후 3시 반이다.', () => { + const prCreatedAt = '2023-09-05T17:30:55Z'; + const prReviewDeadline = '9월 6일 15시 30분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-08-31T17:30:55Z (오늘 8월 31일 오후 5시 반) 이면 마감시간은 내일 오후 3시 반이다.', () => { + const prCreatedAt = '2023-08-31T17:30:55Z'; + const prReviewDeadline = '9월 1일 15시 30분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-08-31T17:30:55Z (오늘 8월 31일 목요일 오후 5시 반) 이면 마감시간은 내일 오후 3시 반이다.', () => { + const prCreatedAt = '2023-08-31T17:30:01Z'; + const prReviewDeadline = '9월 1일 15시 30분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-01T10:30:55Z (오늘 9월 1일 금요일 오전 10시 반) 이면 마감시간은 오늘 오후 8시 반이다.', () => { + const prCreatedAt = '2023-09-01T10:30:55Z'; + const prReviewDeadline = '9월 1일 20시 30분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-01T17:30:55Z (오늘 9월 1일 금요일 오후 5시 반) 이면 마감시간은 다음주 월요일 오후 3시 반이다.', () => { + const prCreatedAt = '2023-09-01T17:30:01Z'; + const prReviewDeadline = '9월 4일 15시 30분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-01T22:30:55Z (오늘 9월 1일 금요일 오후 10시 반) 이면 마감시간은 다음주 월요일 오후 8시이다.', () => { + const prCreatedAt = '2023-09-01T22:30:01Z'; + const prReviewDeadline = '9월 4일 20시 00분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-15T21:30:55Z (오늘 9월 15일 금요일 오후 9시 반) 이면 마감시간은 다음주 월요일 오후 7시 반이다.', () => { + const prCreatedAt = '2023-09-15T21:30:01Z'; + const prReviewDeadline = '9월 18일 19시 30분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-02T17:30:55Z (오늘 9월 2일 토요일 오후 5시 반) 이면 마감시간은 다음주 월요일 오후 3시 반이다.', () => { + const prCreatedAt = '2023-09-02T17:30:01Z'; + const prReviewDeadline = '9월 4일 20시 00분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-03T17:30:55Z (오늘 9월 3일 일요일 오후 5시 반) 이면 마감시간은 내일 오후 8시이다.', () => { + const prCreatedAt = '2023-09-03T17:30:01Z'; + const prReviewDeadline = '9월 4일 20시 00분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-09-30T17:30:55Z (오늘 9월 30일 토요일 오후 5시 반) 이면 마감시간은 다음주 월요일 오후 8시이다.', () => { + const prCreatedAt = '2023-09-30T17:30:01Z'; + const prReviewDeadline = '10월 2일 20시 00분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); + + test('PR 생성시간(한국 기준)이 2023-10-01T11:30:55Z (오늘 10월 1일 일요일 오전 11시 반) 이면 마감시간은 내일 오후 8시이다.', () => { + const prCreatedAt = '2023-10-01T11:30:01Z'; + const prReviewDeadline = '10월 2일 20시 00분'; + + const result = calculatePRDeadline(prCreatedAt); + + expect(result).toEqual(prReviewDeadline); + }); +}); + +function calculatePRDeadline(prCreatedAtKST: string) { + const prCreatedAt = new Date(String(prCreatedAtKST)); + + const prCreatedMinute = prCreatedAt.getUTCMinutes(); + const prCreatedHour = prCreatedAt.getUTCHours(); + const prCreatedDate = prCreatedAt.getUTCDate(); + const prCreatedDay = prCreatedAt.getUTCDay(); + const prCreatedMonth = prCreatedAt.getUTCMonth() + 1; // getUTCMonth()는 0부터 시작하므로 1을 더해줍니다. + + const isFridayAfterTenPM = prCreatedDay === 5 && prCreatedHour >= 22; // 금요일 오후 10시 이후 (금요일: 5, 오후 12시: 12) + const isWeekend = prCreatedDay === 6 || prCreatedDay === 0; // 주말인 경우 + + // 주어진 근무시간(월요일 오전 10시~금요일 오후 10시) 내에 올린 pr인지 판별 + const isNotWorkingTime = isFridayAfterTenPM || isWeekend; + + let nextDay = new Date(prCreatedAt); + nextDay.setUTCDate(prCreatedDate + 1); // 다음 날의 날짜를 설정합니다. + + const nextDayDate = nextDay.getUTCDate(); + const nextDayMonth = nextDay.getUTCMonth() + 1; + + let nextWeekMonday = new Date(prCreatedAt); + const daysUntilMonday = 8 - prCreatedDay; + nextWeekMonday.setUTCDate( + prCreatedDay === 0 ? prCreatedDate + 1 : prCreatedDate + daysUntilMonday + ); + const nextWeekMondayDate = nextWeekMonday.getUTCDate(); + const nextWeekMondayMonth = nextWeekMonday.getUTCMonth() + 1; + + const isFriday = prCreatedDay === 5; + + if (isNotWorkingTime) { + return `${nextWeekMondayMonth}월 ${nextWeekMondayDate}일 20시 00분`; + } + + if (prCreatedHour < 10 && prCreatedHour > 0) + return `${prCreatedMonth}월 ${prCreatedDate}일 20시 00분`; + else if (prCreatedHour === 22 || prCreatedHour === 23) + return `${nextDayMonth}월 ${nextDayDate}일 20시 00분`; + else if (prCreatedHour >= 12) + return `${isFriday ? nextWeekMondayMonth : nextDayMonth}월 ${ + isFriday ? nextWeekMondayDate : nextDayDate + }일 ${prCreatedHour - 2}시 ${prCreatedMinute}분`; + else return `${prCreatedMonth}월 ${prCreatedDate}일 ${prCreatedHour + 10}시 ${prCreatedMinute}분`; +} diff --git a/frontend/__test__/changeCategoryToOption.test.ts b/frontend/__test__/changeCategoryToOption.test.ts new file mode 100644 index 000000000..5f1d34d71 --- /dev/null +++ b/frontend/__test__/changeCategoryToOption.test.ts @@ -0,0 +1,27 @@ +import { Category } from '@type/category'; + +import { Option } from '@components/common/MultiSelect/types'; + +import { changeCategoryToOption } from '@utils/post/changeCategoryToOption'; + +describe('changeCategoryToOption 함수를 이용해서 카테고리 리스트를 셀렉트 컴포넌트에 사용되는 옵션 리스트로 변환한다.', () => { + test('카테고리 리스트로 옵션 리스트를 만든다.', () => { + const categoryList: Category[] = [ + { id: 1, isFavorite: false, name: '갤럭시' }, + { id: 2, isFavorite: true, name: '애플' }, + ]; + + const result: Option[] = changeCategoryToOption(categoryList); + + expect(result).toEqual([ + { + id: 1, + name: '갤럭시', + }, + { + id: 2, + name: '애플', + }, + ]); + }); +}); diff --git a/frontend/__test__/getDeadlineTime.test.ts b/frontend/__test__/getDeadlineTime.test.ts new file mode 100644 index 000000000..162e19740 --- /dev/null +++ b/frontend/__test__/getDeadlineTime.test.ts @@ -0,0 +1,53 @@ +import { getDeadlineTime } from '@utils/post/getDeadlineTime'; + +describe('getDeadlineTime를 이용하여 사용자에게 마감 시간을 알려준다.', () => { + test('5분을 설정했을 때 5분으로 표시된다', () => { + const result = getDeadlineTime({ + day: 0, + hour: 0, + minute: 5, + }); + + expect(result).toBe('5분 후에 마감됩니다.'); + }); + + test('1시간 5분을 설정했을 때 1시간 5분으로 표시된다', () => { + const result = getDeadlineTime({ + day: 0, + hour: 1, + minute: 5, + }); + + expect(result).toBe('1시간 5분 후에 마감됩니다.'); + }); + + test('2일 23시간 59분을 설정했을 때 2일 23시간 59분으로 표시된다', () => { + const result = getDeadlineTime({ + day: 2, + hour: 23, + minute: 59, + }); + + expect(result).toBe('2일 23시간 59분 후에 마감됩니다.'); + }); + + test('0일 0시간 0분을 설정했을 때 "마감 시간을 선택해주세요"를 표시된다', () => { + const result = getDeadlineTime({ + day: 0, + hour: 0, + minute: 0, + }); + + expect(result).toBe('마감 시간을 선택해주세요'); + }); + + test('-1일 -1시간 -1분을 설정했을 때 "마감 시간을 다시 설정해주세요"를 표시된다', () => { + const result = getDeadlineTime({ + day: -1, + hour: -1, + minute: -1, + }); + + expect(result).toBe('마감 시간을 다시 설정해주세요'); + }); +}); diff --git a/frontend/__test__/getPathFragment.test.ts b/frontend/__test__/getPathFragment.test.ts new file mode 100644 index 000000000..edc2d967b --- /dev/null +++ b/frontend/__test__/getPathFragment.test.ts @@ -0,0 +1,21 @@ +import { getPathFragment } from '@utils/getPathFragment'; + +describe('getPathFragment 사용했을 때 path의 값만 나오도록 한다.', () => { + test('/posts/category/12인 경우, /posts/category를 반환한다.', () => { + const result = getPathFragment('/posts/category/12'); + + expect(result).toBe('/posts/category'); + }); + + test('/인 경우, /를 반환한다.', () => { + const result = getPathFragment('/'); + + expect(result).toBe('/'); + }); + + test('/users/posts인 경우, /users/posts를 반환한다.', () => { + const result = getPathFragment('/users/posts'); + + expect(result).toBe('/users/posts'); + }); +}); diff --git a/frontend/__test__/getSelectedState.test.ts b/frontend/__test__/getSelectedState.test.ts new file mode 100644 index 000000000..2f164685f --- /dev/null +++ b/frontend/__test__/getSelectedState.test.ts @@ -0,0 +1,55 @@ +import { SelectedState, getSelectedState } from '@utils/post/getSelectedState'; + +import { MOCK_CATEGORY_LIST } from '@mocks/mockData/categoryList'; + +describe('getSelectedState 사용했을 때 현재 유저에게 어떤 게시글에 대한 종류를 보고 있는지에 대한 정보를 반환한다.', () => { + test('현재 카테고리가 선택되어 있고, 카테고리 아이디가 1번일 때 해당하는 카테고리 이름을 반환한다.', () => { + const categoryId = 1; + const state: SelectedState = { + postType: 'category', + categoryId, + categoryList: MOCK_CATEGORY_LIST, + }; + + const result = getSelectedState(state); + + expect(categoryId).toBe(MOCK_CATEGORY_LIST[0].id); + expect(result).toBe(MOCK_CATEGORY_LIST[0].name); + }); + + test('현재 홈 화면에 있다면, "전체"를 반환한다', () => { + const state: SelectedState = { + postType: 'posts', + categoryId: 0, + categoryList: MOCK_CATEGORY_LIST, + }; + + const result = getSelectedState(state); + + expect(result).toBe('전체'); + }); + + test('현재 내가 작성한 글 페이지에 있다면, "내가 작성한 글"을 반환한다.', () => { + const state: SelectedState = { + postType: 'myPost', + categoryId: 0, + categoryList: MOCK_CATEGORY_LIST, + }; + + const result = getSelectedState(state); + + expect(result).toBe('내가 작성한 글'); + }); + + test('현재 내가 투표한 글 페이지에 있다면, "내가 투표한 글"을 반환한다.', () => { + const state: SelectedState = { + postType: 'myVote', + categoryId: 0, + categoryList: MOCK_CATEGORY_LIST, + }; + + const result = getSelectedState(state); + + expect(result).toBe('내가 투표한 글'); + }); +}); diff --git a/frontend/__test__/getSelectedTimeOption.test.ts b/frontend/__test__/getSelectedTimeOption.test.ts new file mode 100644 index 000000000..7324c2ebc --- /dev/null +++ b/frontend/__test__/getSelectedTimeOption.test.ts @@ -0,0 +1,99 @@ +import { getSelectedTimeOption } from '@utils/post/getSelectedTimeOption'; + +describe('getSelectedTimeOption 함수에서 day, hour, minute 객체를 입력받아 "10분" | "30분" | "1시간" | "6시간" | "1일" | "사용자 지정" | null 을 반환한다.', () => { + test('10분 객체를 입력했을 때 10분을 반환한다.', () => { + const time = { + day: 0, + hour: 0, + minute: 10, + }; + + const result = getSelectedTimeOption(time); + + expect(result).toBe('10분'); + }); + + test('30분 객체를 입력했을 때 30분을 반환한다.', () => { + const time = { + day: 0, + hour: 0, + minute: 30, + }; + + const result = getSelectedTimeOption(time); + + expect(result).toBe('30분'); + }); + + test('1시간 객체를 입력했을 때 1시간을 반환한다.', () => { + const time = { + day: 0, + hour: 1, + minute: 0, + }; + + const result = getSelectedTimeOption(time); + + expect(result).toBe('1시간'); + }); + + test('6시간 객체를 입력했을 때 6시간을 반환한다.', () => { + const time = { + day: 0, + hour: 6, + minute: 0, + }; + + const result = getSelectedTimeOption(time); + + expect(result).toBe('6시간'); + }); + + test('1일 객체를 입력했을 때 1일을 반환한다.', () => { + const time = { + day: 1, + hour: 0, + minute: 0, + }; + + const result = getSelectedTimeOption(time); + + expect(result).toBe('1일'); + }); + + test('2일 객체를 입력했을 때 사용자지정을 반환한다.', () => { + const time = { + day: 2, + hour: 0, + minute: 0, + }; + + const result = getSelectedTimeOption(time); + + expect(result).toBe('사용자지정'); + }); + + test('3분 객체를 입력했을 때 사용자지정을 반환한다.', () => { + const time = { + day: 0, + hour: 0, + minute: 3, + }; + + const result = getSelectedTimeOption(time); + + expect(result).toBe('사용자지정'); + }); + + test('0일 0시간 0분 객체를 입력했을 때 null을 반환한다.', () => { + const time = { + day: 0, + hour: 0, + minute: 0, + }; + + const result = getSelectedTimeOption(time); + + expect(result).toBe(null); + }); +}); diff --git a/frontend/__test__/getTrimmedWord.test.ts b/frontend/__test__/getTrimmedWord.test.ts new file mode 100644 index 000000000..0d6092e60 --- /dev/null +++ b/frontend/__test__/getTrimmedWord.test.ts @@ -0,0 +1,15 @@ +import { getTrimmedWord } from '@utils/getTrimmedWord'; + +test.each([ + ['검색어 입니다', '검색어 입니다'], + [' 완전히 갤럭시 임 ', '완전히 갤럭시 임'], + [' ', ''], + ['', ''], +])( + 'getTrimmedWord 함수에서 단어를 입력했을 때 중복된 공백을 제거한 단어를 반환한다.', + (word, expectedWord) => { + const result = getTrimmedWord(word); + + expect(result).toBe(expectedWord); + } +); diff --git a/frontend/__test__/hook.test.tsx b/frontend/__test__/hook.test.tsx new file mode 100644 index 000000000..790232c0e --- /dev/null +++ b/frontend/__test__/hook.test.tsx @@ -0,0 +1,13 @@ +import { renderHook, act } from '@testing-library/react'; + +import { useCount } from '../src/hooks/useCount'; + +test('useCount hook을 테스트한다.', () => { + const { result } = renderHook(() => useCount()); + + act(() => { + result.current.increase(); + }); + + expect(result.current.count).toBe(1); +}); diff --git a/frontend/__test__/hooks/query/useCancelMembership.test.tsx b/frontend/__test__/hooks/query/useCancelMembership.test.tsx new file mode 100644 index 000000000..82476c565 --- /dev/null +++ b/frontend/__test__/hooks/query/useCancelMembership.test.tsx @@ -0,0 +1,30 @@ +import React, { ReactNode } from 'react'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor } from '@testing-library/react'; + +import { useWithdrawalMembership } from '@hooks/query/user/useWithdrawalMembership'; + +import { MOCK_USER_INFO } from '@mocks/mockData/user'; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: ReactNode }) => ( + {children} +); + +describe('useWithdrawalMembership 훅이 회원 탈퇴를 하는지 확인한다', () => { + test('유저가 회원 탈퇴를 한다', async () => { + const { result } = renderHook(() => useWithdrawalMembership(), { + wrapper, + }); + + const { mutate } = result.current; + + await waitFor(async () => { + mutate(); + + expect(MOCK_USER_INFO.nickname).toBe('cancel'); + }); + }); +}); diff --git a/frontend/__test__/hooks/query/useCategoryList.test.tsx b/frontend/__test__/hooks/query/useCategoryList.test.tsx new file mode 100644 index 000000000..d7fd1a04e --- /dev/null +++ b/frontend/__test__/hooks/query/useCategoryList.test.tsx @@ -0,0 +1,48 @@ +import React, { ReactNode } from 'react'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor } from '@testing-library/react'; + +import { useCategoryList } from '@hooks/query/category/useCategoryList'; + +import { transformCategoryListResponse } from '@api/categoryList'; + +import { MOCK_CATEGORY_LIST, MOCK_GUEST_CATEGORY_LIST } from '@mocks/mockData/categoryList'; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: ReactNode }) => ( + {children} +); + +describe('useCategoryList 훅이 카테고리 목록을 불러오는지 확인한다.', () => { + test('비회원 카테고리 목록을 불러온다.', async () => { + const { result } = renderHook(() => useCategoryList(false), { + wrapper, + }); + + await waitFor(() => + expect(result.current.data).toEqual(transformCategoryListResponse(MOCK_GUEST_CATEGORY_LIST)) + ); + }); + + test('회원 카테고리 목록을 불러온다.', async () => { + const { result } = renderHook(() => useCategoryList(true), { + wrapper, + }); + + await waitFor(() => + expect(result.current.data).toEqual(transformCategoryListResponse(MOCK_CATEGORY_LIST)) + ); + }); + + test('회원 카테고리 목록을 불러온다.', async () => { + const { result } = renderHook(() => useCategoryList(true), { + wrapper, + }); + + await waitFor(() => + expect(result.current.data).toEqual(transformCategoryListResponse(MOCK_CATEGORY_LIST)) + ); + }); +}); diff --git a/frontend/__test__/hooks/query/useCreateVote.test.tsx b/frontend/__test__/hooks/query/useCreateVote.test.tsx new file mode 100644 index 000000000..d0dce660e --- /dev/null +++ b/frontend/__test__/hooks/query/useCreateVote.test.tsx @@ -0,0 +1,30 @@ +import React, { ReactNode } from 'react'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor } from '@testing-library/react'; + +import { useCreateVote } from '@hooks/query/post/useCreateVote'; + +import { MOCK_POST_INFO } from '@mocks/mockData/post'; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: ReactNode }) => ( + {children} +); + +describe('useCreateVote 훅이 선택지를 생성하는 지 확인한다.', () => { + test('선택지를 변경한다.', async () => { + const { result } = renderHook(() => useCreateVote({ isPreview: false, postId: 1 }), { + wrapper, + }); + + const { mutate } = result.current; + + await waitFor(() => { + mutate(1); + + expect(MOCK_POST_INFO.voteInfo.selectedOptionId).toBe(999); + }); + }); +}); diff --git a/frontend/__test__/hooks/query/useEditVote.test.tsx b/frontend/__test__/hooks/query/useEditVote.test.tsx new file mode 100644 index 000000000..3afdcf006 --- /dev/null +++ b/frontend/__test__/hooks/query/useEditVote.test.tsx @@ -0,0 +1,33 @@ +import React, { ReactNode } from 'react'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor } from '@testing-library/react'; + +import { useEditVote } from '@hooks/query/post/useEditVote'; + +import { MOCK_POST_INFO } from '@mocks/mockData/post'; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: ReactNode }) => ( + {children} +); + +describe('useEditVote 훅이 선택지를 바꾸는 지 확인한다.', () => { + test('선택지를 변경한다.', async () => { + const { result } = renderHook(() => useEditVote({ isPreview: false, postId: 1 }), { + wrapper, + }); + + const { mutate } = result.current; + + await waitFor(() => { + mutate({ + newOptionId: 1, + originOptionId: 2, + }); + + expect(MOCK_POST_INFO.voteInfo.selectedOptionId).toBe(888); + }); + }); +}); diff --git a/frontend/__test__/hooks/query/useModifyUser.test.tsx b/frontend/__test__/hooks/query/useModifyUser.test.tsx new file mode 100644 index 000000000..c6fa1a278 --- /dev/null +++ b/frontend/__test__/hooks/query/useModifyUser.test.tsx @@ -0,0 +1,30 @@ +import React, { ReactNode } from 'react'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor } from '@testing-library/react'; + +import { useModifyUser } from '@hooks/query/user/useModifyUser'; + +import { MOCK_USER_INFO } from '@mocks/mockData/user'; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: ReactNode }) => ( + {children} +); + +describe('useModifyUser 훅이 닉네임을 변경하는 지 확인한다', () => { + test('유저가 닉네임을 변경한다', async () => { + const { result } = renderHook(() => useModifyUser(), { + wrapper, + }); + + const { mutate } = result.current; + + await waitFor(async () => { + mutate('wood'); + + expect(MOCK_USER_INFO.nickname).toBe('wood'); + }); + }); +}); diff --git a/frontend/__test__/hooks/query/usePostList.test.tsx b/frontend/__test__/hooks/query/usePostList.test.tsx new file mode 100644 index 000000000..04fd295c1 --- /dev/null +++ b/frontend/__test__/hooks/query/usePostList.test.tsx @@ -0,0 +1,272 @@ +import React, { ReactNode } from 'react'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor } from '@testing-library/react'; + +import { usePostList } from '@hooks/query/usePostList'; + +import { POST_TYPE, SORTING, STATUS } from '@constants/post'; +import { QUERY_KEY } from '@constants/queryKey'; + +import { MOCK_TRANSFORM_GUEST_POST_LIST, MOCK_TRANSFORM_POST_LIST } from '@mocks/mockData/post'; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: ReactNode }) => ( + {children} +); + +beforeEach(() => { + queryClient.invalidateQueries({ + predicate: ({ queryKey }) => queryKey[0] === QUERY_KEY.POSTS, + }); +}); + +describe('usePostList 훅이 게시글 목록을 불러오는지 확인한다.', () => { + test('(회원)전체 게시글 목록을 불러온다.', async () => { + const { result } = renderHook( + () => + usePostList( + { + postSorting: SORTING.POPULAR, + postStatus: STATUS.ALL, + postType: POST_TYPE.ALL, + isLoggedIn: true, + }, + { + categoryId: 0, + keyword: '', + } + ), + { + wrapper, + } + ); + + await waitFor(() => + expect(result.current.data?.pages[0].postList).toEqual(MOCK_TRANSFORM_POST_LIST) + ); + }); + test('(비회원)전체 게시글 목록을 불러온다.', async () => { + const { result } = renderHook( + () => + usePostList( + { + postSorting: SORTING.POPULAR, + postStatus: STATUS.ALL, + postType: POST_TYPE.ALL, + isLoggedIn: false, + }, + { + categoryId: 0, + keyword: '', + } + ), + { + wrapper, + } + ); + + await waitFor(() => + expect(result.current.data?.pages[0].postList).toEqual(MOCK_TRANSFORM_GUEST_POST_LIST) + ); + }); + + test('(회원)카테고리별 게시글 목록을 불러온다.', async () => { + const { result } = renderHook( + () => + usePostList( + { + postSorting: SORTING.POPULAR, + postStatus: STATUS.ALL, + postType: POST_TYPE.CATEGORY, + isLoggedIn: true, + }, + { + categoryId: 1, + keyword: '', + } + ), + { + wrapper, + } + ); + + await waitFor(() => + expect(result.current.data?.pages[0].postList).toEqual(MOCK_TRANSFORM_POST_LIST) + ); + }); + + test('(비회원)카테고리별 게시글 목록을 불러온다.', async () => { + const { result } = renderHook( + () => + usePostList( + { + postSorting: SORTING.POPULAR, + postStatus: STATUS.ALL, + postType: POST_TYPE.CATEGORY, + isLoggedIn: false, + }, + { + categoryId: 1, + keyword: '', + } + ), + { + wrapper, + } + ); + + await waitFor(() => + expect(result.current.data?.pages[0].postList).toEqual(MOCK_TRANSFORM_GUEST_POST_LIST) + ); + }); + + test('(회원만 가능)내가 작성한 게시글 목록을 불러온다.', async () => { + const { result } = renderHook( + () => + usePostList( + { + postSorting: SORTING.POPULAR, + postStatus: STATUS.ALL, + postType: POST_TYPE.MY_POST, + isLoggedIn: true, + }, + { + categoryId: 0, + keyword: '', + } + ), + { + wrapper, + } + ); + + await waitFor(() => + expect(result.current.data?.pages[0].postList).toEqual(MOCK_TRANSFORM_POST_LIST) + ); + }); + + test('(회원만 가능)내가 투표한 게시글 목록을 불러온다.', async () => { + const { result } = renderHook( + () => + usePostList( + { + postSorting: SORTING.POPULAR, + postStatus: STATUS.ALL, + postType: POST_TYPE.MY_VOTE, + isLoggedIn: true, + }, + { + categoryId: 0, + keyword: '', + } + ), + { + wrapper, + } + ); + + await waitFor(() => + expect(result.current.data?.pages[0].postList).toEqual(MOCK_TRANSFORM_POST_LIST) + ); + }); + + test('(회원)내가 검색한 게시글 목록을 불러온다.', async () => { + const { result } = renderHook( + () => + usePostList( + { + postSorting: SORTING.POPULAR, + postStatus: STATUS.ALL, + postType: POST_TYPE.SEARCH, + isLoggedIn: true, + }, + { + categoryId: 0, + keyword: '갤럭시', + } + ), + { + wrapper, + } + ); + + await waitFor(() => + expect(result.current.data?.pages[0].postList).toEqual(MOCK_TRANSFORM_POST_LIST) + ); + }); + + test('(비회원)내가 검색한 게시글 목록을 불러온다.', async () => { + const { result } = renderHook( + () => + usePostList( + { + postSorting: SORTING.POPULAR, + postStatus: STATUS.ALL, + postType: POST_TYPE.SEARCH, + isLoggedIn: false, + }, + { + categoryId: 0, + keyword: '갤럭시', + } + ), + { + wrapper, + } + ); + + await waitFor(() => + expect(result.current.data?.pages[0].postList).toEqual(MOCK_TRANSFORM_GUEST_POST_LIST) + ); + }); + + test('게시글 목록이 존재할 경우 isPostListEmpty는 false를 반환한다.', async () => { + const { result } = renderHook( + () => + usePostList( + { + postSorting: SORTING.POPULAR, + postStatus: STATUS.ALL, + postType: POST_TYPE.SEARCH, + isLoggedIn: true, + }, + { + categoryId: 0, + keyword: '갤럭시', + } + ), + { + wrapper, + } + ); + + await waitFor(() => expect(result.current.data?.pages[0].postList.length).toBe(10)); + await waitFor(() => expect(result.current.isPostListEmpty).toBe(false)); + }); + + test('게시글 목록이 존재하지 않을 경우 isPostListEmpty는 true를 반환한다.', async () => { + const { result } = renderHook( + () => + usePostList( + { + postSorting: SORTING.POPULAR, + postStatus: STATUS.ALL, + postType: POST_TYPE.SEARCH, + isLoggedIn: true, + }, + { + categoryId: 0, + keyword: '999', + } + ), + { + wrapper, + } + ); + + await waitFor(() => expect(result.current.data?.pages[0].postList.length).toBe(0)); + await waitFor(() => expect(result.current.isPostListEmpty).toBe(true)); + }); +}); diff --git a/frontend/__test__/hooks/useMoreComment.test.tsx b/frontend/__test__/hooks/useMoreComment.test.tsx new file mode 100644 index 000000000..b06fff626 --- /dev/null +++ b/frontend/__test__/hooks/useMoreComment.test.tsx @@ -0,0 +1,69 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; + +import { useMoreComment } from '@hooks/useMoreComment'; + +import { MOCK_TRANSFORMED_COMMENT_LIST } from '@mocks/mockData/comment'; + +describe(`useMoreComment 훅에서 댓글 리스트를 입력받고, 10개 단위로 자른 댓글 리스트를 반환한다. +더보기를 했을 때 10개 단위로 추가된다. 더 이상 보여줄 댓글이 없다면 더 이상 데이터를 자를 수 없다고 boolean 변수를 반환한다.`, () => { + test('테스트에 사용되는 댓글 리스트의 개수는 20개 이상이다', () => { + expect(MOCK_TRANSFORMED_COMMENT_LIST.length).toBeGreaterThan(20); + }); + + test('댓글 리스트를 입력받고, 10개 단위로 보여준다. ', () => { + const { result } = renderHook(() => useMoreComment(MOCK_TRANSFORMED_COMMENT_LIST)); + + const { slicedCommentList } = result.current; + + expect(slicedCommentList).toEqual(MOCK_TRANSFORMED_COMMENT_LIST.slice(0, 10)); + }); + + test('더보기를 했을 때 10개 단위로 댓글 리스트에 추가된다.', () => { + const { result } = renderHook(() => useMoreComment(MOCK_TRANSFORMED_COMMENT_LIST)); + + const { handleMoreComment } = result.current; + + act(() => { + handleMoreComment(); + }); + + const { slicedCommentList } = result.current; + + expect(slicedCommentList).toEqual(MOCK_TRANSFORMED_COMMENT_LIST.slice(0, 20)); + }); + + test('10개 초과의 댓글 리스트가 있을 때 hasMoreComment를 true로 반환한다.', () => { + const { result } = renderHook(() => useMoreComment(MOCK_TRANSFORMED_COMMENT_LIST)); + + const { hasMoreComment } = result.current; + + expect(hasMoreComment).toBe(true); + }); + + test('10개의 댓글 리스트가 있을 때 hasMoreComment를 false로 반환한다.', () => { + const TEN_LENGTH_COMMENT_LIST = MOCK_TRANSFORMED_COMMENT_LIST.slice(0, 10); + + const { result } = renderHook(() => useMoreComment(TEN_LENGTH_COMMENT_LIST)); + + const { hasMoreComment } = result.current; + + expect(hasMoreComment).toBe(false); + }); + + test('15개의 댓글 리스트가 있을 때 더보기를 하고 나면 hasMoreComment를 false로 반환한다.', () => { + const FIFTEEN_LENGTH_COMMENT_LIST = MOCK_TRANSFORMED_COMMENT_LIST.slice(0, 15); + + const { result } = renderHook(() => useMoreComment(FIFTEEN_LENGTH_COMMENT_LIST)); + + const { handleMoreComment } = result.current; + + act(() => { + handleMoreComment(); + }); + + const { hasMoreComment } = result.current; + + expect(hasMoreComment).toBe(false); + }); +}); diff --git a/frontend/__test__/hooks/useSearch.test.tsx b/frontend/__test__/hooks/useSearch.test.tsx new file mode 100644 index 000000000..b1691f3b3 --- /dev/null +++ b/frontend/__test__/hooks/useSearch.test.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; + +import { fireEvent, render, renderHook, screen } from '@testing-library/react'; + +import { useSearch } from '@hooks/useSearch'; + +describe('useSearch 훅이 검색을 하는지 확인한다.', () => { + test('초기 값이 없다면 keyword는 빈 문자열이다.', () => { + const { result } = renderHook(() => useSearch(), { wrapper: MemoryRouter }); + + const { keyword } = result.current; + + expect(keyword).toBe(''); + }); + + test('초기 값이 있다면 keyword 값에 설정된다.', () => { + const KEYWORD = '갤럭시'; + + const { result } = renderHook(() => useSearch(KEYWORD), { wrapper: MemoryRouter }); + + const { keyword } = result.current; + + expect(keyword).toBe(KEYWORD); + }); + + test('onChange 이벤트를 한다면 keyword 값에 설정된다.', () => { + const KEYWORD = '갤럭시'; + + const { result } = renderHook(() => useSearch(KEYWORD), { wrapper: MemoryRouter }); + const { keyword, handleKeywordChange } = result.current; + + render(); + + const input = screen.getByLabelText('search-input'); + + fireEvent.change(input, { target: { value: KEYWORD } }); + + expect(result.current.keyword).toBe(KEYWORD); + }); +}); diff --git a/frontend/__test__/hooks/useSelect.test.tsx b/frontend/__test__/hooks/useSelect.test.tsx new file mode 100644 index 000000000..8891cf209 --- /dev/null +++ b/frontend/__test__/hooks/useSelect.test.tsx @@ -0,0 +1,35 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; + +import { useSelect } from '@hooks/useSelect'; + +import { PostStatus } from '@components/post/PostListPage/types'; + +const INIT_SELECTED_OPTION = 'progress'; +const CHANGE_SELECTED_OPTION = 'closed'; + +describe('useSelect 훅이 전체 게시글 목록을 불러오는지 확인한다', () => { + test('초기 값이 설정 되었는지 확인한다.', () => { + const { result } = renderHook(() => useSelect(INIT_SELECTED_OPTION)); + + const { selectedOption } = result.current; + + expect(selectedOption).toBe(INIT_SELECTED_OPTION); + }); + + test('값을 바꾸는 함수가 값을 바꿨는지 확인한다.', () => { + const INIT_SELECTED_OPTION = 'progress'; + + const { result } = renderHook(() => useSelect(INIT_SELECTED_OPTION)); + + const { handleOptionChange } = result.current; + + act(() => { + handleOptionChange(CHANGE_SELECTED_OPTION); + }); + + const { selectedOption } = result.current; + + expect(selectedOption).toBe(CHANGE_SELECTED_OPTION); + }); +}); diff --git a/frontend/__test__/hooks/useWritingOption.test.tsx b/frontend/__test__/hooks/useWritingOption.test.tsx new file mode 100644 index 000000000..1741b6885 --- /dev/null +++ b/frontend/__test__/hooks/useWritingOption.test.tsx @@ -0,0 +1,128 @@ +import { renderHook, act } from '@testing-library/react'; + +import { useWritingOption } from '@hooks/useWritingOption'; + +const MOCK_MAX_VOTE_OPTION = [ + { id: 12341, text: '', imageUrl: '' }, + { id: 123451, text: '', imageUrl: '' }, + { + id: 1234221, + text: '방학 때 강릉으로 강아지와 기차여행을 하려했지만 장마가 와서 취소했어요. 여행을 별로 좋', + imageUrl: '', + }, + { + id: 12342261, + text: '방학 때 강릉으로 강아지와 기차여행을 하려했지만 장마가 와서 취소했어요. 여행을 별로 좋', + imageUrl: 'https://source.unsplash.com/random', + }, + { + id: 1234451, + text: '', + imageUrl: 'https://source.unsplash.com/random', + }, +]; + +const MOCK_MIN_VOTE_OPTION = [ + { id: 12341, text: '', imageUrl: '' }, + { id: 123341, text: '', imageUrl: '' }, +]; +describe('useWritingOption 훅을 테스트 한다.', () => { + test('초기 값이 없다면 기본으로 2개의 선택지가 존재해야 한다.', () => { + const { result } = renderHook(() => useWritingOption()); + + const { optionList } = result.current; + + expect(optionList.length).toBe(2); + + expect(optionList[0].text).toBe(''); + + expect(optionList[0].imageUrl).toBe(''); + }); + + test('기존 데이터가 있는 경우 기존 데이터가 선택지의 초기 값으로 존재해야 한다.', () => { + const { result } = renderHook(() => useWritingOption(MOCK_MIN_VOTE_OPTION)); + + const { optionList } = result.current; + + expect(optionList).toEqual(MOCK_MIN_VOTE_OPTION); + }); + + test('투표 선택지를 추가할 수 있어야 한다. 생성된 선택지는 text와 imageUrl 값을 가지고 있다.', () => { + const { result } = renderHook(() => useWritingOption(MOCK_MIN_VOTE_OPTION)); + + const { addOption } = result.current; + + act(() => { + addOption(); + }); + + const { optionList } = result.current; + + expect(optionList.length).toBe(MOCK_MIN_VOTE_OPTION.length + 1); + + expect(optionList[2].text).toBe(''); + + expect(optionList[2].imageUrl).toBe(''); + }); + + test('투표 선택지가 5개일 땐 투표 선택지를 추가할 수 없다', () => { + const { result } = renderHook(() => useWritingOption(MOCK_MAX_VOTE_OPTION)); + + const { addOption } = result.current; + + act(() => { + addOption(); + }); + + const { optionList } = result.current; + + expect(optionList).toEqual(MOCK_MAX_VOTE_OPTION); + }); + + test('투표 선택지가 3개 이상일때는 투표 선택지의 아이디를 이용하여 삭제할 수 있다.', () => { + const { result } = renderHook(() => useWritingOption(MOCK_MAX_VOTE_OPTION)); + + const { deleteOption } = result.current; + + act(() => { + deleteOption(MOCK_MAX_VOTE_OPTION[0].id); + }); + + const { optionList } = result.current; + + expect(optionList).toEqual(MOCK_MAX_VOTE_OPTION.slice(1, 5)); + }); + + test('투표 선택지가 2개일때는 삭제할 수 없다.', () => { + const { result } = renderHook(() => useWritingOption(MOCK_MIN_VOTE_OPTION)); + + const { deleteOption } = result.current; + + act(() => { + deleteOption(MOCK_MIN_VOTE_OPTION[0].id); + }); + + const { optionList } = result.current; + + expect(optionList).toEqual(MOCK_MIN_VOTE_OPTION); + }); + + test('선택한 이미지가 있을 때 취소할 수 있다.', () => { + const MOCK_IMAGE_OPTION = [ + { id: 12341, text: '', imageUrl: 'https' }, + { id: 123412, text: '', imageUrl: 'imageUrl' }, + ]; + + const { result } = renderHook(() => useWritingOption(MOCK_IMAGE_OPTION)); + + const { removeImage } = result.current; + + act(() => { + removeImage(MOCK_MIN_VOTE_OPTION[0].id); + }); + + const { optionList } = result.current; + + expect(optionList[0].imageUrl).toBe(''); + }); +}); diff --git a/frontend/__test__/isExpiredAccessToken.test.ts b/frontend/__test__/isExpiredAccessToken.test.ts new file mode 100644 index 000000000..230421e6a --- /dev/null +++ b/frontend/__test__/isExpiredAccessToken.test.ts @@ -0,0 +1,44 @@ +import { decodeToken } from '@utils/token/decodeToken'; +import { isExpiredAccessToken } from '@utils/token/isExpiredAccessToken'; + +describe('액세스 토큰이 지났는 지 여부를 검증하여 true/false 값을 반환한다.', () => { + test('액세스 토큰의 만료 시간이 현재 시간 기준으로 지났다면 true를 반환한다.', () => { + const EXPIRED_TIME = 1693929083; + const CURRENT_TIME = EXPIRED_TIME + 10000; + + /** + * { + "memberId": 1, + "iat": 1693837083, + "exp": 1693929083 + } + */ + const ACCESS_TOKEN = decodeToken( + 'eyJtZW1iZXJJZCI6NiwiaWF0IjoxNjkzODM2NTgzLCJleHAiOjE2OTM5MjI5ODMsImFsZyI6IkhTMjU2In0.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNjkzODM3MDgzLCJleHAiOjE2OTM5MjkwODN9.SYzSL7N8Eo40HW9iJN1YVSWK3H-jkODbP5zX9Dvaji4' + ); + + const result = isExpiredAccessToken({ decodedToken: ACCESS_TOKEN, currentTime: CURRENT_TIME }); + + expect(result).toBe(true); + }); + + test('액세스 토큰의 만료 시간이 현재 시간 기준으로 지나지 않았다면 false를 반환한다.', () => { + const EXPIRED_TIME = 1693929083; + const CURRENT_TIME = EXPIRED_TIME - 10000; + + /** + * { + "memberId": 1, + "iat": 1693837083, + "exp": 1693929083 + } + */ + const ACCESS_TOKEN = decodeToken( + 'eyJtZW1iZXJJZCI6NiwiaWF0IjoxNjkzODM2NTgzLCJleHAiOjE2OTM5MjI5ODMsImFsZyI6IkhTMjU2In0.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNjkzODM3MDgzLCJleHAiOjE2OTM5MjkwODN9.SYzSL7N8Eo40HW9iJN1YVSWK3H-jkODbP5zX9Dvaji4' + ); + + const result = isExpiredAccessToken({ decodedToken: ACCESS_TOKEN, currentTime: CURRENT_TIME }); + + expect(result).toBe(false); + }); +}); diff --git a/frontend/__test__/isExpiredRefreshToken.test.ts b/frontend/__test__/isExpiredRefreshToken.test.ts new file mode 100644 index 000000000..d9126d606 --- /dev/null +++ b/frontend/__test__/isExpiredRefreshToken.test.ts @@ -0,0 +1,46 @@ +import { REFRESH_EXPIRATION_TIME } from '@constants/token'; + +import { decodeToken } from '@utils/token/decodeToken'; +import { isExpiredRefreshToken } from '@utils/token/isExpiredRefreshToken'; + +describe('리프레시 토큰이 지났는 지 여부를 검증하여 true/false 값을 반환한다.', () => { + test('액세스 토큰 발급 시간 기준 14일이 지났다면 리프레시 토큰이 만료되었다고 판단하여 true를 반환한다.', () => { + const ISSUED_TIME = 1693837083; + const CURRENT_TIME = ISSUED_TIME + REFRESH_EXPIRATION_TIME + 10000; + + /** + * { + "memberId": 1, + "iat": 1693837083, + "exp": 1693929083 + } + */ + const ACCESS_TOKEN = decodeToken( + 'eyJtZW1iZXJJZCI6NiwiaWF0IjoxNjkzODM2NTgzLCJleHAiOjE2OTM5MjI5ODMsImFsZyI6IkhTMjU2In0.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNjkzODM3MDgzLCJleHAiOjE2OTM5MjkwODN9.SYzSL7N8Eo40HW9iJN1YVSWK3H-jkODbP5zX9Dvaji4' + ); + + const result = isExpiredRefreshToken({ decodedToken: ACCESS_TOKEN, currentTime: CURRENT_TIME }); + + expect(result).toBe(true); + }); + + test('액세스 토큰 발급 시간 기준 14일이 지나지 않았다면 리프레시 토큰이 만료되지 않았다고 판단하여 false를 반환한다.', () => { + const ISSUED_TIME = 1693837083; + const CURRENT_TIME = ISSUED_TIME - 10000; + + /** + * { + "memberId": 1, + "iat": 1693837083, + "exp": 1693929083 + } + */ + const ACCESS_TOKEN = decodeToken( + 'eyJtZW1iZXJJZCI6NiwiaWF0IjoxNjkzODM2NTgzLCJleHAiOjE2OTM5MjI5ODMsImFsZyI6IkhTMjU2In0.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNjkzODM3MDgzLCJleHAiOjE2OTM5MjkwODN9.SYzSL7N8Eo40HW9iJN1YVSWK3H-jkODbP5zX9Dvaji4' + ); + + const result = isExpiredRefreshToken({ decodedToken: ACCESS_TOKEN, currentTime: CURRENT_TIME }); + + expect(result).toBe(false); + }); +}); diff --git a/frontend/__test__/isRefreshTokenRequested.test.ts b/frontend/__test__/isRefreshTokenRequested.test.ts new file mode 100644 index 000000000..31a4ae253 --- /dev/null +++ b/frontend/__test__/isRefreshTokenRequested.test.ts @@ -0,0 +1,82 @@ +import { ACCESS_TOKEN_KEY } from '@constants/localStorage'; +import { REFRESH_EXPIRATION_TIME } from '@constants/token'; + +import { getLocalStorage, setLocalStorage } from '@utils/localStorage'; +import { isRefreshTokenRequested } from '@utils/token/isRefreshTokenRequested'; + +describe('액세스 토큰의 정보를 통해 검증하여 리프레시 토큰 재발급 요청을 보낼지 여부를 true/false 값으로 반환한다.', () => { + test('액세스 토큰이 없다면 비회원 상태라고 판단하여 리프레시 토큰 재발급 요청을 하지 않는다.', () => { + const result = isRefreshTokenRequested(); + + expect(result).toBe(false); + }); + + test('액세스 토큰 발급 시간 기준 14일이 지났다면 리프레시 토큰 재발급 요청을 하지 않고, 액세스 토큰을 삭제한다.', () => { + const ISSUED_TIME = 1693837083 * 1000; + jest.useFakeTimers(); + jest.setSystemTime(new Date(ISSUED_TIME + REFRESH_EXPIRATION_TIME * 1000 + 10000)); + + /** + * { + "memberId": 1, + "iat": 1693837083, + "exp": 1693929083 + } + */ + const ACCESS_TOKEN = + 'eyJtZW1iZXJJZCI6NiwiaWF0IjoxNjkzODM2NTgzLCJleHAiOjE2OTM5MjI5ODMsImFsZyI6IkhTMjU2In0.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNjkzODM3MDgzLCJleHAiOjE2OTM5MjkwODN9.SYzSL7N8Eo40HW9iJN1YVSWK3H-jkODbP5zX9Dvaji4'; + + setLocalStorage(ACCESS_TOKEN_KEY, ACCESS_TOKEN); + + const result = isRefreshTokenRequested(); + + const accessToken = getLocalStorage(ACCESS_TOKEN_KEY); + + expect(result).toBe(false); + expect(accessToken).toBe(null); + }); + + test('액세스 토큰 발급 시간 기준 14일이 지나지 않았고, 액세스 토큰이 만료되었다면 리프레시 토큰 재발급 요청을 한다.', () => { + const EXPIRED_TIME = 1693929083 * 1000; + jest.useFakeTimers(); + jest.setSystemTime(new Date(EXPIRED_TIME + 10000)); + + /** + * { + "memberId": 1, + "iat": 1693837083, + "exp": 1693929083 + } + */ + const ACCESS_TOKEN = + 'eyJtZW1iZXJJZCI6NiwiaWF0IjoxNjkzODM2NTgzLCJleHAiOjE2OTM5MjI5ODMsImFsZyI6IkhTMjU2In0.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNjkzODM3MDgzLCJleHAiOjE2OTM5MjkwODN9.SYzSL7N8Eo40HW9iJN1YVSWK3H-jkODbP5zX9Dvaji4'; + + setLocalStorage(ACCESS_TOKEN_KEY, ACCESS_TOKEN); + + const result = isRefreshTokenRequested(); + + expect(result).toBe(true); + }); + + test('액세스 토큰 발급 시간 기준 14일이 지나지 않았고, 액세스 토큰이 만료되지 않았다면 리프레시 토큰 재발급 요청을 하지 않는다.', () => { + const EXPIRED_TIME = 1693929083 * 1000; + jest.useFakeTimers(); + jest.setSystemTime(new Date(EXPIRED_TIME - 10000)); + + /** + * { + "memberId": 1, + "iat": 1693837083, + "exp": 1693929083 + } + */ + const ACCESS_TOKEN = + 'eyJtZW1iZXJJZCI6NiwiaWF0IjoxNjkzODM2NTgzLCJleHAiOjE2OTM5MjI5ODMsImFsZyI6IkhTMjU2In0.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNjkzODM3MDgzLCJleHAiOjE2OTM5MjkwODN9.SYzSL7N8Eo40HW9iJN1YVSWK3H-jkODbP5zX9Dvaji4'; + + setLocalStorage(ACCESS_TOKEN_KEY, ACCESS_TOKEN); + + const result = isRefreshTokenRequested(); + + expect(result).toBe(false); + }); +}); diff --git a/frontend/__test__/slientLogin.test.ts b/frontend/__test__/slientLogin.test.ts new file mode 100644 index 000000000..14b28b89f --- /dev/null +++ b/frontend/__test__/slientLogin.test.ts @@ -0,0 +1,62 @@ +import { ACCESS_TOKEN_KEY } from '@constants/localStorage'; + +import { getLocalStorage, setLocalStorage } from '@utils/localStorage'; +import { silentLogin } from '@utils/token/silentLogin'; + +import { MOCK_TOKEN } from '@mocks/mockData/token'; + +describe('리프레시 토큰 존재 여부를 이용하여 액세스 토큰과 리프레시 토큰을 재발급한다.', () => { + test('액세스 토큰을 발급받은 지 14일이 지나서 리프레시 토큰이 없다면 액세스 토큰과 리프레시 토큰이 발급되지 않는다.', async () => { + await silentLogin(); + + const accessToken = getLocalStorage(ACCESS_TOKEN_KEY); + + expect(accessToken).toBe(null); + }); + + test('액세스 토큰이 유효 기간을 지나지 않았다면 액세스 토큰과 리프레시 토큰이 발급되지 않는다.', async () => { + jest.useFakeTimers(); + jest.setSystemTime(new Date(1693929083000 - 1000)); + + /** + * { + "memberId": 1, + "iat": 1693837083, + "exp": 1693929083 + } + */ + const ACCESS_TOKEN = + 'eyJtZW1iZXJJZCI6NiwiaWF0IjoxNjkzODM2NTgzLCJleHAiOjE2OTM5MjI5ODMsImFsZyI6IkhTMjU2In0.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNjkzODM3MDgzLCJleHAiOjE2OTM5MjkwODN9.SYzSL7N8Eo40HW9iJN1YVSWK3H-jkODbP5zX9Dvaji4'; + + setLocalStorage(ACCESS_TOKEN_KEY, ACCESS_TOKEN); + + await silentLogin(); + + const accessToken = getLocalStorage(ACCESS_TOKEN_KEY); + + expect(accessToken).toBe(ACCESS_TOKEN); + }); + + test('액세스 토큰을 발급받은 지 14일이 지나지 않아서 리프레시 토큰이 있고, 액세스 토큰이 유효 기간을 지났다면 새로운 액세스 토큰과 리프레시 토큰을 발급하여 로그인을 유지한다.', async () => { + jest.useFakeTimers(); + jest.setSystemTime(new Date(1693929083000 + 1000)); + + /** + * { + "memberId": 1, + "iat": 1693837083, + "exp": 1693929083 + } + */ + const ACCESS_TOKEN = + 'eyJtZW1iZXJJZCI6NiwiaWF0IjoxNjkzODM2NTgzLCJleHAiOjE2OTM5MjI5ODMsImFsZyI6IkhTMjU2In0.eyJtZW1iZXJJZCI6MSwiaWF0IjoxNjkzODM3MDgzLCJleHAiOjE2OTM5MjkwODN9.SYzSL7N8Eo40HW9iJN1YVSWK3H-jkODbP5zX9Dvaji4'; + + setLocalStorage(ACCESS_TOKEN_KEY, ACCESS_TOKEN); + + await silentLogin(); + + const accessToken = getLocalStorage(ACCESS_TOKEN_KEY); + + expect(accessToken).toBe(MOCK_TOKEN.accessToken); + }); +}); diff --git a/frontend/__test__/unit.test.js b/frontend/__test__/unit.test.js new file mode 100644 index 000000000..1ffd93aeb --- /dev/null +++ b/frontend/__test__/unit.test.js @@ -0,0 +1,5 @@ +describe('테스트 설정한다.', () => { + test('1 + 1 = 2', () => { + expect(1 + 1).toBe(2); + }); +}); diff --git a/frontend/env.d.ts b/frontend/env.d.ts new file mode 100644 index 000000000..92d39203d --- /dev/null +++ b/frontend/env.d.ts @@ -0,0 +1,10 @@ +declare module NodeJS { + interface ProcessEnv { + VOTOGETHER_BASE_URL: string; + VOTOGETHER_MOCKING_URL: string; + VOTOGETHER_REST_API_KEY: string; + VOTOGETHER_SERVER_REDIRECT_URL: string; + VOTOGETHER_CHANNEL_TALK_KEY: string; + VOTOGETHER_GOOGLE_TAG_ID: string; + } +} diff --git a/frontend/jest.config.js b/frontend/jest.config.js new file mode 100644 index 000000000..36d9db9ee --- /dev/null +++ b/frontend/jest.config.js @@ -0,0 +1,18 @@ +module.exports = { + testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], + testEnvironment: 'jsdom', + moduleNameMapper: { + '^@assets/(.*)$': '/src/assets/$1', + '^@pages/(.*)$': '/src/pages/$1', + '^@components/(.*)$': '/src/components/$1', + '^@styles/(.*)$': '/src/styles/$1', + '^@api/(.*)$': '/src/api/$1', + '^@type/(.*)$': '/src/types/$1', + '^@utils/(.*)$': '/src/utils/$1', + '^@constants/(.*)$': '/src/constants/$1', + '^@hooks/(.*)$': '/src/hooks/$1', + '^@mocks/(.*)$': '/src/mocks/$1', + }, + setupFilesAfterEnv: ['./jest.setup.js'], + transformIgnorePatterns: ['/node_modules/'], +}; diff --git a/frontend/jest.setup.js b/frontend/jest.setup.js new file mode 100644 index 000000000..7cc77997d --- /dev/null +++ b/frontend/jest.setup.js @@ -0,0 +1,22 @@ +import 'whatwg-fetch'; + +import dotenv from 'dotenv'; +import { setupServer } from 'msw/node'; + +import { handlers } from './src/mocks/handlers'; + +dotenv.config({ path: './.env.test' }); + +export const server = setupServer(...handlers); + +beforeAll(() => { + server.listen(); +}); + +afterEach(() => { + server.resetHandlers(); +}); + +afterAll(() => { + server.close(); +}); diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 000000000..b2f3c4de2 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,40401 @@ +{ + "name": "votogether", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "votogether", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@tanstack/react-query": "^4.29.19", + "dotenv": "^16.3.1", + "msw": "^1.2.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.14.1" + }, + "devDependencies": { + "@babel/preset-env": "^7.22.6", + "@babel/preset-react": "^7.22.5", + "@babel/preset-typescript": "^7.22.5", + "@storybook/addon-essentials": "^7.0.26", + "@storybook/addon-interactions": "^7.0.26", + "@storybook/addon-links": "^7.0.26", + "@storybook/blocks": "^7.0.26", + "@storybook/react": "^7.0.26", + "@storybook/react-webpack5": "^7.0.26", + "@storybook/testing-library": "^0.0.14-next.2", + "@testing-library/react": "^14.0.0", + "@types/jest": "^29.5.2", + "@types/react": "^18.2.14", + "@types/react-dom": "^18.2.6", + "@types/styled-components": "^5.1.26", + "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^11.0.0", + "dotenv-webpack": "^8.0.1", + "esbuild-loader": "^4.0.2", + "eslint": "^8.44.0", + "eslint-config-prettier": "^8.8.0", + "eslint-config-react-app": "^7.0.1", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-storybook": "^0.6.12", + "fork-ts-checker-webpack-plugin": "^8.0.0", + "html-webpack-plugin": "^5.5.3", + "husky": "^8.0.3", + "jest": "^29.6.0", + "jest-environment-jsdom": "^29.6.0", + "lint-staged": "^13.2.3", + "msw-storybook-addon": "^1.8.0", + "prettier": "^2.8.8", + "storybook": "^7.0.26", + "styled-components": "^6.0.2", + "tsconfig-paths-webpack-plugin": "^4.0.1", + "typescript": "^5.1.6", + "webpack": "^5.88.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1", + "webpack-merge": "^5.9.0", + "whatwg-fetch": "^3.6.2" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@aw-web-design/x-default-browser": { + "version": "1.4.88", + "resolved": "https://registry.npmjs.org/@aw-web-design/x-default-browser/-/x-default-browser-1.4.88.tgz", + "integrity": "sha512-AkEmF0wcwYC2QkhK703Y83fxWARttIWXDmQN8+cof8FmFZ5BRhnNXGymeb1S73bOCLfWjYELxtujL56idCN/XA==", + "dev": true, + "dependencies": { + "default-browser-id": "3.0.0" + }, + "bin": { + "x-default-browser": "bin/x-default-browser.js" + } + }, + "node_modules/@babel/cli": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.6.tgz", + "integrity": "sha512-Be3/RfEDmkMRGT1+ru5nTkfcvWz5jDOYg1V9rXqTz2u9Qt96O1ryboGvxVBp7wOnYWDB8DNHIWb6DThrpudfOw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/cli/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@babel/cli/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@babel/cli/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz", + "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.6.tgz", + "integrity": "sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.6", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.22.6.tgz", + "integrity": "sha512-KAom7E7d6bAh5/PflF3luynWlDLOIqfX+ZJcL0LRs6/6rtXJmJxPiWuIGfxNPtcWdtQ5lSSJbKbQlz/c/R60Ng==", + "dev": true, + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "eslint-visitor-keys": "^2.1.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.11.0", + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", + "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz", + "integrity": "sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-validator-option": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.6.tgz", + "integrity": "sha512-iwdzgtSiBxF6ni6mzVnZCF3xt5qE6cEA0J7nFt8QOAWZ0zjCFceEgpn3vtb2V7WFR6QzP2jmIFOHMTRo7eNJjQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@nicolo-ribaudo/semver-v6": "^6.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.6.tgz", + "integrity": "sha512-nBookhLKxAWo/TUCmhnaEJyLz2dekjQvv5SRpE9epWQBcpedWLKt8aZdsuT9XV5ovzR3fENLjRXVT0GsSlGGhA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "regexpu-core": "^5.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.1.tgz", + "integrity": "sha512-kX4oXixDxG197yhX+J3Wp+NpL2wuCFjWQAr6yX2jtCnflK9ulMI51ULFGIrWiX1jGfvAxdHp+XQCcP2bZGPs9A==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz", + "integrity": "sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-wrap-function": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", + "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz", + "integrity": "sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", + "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.6.tgz", + "integrity": "sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", + "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", + "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-external-helpers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.22.5.tgz", + "integrity": "sha512-ngnNEWxmykPk82mH4ajZT0qTztr3Je6hrMuKAslZVM8G1YZTENJSYwrIGtt6KOtznug3exmAtF4so/nPqJuA4A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", + "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.22.6.tgz", + "integrity": "sha512-cgskJ9W7kxTk/wBM16JNHhlTkeyDK6slMJg1peaI4LM3y2HtTv+6I85sW9UXSUZilndIBvDBETA1BRoOYdxWKw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/plugin-syntax-decorators": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.22.5.tgz", + "integrity": "sha512-avpUOBS7IU6al8MmF1XpAyj9QYeLPuSDJI5D4pVMSMdL7xQokKqJPYQC67RCT0aCTashUXPiGwMJ0DEXXCEmMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.22.5.tgz", + "integrity": "sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz", + "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", + "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", + "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", + "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", + "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", + "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", + "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.22.5.tgz", + "integrity": "sha512-tujNbZdxdG0/54g/oua8ISToaXTFBf8EnSb5PgQSciIXWOWKX3S4+JR7ZE9ol8FZwf9kxitzkGQ+QWeov/mCiA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-flow": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", + "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", + "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", + "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", + "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", + "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", + "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", + "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", + "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", + "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", + "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz", + "integrity": "sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", + "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", + "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", + "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz", + "integrity": "sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", + "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", + "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.6.tgz", + "integrity": "sha512-+AGkst7Kqq3QUflKGkhWWMRb9vaKamoreNmYc+sjsIpOp+TsyU0exhp3RlwjQa/HdlKkPt3AMDwfg8Hpt9Vwqg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "babel-plugin-polyfill-corejs2": "^0.4.3", + "babel-plugin-polyfill-corejs3": "^0.8.1", + "babel-plugin-polyfill-regenerator": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.5.tgz", + "integrity": "sha512-SMubA9S7Cb5sGSFFUlqxyClTA9zWJ8qGQrppNUm05LtFuN1ELRFNndkix4zUJrC9F+YivWwa1dHMSyo0e0N9dA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", + "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.6.tgz", + "integrity": "sha512-IHr0AXHGk8oh8HYSs45Mxuv6iySUBwDTIzJSnXN7PURqHdxJVQlCoXmKJgyvSS9bcNf9NVRVE35z+LkCvGmi6w==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.5", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.22.5", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.6", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.22.5", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.5", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.5", + "@babel/plugin-transform-for-of": "^7.22.5", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.5", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-modules-systemjs": "^7.22.5", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", + "@babel/plugin-transform-numeric-separator": "^7.22.5", + "@babel/plugin-transform-object-rest-spread": "^7.22.5", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.6", + "@babel/plugin-transform-parameters": "^7.22.5", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.5", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.5", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.5", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "babel-plugin-polyfill-corejs2": "^0.4.3", + "babel-plugin-polyfill-corejs3": "^0.8.1", + "babel-plugin-polyfill-regenerator": "^0.5.0", + "core-js-compat": "^3.31.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-flow": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.22.5.tgz", + "integrity": "sha512-ta2qZ+LSiGCrP5pgcGt8xMnnkXQrq8Sa4Ulhy06BOlF5QbLw9q5hIx7bn5MrsvyTGAfh6kTOo07Q+Pfld/8Y5Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-transform-flow-strip-types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.5.tgz", + "integrity": "sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-transform-react-display-name": "^7.22.5", + "@babel/plugin-transform-react-jsx": "^7.22.5", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz", + "integrity": "sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-typescript": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.22.5.tgz", + "integrity": "sha512-vV6pm/4CijSQ8Y47RH5SopXzursN35RQINfGJkmOlcpAtGuf94miFvIPhCKGQN7WGIcsgG1BHEX2KVdTYwTwUQ==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.5", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register/node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/register/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.6.tgz", + "integrity": "sha512-53CijMvKlLIDlOTrdWiHileRddlIiwUIyCKqYa7lYnnPldXCG5dUSN38uT0cA6i7rHWNKJLH0VU/Kxdr1GzB3w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.6", + "@babel/types": "^7.22.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@base2/pretty-print-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz", + "integrity": "sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==", + "dev": true + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dev": true, + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "dev": true + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "dev": true + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "dev": true, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", + "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", + "dev": true, + "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/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", + "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fal-works/esbuild-plugin-global-externals": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", + "integrity": "sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==", + "dev": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.0.tgz", + "integrity": "sha512-anb6L1yg7uPQpytNVA5skRaXy3BmrsU8icRhTVNbWdjYWDDfy8M1Kq5HIVRpYoABdbpqsc5Dr+jtu4+qWRQBiQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.6.0", + "jest-util": "^29.6.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/console/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.0.tgz", + "integrity": "sha512-5dbMHfY/5R9m8NbgmB3JlxQqooZ/ooPSOiwEQZZ+HODwJTbIu37seVcZNBK29aMdXtjvTRB3f6LCvkKq+r8uQA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.6.0", + "@jest/reporters": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/transform": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.6.0", + "jest-haste-map": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.0", + "jest-resolve-dependencies": "^29.6.0", + "jest-runner": "^29.6.0", + "jest-runtime": "^29.6.0", + "jest-snapshot": "^29.6.0", + "jest-util": "^29.6.0", + "jest-validate": "^29.6.0", + "jest-watcher": "^29.6.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.0.tgz", + "integrity": "sha512-bUZLYUxYlUIsslBbxII0fq0kr1+friI3Gty+cRLmocGB1jdcAHs7FS8QdCDqedE8q4DZE1g/AJHH6OJZBLGGsg==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-mock": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment/node_modules/jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.0.tgz", + "integrity": "sha512-a7pISPW28Q3c0/pLwz4mQ6tbAI+hc8/0CJp9ix6e9U4dQ6TiHQX82CT5DV5BMWaw8bFH4E6zsfZxXdn6Ka23Bw==", + "dev": true, + "dependencies": { + "expect": "^29.6.0", + "jest-snapshot": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.0.tgz", + "integrity": "sha512-LLSQQN7oypMSETKoPWpsWYVKJd9LQWmSDDAc4hUQ4JocVC7LAMy9R3ZMhlnLwbcFvQORZnZR7HM893Px6cJhvA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.0.tgz", + "integrity": "sha512-nuCU46AsZoskthWSDS2Aj6LARgyNcp5Fjx2qxsO/fPl1Wp1CJ+dBDqs0OkEcJK8FBeV/MbjH5efe79M2sHcV+A==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.0", + "jest-mock": "^29.6.0", + "jest-util": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.0.tgz", + "integrity": "sha512-IQQ3hZ2D/hwEwXSMv5GbfhzdH0nTQR3KPYxnuW6gYWbd6+7/zgMz7Okn6EgBbNtJNONq03k5EKA6HqGyzRbpeg==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.0", + "@jest/expect": "^29.6.0", + "@jest/types": "^29.6.0", + "jest-mock": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals/node_modules/jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.0.tgz", + "integrity": "sha512-dWEq4HI0VvHcAD6XTtyBKKARLytyyWPIy1SvGOcU91106MfvHPdxZgupFwVHd8TFpZPpA3SebYjtwS5BUS76Rw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/transform": "^29.6.0", + "@jest/types": "^29.6.0", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.6.0", + "jest-util": "^29.6.0", + "jest-worker": "^29.6.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/jest-worker": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.0.tgz", + "integrity": "sha512-oiQHH1SnKmZIwwPnpOrXTq4kHBk3lKGY/07DpnH0sAu+x7J8rXlbLDROZsU6vy9GwB0hPiZeZpu6YlJ48QoKcA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.6.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", + "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.0.tgz", + "integrity": "sha512-9qLb7xITeyWhM4yatn2muqfomuoCTOhv0QV9i7XiIyYi3QLfnvPv5NeJp5u0PZeutAOROMLKakOkmoAisOr3YQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.0.tgz", + "integrity": "sha512-HYCS3LKRQotKWj2mnA3AN13PPevYZu8MJKm12lzYojpJNnn6kI/3PWmr1At/e3tUu+FHQDiOyaDVuR4EV3ezBw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.6.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.0.tgz", + "integrity": "sha512-bhP/KxPo3e322FJ0nKAcb6WVK76ZYyQd1lWygJzoSqP8SYMSLdxHqP4wnPTI4WvbB8PKPDV30y5y7Tya4RHOBA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.0", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.0.tgz", + "integrity": "sha512-8XCgL9JhqbJTFnMRjEAO+TuW251+MoMd5BSzLiE3vvzpQ8RlBxy8NoyNkDhs3K3OL3HeVinlOl9or5p7GTeOLg==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "dev": true + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "node_modules/@mdx-js/react": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", + "integrity": "sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==", + "dev": true, + "dependencies": { + "@types/mdx": "^2.0.0", + "@types/react": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@mswjs/cookies": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.2.tgz", + "integrity": "sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==", + "dependencies": { + "@types/set-cookie-parser": "^2.4.0", + "set-cookie-parser": "^2.4.6" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.17.9", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.17.9.tgz", + "integrity": "sha512-4LVGt03RobMH/7ZrbHqRxQrS9cc2uh+iNKSj8UWr8M26A2i793ju+csaB5zaqYltqJmA2jUq4VeYfKmVqvsXQg==", + "dependencies": { + "@open-draft/until": "^1.0.3", + "@types/debug": "^4.1.7", + "@xmldom/xmldom": "^0.8.3", + "debug": "^4.3.3", + "headers-polyfill": "^3.1.0", + "outvariant": "^1.2.1", + "strict-event-emitter": "^0.2.4", + "web-encoding": "^1.1.5" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@mswjs/interceptors/node_modules/strict-event-emitter": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz", + "integrity": "sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==", + "dependencies": { + "events": "^3.3.0" + } + }, + "node_modules/@ndelangen/get-tarball": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@ndelangen/get-tarball/-/get-tarball-3.0.9.tgz", + "integrity": "sha512-9JKTEik4vq+yGosHYhZ1tiH/3WpUS0Nh0kej4Agndhox8pAdWhEx5knFVRcb/ya9knCRCs1rPxNrSXTDdfVqpA==", + "dev": true, + "dependencies": { + "gunzip-maybe": "^1.4.2", + "pump": "^3.0.0", + "tar-fs": "^2.1.1" + } + }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/semver-v6": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz", + "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@open-draft/until": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", + "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==" + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz", + "integrity": "sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==", + "dev": true, + "dependencies": { + "ansi-html-community": "^0.0.8", + "common-path-prefix": "^3.0.0", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "find-up": "^5.0.0", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^3.0.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <4.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@remix-run/router": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.1.tgz", + "integrity": "sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ==", + "engines": { + "node": ">=14" + } + }, + "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==", + "dev": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@storybook/addon-actions": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.0.26.tgz", + "integrity": "sha512-vVoqE0Zw0g1PPnGfho8vRwjpXhQCpRNBQ/2U83/CSodHWL/MBYENG0XMby90TC72M26gNmEh0dn1YCUXvLdiew==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "polished": "^4.2.2", + "prop-types": "^15.7.2", + "react-inspector": "^6.0.0", + "telejson": "^7.0.3", + "ts-dedent": "^2.0.0", + "uuid": "^9.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-actions/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@storybook/addon-backgrounds": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.0.26.tgz", + "integrity": "sha512-sjTkOnSsVBBl1GruVVsNKWEuLCbKjkNun1mzIklfYAiHz9hTZIhe9MA2SGZoDozMUDIXQqSoMDEc3rnDtfqsnQ==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "memoizerific": "^1.11.3", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-controls": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.0.26.tgz", + "integrity": "sha512-mp1WuOYCPvR33orHn0XPABY5roF9Le8HnZwTpvfkrRMeMqLnYLnkCTZqY3JN/IOVlyQuYdqodP5CPDHNDLmvVg==", + "dev": true, + "dependencies": { + "@storybook/blocks": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/manager-api": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "lodash": "^4.17.21", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-docs": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.0.26.tgz", + "integrity": "sha512-C8DOwfmPBWDUS1IJbyJxykgVVHVzSSL+JFh3FwtF0hsqwjlNW4OvGDFbz0oAxyxs4V46xVcvh4E95e3GkW36BQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.20.2", + "@babel/plugin-transform-react-jsx": "^7.19.0", + "@jest/transform": "^29.3.1", + "@mdx-js/react": "^2.1.5", + "@storybook/blocks": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/csf-plugin": "7.0.26", + "@storybook/csf-tools": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/mdx2-csf": "^1.0.0", + "@storybook/node-logger": "7.0.26", + "@storybook/postinstall": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/react-dom-shim": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "fs-extra": "^11.1.0", + "remark-external-links": "^8.0.0", + "remark-slug": "^6.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/addon-essentials": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.0.26.tgz", + "integrity": "sha512-r+IOtxbIqlCKO8fDgLppubYm+GEW3ZDxjPwXMQdDGem9ENpz0QLKb49r89+UYqnnaYjuYKjDNUOqy0gX2HfUXQ==", + "dev": true, + "dependencies": { + "@storybook/addon-actions": "7.0.26", + "@storybook/addon-backgrounds": "7.0.26", + "@storybook/addon-controls": "7.0.26", + "@storybook/addon-docs": "7.0.26", + "@storybook/addon-highlight": "7.0.26", + "@storybook/addon-measure": "7.0.26", + "@storybook/addon-outline": "7.0.26", + "@storybook/addon-toolbars": "7.0.26", + "@storybook/addon-viewport": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/manager-api": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/preview-api": "7.0.26", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/addon-highlight": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.0.26.tgz", + "integrity": "sha512-+I+MoM7yXCA3YR2FwTSxSs6/IBpcc3Ey88WboGthR23ERmsgZOtum1S7KZ6cffNCOq4U0LzPkjKX2bICytFrIQ==", + "dev": true, + "dependencies": { + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/preview-api": "7.0.26" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/addon-interactions": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-7.0.26.tgz", + "integrity": "sha512-trIbPFLdxF6XgGORhx8eSGmGZ/4/AekJyFluf2lgutGi4TPL5Xzrx3o1kTFPVdLAPplBuDIlVI4HSGHHH2zeTw==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/instrumenter": "7.0.26", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "jest-mock": "^27.0.6", + "polished": "^4.2.2", + "ts-dedent": "^2.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-links": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.0.26.tgz", + "integrity": "sha512-og+8AUAUpHsT+MVjhdQmRNJw9RUkHn5FFoou003b9V4UlPPNDYTo/tNEqOhUXn2l/ESAROJlR/q/8Qjdes24pA==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/router": "7.0.26", + "@storybook/types": "7.0.26", + "prop-types": "^15.7.2", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-measure": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.0.26.tgz", + "integrity": "sha512-iAnI6q3GB8uSydK+S4m4ANpy0GpMpHhmU0oBtu6OmyyzHUH1RJ7/fGfBnzx6YT+rIOlqSFocxYGn74ylsp33Wg==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/types": "7.0.26" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-outline": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.0.26.tgz", + "integrity": "sha512-oL7D0IWO0M6hMw5cWEC6JdKXlGadlVIdhIrVN+0gdFxuxCHTGpebQ02DCvyfls29UssEOxPaO1XMdu9tDlctbg==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/types": "7.0.26", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-toolbars": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.0.26.tgz", + "integrity": "sha512-DrwqcWuCLjaTNFtAYUxO2VaLrr2ibhB3ZQwW7J6a4YFCJaV49wempGPq3BzTWvrPUtMxGp7J3ZusdH9jBgCzjA==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-viewport": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.0.26.tgz", + "integrity": "sha512-veAYxnR11sojXC7tlnBZ/USiafhWCsZNvjxmywl/XCh3MeDGFFDb2NN1s/7irAYXfNMOhgPGZED19BN9cQ8QRQ==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "memoizerific": "^1.11.3", + "prop-types": "^15.7.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/addons": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-7.0.26.tgz", + "integrity": "sha512-zn7vdgXkQ4DpCJaawJsNPnh0NzXVXd2qfVtzYWWKT4eyj43VXxoVX2Z4woAD8h6G57JJg67+7hChRebUmd284A==", + "dev": true, + "dependencies": { + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/types": "7.0.26" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/api": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/api/-/api-7.0.26.tgz", + "integrity": "sha512-czS5iWE3Px3e0sXjgt1T+LDiT6Tl4gXYPmHIaWpKGDCh4W2zrGolOvB0WqDt3IKhDGnXxaJF5jn705OGBQOptw==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/manager-api": "7.0.26" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/blocks": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.0.26.tgz", + "integrity": "sha512-VNYB6Y1Ocja8HVg4Bm1w7LvqRSEc9aLVD8BnI8BInHvekvxhaxTkfpA18qds7d8+RmerrJqAUhGx0jkIB/cvwA==", + "dev": true, + "dependencies": { + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/docs-tools": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "@types/lodash": "^4.14.167", + "color-convert": "^2.0.1", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "markdown-to-jsx": "^7.1.8", + "memoizerific": "^1.11.3", + "polished": "^4.2.2", + "react-colorful": "^5.1.2", + "telejson": "^7.0.3", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/blocks/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@storybook/blocks/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@storybook/builder-manager": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.0.26.tgz", + "integrity": "sha512-1Uk3dL3Yu5AuimfHAghBHs11wf7B+a+277astqLx7HSeh3L49zcDZS4NhGHKmtQjsEorbvmtty3s16q2k+fM8A==", + "dev": true, + "dependencies": { + "@fal-works/esbuild-plugin-global-externals": "^2.1.2", + "@storybook/core-common": "7.0.26", + "@storybook/manager": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@types/ejs": "^3.1.1", + "@types/find-cache-dir": "^3.2.1", + "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", + "browser-assert": "^1.2.1", + "ejs": "^3.1.8", + "esbuild": "^0.17.0", + "esbuild-plugin-alias": "^0.2.1", + "express": "^4.17.3", + "find-cache-dir": "^3.0.0", + "fs-extra": "^11.1.0", + "process": "^0.11.10", + "util": "^0.12.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/builder-webpack5": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-7.0.26.tgz", + "integrity": "sha512-LfntlZKm0PB6hrgXd7IlzjuCLzjQezYHt3GQfZRxzu7MAu/bgu7xtr7lMaIJOQd2ckpvEN7xhJ89t2mvdk5y0A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.10", + "@storybook/addons": "7.0.26", + "@storybook/api": "7.0.26", + "@storybook/channel-postmessage": "7.0.26", + "@storybook/channel-websocket": "7.0.26", + "@storybook/channels": "7.0.26", + "@storybook/client-api": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/core-webpack": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/preview": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/router": "7.0.26", + "@storybook/store": "7.0.26", + "@storybook/theming": "7.0.26", + "@types/node": "^16.0.0", + "@types/semver": "^7.3.4", + "babel-loader": "^9.0.0", + "babel-plugin-named-exports-order": "^0.0.2", + "browser-assert": "^1.2.1", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "css-loader": "^6.7.1", + "express": "^4.17.3", + "fork-ts-checker-webpack-plugin": "^7.2.8", + "fs-extra": "^11.1.0", + "html-webpack-plugin": "^5.5.0", + "path-browserify": "^1.0.1", + "process": "^0.11.10", + "semver": "^7.3.7", + "style-loader": "^3.3.1", + "terser-webpack-plugin": "^5.3.1", + "ts-dedent": "^2.0.0", + "util": "^0.12.4", + "util-deprecate": "^1.0.2", + "webpack": "5", + "webpack-dev-middleware": "^5.3.1", + "webpack-hot-middleware": "^2.25.1", + "webpack-virtual-modules": "^0.4.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "node_modules/@storybook/builder-webpack5/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@storybook/builder-webpack5/node_modules/fork-ts-checker-webpack-plugin": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.3.0.tgz", + "integrity": "sha512-IN+XTzusCjR5VgntYFgxbxVx3WraPRnKehBFrf00cMSrtUuW9MsG9dhL6MWpY6MkjC3wVwoujfCDgZZCQwbswA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "vue-template-compiler": "*", + "webpack": "^5.11.0" + }, + "peerDependenciesMeta": { + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@storybook/builder-webpack5/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@storybook/channel-postmessage": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-7.0.26.tgz", + "integrity": "sha512-ZvFLr/tUD9dWIjQtIn1JXHjqrbOP/uEEOqzwpKSVj0Cl4Vgc12s8hecbzBufkOF7fwLsFvfieSi7ENOmjoncdQ==", + "dev": true, + "dependencies": { + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "qs": "^6.10.0", + "telejson": "^7.0.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/channel-websocket": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/channel-websocket/-/channel-websocket-7.0.26.tgz", + "integrity": "sha512-c+0VcZf78RGnT/pWrH85yydt0azRKAHZF3SHWKM4+W8qOFr0Mk0+jqhPh1uoUoPDpBZDTKS/nzXY8cwUVwF/eA==", + "dev": true, + "dependencies": { + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/global": "^5.0.0", + "telejson": "^7.0.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/channels": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.0.26.tgz", + "integrity": "sha512-Br3XILhrtuL5Sdp91I04kKjJzSqU/N8gGL6B6nIfnuaHUvGMDuMCHAB+g7aoiyH5dnpDZ6yBVGNwtYAyJA+0Og==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/cli": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.0.26.tgz", + "integrity": "sha512-sZ136wRUYTdhhm/thegFoI47wOzl2X+K9eaiTTp0ARwnIUhXAPDQ0MKOD36hKbCX5T/pBE7r++7WoEReIbUDqQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.20.2", + "@babel/preset-env": "^7.20.2", + "@ndelangen/get-tarball": "^3.0.7", + "@storybook/codemod": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/core-server": "7.0.26", + "@storybook/csf-tools": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/telemetry": "7.0.26", + "@storybook/types": "7.0.26", + "@types/semver": "^7.3.4", + "chalk": "^4.1.0", + "commander": "^6.2.1", + "cross-spawn": "^7.0.3", + "detect-indent": "^6.1.0", + "envinfo": "^7.7.3", + "execa": "^5.0.0", + "express": "^4.17.3", + "find-up": "^5.0.0", + "fs-extra": "^11.1.0", + "get-npm-tarball-url": "^2.0.3", + "get-port": "^5.1.1", + "giget": "^1.0.0", + "globby": "^11.0.2", + "jscodeshift": "^0.14.0", + "leven": "^3.1.0", + "ora": "^5.4.1", + "prettier": "^2.8.0", + "prompts": "^2.4.0", + "puppeteer-core": "^2.1.1", + "read-pkg-up": "^7.0.1", + "semver": "^7.3.7", + "shelljs": "^0.8.5", + "simple-update-notifier": "^1.0.0", + "strip-json-comments": "^3.0.1", + "tempy": "^1.0.1", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "bin": { + "getstorybook": "bin/index.js", + "sb": "bin/index.js" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@storybook/cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@storybook/cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@storybook/cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@storybook/cli/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@storybook/cli/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/cli/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/@storybook/cli/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/cli/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/cli/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/@storybook/cli/node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@storybook/cli/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/cli/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/cli/node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/cli/node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/cli/node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/cli/node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/cli/node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/cli/node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/cli/node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/cli/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/cli/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/cli/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@storybook/client-api": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/client-api/-/client-api-7.0.26.tgz", + "integrity": "sha512-55Oy5Es8ACABWT01iddUJHt8oT4VnuCvec/FUC4iN7ITiOGjk7YzZB3NftmD6C5+pVQC99buspuwg7IFxmj+Aw==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/preview-api": "7.0.26" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/client-logger": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.0.26.tgz", + "integrity": "sha512-OMVLbgceoeuM8sWOfTX/9a4zCrH78G32hg7x8yXLZnRJ9OLaHJHzUM0Onc4MLudqVUdaKH0c8ejpBXUyIr1rJQ==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/codemod": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.0.26.tgz", + "integrity": "sha512-H9sV59FfGrGzGM+UZQclNglnc4cOkQvvF3EOWlR3BfDhx+STSB9VbCR308ygjUYw2TXZ2s5seCvHtVvA2yhILA==", + "dev": true, + "dependencies": { + "@babel/core": "~7.21.0", + "@babel/preset-env": "~7.21.0", + "@babel/types": "~7.21.2", + "@storybook/csf": "^0.1.0", + "@storybook/csf-tools": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/types": "7.0.26", + "cross-spawn": "^7.0.3", + "globby": "^11.0.2", + "jscodeshift": "^0.14.0", + "lodash": "^4.17.21", + "prettier": "^2.8.0", + "recast": "^0.23.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/codemod/node_modules/@babel/core": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", + "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-compilation-targets": "^7.21.5", + "@babel/helper-module-transforms": "^7.21.5", + "@babel/helpers": "^7.21.5", + "@babel/parser": "^7.21.8", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@storybook/codemod/node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@storybook/codemod/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@storybook/codemod/node_modules/@babel/preset-env": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.5.tgz", + "integrity": "sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.21.5", + "@babel/helper-compilation-targets": "^7.21.5", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-validator-option": "^7.21.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7", + "@babel/plugin-proposal-async-generator-functions": "^7.20.7", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.21.0", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.21.5", + "@babel/plugin-transform-async-to-generator": "^7.20.7", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.21.0", + "@babel/plugin-transform-classes": "^7.21.0", + "@babel/plugin-transform-computed-properties": "^7.21.5", + "@babel/plugin-transform-destructuring": "^7.21.3", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.21.5", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.20.11", + "@babel/plugin-transform-modules-commonjs": "^7.21.5", + "@babel/plugin-transform-modules-systemjs": "^7.20.11", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.21.3", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.21.5", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.20.7", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.21.5", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.21.5", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@storybook/codemod/node_modules/@babel/types": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", + "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@storybook/codemod/node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@storybook/codemod/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@storybook/codemod/node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@storybook/components": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.0.26.tgz", + "integrity": "sha512-n0TVWEF4Bc9JAyEIaN0PqwglbaYYRcPVG7ka+5wgGmBiuDlWI1SXd4EXxv2u0mVibHvtkHvOn6/GaZ1vG45p6g==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/global": "^5.0.0", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "memoizerific": "^1.11.3", + "use-resize-observer": "^9.1.0", + "util-deprecate": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/core-client": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.0.26.tgz", + "integrity": "sha512-1DA8mLnr0f6EuL74859IDK99a7CGNgMIN0/cAVNgYxq0WA4j+9ajsJ+/RIAgnS2NLVLR9kbezUtBEx4/H88IRA==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/preview-api": "7.0.26" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/core-common": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.0.26.tgz", + "integrity": "sha512-rojZblzB0egNXX0bZ7R3TuPDiBSIhxpZCrorrDMHOZ8F+zuBxyTiZ0yMxEDn7i46T2n1vX+hUHhwZVxZrLn/ZQ==", + "dev": true, + "dependencies": { + "@storybook/node-logger": "7.0.26", + "@storybook/types": "7.0.26", + "@types/node": "^16.0.0", + "@types/node-fetch": "^2.6.4", + "@types/pretty-hrtime": "^1.0.0", + "chalk": "^4.1.0", + "esbuild": "^0.17.0", + "esbuild-register": "^3.4.0", + "file-system-cache": "2.3.0", + "find-up": "^5.0.0", + "fs-extra": "^11.1.0", + "glob": "^8.1.0", + "glob-promise": "^6.0.2", + "handlebars": "^4.7.7", + "lazy-universal-dotenv": "^4.0.0", + "node-fetch": "^2.0.0", + "picomatch": "^2.3.0", + "pkg-dir": "^5.0.0", + "pretty-hrtime": "^1.0.3", + "resolve-from": "^5.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/core-common/node_modules/@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "node_modules/@storybook/core-common/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@storybook/core-common/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@storybook/core-common/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@storybook/core-common/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@storybook/core-common/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/core-common/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/core-common/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/core-common/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/core-common/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/core-common/node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/core-common/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/core-events": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.0.26.tgz", + "integrity": "sha512-ckZszphEAYs9wp8tPVhayEMzk8JxCiQfzbq0S45sbdqdTrl40PmsOjv5iPNaUYElI/Stfz+v4gDCEUfOsxyC+w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/core-server": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.0.26.tgz", + "integrity": "sha512-QieqH19jBPZafxJVmCVK6GTYkRN/CJ8RQUvyRH2KNhqXP0tHYfL51FlU70ldo/vHX6Ax4Cje5hx/Nln9+DOMNg==", + "dev": true, + "dependencies": { + "@aw-web-design/x-default-browser": "1.4.88", + "@discoveryjs/json-ext": "^0.5.3", + "@storybook/builder-manager": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/csf-tools": "7.0.26", + "@storybook/docs-mdx": "^0.1.0", + "@storybook/global": "^5.0.0", + "@storybook/manager": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/telemetry": "7.0.26", + "@storybook/types": "7.0.26", + "@types/detect-port": "^1.3.0", + "@types/node": "^16.0.0", + "@types/node-fetch": "^2.5.7", + "@types/pretty-hrtime": "^1.0.0", + "@types/semver": "^7.3.4", + "better-opn": "^2.1.1", + "chalk": "^4.1.0", + "cli-table3": "^0.6.1", + "compression": "^1.7.4", + "detect-port": "^1.3.0", + "express": "^4.17.3", + "fs-extra": "^11.1.0", + "globby": "^11.0.2", + "ip": "^2.0.0", + "lodash": "^4.17.21", + "node-fetch": "^2.6.7", + "open": "^8.4.0", + "pretty-hrtime": "^1.0.3", + "prompts": "^2.4.0", + "read-pkg-up": "^7.0.1", + "semver": "^7.3.7", + "serve-favicon": "^2.5.0", + "telejson": "^7.0.3", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2", + "watchpack": "^2.2.0", + "ws": "^8.2.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/core-server/node_modules/@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "node_modules/@storybook/core-server/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@storybook/core-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@storybook/core-server/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@storybook/core-server/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@storybook/core-server/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/core-server/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/@storybook/core-server/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/core-server/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/@storybook/core-server/node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@storybook/core-server/node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/core-server/node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/core-server/node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/core-server/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/core-server/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/core-server/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/core-server/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@storybook/core-webpack": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-7.0.26.tgz", + "integrity": "sha512-mIi+D+15sGRh8CWE7mMgvxX8KRfrACcR+AuEDi9rfQBB2PX0Okkrh6GAxPWjjeFFG7DF4RXdkusgC8/seYCTXg==", + "dev": true, + "dependencies": { + "@storybook/core-common": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/types": "7.0.26", + "@types/node": "^16.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/core-webpack/node_modules/@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "node_modules/@storybook/csf": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.1.tgz", + "integrity": "sha512-4hE3AlNVxR60Wc5KSC68ASYzUobjPqtSKyhV6G+ge0FIXU55N5nTY7dXGRZHQGDBPq+XqchMkIdlkHPRs8nTHg==", + "dev": true, + "dependencies": { + "type-fest": "^2.19.0" + } + }, + "node_modules/@storybook/csf-plugin": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.0.26.tgz", + "integrity": "sha512-D+wZvKlFxI/Vur8SRvkwKujOdV8ZL6xKiCX/07nFJXhhZoaeM+E78xPCL613Hj15GloujMkAnv7CT2rCiFJYow==", + "dev": true, + "dependencies": { + "@storybook/csf-tools": "7.0.26", + "unplugin": "^0.10.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/csf-tools": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.0.26.tgz", + "integrity": "sha512-O8WJNOkvgrGV6gS/5ERkgqiXOxoXMuHtzdJpIM9DHPhzkSxB1Inl3WrX/dRRDNtmiHf87hBUuzhgo7YR7z4tuQ==", + "dev": true, + "dependencies": { + "@babel/generator": "~7.21.1", + "@babel/parser": "~7.21.2", + "@babel/traverse": "~7.21.2", + "@babel/types": "~7.21.2", + "@storybook/csf": "^0.1.0", + "@storybook/types": "7.0.26", + "fs-extra": "^11.1.0", + "recast": "^0.23.1", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/csf-tools/node_modules/@babel/generator": { + "version": "7.21.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.9.tgz", + "integrity": "sha512-F3fZga2uv09wFdEjEQIJxXALXfz0+JaOb7SabvVMmjHxeVTuGW8wgE8Vp1Hd7O+zMTYtcfEISGRzPkeiaPPsvg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@storybook/csf-tools/node_modules/@babel/parser": { + "version": "7.21.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.9.tgz", + "integrity": "sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@storybook/csf-tools/node_modules/@babel/traverse": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", + "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.5", + "@babel/types": "^7.21.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@storybook/csf-tools/node_modules/@babel/types": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", + "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@storybook/csf/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/docs-mdx": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@storybook/docs-mdx/-/docs-mdx-0.1.0.tgz", + "integrity": "sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==", + "dev": true + }, + "node_modules/@storybook/docs-tools": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.0.26.tgz", + "integrity": "sha512-Ibpm/OTR2XmJgix5w+wMYbDwN0zp5e/pcqSHy36OvkBOG588IKSSzYdBjGdTLPHWBoehp2Kyndw/5dL/09ftXA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.10", + "@storybook/core-common": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/types": "7.0.26", + "@types/doctrine": "^0.0.3", + "doctrine": "^3.0.0", + "lodash": "^4.17.21" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/global": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", + "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", + "dev": true + }, + "node_modules/@storybook/instrumenter": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-7.0.26.tgz", + "integrity": "sha512-7Ty0LTslgkm5RyH6CqTAKhWz/cF6wq/sNdMYKwvVZHWNZ2LKMtXD0RWM2caCPruAGOQ9+52H+3s4TZGKaPSSWQ==", + "dev": true, + "dependencies": { + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/preview-api": "7.0.26" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/manager": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.0.26.tgz", + "integrity": "sha512-mxjU/pmHr8xL96HCipqazvZWQkxBPCbpZ2+YsJuJoLFN4m7RoOK21VK0euBW24NlSg7Vp57XGQcrJCv6xUTKMg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/manager-api": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.0.26.tgz", + "integrity": "sha512-/2p6lU7r30qMXob/UnzRL9yq7XjoE+YQXv1KhrcePfMBARbelYw9RYhYT/AkXGtb9/Fa95uG3lNvoDLC1IQfMQ==", + "dev": true, + "dependencies": { + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/global": "^5.0.0", + "@storybook/router": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "semver": "^7.3.7", + "store2": "^2.14.2", + "telejson": "^7.0.3", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/manager-api/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/manager-api/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/manager-api/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@storybook/mdx2-csf": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@storybook/mdx2-csf/-/mdx2-csf-1.1.0.tgz", + "integrity": "sha512-TXJJd5RAKakWx4BtpwvSNdgTDkKM6RkXU8GK34S/LhidQ5Pjz3wcnqb0TxEkfhK/ztbP8nKHqXFwLfa2CYkvQw==", + "dev": true + }, + "node_modules/@storybook/node-logger": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.0.26.tgz", + "integrity": "sha512-3Jqv3fRb8+Mn/aNl4IztgUAS/pvouVzpfHDc8+6KYAoFMeDXwHVlfF/+gRCpd/fbYaTHGrycIs5G48bC190Dgg==", + "dev": true, + "dependencies": { + "@types/npmlog": "^4.1.2", + "chalk": "^4.1.0", + "npmlog": "^5.0.1", + "pretty-hrtime": "^1.0.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/node-logger/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@storybook/node-logger/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@storybook/node-logger/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@storybook/node-logger/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@storybook/node-logger/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/node-logger/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/postinstall": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.0.26.tgz", + "integrity": "sha512-NhJBpQ+49RWF63UkdwrEwBLJBjAZeTlruPWfXGUb343iaGNNTsD3jajbToFHncibewH83yk6MeGfiyUva60oJw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/preset-react-webpack": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-7.0.26.tgz", + "integrity": "sha512-uJTW7of4eF8upoP2W0N5FVi1DG2f6CXkkI5qX4WmYFAmCtShor75EZTcv50QF4GOKJs9NlHDgC2+i6gT24u1rg==", + "dev": true, + "dependencies": { + "@babel/preset-flow": "^7.18.6", + "@babel/preset-react": "^7.18.6", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.5", + "@storybook/core-webpack": "7.0.26", + "@storybook/docs-tools": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/react": "7.0.26", + "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0", + "@types/node": "^16.0.0", + "@types/semver": "^7.3.4", + "babel-plugin-add-react-displayname": "^0.0.5", + "babel-plugin-react-docgen": "^4.2.1", + "fs-extra": "^11.1.0", + "react-refresh": "^0.11.0", + "semver": "^7.3.7", + "webpack": "5" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@babel/core": "^7.11.5", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@storybook/preset-react-webpack/node_modules/@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "node_modules/@storybook/preset-react-webpack/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/preset-react-webpack/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/preset-react-webpack/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@storybook/preview": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.0.26.tgz", + "integrity": "sha512-9Uaxl/MEMYqjLlKAeAF2ATuaM0yQagXUfu2bEOpuor2ys9XoisDkvB7jfsCVqMZHeQ+mCdYyBICHhgqzxcO2Zg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/preview-api": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.0.26.tgz", + "integrity": "sha512-uJwA4errBOZOoDF2T7Z2oLqjAYvvjMr31sTsOoT0niJtWr29RQp8yS6VoSrsuh+y3FAVqBEl5pS+DX3IGLjvxw==", + "dev": true, + "dependencies": { + "@storybook/channel-postmessage": "7.0.26", + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/global": "^5.0.0", + "@storybook/types": "7.0.26", + "@types/qs": "^6.9.5", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "qs": "^6.10.0", + "synchronous-promise": "^2.0.15", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/react": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-7.0.26.tgz", + "integrity": "sha512-+YK/1vF2Pd/PX7Ss5yPCIh9hee7iMVbu86gdjV9n9r6G244jQ7HLtdA01JKfq92/UgoysSWUjUECrxrUvcsh5w==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/core-client": "7.0.26", + "@storybook/docs-tools": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/preview-api": "7.0.26", + "@storybook/react-dom-shim": "7.0.26", + "@storybook/types": "7.0.26", + "@types/escodegen": "^0.0.6", + "@types/estree": "^0.0.51", + "@types/node": "^16.0.0", + "acorn": "^7.4.1", + "acorn-jsx": "^5.3.1", + "acorn-walk": "^7.2.0", + "escodegen": "^2.0.0", + "html-tags": "^3.1.0", + "lodash": "^4.17.21", + "prop-types": "^15.7.2", + "react-element-to-jsx-string": "^15.0.0", + "ts-dedent": "^2.0.0", + "type-fest": "^2.19.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@storybook/react-docgen-typescript-plugin": { + "version": "1.0.6--canary.9.0c3f3b7.0", + "resolved": "https://registry.npmjs.org/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.6--canary.9.0c3f3b7.0.tgz", + "integrity": "sha512-KUqXC3oa9JuQ0kZJLBhVdS4lOneKTOopnNBK4tUAgoxWQ3u/IjzdueZjFr7gyBrXMoU6duutk3RQR9u8ZpYJ4Q==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "endent": "^2.0.1", + "find-cache-dir": "^3.3.1", + "flat-cache": "^3.0.4", + "micromatch": "^4.0.2", + "react-docgen-typescript": "^2.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.x", + "webpack": ">= 4" + } + }, + "node_modules/@storybook/react-dom-shim": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.0.26.tgz", + "integrity": "sha512-heobG4IovYAD9fo7qmUHylCSQjDd1eXDCOaTiy+XVKobHAJgkz1gKqbaFSP6KLkPE4cKyScku2K9mY0tcKIhMw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/react-webpack5": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-7.0.26.tgz", + "integrity": "sha512-46LmHKEW5LNp13/rBUaEznTX35ZO8cuuVBlC7ySCR1+SGBJif3EBBe8VMyEI3FO48nwrenK9m+FtLqu5TDocCA==", + "dev": true, + "dependencies": { + "@storybook/builder-webpack5": "7.0.26", + "@storybook/preset-react-webpack": "7.0.26", + "@storybook/react": "7.0.26", + "@types/node": "^16.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@babel/core": "^7.11.5", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@storybook/react-webpack5/node_modules/@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "node_modules/@storybook/react/node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "node_modules/@storybook/react/node_modules/@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "node_modules/@storybook/react/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@storybook/react/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/router": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.0.26.tgz", + "integrity": "sha512-OfLittKxdahsgKsmQFoBX9q5tN/aqKMhhc/WbW88UPAQCUcEuazB0CwM+LI9YXY+n5L+vpLI4lGlgaqvPy4hHw==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "memoizerific": "^1.11.3", + "qs": "^6.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/store": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/store/-/store-7.0.26.tgz", + "integrity": "sha512-gJ9LDv8Mos8kPHj7SDEpBxQVL756j+15XUqBeBjgK+/TihnzIFeeX9QaTLo+As8bhgF/P2MVR+v0Qv9Zlm9MgQ==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/preview-api": "7.0.26" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/telemetry": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.0.26.tgz", + "integrity": "sha512-TgvtARAiD+SNyWJJfQdPiWW5JQkbX1UdHKEqEhoJXsGDkEi2Zpb+1tdeP1qZ3Gfbd1K0/LDpXGcqLv6/deSEdg==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "7.0.26", + "@storybook/core-common": "7.0.26", + "chalk": "^4.1.0", + "detect-package-manager": "^2.0.1", + "fetch-retry": "^5.0.2", + "fs-extra": "^11.1.0", + "isomorphic-unfetch": "^3.1.0", + "nanoid": "^3.3.1", + "read-pkg-up": "^7.0.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/telemetry/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@storybook/telemetry/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@storybook/telemetry/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@storybook/telemetry/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@storybook/telemetry/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/telemetry/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/@storybook/telemetry/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/@storybook/telemetry/node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/telemetry/node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/telemetry/node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/telemetry/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@storybook/telemetry/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/telemetry/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/testing-library": { + "version": "0.0.14-next.2", + "resolved": "https://registry.npmjs.org/@storybook/testing-library/-/testing-library-0.0.14-next.2.tgz", + "integrity": "sha512-i/SLSGm0o978ELok/SB4Qg1sZ3zr+KuuCkzyFqcCD0r/yf+bG35aQGkFqqxfSAdDxuQom0NO02FE+qys5Eapdg==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "^7.0.0-beta.0 || ^7.0.0-rc.0 || ^7.0.0", + "@storybook/instrumenter": "^7.0.0-beta.0 || ^7.0.0-rc.0 || ^7.0.0", + "@testing-library/dom": "^8.3.0", + "@testing-library/user-event": "^13.2.1", + "ts-dedent": "^2.2.0" + } + }, + "node_modules/@storybook/theming": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.0.26.tgz", + "integrity": "sha512-7hxpT2yq+xZonSsEZHOF+HDHx6GE0qlys3EQ63K9XCJ8VeBnq9M5zHvMK9iXl90093ufxpvWsfDWgtja2zvmTw==", + "dev": true, + "dependencies": { + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@storybook/client-logger": "7.0.26", + "@storybook/global": "^5.0.0", + "memoizerific": "^1.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/types": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.0.26.tgz", + "integrity": "sha512-5RBi6agtDglNXdffmw4+Fyv2dUdlIdeOdUj0O5+JRYajTxfHdurZd9r/42z4OstN+ORDkLA/svt8Q9JyRpIb6Q==", + "dev": true, + "dependencies": { + "@storybook/channels": "7.0.26", + "@types/babel__core": "^7.0.0", + "@types/express": "^4.7.0", + "file-system-cache": "2.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@tanstack/query-core": { + "version": "4.29.19", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.19.tgz", + "integrity": "sha512-uPe1DukeIpIHpQi6UzIgBcXsjjsDaLnc7hF+zLBKnaUlh7jFE/A+P8t4cU4VzKPMFB/C970n/9SxtpO5hmIRgw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.29.19", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.19.tgz", + "integrity": "sha512-XiTIOHHQ5Cw1WUlHaD4fmVUMhoWjuNJlAeJGq7eM4BraI5z7y8WkZO+NR8PSuRnQGblpuVdjClQbDFtwxTtTUw==", + "dependencies": { + "@tanstack/query-core": "4.29.19", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@testing-library/dom": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@testing-library/dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", + "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/@testing-library/dom": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", + "integrity": "sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@testing-library/react/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/react/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@testing-library/react/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/react/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@testing-library/react/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", + "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/detect-port": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/detect-port/-/detect-port-1.3.3.tgz", + "integrity": "sha512-bV/jQlAJ/nPY3XqSatkGpu+nGzou+uSwrH1cROhn+jBFg47yaNH+blW4C7p9KhopC7QxCv/6M86s37k8dMk0Yg==", + "dev": true + }, + "node_modules/@types/doctrine": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.3.tgz", + "integrity": "sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==", + "dev": true + }, + "node_modules/@types/ejs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.2.tgz", + "integrity": "sha512-ZmiaE3wglXVWBM9fyVC17aGPkLo/UgaOjEiI2FXQfyczrCefORPxIe+2dVmnmk3zkVIbizjrlQzmPGhSYGXG5g==", + "dev": true + }, + "node_modules/@types/escodegen": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.6.tgz", + "integrity": "sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==", + "dev": true + }, + "node_modules/@types/eslint": { + "version": "8.40.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.2.tgz", + "integrity": "sha512-PRVjQ4Eh9z9pmmtaq8nTjZjQwKFk7YIHIud3lRoKRBgUQjgjRmoGxxGEPXQkF+lH7QkHJRNr5F4aBgYCW0lqpQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/find-cache-dir": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz", + "integrity": "sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==", + "dev": true + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true + }, + "node_modules/@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.2.tgz", + "integrity": "sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@types/jest/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/@types/js-levenshtein": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz", + "integrity": "sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==" + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, + "node_modules/@types/mdx": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.5.tgz", + "integrity": "sha512-76CqzuD6Q7LC+AtbPqrvD9AqsN0k8bsYo2bM2J8pmNldP1aIPAbzUQ7QbobyXL4eLr1wK5x8FZFe8eF/ubRuBg==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "node_modules/@types/mime-types": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", + "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, + "node_modules/@types/node": { + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", + "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==" + }, + "node_modules/@types/node-fetch": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", + "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "node_modules/@types/npmlog": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/npmlog/-/npmlog-4.1.4.tgz", + "integrity": "sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true + }, + "node_modules/@types/pretty-hrtime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/pretty-hrtime/-/pretty-hrtime-1.0.1.tgz", + "integrity": "sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/set-cookie-parser": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz", + "integrity": "sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/styled-components": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==", + "dev": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "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==", + "dev": true, + "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/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.61.0.tgz", + "integrity": "sha512-r4RTnwTcaRRVUyKb7JO4DiOGmcMCat+uNs6HqJBfX7K2nlq5TagYZShhbhAw7hFT3bHaYgxMw6pKP0fhu05VMA==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.61.0" + }, + "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/parser": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.61.0.tgz", + "integrity": "sha512-yGr4Sgyh8uO6fSi9hw3jAFXNBHbCtKKFMdX2IkT3ZqpKmtAq3lHS4ixB/COFuAIJpwl9/AqF7j72ZDWYKmIfvg==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.61.0.tgz", + "integrity": "sha512-Fud90PxONnnLZ36oR5ClJBLTLfU4pIWBmnvGwTbEa2cXIqj70AEDEmOmpkFComjBZ/037ueKrOdHuYmSFVD7Rw==", + "dev": true, + "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/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@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==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.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/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@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==", + "dev": true, + "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/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.9.tgz", + "integrity": "sha512-4VSbbcMoxc4KLjb1gs96SRmi7w4h1SF+fCoiK0XaQX62buCc1G5d0DC5bJ9xJBNPDSVCmIrcl8BiYxzjrqaaJA==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@yarnpkg/esbuild-plugin-pnp": { + "version": "3.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@yarnpkg/esbuild-plugin-pnp/-/esbuild-plugin-pnp-3.0.0-rc.15.tgz", + "integrity": "sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "esbuild": ">=0.10.0" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-globals/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aggregate-error/node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "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": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/app-root-dir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", + "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", + "dev": true + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "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==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dev": true, + "dependencies": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "node_modules/ast-types": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", + "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "dev": true, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-jest": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.0.tgz", + "integrity": "sha512-Jj8Bq2yKsk11XLk06Nm8SdvYkAcecH+GuhxB8DnK5SncjHnJ88TQjSnGgE7jpajpnSvz9DZ6X8hXrDkD/6/TPQ==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.6.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/babel-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-loader": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-add-react-displayname": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz", + "integrity": "sha512-LY3+Y0XVDYcShHHorshrDbt4KFWL4bSeniCtl4SYZbask+Syngk1uMPCeN9+nSiZo6zX5s0RTq/J9Pnaaf/KHw==", + "dev": true + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-named-exports-order": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-named-exports-order/-/babel-plugin-named-exports-order-0.0.2.tgz", + "integrity": "sha512-OgOYHOLoRK+/mvXU9imKHlG6GkPLYrUCvFXG/CM93R/aNNO8pOOF4aS+S8CCHMDQoNSeiOYEZb/G6RwL95Jktw==", + "dev": true + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.4.tgz", + "integrity": "sha512-9WeK9snM1BfxB38goUEv2FLnA6ja07UMfazFHzCXUb3NyDZAwfXvQiURQ6guTTMeHcOsdknULm1PDhs4uWtKyA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.1", + "@nicolo-ribaudo/semver-v6": "^6.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.2.tgz", + "integrity": "sha512-Cid+Jv1BrY9ReW9lIfNlNpsI53N+FN7gE+f73zLAUbr9C52W4gKLWSByx47pfDJsEysojKArqOtOKZSVIIUTuQ==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.1", + "core-js-compat": "^3.31.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.1.tgz", + "integrity": "sha512-L8OyySuI6OSQ5hFy9O+7zFjyr4WhAfRjLIOkhQGYl+emwJkd/S4XXT1JpfrgR1jrQ1NcGiOh+yAdGlF8pnC3Jw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-react-docgen": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/babel-plugin-react-docgen/-/babel-plugin-react-docgen-4.2.1.tgz", + "integrity": "sha512-UQ0NmGHj/HAqi5Bew8WvNfCk8wSsmdgNd8ZdMjBCICtyCJCq9LiqgqvjCYe570/Wg7AQArSq1VQ60Dd/CHN7mQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.14.2", + "lodash": "^4.17.15", + "react-docgen": "^5.0.0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "dev": true + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-react-app": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", + "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/better-opn": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-2.1.1.tgz", + "integrity": "sha512-kIPXZS5qwyKiX/HcRvDYfmBQUa8XP17I0mYZZ0y4UhpYOSvtsLHDYqmomS+Mj20aDvD3knEiQ0ecQy2nhio3yA==", + "dev": true, + "dependencies": { + "open": "^7.0.3" + }, + "engines": { + "node": ">8.0.0" + } + }, + "node_modules/better-opn/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-assert": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", + "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==", + "dev": true + }, + "node_modules/browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==", + "dev": true, + "dependencies": { + "pako": "~0.2.0" + } + }, + "node_modules/browserslist": { + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c8": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-7.14.0.tgz", + "integrity": "sha512-i04rtkkcNcCf7zsQcSv/T9EbUn4RXQ6mropeMcjFOsQXQ0iGLAr/xT6TImQg4+U9hmNpN9XdvPkjUL1IzbgxJw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^2.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-reports": "^3.1.4", + "rimraf": "^3.0.2", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/c8/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/clean-css": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", + "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/clean-webpack-plugin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", + "integrity": "sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==", + "dev": true, + "dependencies": { + "del": "^4.1.1" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "webpack": ">=4.0.0 <6.0.0" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", + "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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==", + "dev": true + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "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/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-js-compat": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz", + "integrity": "sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.31.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.31.1.tgz", + "integrity": "sha512-w+C62kvWti0EPs4KPMCMVv9DriHSXfQOCQ94bGGBiEW5rrbtt/Rz8n5Krhfw9cpFyzXBjf3DB3QnPdEzGDY4Fw==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/css-loader": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.21", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.3", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-loader/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-loader/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dev": true, + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-equal": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", + "integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.0", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "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/defu": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.2.tgz", + "integrity": "sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==", + "dev": true + }, + "node_modules/del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/del/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/del/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/globby/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/detect-package-manager": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-2.0.1.tgz", + "integrity": "sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==", + "dev": true, + "dependencies": { + "execa": "^5.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/detect-port": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", + "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", + "dev": true, + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", + "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/dotenv-defaults": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", + "integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==", + "dev": true, + "dependencies": { + "dotenv": "^8.2.0" + } + }, + "node_modules/dotenv-defaults/node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/dotenv-webpack": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-8.0.1.tgz", + "integrity": "sha512-CdrgfhZOnx4uB18SgaoP9XHRN2v48BbjuXQsZY5ixs5A8579NxQkmMxRtI7aTwSiSQcM2ao12Fdu+L3ZS3bG4w==", + "dev": true, + "dependencies": { + "dotenv-defaults": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "webpack": "^4 || ^5" + } + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexify/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/duplexify/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.450", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.450.tgz", + "integrity": "sha512-BLG5HxSELlrMx7dJ2s+8SFlsCtJp37Zpk2VAxyC6CZtbc+9AJeZHfYHbrlSgdXp6saQ8StMqOTEDaBKgA7u1sw==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "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==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/endent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/endent/-/endent-2.1.0.tgz", + "integrity": "sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w==", + "dev": true, + "dependencies": { + "dedent": "^0.7.0", + "fast-json-parse": "^1.0.3", + "objectorarray": "^1.0.5" + } + }, + "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==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", + "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "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==", + "dev": true, + "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-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/es-module-lexer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", + "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", + "dev": true + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/esbuild-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-4.0.2.tgz", + "integrity": "sha512-kj88m0yrtTEJDeUEF+3TZsq7t9VPzQQj7UmXAzUbIaipoYSrd0UxKAcg4l9CBgP8uVoploiw+nKr8DIv6Y9gXw==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.0", + "get-tsconfig": "^4.7.0", + "loader-utils": "^2.0.4", + "webpack-sources": "^1.4.3" + }, + "funding": { + "url": "https://github.com/esbuild-kit/esbuild-loader?sponsor=1" + }, + "peerDependencies": { + "webpack": "^4.40.0 || ^5.0.0" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/android-arm": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.2.tgz", + "integrity": "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/android-arm64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz", + "integrity": "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/android-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.2.tgz", + "integrity": "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz", + "integrity": "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/darwin-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz", + "integrity": "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz", + "integrity": "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz", + "integrity": "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-arm": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz", + "integrity": "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-arm64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz", + "integrity": "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-ia32": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz", + "integrity": "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-loong64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz", + "integrity": "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz", + "integrity": "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz", + "integrity": "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz", + "integrity": "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-s390x": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz", + "integrity": "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz", + "integrity": "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz", + "integrity": "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz", + "integrity": "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/sunos-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz", + "integrity": "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/win32-arm64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz", + "integrity": "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/win32-ia32": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz", + "integrity": "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/win32-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz", + "integrity": "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/esbuild": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.2.tgz", + "integrity": "sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.2", + "@esbuild/android-arm64": "0.19.2", + "@esbuild/android-x64": "0.19.2", + "@esbuild/darwin-arm64": "0.19.2", + "@esbuild/darwin-x64": "0.19.2", + "@esbuild/freebsd-arm64": "0.19.2", + "@esbuild/freebsd-x64": "0.19.2", + "@esbuild/linux-arm": "0.19.2", + "@esbuild/linux-arm64": "0.19.2", + "@esbuild/linux-ia32": "0.19.2", + "@esbuild/linux-loong64": "0.19.2", + "@esbuild/linux-mips64el": "0.19.2", + "@esbuild/linux-ppc64": "0.19.2", + "@esbuild/linux-riscv64": "0.19.2", + "@esbuild/linux-s390x": "0.19.2", + "@esbuild/linux-x64": "0.19.2", + "@esbuild/netbsd-x64": "0.19.2", + "@esbuild/openbsd-x64": "0.19.2", + "@esbuild/sunos-x64": "0.19.2", + "@esbuild/win32-arm64": "0.19.2", + "@esbuild/win32-ia32": "0.19.2", + "@esbuild/win32-x64": "0.19.2" + } + }, + "node_modules/esbuild-loader/node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/esbuild-plugin-alias": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/esbuild-plugin-alias/-/esbuild-plugin-alias-0.2.1.tgz", + "integrity": "sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==", + "dev": true + }, + "node_modules/esbuild-register": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.4.2.tgz", + "integrity": "sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", + "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", + "dev": true, + "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-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "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==", + "dev": true, + "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-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==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-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==", + "dev": true, + "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-storybook": { + "version": "0.6.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.6.12.tgz", + "integrity": "sha512-XbIvrq6hNVG6rpdBr+eBw63QhOMLpZneQVSooEDow8aQCWGCk/5vqtap1yxpVydNfSxi3S/3mBBRLQqKUqQRww==", + "dev": true, + "dependencies": { + "@storybook/csf": "^0.0.1", + "@typescript-eslint/utils": "^5.45.0", + "requireindex": "^1.1.0", + "ts-dedent": "^2.2.0" + }, + "engines": { + "node": "12.x || 14.x || >= 16" + }, + "peerDependencies": { + "eslint": ">=6" + } + }, + "node_modules/eslint-plugin-storybook/node_modules/@storybook/csf": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.1.tgz", + "integrity": "sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/eslint-plugin-testing-library": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.0.tgz", + "integrity": "sha512-ELY7Gefo+61OfXKlQeXNIDVVLPcvKTeiQOoMZG9TeuWa7Ln4dUNRv8JdRWBQI9Mbb427XGlVB1aa1QPZxBJM8Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.58.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", + "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", + "dev": true, + "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/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-to-babel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/estree-to-babel/-/estree-to-babel-3.2.1.tgz", + "integrity": "sha512-YNF+mZ/Wu2FU/gvmzuWtYc8rloubL7wfXCTgouFrnjGVXPA/EeYYA7pupXWrb3Iv1cTBeSSxxJIbK23l4MRNqg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.1.6", + "@babel/types": "^7.2.0", + "c8": "^7.6.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "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/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.0.tgz", + "integrity": "sha512-AV+HaBtnDJ2YEUhPPo25HyUHBLaetM+y/Dq6pEC8VPQyt1dK+k8MfGkMy46djy2bddcqESc1kl4/K1uLWSfk9g==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.6.0", + "@types/node": "*", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-util": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "dev": true, + "dependencies": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/extract-zip/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-parse": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", + "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fetch-retry": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-5.0.6.tgz", + "integrity": "sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==", + "dev": true + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-system-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/file-system-cache/-/file-system-cache-2.3.0.tgz", + "integrity": "sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==", + "dev": true, + "dependencies": { + "fs-extra": "11.1.1", + "ramda": "0.29.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "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/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/flow-parser": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.211.0.tgz", + "integrity": "sha512-Ftqkqisn4MA8u+1I7KGYz35y/RtLsRETsK4qrH6KkDUjxnC4mgq3CcXbckHpGyfTErqMyVhJnlJ56feEn9Cn7A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "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/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", + "integrity": "sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "webpack": "^5.11.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/fs-monkey": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", + "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", + "dev": true + }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "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-npm-tarball-url": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/get-npm-tarball-url/-/get-npm-tarball-url-2.0.3.tgz", + "integrity": "sha512-R/PW6RqyaBQNWYaSyfrh54/qtcnOp22FHCCiRhSSZj0FP3KQWCsxxt0DzIdVTbwTqe9CtQfvl/FPD4UIPt4pqw==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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.7.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.0.tgz", + "integrity": "sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/giget": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.1.2.tgz", + "integrity": "sha512-HsLoS07HiQ5oqvObOI+Qb2tyZH4Gj5nYGfF9qQcZNrPw+uEFhdXtgJr01aO2pWadGHucajYDLxxbtQkm97ON2A==", + "dev": true, + "dependencies": { + "colorette": "^2.0.19", + "defu": "^6.1.2", + "https-proxy-agent": "^5.0.1", + "mri": "^1.2.0", + "node-fetch-native": "^1.0.2", + "pathe": "^1.1.0", + "tar": "^6.1.13" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/github-slugger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", + "dev": true + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/glob-promise": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-6.0.3.tgz", + "integrity": "sha512-m+kxywR5j/2Z2V9zvHKfwwL5Gp7gIFEBX+deTB9w2lJB+wSuw9kcS43VfvTAMk8TXL5JCl/cCjsR+tgNVspGyA==", + "dev": true, + "dependencies": { + "@types/glob": "^8.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/ahmadnassri" + }, + "peerDependencies": { + "glob": "^8.0.3" + } + }, + "node_modules/glob-promise/node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "dev": true, + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, + "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==", + "dev": true + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/graphql": { + "version": "16.7.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz", + "integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/gunzip-maybe": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", + "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==", + "dev": true, + "dependencies": { + "browserify-zlib": "^0.1.4", + "is-deflate": "^1.0.0", + "is-gzip": "^1.0.0", + "peek-stream": "^1.1.0", + "pumpify": "^1.3.3", + "through2": "^2.0.3" + }, + "bin": { + "gunzip-maybe": "bin.js" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "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==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/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==", + "dev": true, + "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/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/headers-polyfill": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.1.2.tgz", + "integrity": "sha512-tWCK4biJ6hcLqTviLXVR9DTRfYGQMXEIUj3gwJ2rZ5wO/at3XtkI4g8mCvFdUF9l1KMBNCfmNAdnahm1cgavQA==" + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-entities": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", + "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==", + "dev": true, + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "webpack": "^5.20.0" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/inquirer": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/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/inquirer/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/inquirer/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/inquirer/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/inquirer/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/inquirer/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/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "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-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-deflate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz", + "integrity": "sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==", + "dev": true + }, + "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==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "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-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-gzip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", + "integrity": "sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==" + }, + "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==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-in-cwd/node_modules/is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "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==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jake/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jake/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jake/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.0.tgz", + "integrity": "sha512-do1J9gGrQ68E4UfMz/4OM71p9qCqQxu32N/9ZfeYFSSlx0uUOuxeyZxtJZNaUTW12ZA11ERhmBjBhy1Ho96R4g==", + "dev": true, + "dependencies": { + "@jest/core": "^29.6.0", + "@jest/types": "^29.6.0", + "import-local": "^3.0.2", + "jest-cli": "^29.6.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-circus": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.0.tgz", + "integrity": "sha512-LtG45qEKhse2Ws5zNR4DnZATReLGQXzBZGZnJ0DU37p6d4wDhu41vvczCQ3Ou+llR6CRYDBshsubV7H4jZvIkw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.0", + "@jest/expect": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.6.0", + "jest-matcher-utils": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-runtime": "^29.6.0", + "jest-snapshot": "^29.6.0", + "jest-util": "^29.6.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.6.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-circus/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.0.tgz", + "integrity": "sha512-WvZIaanK/abkw6s01924DQ2QLwM5Q4Y4iPbSDb9Zg6smyXGqqcPQ7ft9X8D7B0jICz312eSzM6UlQNxuZJBrMw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/types": "^29.6.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.6.0", + "jest-util": "^29.6.0", + "jest-validate": "^29.6.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-cli/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-config": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.0.tgz", + "integrity": "sha512-fKA4jM91PDqWVkMpb1FVKxIuhg3hC6hgaen57cr1rRZkR96dCatvJZsk3ik7/GNu9ERj9wgAspOmyvkFoGsZhA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.6.0", + "@jest/types": "^29.6.0", + "babel-jest": "^29.6.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.6.0", + "jest-environment-node": "^29.6.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.0", + "jest-runner": "^29.6.0", + "jest-util": "^29.6.0", + "jest-validate": "^29.6.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.6.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.0.tgz", + "integrity": "sha512-ZRm7cd2m9YyZ0N3iMyuo1iUiprxQ/MFpYWXzEEj7hjzL3WnDffKW8192XBDcrAI8j7hnrM1wed3bL/oEnYF/8w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.0.tgz", + "integrity": "sha512-d0Jem4RBAlFUyV6JSXPSHVUpNo5RleSj+iJEy1G3+ZCrzHDjWs/1jUfrbnJKHdJdAx5BCEce/Ju379WqHhQk4w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.6.0", + "pretty-format": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-each/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.6.0.tgz", + "integrity": "sha512-/cOhoyv+uMbOh4nQPyqtkPas/uUxr5AbK6TPqMMFyj1qEJURY78RhqgBjOFIX02+Lvu5V0RWLq2qKY1dHubFOQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.0", + "@jest/fake-timers": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.6.0", + "jest-util": "^29.6.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.0.tgz", + "integrity": "sha512-BOf5Q2/nFCdBOnyBM5c5/6DbdQYgc+0gyUQ8l8qhUAB8O7pM+4QJXIXJsRZJaxd5SHV6y5VArTVhOfogoqcP8Q==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.0", + "@jest/fake-timers": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-mock": "^29.6.0", + "jest-util": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.0.tgz", + "integrity": "sha512-dY1DKufptj7hcJSuhpqlYPGcnN3XjlOy/g0jinpRTMsbb40ivZHiuIPzeminOZkrek8C+oDxC54ILGO3vMLojg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.0", + "jest-worker": "^29.6.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-haste-map/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-haste-map/node_modules/jest-worker": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.0.tgz", + "integrity": "sha512-oiQHH1SnKmZIwwPnpOrXTq4kHBk3lKGY/07DpnH0sAu+x7J8rXlbLDROZsU6vy9GwB0hPiZeZpu6YlJ48QoKcA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.6.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.0.tgz", + "integrity": "sha512-JdV6EZOPxHR1gd6ccxjNowuROkT2jtGU5G/g58RcJX1xe5mrtLj0g6/ZkyMoXF4cs+tTkHMFX6pcIrB1QPQwCw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-matcher-utils": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.0.tgz", + "integrity": "sha512-oSlqfGN+sbkB2Q5um/zL7z80w84FEAcLKzXBZIPyRk2F2Srg1ubhrHVKW68JCvb2+xKzAeGw35b+6gciS24PHw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.6.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.0.tgz", + "integrity": "sha512-mkCp56cETbpoNtsaeWVy6SKzk228mMi9FPHSObaRIhbR2Ujw9PqjW/yqVHD2tN1bHbC8ol6h3UEo7dOPmIYwIA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "dev": true, + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock/node_modules/@types/yargs": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", + "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-mock/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-mock/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-mock/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-mock/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-mock/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.0.tgz", + "integrity": "sha512-+hrpY4LzAONoZA/rvB6rnZLkOSA6UgJLpdCWrOZNSgGxWMumzRLu7dLUSCabAHzoHIDQ9qXfr3th1zYNJ0E8sQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.6.0", + "jest-validate": "^29.6.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.0.tgz", + "integrity": "sha512-eOfPog9K3hJdJk/3i6O6bQhXS+3uXhMDkLJGX+xmMPp7T1d/zdcFofbDnHgNoEkhD/mSimC5IagLEP7lpLLu/A==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-resolve/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.0.tgz", + "integrity": "sha512-4fZuGV2lOxS2BiqEG9/AI8E6O+jo+QZjMVcgi1x5E6aDql0Gd/EFIbUQ0pSS09y8cya1vJB/qC2xsE468jqtSg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.6.0", + "@jest/environment": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/transform": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.6.0", + "jest-haste-map": "^29.6.0", + "jest-leak-detector": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-resolve": "^29.6.0", + "jest-runtime": "^29.6.0", + "jest-util": "^29.6.0", + "jest-watcher": "^29.6.0", + "jest-worker": "^29.6.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/jest-worker": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.0.tgz", + "integrity": "sha512-oiQHH1SnKmZIwwPnpOrXTq4kHBk3lKGY/07DpnH0sAu+x7J8rXlbLDROZsU6vy9GwB0hPiZeZpu6YlJ48QoKcA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.6.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.0.tgz", + "integrity": "sha512-5FavYo3EeXLHIvnJf+r7Cj0buePAbe4mzRB9oeVxDS0uVmouSBjWeGgyRjZkw7ArxOoZI8gO6f8SGMJ2HFlwwg==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.0", + "@jest/fake-timers": "^29.6.0", + "@jest/globals": "^29.6.0", + "@jest/source-map": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/transform": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-mock": "^29.6.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.0", + "jest-snapshot": "^29.6.0", + "jest-util": "^29.6.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.0.tgz", + "integrity": "sha512-H3kUE9NwWDEDoutcOSS921IqdlkdjgnMdj1oMyxAHNflscdLc9dB8OudZHV6kj4OHJxbMxL8CdI5DlwYrs4wQg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.6.0", + "@jest/transform": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.6.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.6.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-util": "^29.6.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.6.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.0.tgz", + "integrity": "sha512-S0USx9YwcvEm4pQ5suisVm/RVxBmi0GFR7ocJhIeaCuW5AXnAnffXbaVKvIFodyZNOc9ygzVtTxmBf40HsHXaA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.0.tgz", + "integrity": "sha512-MLTrAJsb1+W7svbeZ+A7pAnyXMaQrjvPDKCy7OlfsfB6TMVc69v7WjUWfiR6r3snULFWZASiKgvNVDuATta1dg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.6.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-validate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.0.tgz", + "integrity": "sha512-LdsQqFNX60mRdRRe+zsELnYRH1yX6KL+ukbh+u6WSQeTheZZe1TlLJNKRQiZ7e0VbvMkywmMWL/KV35noOJCcw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.6.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-watcher/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jscodeshift": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", + "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.13.16", + "@babel/parser": "^7.13.16", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/preset-flow": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", + "@babel/register": "^7.13.16", + "babel-core": "^7.0.0-bridge.0", + "chalk": "^4.1.2", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "neo-async": "^2.5.0", + "node-dir": "^0.1.17", + "recast": "^0.21.0", + "temp": "^0.8.4", + "write-file-atomic": "^2.3.0" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/jscodeshift/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jscodeshift/node_modules/ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jscodeshift/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jscodeshift/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jscodeshift/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jscodeshift/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jscodeshift/node_modules/recast": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", + "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", + "dev": true, + "dependencies": { + "ast-types": "0.15.2", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/jscodeshift/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jscodeshift/node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "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==", + "dev": true, + "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/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "language-subtag-registry": "~0.3.2" + } + }, + "node_modules/launch-editor": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", + "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.7.3" + } + }, + "node_modules/lazy-universal-dotenv": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-4.0.0.tgz", + "integrity": "sha512-aXpZJRnTkpK6gQ/z4nk+ZBLd/Qdp118cvPruLSIQzQNRhKwEcdXCOzXuF55VDqIiuAaY3UGZ10DJtvZzDcvsxg==", + "dev": true, + "dependencies": { + "app-root-dir": "^1.0.2", + "dotenv": "^16.0.0", + "dotenv-expand": "^10.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lint-staged": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.3.tgz", + "integrity": "sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg==", + "dev": true, + "dependencies": { + "chalk": "5.2.0", + "cli-truncate": "^3.1.0", + "commander": "^10.0.0", + "debug": "^4.3.4", + "execa": "^7.0.0", + "lilconfig": "2.1.0", + "listr2": "^5.0.7", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.3", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.2.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/lint-staged/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==", + "dev": true, + "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/lint-staged/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/lint-staged/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==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/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==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/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==", + "dev": true, + "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/lint-staged/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==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/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==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/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==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/listr2": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", + "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.19", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.8.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/listr2/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/listr2/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/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/log-symbols/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/log-symbols/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/log-symbols/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/log-symbols/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/log-symbols/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/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-update/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/map-or-similar": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", + "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", + "dev": true + }, + "node_modules/markdown-to-jsx": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.2.1.tgz", + "integrity": "sha512-9HrdzBAo0+sFz9ZYAGT5fB8ilzTW+q6lPocRxrIesMO+aB40V9MgFfbfMXxlGjf22OpRy+IXlvVaQenicdpgbg==", + "dev": true, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/mdast-util-definitions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", + "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", + "dev": true, + "dependencies": { + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", + "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/memoizerific": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", + "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", + "dev": true, + "dependencies": { + "map-or-similar": "^1.5.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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/msw": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/msw/-/msw-1.2.3.tgz", + "integrity": "sha512-Fqy/TaLKR32x4IkMwudJHJysBzVM/v/lSoMPS9f3QaHLOmb3xHN9YurSUnRt+2eEvNXLjVPij1wMBQtLmTbKsg==", + "hasInstallScript": true, + "dependencies": { + "@mswjs/cookies": "^0.2.2", + "@mswjs/interceptors": "^0.17.5", + "@open-draft/until": "^1.0.3", + "@types/cookie": "^0.4.1", + "@types/js-levenshtein": "^1.1.1", + "chalk": "4.1.1", + "chokidar": "^3.4.2", + "cookie": "^0.4.2", + "graphql": "^15.0.0 || ^16.0.0", + "headers-polyfill": "^3.1.2", + "inquirer": "^8.2.0", + "is-node-process": "^1.2.0", + "js-levenshtein": "^1.1.6", + "node-fetch": "^2.6.7", + "outvariant": "^1.4.0", + "path-to-regexp": "^6.2.0", + "strict-event-emitter": "^0.4.3", + "type-fest": "^2.19.0", + "yargs": "^17.3.1" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.4.x <= 5.1.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw-storybook-addon": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/msw-storybook-addon/-/msw-storybook-addon-1.8.0.tgz", + "integrity": "sha512-dw3vZwqjixmiur0vouRSOax7wPSu9Og2Hspy9JZFHf49bZRjwDiLF0Pfn2NXEkGviYJOJiGxS1ejoTiUwoSg4A==", + "dev": true, + "dependencies": { + "is-node-process": "^1.0.1" + }, + "peerDependencies": { + "msw": ">=0.35.0 <2.0.0" + } + }, + "node_modules/msw/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/msw/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "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/msw/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/msw/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/msw/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/msw/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/msw/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/msw/node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + }, + "node_modules/msw/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/msw/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/msw/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/msw/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true + }, + "node_modules/node-dir": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", + "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.10.5" + } + }, + "node_modules/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch-native": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.2.0.tgz", + "integrity": "sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ==", + "dev": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.6.tgz", + "integrity": "sha512-vSZ4miHQ4FojLjmz2+ux4B0/XA16jfwt/LBzIUftDpRd8tujHFkXjMyLwjS08fIZCzesj2z7gJukOKJwqebJAQ==", + "dev": true + }, + "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==", + "dev": true, + "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==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/objectorarray": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/objectorarray/-/objectorarray-1.0.5.tgz", + "integrity": "sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==", + "dev": true + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/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/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "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==", + "dev": true, + "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/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/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/ora/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/ora/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/ora/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/ora/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/ora/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/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/outvariant": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", + "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==" + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "node_modules/peek-stream": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", + "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "duplexify": "^3.5.0", + "through2": "^2.0.3" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/polished": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", + "integrity": "sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss": { + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer-core": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-2.1.1.tgz", + "integrity": "sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w==", + "dev": true, + "dependencies": { + "@types/mime-types": "^2.1.0", + "debug": "^4.1.0", + "extract-zip": "^1.6.6", + "https-proxy-agent": "^4.0.0", + "mime": "^2.0.3", + "mime-types": "^2.1.25", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^2.6.1", + "ws": "^6.1.0" + }, + "engines": { + "node": ">=8.16.0" + } + }, + "node_modules/puppeteer-core/node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/puppeteer-core/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/puppeteer-core/node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/puppeteer-core/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/puppeteer-core/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "dev": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ramda": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", + "integrity": "sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "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-colorful": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", + "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", + "dev": true, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-docgen": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-5.4.3.tgz", + "integrity": "sha512-xlLJyOlnfr8lLEEeaDZ+X2J/KJoe6Nr9AzxnkdQWush5hz2ZSu66w6iLMOScMmxoSHWpWMn+k3v5ZiyCfcWsOA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@babel/generator": "^7.12.11", + "@babel/runtime": "^7.7.6", + "ast-types": "^0.14.2", + "commander": "^2.19.0", + "doctrine": "^3.0.0", + "estree-to-babel": "^3.1.0", + "neo-async": "^2.6.1", + "node-dir": "^0.1.10", + "strip-indent": "^3.0.0" + }, + "bin": { + "react-docgen": "bin/react-docgen.js" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/react-docgen-typescript": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", + "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", + "dev": true, + "peerDependencies": { + "typescript": ">= 4.3.x" + } + }, + "node_modules/react-docgen/node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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-element-to-jsx-string": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz", + "integrity": "sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==", + "dev": true, + "dependencies": { + "@base2/pretty-print-object": "1.0.1", + "is-plain-object": "5.0.0", + "react-is": "18.1.0" + }, + "peerDependencies": { + "react": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0", + "react-dom": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/react-element-to-jsx-string/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-element-to-jsx-string/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "node_modules/react-inspector": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", + "integrity": "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==", + "dev": true, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0 || ^18.0.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==", + "dev": true + }, + "node_modules/react-refresh": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", + "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.1.tgz", + "integrity": "sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g==", + "dependencies": { + "@remix-run/router": "1.7.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.1.tgz", + "integrity": "sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw==", + "dependencies": { + "@remix-run/router": "1.7.1", + "react-router": "6.14.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recast": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.2.tgz", + "integrity": "sha512-Qv6cPfVZyMOtPszK6PgW70pUgm7gPlFitAPf0Q69rlOA0zLw2XdDcNmPbVGYicFGT9O8I7TZ/0ryJD+6COvIPw==", + "dev": true, + "dependencies": { + "assert": "^2.0.0", + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "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==", + "dev": true, + "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/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remark-external-links": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/remark-external-links/-/remark-external-links-8.0.0.tgz", + "integrity": "sha512-5vPSX0kHoSsqtdftSHhIYofVINC8qmp0nctkeU9YoJwV3YfiBRiI6cbFRJ0oI/1F9xS+bopXG0m2KS8VFscuKA==", + "dev": true, + "dependencies": { + "extend": "^3.0.0", + "is-absolute-url": "^3.0.0", + "mdast-util-definitions": "^4.0.0", + "space-separated-tokens": "^1.0.0", + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-slug": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/remark-slug/-/remark-slug-6.1.0.tgz", + "integrity": "sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ==", + "dev": true, + "dependencies": { + "github-slugger": "^1.0.0", + "mdast-util-to-string": "^1.0.0", + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "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-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "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/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dev": true, + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-favicon": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.5.0.tgz", + "integrity": "sha512-FMW2RvqNr03x+C0WxTyu6sOv21oOjkq5j8tjquWccwa6ScNyGFOGJVpuS1NmTVGBAHS07xnSKotgf2ehQmf9iA==", + "dev": true, + "dependencies": { + "etag": "~1.8.1", + "fresh": "0.5.2", + "ms": "2.1.1", + "parseurl": "~1.3.2", + "safe-buffer": "5.1.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-favicon/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/serve-favicon/node_modules/safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shelljs/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shelljs/node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/shelljs/node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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==", + "dev": true, + "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/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/store2": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.2.tgz", + "integrity": "sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==", + "dev": true + }, + "node_modules/storybook": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.0.26.tgz", + "integrity": "sha512-N6+/QBIahTnOJ3mQFNh+PIimjw+yUUoBlnMq8kE1Rg6QFi8ErEK8xte6uppiTh+7ShpqeLhp9ipuDV6DwJ9Aqg==", + "dev": true, + "dependencies": { + "@storybook/cli": "7.0.26" + }, + "bin": { + "sb": "index.js", + "storybook": "index.js" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/strict-event-emitter": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", + "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", + "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/styled-components": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.0.2.tgz", + "integrity": "sha512-CRWTuYme0W4zVqzXpODByyocgVbBpRoXmaEgPGb67dvweV1igp7Ik4Z5C9e83wZ2l2hPg/XKV7cjuNxhRlC7Mg==", + "dev": true, + "dependencies": { + "@babel/cli": "^7.21.0", + "@babel/core": "^7.21.0", + "@babel/helper-module-imports": "^7.18.6", + "@babel/plugin-external-helpers": "^7.18.6", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@babel/traverse": "^7.21.2", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/unitless": "^0.8.0", + "@types/stylis": "^4.0.2", + "css-to-react-native": "^3.2.0", + "csstype": "^3.1.2", + "postcss": "^8.4.23", + "shallowequal": "^1.1.0", + "stylis": "^4.3.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "babel-plugin-styled-components": ">= 2", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "babel-plugin-styled-components": { + "optional": true + } + } + }, + "node_modules/stylis": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", + "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==", + "dev": true + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/synchronous-promise": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.17.tgz", + "integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==", + "dev": true + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/telejson": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/telejson/-/telejson-7.1.0.tgz", + "integrity": "sha512-jFJO4P5gPebZAERPkJsqMAQ0IMA1Hi0AoSfxpnUaV6j6R2SZqlpkbS20U6dEUtA3RUYt2Ak/mTlkQzHH9Rv/hA==", + "dev": true, + "dependencies": { + "memoizerific": "^1.11.3" + } + }, + "node_modules/temp": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", + "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "dev": true, + "dependencies": { + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/temp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/tempy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", + "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", + "dev": true, + "dependencies": { + "del": "^6.0.0", + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dev": true, + "dependencies": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.2.tgz", + "integrity": "sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true, + "engines": { + "node": ">=6.10" + } + }, + "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==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.1.tgz", + "integrity": "sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths/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==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "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==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "dev": true, + "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/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "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==", + "dev": true, + "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/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unplugin": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-0.10.2.tgz", + "integrity": "sha512-6rk7GUa4ICYjae5PrAllvcDeuT8pA9+j5J5EkxbMFaV+SalHhxZ7X2dohMzu6C3XzsMT+6jwR/+pwPNR3uK9MA==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "chokidar": "^3.5.3", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.4.5" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-resize-observer": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", + "integrity": "sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==", + "dev": true, + "dependencies": { + "@juggle/resize-observer": "^3.3.1" + }, + "peerDependencies": { + "react": "16.8.0 - 18", + "react-dom": "16.8.0 - 18" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "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==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "dependencies": { + "util": "^0.12.3" + }, + "optionalDependencies": { + "@zxing/text-encoding": "0.9.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/webpack": { + "version": "5.88.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz", + "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-hot-middleware": { + "version": "2.25.4", + "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.25.4.tgz", + "integrity": "sha512-IRmTspuHM06aZh98OhBJtqLpeWFM8FXJS5UYpKYxCJzyFoyWj1w6VGFfomZU7OPA55dMLrQK0pRT1eQ3PACr4w==", + "dev": true, + "dependencies": { + "ansi-html-community": "0.0.8", + "html-entities": "^2.1.0", + "strip-ansi": "^6.0.0" + } + }, + "node_modules/webpack-merge": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", + "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.6.tgz", + "integrity": "sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==", + "dev": true + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", + "dev": true + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "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==", + "dev": true, + "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-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "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/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@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==", + "dev": true + }, + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@aw-web-design/x-default-browser": { + "version": "1.4.88", + "resolved": "https://registry.npmjs.org/@aw-web-design/x-default-browser/-/x-default-browser-1.4.88.tgz", + "integrity": "sha512-AkEmF0wcwYC2QkhK703Y83fxWARttIWXDmQN8+cof8FmFZ5BRhnNXGymeb1S73bOCLfWjYELxtujL56idCN/XA==", + "dev": true, + "requires": { + "default-browser-id": "3.0.0" + } + }, + "@babel/cli": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.6.tgz", + "integrity": "sha512-Be3/RfEDmkMRGT1+ru5nTkfcvWz5jDOYg1V9rXqTz2u9Qt96O1ryboGvxVBp7wOnYWDB8DNHIWb6DThrpudfOw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.17", + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, + "@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dev": true, + "requires": { + "@babel/highlight": "^7.22.5" + } + }, + "@babel/compat-data": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz", + "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==", + "dev": true + }, + "@babel/core": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.6.tgz", + "integrity": "sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.6", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2" + } + }, + "@babel/eslint-parser": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.22.6.tgz", + "integrity": "sha512-KAom7E7d6bAh5/PflF3luynWlDLOIqfX+ZJcL0LRs6/6rtXJmJxPiWuIGfxNPtcWdtQ5lSSJbKbQlz/c/R60Ng==", + "dev": true, + "requires": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "eslint-visitor-keys": "^2.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", + "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz", + "integrity": "sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-validator-option": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.6.tgz", + "integrity": "sha512-iwdzgtSiBxF6ni6mzVnZCF3xt5qE6cEA0J7nFt8QOAWZ0zjCFceEgpn3vtb2V7WFR6QzP2jmIFOHMTRo7eNJjQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@nicolo-ribaudo/semver-v6": "^6.3.3" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.6.tgz", + "integrity": "sha512-nBookhLKxAWo/TUCmhnaEJyLz2dekjQvv5SRpE9epWQBcpedWLKt8aZdsuT9XV5ovzR3fENLjRXVT0GsSlGGhA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "regexpu-core": "^5.3.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.1.tgz", + "integrity": "sha512-kX4oXixDxG197yhX+J3Wp+NpL2wuCFjWQAr6yX2jtCnflK9ulMI51ULFGIrWiX1jGfvAxdHp+XQCcP2bZGPs9A==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "requires": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz", + "integrity": "sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-wrap-function": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-replace-supers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", + "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz", + "integrity": "sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helpers": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", + "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", + "dev": true, + "requires": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5" + } + }, + "@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.6.tgz", + "integrity": "sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw==", + "dev": true + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", + "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", + "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.5" + } + }, + "@babel/plugin-external-helpers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.22.5.tgz", + "integrity": "sha512-ngnNEWxmykPk82mH4ajZT0qTztr3Je6hrMuKAslZVM8G1YZTENJSYwrIGtt6KOtznug3exmAtF4so/nPqJuA4A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", + "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-decorators": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.22.6.tgz", + "integrity": "sha512-cgskJ9W7kxTk/wBM16JNHhlTkeyDK6slMJg1peaI4LM3y2HtTv+6I85sW9UXSUZilndIBvDBETA1BRoOYdxWKw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/plugin-syntax-decorators": "^7.22.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-decorators": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.22.5.tgz", + "integrity": "sha512-avpUOBS7IU6al8MmF1XpAyj9QYeLPuSDJI5D4pVMSMdL7xQokKqJPYQC67RCT0aCTashUXPiGwMJ0DEXXCEmMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-flow": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.22.5.tgz", + "integrity": "sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-async-generator-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz", + "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", + "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-static-block": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", + "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", + "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", + "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dynamic-import": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", + "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-export-namespace-from": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", + "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-transform-flow-strip-types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.22.5.tgz", + "integrity": "sha512-tujNbZdxdG0/54g/oua8ISToaXTFBf8EnSb5PgQSciIXWOWKX3S4+JR7ZE9ol8FZwf9kxitzkGQ+QWeov/mCiA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-flow": "^7.22.5" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", + "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-json-strings": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", + "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", + "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", + "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", + "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", + "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", + "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-transform-numeric-separator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", + "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-transform-object-rest-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", + "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.5" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + } + }, + "@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", + "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-transform-optional-chaining": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz", + "integrity": "sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", + "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-methods": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-property-in-object": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", + "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", + "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz", + "integrity": "sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "dev": true, + "requires": { + "@babel/plugin-transform-react-jsx": "^7.22.5" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", + "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", + "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.1" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.6.tgz", + "integrity": "sha512-+AGkst7Kqq3QUflKGkhWWMRb9vaKamoreNmYc+sjsIpOp+TsyU0exhp3RlwjQa/HdlKkPt3AMDwfg8Hpt9Vwqg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "babel-plugin-polyfill-corejs2": "^0.4.3", + "babel-plugin-polyfill-corejs3": "^0.8.1", + "babel-plugin-polyfill-regenerator": "^0.5.0" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.5.tgz", + "integrity": "sha512-SMubA9S7Cb5sGSFFUlqxyClTA9zWJ8qGQrppNUm05LtFuN1ELRFNndkix4zUJrC9F+YivWwa1dHMSyo0e0N9dA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", + "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/preset-env": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.6.tgz", + "integrity": "sha512-IHr0AXHGk8oh8HYSs45Mxuv6iySUBwDTIzJSnXN7PURqHdxJVQlCoXmKJgyvSS9bcNf9NVRVE35z+LkCvGmi6w==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.5", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.22.5", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.6", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.22.5", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.5", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.5", + "@babel/plugin-transform-for-of": "^7.22.5", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.5", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-modules-systemjs": "^7.22.5", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", + "@babel/plugin-transform-numeric-separator": "^7.22.5", + "@babel/plugin-transform-object-rest-spread": "^7.22.5", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.6", + "@babel/plugin-transform-parameters": "^7.22.5", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.5", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.5", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.5", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "babel-plugin-polyfill-corejs2": "^0.4.3", + "babel-plugin-polyfill-corejs3": "^0.8.1", + "babel-plugin-polyfill-regenerator": "^0.5.0", + "core-js-compat": "^3.31.0" + } + }, + "@babel/preset-flow": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.22.5.tgz", + "integrity": "sha512-ta2qZ+LSiGCrP5pgcGt8xMnnkXQrq8Sa4Ulhy06BOlF5QbLw9q5hIx7bn5MrsvyTGAfh6kTOo07Q+Pfld/8Y5Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-transform-flow-strip-types": "^7.22.5" + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/preset-react": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.5.tgz", + "integrity": "sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-transform-react-display-name": "^7.22.5", + "@babel/plugin-transform-react-jsx": "^7.22.5", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.22.5" + } + }, + "@babel/preset-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz", + "integrity": "sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-typescript": "^7.22.5" + } + }, + "@babel/register": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.22.5.tgz", + "integrity": "sha512-vV6pm/4CijSQ8Y47RH5SopXzursN35RQINfGJkmOlcpAtGuf94miFvIPhCKGQN7WGIcsgG1BHEX2KVdTYwTwUQ==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.5", + "source-map-support": "^0.5.16" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } + } + }, + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "@babel/runtime": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/traverse": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.6.tgz", + "integrity": "sha512-53CijMvKlLIDlOTrdWiHileRddlIiwUIyCKqYa7lYnnPldXCG5dUSN38uT0cA6i7rHWNKJLH0VU/Kxdr1GzB3w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.6", + "@babel/types": "^7.22.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + } + }, + "@base2/pretty-print-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz", + "integrity": "sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==", + "dev": true + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true + }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, + "@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dev": true, + "requires": { + "@emotion/memoize": "^0.8.1" + } + }, + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "dev": true + }, + "@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "dev": true + }, + "@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "dev": true + }, + "@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "dev": true, + "optional": true + }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", + "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", + "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", + "dev": true + }, + "@fal-works/esbuild-plugin-global-externals": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", + "integrity": "sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.0.tgz", + "integrity": "sha512-anb6L1yg7uPQpytNVA5skRaXy3BmrsU8icRhTVNbWdjYWDDfy8M1Kq5HIVRpYoABdbpqsc5Dr+jtu4+qWRQBiQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.6.0", + "jest-util": "^29.6.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/core": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.0.tgz", + "integrity": "sha512-5dbMHfY/5R9m8NbgmB3JlxQqooZ/ooPSOiwEQZZ+HODwJTbIu37seVcZNBK29aMdXtjvTRB3f6LCvkKq+r8uQA==", + "dev": true, + "requires": { + "@jest/console": "^29.6.0", + "@jest/reporters": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/transform": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.6.0", + "jest-haste-map": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.0", + "jest-resolve-dependencies": "^29.6.0", + "jest-runner": "^29.6.0", + "jest-runtime": "^29.6.0", + "jest-snapshot": "^29.6.0", + "jest-util": "^29.6.0", + "jest-validate": "^29.6.0", + "jest-watcher": "^29.6.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/environment": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.0.tgz", + "integrity": "sha512-bUZLYUxYlUIsslBbxII0fq0kr1+friI3Gty+cRLmocGB1jdcAHs7FS8QdCDqedE8q4DZE1g/AJHH6OJZBLGGsg==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-mock": "^29.6.0" + }, + "dependencies": { + "jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + } + } + } + }, + "@jest/expect": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.0.tgz", + "integrity": "sha512-a7pISPW28Q3c0/pLwz4mQ6tbAI+hc8/0CJp9ix6e9U4dQ6TiHQX82CT5DV5BMWaw8bFH4E6zsfZxXdn6Ka23Bw==", + "dev": true, + "requires": { + "expect": "^29.6.0", + "jest-snapshot": "^29.6.0" + } + }, + "@jest/expect-utils": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.0.tgz", + "integrity": "sha512-LLSQQN7oypMSETKoPWpsWYVKJd9LQWmSDDAc4hUQ4JocVC7LAMy9R3ZMhlnLwbcFvQORZnZR7HM893Px6cJhvA==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3" + } + }, + "@jest/fake-timers": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.0.tgz", + "integrity": "sha512-nuCU46AsZoskthWSDS2Aj6LARgyNcp5Fjx2qxsO/fPl1Wp1CJ+dBDqs0OkEcJK8FBeV/MbjH5efe79M2sHcV+A==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.0", + "jest-mock": "^29.6.0", + "jest-util": "^29.6.0" + }, + "dependencies": { + "jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + } + } + } + }, + "@jest/globals": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.0.tgz", + "integrity": "sha512-IQQ3hZ2D/hwEwXSMv5GbfhzdH0nTQR3KPYxnuW6gYWbd6+7/zgMz7Okn6EgBbNtJNONq03k5EKA6HqGyzRbpeg==", + "dev": true, + "requires": { + "@jest/environment": "^29.6.0", + "@jest/expect": "^29.6.0", + "@jest/types": "^29.6.0", + "jest-mock": "^29.6.0" + }, + "dependencies": { + "jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + } + } + } + }, + "@jest/reporters": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.0.tgz", + "integrity": "sha512-dWEq4HI0VvHcAD6XTtyBKKARLytyyWPIy1SvGOcU91106MfvHPdxZgupFwVHd8TFpZPpA3SebYjtwS5BUS76Rw==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/transform": "^29.6.0", + "@jest/types": "^29.6.0", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.6.0", + "jest-util": "^29.6.0", + "jest-worker": "^29.6.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.0.tgz", + "integrity": "sha512-oiQHH1SnKmZIwwPnpOrXTq4kHBk3lKGY/07DpnH0sAu+x7J8rXlbLDROZsU6vy9GwB0hPiZeZpu6YlJ48QoKcA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.6.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/schemas": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, + "@jest/source-map": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", + "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.0.tgz", + "integrity": "sha512-9qLb7xITeyWhM4yatn2muqfomuoCTOhv0QV9i7XiIyYi3QLfnvPv5NeJp5u0PZeutAOROMLKakOkmoAisOr3YQ==", + "dev": true, + "requires": { + "@jest/console": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.0.tgz", + "integrity": "sha512-HYCS3LKRQotKWj2mnA3AN13PPevYZu8MJKm12lzYojpJNnn6kI/3PWmr1At/e3tUu+FHQDiOyaDVuR4EV3ezBw==", + "dev": true, + "requires": { + "@jest/test-result": "^29.6.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.0.tgz", + "integrity": "sha512-bhP/KxPo3e322FJ0nKAcb6WVK76ZYyQd1lWygJzoSqP8SYMSLdxHqP4wnPTI4WvbB8PKPDV30y5y7Tya4RHOBA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.0", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + } + } + }, + "@jest/types": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.0.tgz", + "integrity": "sha512-8XCgL9JhqbJTFnMRjEAO+TuW251+MoMd5BSzLiE3vvzpQ8RlBxy8NoyNkDhs3K3OL3HeVinlOl9or5p7GTeOLg==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + } + } + }, + "@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "dev": true + }, + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "@mdx-js/react": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", + "integrity": "sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==", + "dev": true, + "requires": { + "@types/mdx": "^2.0.0", + "@types/react": ">=16" + } + }, + "@mswjs/cookies": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.2.tgz", + "integrity": "sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==", + "requires": { + "@types/set-cookie-parser": "^2.4.0", + "set-cookie-parser": "^2.4.6" + } + }, + "@mswjs/interceptors": { + "version": "0.17.9", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.17.9.tgz", + "integrity": "sha512-4LVGt03RobMH/7ZrbHqRxQrS9cc2uh+iNKSj8UWr8M26A2i793ju+csaB5zaqYltqJmA2jUq4VeYfKmVqvsXQg==", + "requires": { + "@open-draft/until": "^1.0.3", + "@types/debug": "^4.1.7", + "@xmldom/xmldom": "^0.8.3", + "debug": "^4.3.3", + "headers-polyfill": "^3.1.0", + "outvariant": "^1.2.1", + "strict-event-emitter": "^0.2.4", + "web-encoding": "^1.1.5" + }, + "dependencies": { + "strict-event-emitter": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz", + "integrity": "sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==", + "requires": { + "events": "^3.3.0" + } + } + } + }, + "@ndelangen/get-tarball": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@ndelangen/get-tarball/-/get-tarball-3.0.9.tgz", + "integrity": "sha512-9JKTEik4vq+yGosHYhZ1tiH/3WpUS0Nh0kej4Agndhox8pAdWhEx5knFVRcb/ya9knCRCs1rPxNrSXTDdfVqpA==", + "dev": true, + "requires": { + "gunzip-maybe": "^1.4.2", + "pump": "^3.0.0", + "tar-fs": "^2.1.1" + } + }, + "@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, + "@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "requires": { + "eslint-scope": "5.1.1" + } + }, + "@nicolo-ribaudo/semver-v6": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz", + "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@open-draft/until": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", + "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==" + }, + "@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz", + "integrity": "sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==", + "dev": true, + "requires": { + "ansi-html-community": "^0.0.8", + "common-path-prefix": "^3.0.0", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "find-up": "^5.0.0", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^3.0.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + } + } + }, + "@remix-run/router": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.1.tgz", + "integrity": "sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ==" + }, + "@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==", + "dev": true + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@storybook/addon-actions": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.0.26.tgz", + "integrity": "sha512-vVoqE0Zw0g1PPnGfho8vRwjpXhQCpRNBQ/2U83/CSodHWL/MBYENG0XMby90TC72M26gNmEh0dn1YCUXvLdiew==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "polished": "^4.2.2", + "prop-types": "^15.7.2", + "react-inspector": "^6.0.0", + "telejson": "^7.0.3", + "ts-dedent": "^2.0.0", + "uuid": "^9.0.0" + }, + "dependencies": { + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true + } + } + }, + "@storybook/addon-backgrounds": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.0.26.tgz", + "integrity": "sha512-sjTkOnSsVBBl1GruVVsNKWEuLCbKjkNun1mzIklfYAiHz9hTZIhe9MA2SGZoDozMUDIXQqSoMDEc3rnDtfqsnQ==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "memoizerific": "^1.11.3", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/addon-controls": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.0.26.tgz", + "integrity": "sha512-mp1WuOYCPvR33orHn0XPABY5roF9Le8HnZwTpvfkrRMeMqLnYLnkCTZqY3JN/IOVlyQuYdqodP5CPDHNDLmvVg==", + "dev": true, + "requires": { + "@storybook/blocks": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/manager-api": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "lodash": "^4.17.21", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/addon-docs": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.0.26.tgz", + "integrity": "sha512-C8DOwfmPBWDUS1IJbyJxykgVVHVzSSL+JFh3FwtF0hsqwjlNW4OvGDFbz0oAxyxs4V46xVcvh4E95e3GkW36BQ==", + "dev": true, + "requires": { + "@babel/core": "^7.20.2", + "@babel/plugin-transform-react-jsx": "^7.19.0", + "@jest/transform": "^29.3.1", + "@mdx-js/react": "^2.1.5", + "@storybook/blocks": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/csf-plugin": "7.0.26", + "@storybook/csf-tools": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/mdx2-csf": "^1.0.0", + "@storybook/node-logger": "7.0.26", + "@storybook/postinstall": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/react-dom-shim": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "fs-extra": "^11.1.0", + "remark-external-links": "^8.0.0", + "remark-slug": "^6.0.0", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/addon-essentials": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.0.26.tgz", + "integrity": "sha512-r+IOtxbIqlCKO8fDgLppubYm+GEW3ZDxjPwXMQdDGem9ENpz0QLKb49r89+UYqnnaYjuYKjDNUOqy0gX2HfUXQ==", + "dev": true, + "requires": { + "@storybook/addon-actions": "7.0.26", + "@storybook/addon-backgrounds": "7.0.26", + "@storybook/addon-controls": "7.0.26", + "@storybook/addon-docs": "7.0.26", + "@storybook/addon-highlight": "7.0.26", + "@storybook/addon-measure": "7.0.26", + "@storybook/addon-outline": "7.0.26", + "@storybook/addon-toolbars": "7.0.26", + "@storybook/addon-viewport": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/manager-api": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/preview-api": "7.0.26", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/addon-highlight": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.0.26.tgz", + "integrity": "sha512-+I+MoM7yXCA3YR2FwTSxSs6/IBpcc3Ey88WboGthR23ERmsgZOtum1S7KZ6cffNCOq4U0LzPkjKX2bICytFrIQ==", + "dev": true, + "requires": { + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/preview-api": "7.0.26" + } + }, + "@storybook/addon-interactions": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-7.0.26.tgz", + "integrity": "sha512-trIbPFLdxF6XgGORhx8eSGmGZ/4/AekJyFluf2lgutGi4TPL5Xzrx3o1kTFPVdLAPplBuDIlVI4HSGHHH2zeTw==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/instrumenter": "7.0.26", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "jest-mock": "^27.0.6", + "polished": "^4.2.2", + "ts-dedent": "^2.2.0" + } + }, + "@storybook/addon-links": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.0.26.tgz", + "integrity": "sha512-og+8AUAUpHsT+MVjhdQmRNJw9RUkHn5FFoou003b9V4UlPPNDYTo/tNEqOhUXn2l/ESAROJlR/q/8Qjdes24pA==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/router": "7.0.26", + "@storybook/types": "7.0.26", + "prop-types": "^15.7.2", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/addon-measure": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.0.26.tgz", + "integrity": "sha512-iAnI6q3GB8uSydK+S4m4ANpy0GpMpHhmU0oBtu6OmyyzHUH1RJ7/fGfBnzx6YT+rIOlqSFocxYGn74ylsp33Wg==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/types": "7.0.26" + } + }, + "@storybook/addon-outline": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.0.26.tgz", + "integrity": "sha512-oL7D0IWO0M6hMw5cWEC6JdKXlGadlVIdhIrVN+0gdFxuxCHTGpebQ02DCvyfls29UssEOxPaO1XMdu9tDlctbg==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/types": "7.0.26", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/addon-toolbars": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.0.26.tgz", + "integrity": "sha512-DrwqcWuCLjaTNFtAYUxO2VaLrr2ibhB3ZQwW7J6a4YFCJaV49wempGPq3BzTWvrPUtMxGp7J3ZusdH9jBgCzjA==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26" + } + }, + "@storybook/addon-viewport": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.0.26.tgz", + "integrity": "sha512-veAYxnR11sojXC7tlnBZ/USiafhWCsZNvjxmywl/XCh3MeDGFFDb2NN1s/7irAYXfNMOhgPGZED19BN9cQ8QRQ==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "memoizerific": "^1.11.3", + "prop-types": "^15.7.2" + } + }, + "@storybook/addons": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-7.0.26.tgz", + "integrity": "sha512-zn7vdgXkQ4DpCJaawJsNPnh0NzXVXd2qfVtzYWWKT4eyj43VXxoVX2Z4woAD8h6G57JJg67+7hChRebUmd284A==", + "dev": true, + "requires": { + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/types": "7.0.26" + } + }, + "@storybook/api": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/api/-/api-7.0.26.tgz", + "integrity": "sha512-czS5iWE3Px3e0sXjgt1T+LDiT6Tl4gXYPmHIaWpKGDCh4W2zrGolOvB0WqDt3IKhDGnXxaJF5jn705OGBQOptw==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/manager-api": "7.0.26" + } + }, + "@storybook/blocks": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.0.26.tgz", + "integrity": "sha512-VNYB6Y1Ocja8HVg4Bm1w7LvqRSEc9aLVD8BnI8BInHvekvxhaxTkfpA18qds7d8+RmerrJqAUhGx0jkIB/cvwA==", + "dev": true, + "requires": { + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/docs-tools": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "@types/lodash": "^4.14.167", + "color-convert": "^2.0.1", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "markdown-to-jsx": "^7.1.8", + "memoizerific": "^1.11.3", + "polished": "^4.2.2", + "react-colorful": "^5.1.2", + "telejson": "^7.0.3", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "dependencies": { + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "@storybook/builder-manager": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.0.26.tgz", + "integrity": "sha512-1Uk3dL3Yu5AuimfHAghBHs11wf7B+a+277astqLx7HSeh3L49zcDZS4NhGHKmtQjsEorbvmtty3s16q2k+fM8A==", + "dev": true, + "requires": { + "@fal-works/esbuild-plugin-global-externals": "^2.1.2", + "@storybook/core-common": "7.0.26", + "@storybook/manager": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@types/ejs": "^3.1.1", + "@types/find-cache-dir": "^3.2.1", + "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", + "browser-assert": "^1.2.1", + "ejs": "^3.1.8", + "esbuild": "^0.17.0", + "esbuild-plugin-alias": "^0.2.1", + "express": "^4.17.3", + "find-cache-dir": "^3.0.0", + "fs-extra": "^11.1.0", + "process": "^0.11.10", + "util": "^0.12.4" + } + }, + "@storybook/builder-webpack5": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-7.0.26.tgz", + "integrity": "sha512-LfntlZKm0PB6hrgXd7IlzjuCLzjQezYHt3GQfZRxzu7MAu/bgu7xtr7lMaIJOQd2ckpvEN7xhJ89t2mvdk5y0A==", + "dev": true, + "requires": { + "@babel/core": "^7.12.10", + "@storybook/addons": "7.0.26", + "@storybook/api": "7.0.26", + "@storybook/channel-postmessage": "7.0.26", + "@storybook/channel-websocket": "7.0.26", + "@storybook/channels": "7.0.26", + "@storybook/client-api": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/components": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/core-webpack": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/preview": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/router": "7.0.26", + "@storybook/store": "7.0.26", + "@storybook/theming": "7.0.26", + "@types/node": "^16.0.0", + "@types/semver": "^7.3.4", + "babel-loader": "^9.0.0", + "babel-plugin-named-exports-order": "^0.0.2", + "browser-assert": "^1.2.1", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "css-loader": "^6.7.1", + "express": "^4.17.3", + "fork-ts-checker-webpack-plugin": "^7.2.8", + "fs-extra": "^11.1.0", + "html-webpack-plugin": "^5.5.0", + "path-browserify": "^1.0.1", + "process": "^0.11.10", + "semver": "^7.3.7", + "style-loader": "^3.3.1", + "terser-webpack-plugin": "^5.3.1", + "ts-dedent": "^2.0.0", + "util": "^0.12.4", + "util-deprecate": "^1.0.2", + "webpack": "5", + "webpack-dev-middleware": "^5.3.1", + "webpack-hot-middleware": "^2.25.1", + "webpack-virtual-modules": "^0.4.3" + }, + "dependencies": { + "@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fork-ts-checker-webpack-plugin": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.3.0.tgz", + "integrity": "sha512-IN+XTzusCjR5VgntYFgxbxVx3WraPRnKehBFrf00cMSrtUuW9MsG9dhL6MWpY6MkjC3wVwoujfCDgZZCQwbswA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "dependencies": { + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + } + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@storybook/channel-postmessage": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-7.0.26.tgz", + "integrity": "sha512-ZvFLr/tUD9dWIjQtIn1JXHjqrbOP/uEEOqzwpKSVj0Cl4Vgc12s8hecbzBufkOF7fwLsFvfieSi7ENOmjoncdQ==", + "dev": true, + "requires": { + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "qs": "^6.10.0", + "telejson": "^7.0.3" + } + }, + "@storybook/channel-websocket": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/channel-websocket/-/channel-websocket-7.0.26.tgz", + "integrity": "sha512-c+0VcZf78RGnT/pWrH85yydt0azRKAHZF3SHWKM4+W8qOFr0Mk0+jqhPh1uoUoPDpBZDTKS/nzXY8cwUVwF/eA==", + "dev": true, + "requires": { + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/global": "^5.0.0", + "telejson": "^7.0.3" + } + }, + "@storybook/channels": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.0.26.tgz", + "integrity": "sha512-Br3XILhrtuL5Sdp91I04kKjJzSqU/N8gGL6B6nIfnuaHUvGMDuMCHAB+g7aoiyH5dnpDZ6yBVGNwtYAyJA+0Og==", + "dev": true + }, + "@storybook/cli": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.0.26.tgz", + "integrity": "sha512-sZ136wRUYTdhhm/thegFoI47wOzl2X+K9eaiTTp0ARwnIUhXAPDQ0MKOD36hKbCX5T/pBE7r++7WoEReIbUDqQ==", + "dev": true, + "requires": { + "@babel/core": "^7.20.2", + "@babel/preset-env": "^7.20.2", + "@ndelangen/get-tarball": "^3.0.7", + "@storybook/codemod": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/core-server": "7.0.26", + "@storybook/csf-tools": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/telemetry": "7.0.26", + "@storybook/types": "7.0.26", + "@types/semver": "^7.3.4", + "chalk": "^4.1.0", + "commander": "^6.2.1", + "cross-spawn": "^7.0.3", + "detect-indent": "^6.1.0", + "envinfo": "^7.7.3", + "execa": "^5.0.0", + "express": "^4.17.3", + "find-up": "^5.0.0", + "fs-extra": "^11.1.0", + "get-npm-tarball-url": "^2.0.3", + "get-port": "^5.1.1", + "giget": "^1.0.0", + "globby": "^11.0.2", + "jscodeshift": "^0.14.0", + "leven": "^3.1.0", + "ora": "^5.4.1", + "prettier": "^2.8.0", + "prompts": "^2.4.0", + "puppeteer-core": "^2.1.1", + "read-pkg-up": "^7.0.1", + "semver": "^7.3.7", + "shelljs": "^0.8.5", + "simple-update-notifier": "^1.0.0", + "strip-json-comments": "^3.0.1", + "tempy": "^1.0.1", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@storybook/client-api": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/client-api/-/client-api-7.0.26.tgz", + "integrity": "sha512-55Oy5Es8ACABWT01iddUJHt8oT4VnuCvec/FUC4iN7ITiOGjk7YzZB3NftmD6C5+pVQC99buspuwg7IFxmj+Aw==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/preview-api": "7.0.26" + } + }, + "@storybook/client-logger": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.0.26.tgz", + "integrity": "sha512-OMVLbgceoeuM8sWOfTX/9a4zCrH78G32hg7x8yXLZnRJ9OLaHJHzUM0Onc4MLudqVUdaKH0c8ejpBXUyIr1rJQ==", + "dev": true, + "requires": { + "@storybook/global": "^5.0.0" + } + }, + "@storybook/codemod": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.0.26.tgz", + "integrity": "sha512-H9sV59FfGrGzGM+UZQclNglnc4cOkQvvF3EOWlR3BfDhx+STSB9VbCR308ygjUYw2TXZ2s5seCvHtVvA2yhILA==", + "dev": true, + "requires": { + "@babel/core": "~7.21.0", + "@babel/preset-env": "~7.21.0", + "@babel/types": "~7.21.2", + "@storybook/csf": "^0.1.0", + "@storybook/csf-tools": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/types": "7.0.26", + "cross-spawn": "^7.0.3", + "globby": "^11.0.2", + "jscodeshift": "^0.14.0", + "lodash": "^4.17.21", + "prettier": "^2.8.0", + "recast": "^0.23.1" + }, + "dependencies": { + "@babel/core": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", + "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-compilation-targets": "^7.21.5", + "@babel/helper-module-transforms": "^7.21.5", + "@babel/helpers": "^7.21.5", + "@babel/parser": "^7.21.8", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/preset-env": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.5.tgz", + "integrity": "sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.21.5", + "@babel/helper-compilation-targets": "^7.21.5", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-validator-option": "^7.21.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7", + "@babel/plugin-proposal-async-generator-functions": "^7.20.7", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.21.0", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.21.5", + "@babel/plugin-transform-async-to-generator": "^7.20.7", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.21.0", + "@babel/plugin-transform-classes": "^7.21.0", + "@babel/plugin-transform-computed-properties": "^7.21.5", + "@babel/plugin-transform-destructuring": "^7.21.3", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.21.5", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.20.11", + "@babel/plugin-transform-modules-commonjs": "^7.21.5", + "@babel/plugin-transform-modules-systemjs": "^7.20.11", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.21.3", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.21.5", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.20.7", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.21.5", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.21.5", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + } + }, + "@babel/types": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", + "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + } + } + } + }, + "@storybook/components": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.0.26.tgz", + "integrity": "sha512-n0TVWEF4Bc9JAyEIaN0PqwglbaYYRcPVG7ka+5wgGmBiuDlWI1SXd4EXxv2u0mVibHvtkHvOn6/GaZ1vG45p6g==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/global": "^5.0.0", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "memoizerific": "^1.11.3", + "use-resize-observer": "^9.1.0", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/core-client": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.0.26.tgz", + "integrity": "sha512-1DA8mLnr0f6EuL74859IDK99a7CGNgMIN0/cAVNgYxq0WA4j+9ajsJ+/RIAgnS2NLVLR9kbezUtBEx4/H88IRA==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/preview-api": "7.0.26" + } + }, + "@storybook/core-common": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.0.26.tgz", + "integrity": "sha512-rojZblzB0egNXX0bZ7R3TuPDiBSIhxpZCrorrDMHOZ8F+zuBxyTiZ0yMxEDn7i46T2n1vX+hUHhwZVxZrLn/ZQ==", + "dev": true, + "requires": { + "@storybook/node-logger": "7.0.26", + "@storybook/types": "7.0.26", + "@types/node": "^16.0.0", + "@types/node-fetch": "^2.6.4", + "@types/pretty-hrtime": "^1.0.0", + "chalk": "^4.1.0", + "esbuild": "^0.17.0", + "esbuild-register": "^3.4.0", + "file-system-cache": "2.3.0", + "find-up": "^5.0.0", + "fs-extra": "^11.1.0", + "glob": "^8.1.0", + "glob-promise": "^6.0.2", + "handlebars": "^4.7.7", + "lazy-universal-dotenv": "^4.0.0", + "node-fetch": "^2.0.0", + "picomatch": "^2.3.0", + "pkg-dir": "^5.0.0", + "pretty-hrtime": "^1.0.3", + "resolve-from": "^5.0.0", + "ts-dedent": "^2.0.0" + }, + "dependencies": { + "@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "requires": { + "find-up": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@storybook/core-events": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.0.26.tgz", + "integrity": "sha512-ckZszphEAYs9wp8tPVhayEMzk8JxCiQfzbq0S45sbdqdTrl40PmsOjv5iPNaUYElI/Stfz+v4gDCEUfOsxyC+w==", + "dev": true + }, + "@storybook/core-server": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.0.26.tgz", + "integrity": "sha512-QieqH19jBPZafxJVmCVK6GTYkRN/CJ8RQUvyRH2KNhqXP0tHYfL51FlU70ldo/vHX6Ax4Cje5hx/Nln9+DOMNg==", + "dev": true, + "requires": { + "@aw-web-design/x-default-browser": "1.4.88", + "@discoveryjs/json-ext": "^0.5.3", + "@storybook/builder-manager": "7.0.26", + "@storybook/core-common": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/csf-tools": "7.0.26", + "@storybook/docs-mdx": "^0.1.0", + "@storybook/global": "^5.0.0", + "@storybook/manager": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/telemetry": "7.0.26", + "@storybook/types": "7.0.26", + "@types/detect-port": "^1.3.0", + "@types/node": "^16.0.0", + "@types/node-fetch": "^2.5.7", + "@types/pretty-hrtime": "^1.0.0", + "@types/semver": "^7.3.4", + "better-opn": "^2.1.1", + "chalk": "^4.1.0", + "cli-table3": "^0.6.1", + "compression": "^1.7.4", + "detect-port": "^1.3.0", + "express": "^4.17.3", + "fs-extra": "^11.1.0", + "globby": "^11.0.2", + "ip": "^2.0.0", + "lodash": "^4.17.21", + "node-fetch": "^2.6.7", + "open": "^8.4.0", + "pretty-hrtime": "^1.0.3", + "prompts": "^2.4.0", + "read-pkg-up": "^7.0.1", + "semver": "^7.3.7", + "serve-favicon": "^2.5.0", + "telejson": "^7.0.3", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2", + "watchpack": "^2.2.0", + "ws": "^8.2.3" + }, + "dependencies": { + "@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@storybook/core-webpack": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-7.0.26.tgz", + "integrity": "sha512-mIi+D+15sGRh8CWE7mMgvxX8KRfrACcR+AuEDi9rfQBB2PX0Okkrh6GAxPWjjeFFG7DF4RXdkusgC8/seYCTXg==", + "dev": true, + "requires": { + "@storybook/core-common": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/types": "7.0.26", + "@types/node": "^16.0.0", + "ts-dedent": "^2.0.0" + }, + "dependencies": { + "@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + } + } + }, + "@storybook/csf": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.1.tgz", + "integrity": "sha512-4hE3AlNVxR60Wc5KSC68ASYzUobjPqtSKyhV6G+ge0FIXU55N5nTY7dXGRZHQGDBPq+XqchMkIdlkHPRs8nTHg==", + "dev": true, + "requires": { + "type-fest": "^2.19.0" + }, + "dependencies": { + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + } + } + }, + "@storybook/csf-plugin": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.0.26.tgz", + "integrity": "sha512-D+wZvKlFxI/Vur8SRvkwKujOdV8ZL6xKiCX/07nFJXhhZoaeM+E78xPCL613Hj15GloujMkAnv7CT2rCiFJYow==", + "dev": true, + "requires": { + "@storybook/csf-tools": "7.0.26", + "unplugin": "^0.10.2" + } + }, + "@storybook/csf-tools": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.0.26.tgz", + "integrity": "sha512-O8WJNOkvgrGV6gS/5ERkgqiXOxoXMuHtzdJpIM9DHPhzkSxB1Inl3WrX/dRRDNtmiHf87hBUuzhgo7YR7z4tuQ==", + "dev": true, + "requires": { + "@babel/generator": "~7.21.1", + "@babel/parser": "~7.21.2", + "@babel/traverse": "~7.21.2", + "@babel/types": "~7.21.2", + "@storybook/csf": "^0.1.0", + "@storybook/types": "7.0.26", + "fs-extra": "^11.1.0", + "recast": "^0.23.1", + "ts-dedent": "^2.0.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.21.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.9.tgz", + "integrity": "sha512-F3fZga2uv09wFdEjEQIJxXALXfz0+JaOb7SabvVMmjHxeVTuGW8wgE8Vp1Hd7O+zMTYtcfEISGRzPkeiaPPsvg==", + "dev": true, + "requires": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/parser": { + "version": "7.21.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.9.tgz", + "integrity": "sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g==", + "dev": true + }, + "@babel/traverse": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", + "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.5", + "@babel/types": "^7.21.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", + "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@storybook/docs-mdx": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@storybook/docs-mdx/-/docs-mdx-0.1.0.tgz", + "integrity": "sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==", + "dev": true + }, + "@storybook/docs-tools": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.0.26.tgz", + "integrity": "sha512-Ibpm/OTR2XmJgix5w+wMYbDwN0zp5e/pcqSHy36OvkBOG588IKSSzYdBjGdTLPHWBoehp2Kyndw/5dL/09ftXA==", + "dev": true, + "requires": { + "@babel/core": "^7.12.10", + "@storybook/core-common": "7.0.26", + "@storybook/preview-api": "7.0.26", + "@storybook/types": "7.0.26", + "@types/doctrine": "^0.0.3", + "doctrine": "^3.0.0", + "lodash": "^4.17.21" + } + }, + "@storybook/global": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", + "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", + "dev": true + }, + "@storybook/instrumenter": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-7.0.26.tgz", + "integrity": "sha512-7Ty0LTslgkm5RyH6CqTAKhWz/cF6wq/sNdMYKwvVZHWNZ2LKMtXD0RWM2caCPruAGOQ9+52H+3s4TZGKaPSSWQ==", + "dev": true, + "requires": { + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/preview-api": "7.0.26" + } + }, + "@storybook/manager": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.0.26.tgz", + "integrity": "sha512-mxjU/pmHr8xL96HCipqazvZWQkxBPCbpZ2+YsJuJoLFN4m7RoOK21VK0euBW24NlSg7Vp57XGQcrJCv6xUTKMg==", + "dev": true + }, + "@storybook/manager-api": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.0.26.tgz", + "integrity": "sha512-/2p6lU7r30qMXob/UnzRL9yq7XjoE+YQXv1KhrcePfMBARbelYw9RYhYT/AkXGtb9/Fa95uG3lNvoDLC1IQfMQ==", + "dev": true, + "requires": { + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/global": "^5.0.0", + "@storybook/router": "7.0.26", + "@storybook/theming": "7.0.26", + "@storybook/types": "7.0.26", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "semver": "^7.3.7", + "store2": "^2.14.2", + "telejson": "^7.0.3", + "ts-dedent": "^2.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@storybook/mdx2-csf": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@storybook/mdx2-csf/-/mdx2-csf-1.1.0.tgz", + "integrity": "sha512-TXJJd5RAKakWx4BtpwvSNdgTDkKM6RkXU8GK34S/LhidQ5Pjz3wcnqb0TxEkfhK/ztbP8nKHqXFwLfa2CYkvQw==", + "dev": true + }, + "@storybook/node-logger": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.0.26.tgz", + "integrity": "sha512-3Jqv3fRb8+Mn/aNl4IztgUAS/pvouVzpfHDc8+6KYAoFMeDXwHVlfF/+gRCpd/fbYaTHGrycIs5G48bC190Dgg==", + "dev": true, + "requires": { + "@types/npmlog": "^4.1.2", + "chalk": "^4.1.0", + "npmlog": "^5.0.1", + "pretty-hrtime": "^1.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@storybook/postinstall": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.0.26.tgz", + "integrity": "sha512-NhJBpQ+49RWF63UkdwrEwBLJBjAZeTlruPWfXGUb343iaGNNTsD3jajbToFHncibewH83yk6MeGfiyUva60oJw==", + "dev": true + }, + "@storybook/preset-react-webpack": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-7.0.26.tgz", + "integrity": "sha512-uJTW7of4eF8upoP2W0N5FVi1DG2f6CXkkI5qX4WmYFAmCtShor75EZTcv50QF4GOKJs9NlHDgC2+i6gT24u1rg==", + "dev": true, + "requires": { + "@babel/preset-flow": "^7.18.6", + "@babel/preset-react": "^7.18.6", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.5", + "@storybook/core-webpack": "7.0.26", + "@storybook/docs-tools": "7.0.26", + "@storybook/node-logger": "7.0.26", + "@storybook/react": "7.0.26", + "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0", + "@types/node": "^16.0.0", + "@types/semver": "^7.3.4", + "babel-plugin-add-react-displayname": "^0.0.5", + "babel-plugin-react-docgen": "^4.2.1", + "fs-extra": "^11.1.0", + "react-refresh": "^0.11.0", + "semver": "^7.3.7", + "webpack": "5" + }, + "dependencies": { + "@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@storybook/preview": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.0.26.tgz", + "integrity": "sha512-9Uaxl/MEMYqjLlKAeAF2ATuaM0yQagXUfu2bEOpuor2ys9XoisDkvB7jfsCVqMZHeQ+mCdYyBICHhgqzxcO2Zg==", + "dev": true + }, + "@storybook/preview-api": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.0.26.tgz", + "integrity": "sha512-uJwA4errBOZOoDF2T7Z2oLqjAYvvjMr31sTsOoT0niJtWr29RQp8yS6VoSrsuh+y3FAVqBEl5pS+DX3IGLjvxw==", + "dev": true, + "requires": { + "@storybook/channel-postmessage": "7.0.26", + "@storybook/channels": "7.0.26", + "@storybook/client-logger": "7.0.26", + "@storybook/core-events": "7.0.26", + "@storybook/csf": "^0.1.0", + "@storybook/global": "^5.0.0", + "@storybook/types": "7.0.26", + "@types/qs": "^6.9.5", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "qs": "^6.10.0", + "synchronous-promise": "^2.0.15", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/react": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-7.0.26.tgz", + "integrity": "sha512-+YK/1vF2Pd/PX7Ss5yPCIh9hee7iMVbu86gdjV9n9r6G244jQ7HLtdA01JKfq92/UgoysSWUjUECrxrUvcsh5w==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/core-client": "7.0.26", + "@storybook/docs-tools": "7.0.26", + "@storybook/global": "^5.0.0", + "@storybook/preview-api": "7.0.26", + "@storybook/react-dom-shim": "7.0.26", + "@storybook/types": "7.0.26", + "@types/escodegen": "^0.0.6", + "@types/estree": "^0.0.51", + "@types/node": "^16.0.0", + "acorn": "^7.4.1", + "acorn-jsx": "^5.3.1", + "acorn-walk": "^7.2.0", + "escodegen": "^2.0.0", + "html-tags": "^3.1.0", + "lodash": "^4.17.21", + "prop-types": "^15.7.2", + "react-element-to-jsx-string": "^15.0.0", + "ts-dedent": "^2.0.0", + "type-fest": "^2.19.0", + "util-deprecate": "^1.0.2" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + } + } + }, + "@storybook/react-docgen-typescript-plugin": { + "version": "1.0.6--canary.9.0c3f3b7.0", + "resolved": "https://registry.npmjs.org/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.6--canary.9.0c3f3b7.0.tgz", + "integrity": "sha512-KUqXC3oa9JuQ0kZJLBhVdS4lOneKTOopnNBK4tUAgoxWQ3u/IjzdueZjFr7gyBrXMoU6duutk3RQR9u8ZpYJ4Q==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "endent": "^2.0.1", + "find-cache-dir": "^3.3.1", + "flat-cache": "^3.0.4", + "micromatch": "^4.0.2", + "react-docgen-typescript": "^2.2.2", + "tslib": "^2.0.0" + } + }, + "@storybook/react-dom-shim": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.0.26.tgz", + "integrity": "sha512-heobG4IovYAD9fo7qmUHylCSQjDd1eXDCOaTiy+XVKobHAJgkz1gKqbaFSP6KLkPE4cKyScku2K9mY0tcKIhMw==", + "dev": true + }, + "@storybook/react-webpack5": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-7.0.26.tgz", + "integrity": "sha512-46LmHKEW5LNp13/rBUaEznTX35ZO8cuuVBlC7ySCR1+SGBJif3EBBe8VMyEI3FO48nwrenK9m+FtLqu5TDocCA==", + "dev": true, + "requires": { + "@storybook/builder-webpack5": "7.0.26", + "@storybook/preset-react-webpack": "7.0.26", + "@storybook/react": "7.0.26", + "@types/node": "^16.0.0" + }, + "dependencies": { + "@types/node": { + "version": "16.18.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz", + "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ==", + "dev": true + } + } + }, + "@storybook/router": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.0.26.tgz", + "integrity": "sha512-OfLittKxdahsgKsmQFoBX9q5tN/aqKMhhc/WbW88UPAQCUcEuazB0CwM+LI9YXY+n5L+vpLI4lGlgaqvPy4hHw==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "memoizerific": "^1.11.3", + "qs": "^6.10.0" + } + }, + "@storybook/store": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/store/-/store-7.0.26.tgz", + "integrity": "sha512-gJ9LDv8Mos8kPHj7SDEpBxQVL756j+15XUqBeBjgK+/TihnzIFeeX9QaTLo+As8bhgF/P2MVR+v0Qv9Zlm9MgQ==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/preview-api": "7.0.26" + } + }, + "@storybook/telemetry": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.0.26.tgz", + "integrity": "sha512-TgvtARAiD+SNyWJJfQdPiWW5JQkbX1UdHKEqEhoJXsGDkEi2Zpb+1tdeP1qZ3Gfbd1K0/LDpXGcqLv6/deSEdg==", + "dev": true, + "requires": { + "@storybook/client-logger": "7.0.26", + "@storybook/core-common": "7.0.26", + "chalk": "^4.1.0", + "detect-package-manager": "^2.0.1", + "fetch-retry": "^5.0.2", + "fs-extra": "^11.1.0", + "isomorphic-unfetch": "^3.1.0", + "nanoid": "^3.3.1", + "read-pkg-up": "^7.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "@storybook/testing-library": { + "version": "0.0.14-next.2", + "resolved": "https://registry.npmjs.org/@storybook/testing-library/-/testing-library-0.0.14-next.2.tgz", + "integrity": "sha512-i/SLSGm0o978ELok/SB4Qg1sZ3zr+KuuCkzyFqcCD0r/yf+bG35aQGkFqqxfSAdDxuQom0NO02FE+qys5Eapdg==", + "dev": true, + "requires": { + "@storybook/client-logger": "^7.0.0-beta.0 || ^7.0.0-rc.0 || ^7.0.0", + "@storybook/instrumenter": "^7.0.0-beta.0 || ^7.0.0-rc.0 || ^7.0.0", + "@testing-library/dom": "^8.3.0", + "@testing-library/user-event": "^13.2.1", + "ts-dedent": "^2.2.0" + } + }, + "@storybook/theming": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.0.26.tgz", + "integrity": "sha512-7hxpT2yq+xZonSsEZHOF+HDHx6GE0qlys3EQ63K9XCJ8VeBnq9M5zHvMK9iXl90093ufxpvWsfDWgtja2zvmTw==", + "dev": true, + "requires": { + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@storybook/client-logger": "7.0.26", + "@storybook/global": "^5.0.0", + "memoizerific": "^1.11.3" + } + }, + "@storybook/types": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.0.26.tgz", + "integrity": "sha512-5RBi6agtDglNXdffmw4+Fyv2dUdlIdeOdUj0O5+JRYajTxfHdurZd9r/42z4OstN+ORDkLA/svt8Q9JyRpIb6Q==", + "dev": true, + "requires": { + "@storybook/channels": "7.0.26", + "@types/babel__core": "^7.0.0", + "@types/express": "^4.7.0", + "file-system-cache": "2.3.0" + } + }, + "@tanstack/query-core": { + "version": "4.29.19", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.19.tgz", + "integrity": "sha512-uPe1DukeIpIHpQi6UzIgBcXsjjsDaLnc7hF+zLBKnaUlh7jFE/A+P8t4cU4VzKPMFB/C970n/9SxtpO5hmIRgw==" + }, + "@tanstack/react-query": { + "version": "4.29.19", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.19.tgz", + "integrity": "sha512-XiTIOHHQ5Cw1WUlHaD4fmVUMhoWjuNJlAeJGq7eM4BraI5z7y8WkZO+NR8PSuRnQGblpuVdjClQbDFtwxTtTUw==", + "requires": { + "@tanstack/query-core": "4.29.19", + "use-sync-external-store": "^1.2.0" + } + }, + "@testing-library/dom": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", + "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" + }, + "dependencies": { + "@testing-library/dom": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", + "integrity": "sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@types/aria-query": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", + "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", + "dev": true + }, + "@types/babel__core": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7" + } + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "requires": { + "@types/ms": "*" + } + }, + "@types/detect-port": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/detect-port/-/detect-port-1.3.3.tgz", + "integrity": "sha512-bV/jQlAJ/nPY3XqSatkGpu+nGzou+uSwrH1cROhn+jBFg47yaNH+blW4C7p9KhopC7QxCv/6M86s37k8dMk0Yg==", + "dev": true + }, + "@types/doctrine": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.3.tgz", + "integrity": "sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==", + "dev": true + }, + "@types/ejs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.2.tgz", + "integrity": "sha512-ZmiaE3wglXVWBM9fyVC17aGPkLo/UgaOjEiI2FXQfyczrCefORPxIe+2dVmnmk3zkVIbizjrlQzmPGhSYGXG5g==", + "dev": true + }, + "@types/escodegen": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.6.tgz", + "integrity": "sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==", + "dev": true + }, + "@types/eslint": { + "version": "8.40.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.2.tgz", + "integrity": "sha512-PRVjQ4Eh9z9pmmtaq8nTjZjQwKFk7YIHIud3lRoKRBgUQjgjRmoGxxGEPXQkF+lH7QkHJRNr5F4aBgYCW0lqpQ==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/find-cache-dir": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz", + "integrity": "sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==", + "dev": true + }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true + }, + "@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", + "dev": true + }, + "@types/http-proxy": { + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "29.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.2.tgz", + "integrity": "sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } + } + }, + "@types/js-levenshtein": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz", + "integrity": "sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==" + }, + "@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, + "@types/mdx": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.5.tgz", + "integrity": "sha512-76CqzuD6Q7LC+AtbPqrvD9AqsN0k8bsYo2bM2J8pmNldP1aIPAbzUQ7QbobyXL4eLr1wK5x8FZFe8eF/ubRuBg==", + "dev": true + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "@types/mime-types": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", + "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==", + "dev": true + }, + "@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, + "@types/node": { + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", + "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==" + }, + "@types/node-fetch": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", + "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "@types/npmlog": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/npmlog/-/npmlog-4.1.4.tgz", + "integrity": "sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true + }, + "@types/pretty-hrtime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/pretty-hrtime/-/pretty-hrtime-1.0.1.tgz", + "integrity": "sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ==", + "dev": true + }, + "@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==", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@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==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@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==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", + "dev": true + }, + "@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "dev": true, + "requires": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/set-cookie-parser": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz", + "integrity": "sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==", + "requires": { + "@types/node": "*" + } + }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/styled-components": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==", + "dev": true + }, + "@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, + "@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, + "@types/ws": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@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==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.61.0.tgz", + "integrity": "sha512-r4RTnwTcaRRVUyKb7JO4DiOGmcMCat+uNs6HqJBfX7K2nlq5TagYZShhbhAw7hFT3bHaYgxMw6pKP0fhu05VMA==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "5.61.0" + } + }, + "@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==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.61.0", + "@typescript-eslint/types": "5.61.0", + "@typescript-eslint/typescript-estree": "5.61.0", + "debug": "^4.3.4" + } + }, + "@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==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.61.0", + "@typescript-eslint/visitor-keys": "5.61.0" + } + }, + "@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==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.61.0", + "@typescript-eslint/utils": "5.61.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@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==", + "dev": true + }, + "@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==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@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==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@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==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.61.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true + }, + "@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true + }, + "@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true + }, + "@xmldom/xmldom": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.9.tgz", + "integrity": "sha512-4VSbbcMoxc4KLjb1gs96SRmi7w4h1SF+fCoiK0XaQX62buCc1G5d0DC5bJ9xJBNPDSVCmIrcl8BiYxzjrqaaJA==" + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "@yarnpkg/esbuild-plugin-pnp": { + "version": "3.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@yarnpkg/esbuild-plugin-pnp/-/esbuild-plugin-pnp-3.0.0-rc.15.tgz", + "integrity": "sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==", + "dev": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true + }, + "acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "requires": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + }, + "dependencies": { + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + } + } + }, + "acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + } + } + }, + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" + } + } + }, + "ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "app-root-dir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", + "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", + "dev": true + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "dequal": "^2.0.3" + } + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "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" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "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" + } + }, + "assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dev": true, + "requires": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "ast-types": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", + "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", + "dev": true, + "requires": { + "tslib": "^2.0.1" + } + }, + "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==", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "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==" + }, + "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==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "dequal": "^2.0.3" + } + }, + "babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "dev": true + }, + "babel-jest": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.0.tgz", + "integrity": "sha512-Jj8Bq2yKsk11XLk06Nm8SdvYkAcecH+GuhxB8DnK5SncjHnJ88TQjSnGgE7jpajpnSvz9DZ6X8hXrDkD/6/TPQ==", + "dev": true, + "requires": { + "@jest/transform": "^29.6.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "babel-loader": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.0.0" + } + }, + "babel-plugin-add-react-displayname": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz", + "integrity": "sha512-LY3+Y0XVDYcShHHorshrDbt4KFWL4bSeniCtl4SYZbask+Syngk1uMPCeN9+nSiZo6zX5s0RTq/J9Pnaaf/KHw==", + "dev": true + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + } + }, + "babel-plugin-named-exports-order": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-named-exports-order/-/babel-plugin-named-exports-order-0.0.2.tgz", + "integrity": "sha512-OgOYHOLoRK+/mvXU9imKHlG6GkPLYrUCvFXG/CM93R/aNNO8pOOF4aS+S8CCHMDQoNSeiOYEZb/G6RwL95Jktw==", + "dev": true + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.4.tgz", + "integrity": "sha512-9WeK9snM1BfxB38goUEv2FLnA6ja07UMfazFHzCXUb3NyDZAwfXvQiURQ6guTTMeHcOsdknULm1PDhs4uWtKyA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.1", + "@nicolo-ribaudo/semver-v6": "^6.3.3" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.2.tgz", + "integrity": "sha512-Cid+Jv1BrY9ReW9lIfNlNpsI53N+FN7gE+f73zLAUbr9C52W4gKLWSByx47pfDJsEysojKArqOtOKZSVIIUTuQ==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.1", + "core-js-compat": "^3.31.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.1.tgz", + "integrity": "sha512-L8OyySuI6OSQ5hFy9O+7zFjyr4WhAfRjLIOkhQGYl+emwJkd/S4XXT1JpfrgR1jrQ1NcGiOh+yAdGlF8pnC3Jw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.1" + } + }, + "babel-plugin-react-docgen": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/babel-plugin-react-docgen/-/babel-plugin-react-docgen-4.2.1.tgz", + "integrity": "sha512-UQ0NmGHj/HAqi5Bew8WvNfCk8wSsmdgNd8ZdMjBCICtyCJCq9LiqgqvjCYe570/Wg7AQArSq1VQ60Dd/CHN7mQ==", + "dev": true, + "requires": { + "ast-types": "^0.14.2", + "lodash": "^4.17.15", + "react-docgen": "^5.0.0" + } + }, + "babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "dev": true + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "babel-preset-react-app": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", + "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", + "dev": true, + "requires": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "better-opn": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-2.1.1.tgz", + "integrity": "sha512-kIPXZS5qwyKiX/HcRvDYfmBQUa8XP17I0mYZZ0y4UhpYOSvtsLHDYqmomS+Mj20aDvD3knEiQ0ecQy2nhio3yA==", + "dev": true, + "requires": { + "open": "^7.0.3" + }, + "dependencies": { + "open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + } + } + }, + "big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dev": true, + "requires": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "requires": { + "big-integer": "^1.6.44" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-assert": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", + "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==", + "dev": true + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==", + "dev": true, + "requires": { + "pako": "~0.2.0" + } + }, + "browserslist": { + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true + }, + "c8": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-7.14.0.tgz", + "integrity": "sha512-i04rtkkcNcCf7zsQcSv/T9EbUn4RXQ6mropeMcjFOsQXQ0iGLAr/xT6TImQg4+U9hmNpN9XdvPkjUL1IzbgxJw==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^2.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-reports": "^3.1.4", + "rimraf": "^3.0.2", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9" + }, + "dependencies": { + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + } + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "requires": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001512", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz", + "integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==", + "dev": true + }, + "case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "clean-css": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", + "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "clean-webpack-plugin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", + "integrity": "sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==", + "dev": true, + "requires": { + "del": "^4.1.1" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", + "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==" + }, + "cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + } + }, + "cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "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==", + "dev": true + }, + "connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "requires": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "dependencies": { + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + } + } + }, + "core-js-compat": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz", + "integrity": "sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==", + "dev": true, + "requires": { + "browserslist": "^4.21.5" + } + }, + "core-js-pure": { + "version": "3.31.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.31.1.tgz", + "integrity": "sha512-w+C62kvWti0EPs4KPMCMVv9DriHSXfQOCQ94bGGBiEW5rrbtt/Rz8n5Krhfw9cpFyzXBjf3DB3QnPdEzGDY4Fw==", + "dev": true + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "dev": true + }, + "css-loader": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.21", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.3", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dev": true, + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "dev": true + }, + "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==", + "dev": true + }, + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "dependencies": { + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-equal": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", + "integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.0", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + } + }, + "default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "requires": { + "execa": "^5.0.0" + } + }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "requires": { + "clone": "^1.0.2" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "defu": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.2.tgz", + "integrity": "sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==", + "dev": true + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + } + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "detect-package-manager": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-2.0.1.tgz", + "integrity": "sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==", + "dev": true, + "requires": { + "execa": "^5.1.1" + } + }, + "detect-port": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", + "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", + "dev": true, + "requires": { + "address": "^1.0.1", + "debug": "4" + } + }, + "diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "dns-packet": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", + "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", + "dev": true, + "requires": { + "@leichtgewicht/ip-codec": "^2.0.1" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + } + } + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "requires": { + "domelementtype": "^2.2.0" + } + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==" + }, + "dotenv-defaults": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", + "integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==", + "dev": true, + "requires": { + "dotenv": "^8.2.0" + }, + "dependencies": { + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true + } + } + }, + "dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true + }, + "dotenv-webpack": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-8.0.1.tgz", + "integrity": "sha512-CdrgfhZOnx4uB18SgaoP9XHRN2v48BbjuXQsZY5ixs5A8579NxQkmMxRtI7aTwSiSQcM2ao12Fdu+L3ZS3bG4w==", + "dev": true, + "requires": { + "dotenv-defaults": "^2.0.2" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dev": true, + "requires": { + "jake": "^10.8.5" + } + }, + "electron-to-chromium": { + "version": "1.4.450", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.450.tgz", + "integrity": "sha512-BLG5HxSELlrMx7dJ2s+8SFlsCtJp37Zpk2VAxyC6CZtbc+9AJeZHfYHbrlSgdXp6saQ8StMqOTEDaBKgA7u1sw==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "endent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/endent/-/endent-2.1.0.tgz", + "integrity": "sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w==", + "dev": true, + "requires": { + "dedent": "^0.7.0", + "fast-json-parse": "^1.0.3", + "objectorarray": "^1.0.5" + } + }, + "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==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + }, + "envinfo": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", + "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "requires": { + "stackframe": "^1.3.4" + } + }, + "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==", + "dev": true, + "requires": { + "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" + } + }, + "es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "es-module-lexer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", + "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "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==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", + "dev": true + }, + "esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "esbuild-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-4.0.2.tgz", + "integrity": "sha512-kj88m0yrtTEJDeUEF+3TZsq7t9VPzQQj7UmXAzUbIaipoYSrd0UxKAcg4l9CBgP8uVoploiw+nKr8DIv6Y9gXw==", + "dev": true, + "requires": { + "esbuild": "^0.19.0", + "get-tsconfig": "^4.7.0", + "loader-utils": "^2.0.4", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.2.tgz", + "integrity": "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz", + "integrity": "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.2.tgz", + "integrity": "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz", + "integrity": "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz", + "integrity": "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz", + "integrity": "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz", + "integrity": "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz", + "integrity": "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz", + "integrity": "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz", + "integrity": "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz", + "integrity": "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz", + "integrity": "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz", + "integrity": "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz", + "integrity": "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz", + "integrity": "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz", + "integrity": "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz", + "integrity": "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz", + "integrity": "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz", + "integrity": "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz", + "integrity": "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz", + "integrity": "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz", + "integrity": "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==", + "dev": true, + "optional": true + }, + "esbuild": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.2.tgz", + "integrity": "sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.19.2", + "@esbuild/android-arm64": "0.19.2", + "@esbuild/android-x64": "0.19.2", + "@esbuild/darwin-arm64": "0.19.2", + "@esbuild/darwin-x64": "0.19.2", + "@esbuild/freebsd-arm64": "0.19.2", + "@esbuild/freebsd-x64": "0.19.2", + "@esbuild/linux-arm": "0.19.2", + "@esbuild/linux-arm64": "0.19.2", + "@esbuild/linux-ia32": "0.19.2", + "@esbuild/linux-loong64": "0.19.2", + "@esbuild/linux-mips64el": "0.19.2", + "@esbuild/linux-ppc64": "0.19.2", + "@esbuild/linux-riscv64": "0.19.2", + "@esbuild/linux-s390x": "0.19.2", + "@esbuild/linux-x64": "0.19.2", + "@esbuild/netbsd-x64": "0.19.2", + "@esbuild/openbsd-x64": "0.19.2", + "@esbuild/sunos-x64": "0.19.2", + "@esbuild/win32-arm64": "0.19.2", + "@esbuild/win32-ia32": "0.19.2", + "@esbuild/win32-x64": "0.19.2" + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } + } + }, + "esbuild-plugin-alias": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/esbuild-plugin-alias/-/esbuild-plugin-alias-0.2.1.tgz", + "integrity": "sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==", + "dev": true + }, + "esbuild-register": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.4.2.tgz", + "integrity": "sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "eslint": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", + "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true + }, + "eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "dev": true, + "requires": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + } + }, + "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==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "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==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dev": true, + "requires": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + } + }, + "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==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + } + } + }, + "eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "^5.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "@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" + } + }, + "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==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true + }, + "eslint-plugin-storybook": { + "version": "0.6.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.6.12.tgz", + "integrity": "sha512-XbIvrq6hNVG6rpdBr+eBw63QhOMLpZneQVSooEDow8aQCWGCk/5vqtap1yxpVydNfSxi3S/3mBBRLQqKUqQRww==", + "dev": true, + "requires": { + "@storybook/csf": "^0.0.1", + "@typescript-eslint/utils": "^5.45.0", + "requireindex": "^1.1.0", + "ts-dedent": "^2.2.0" + }, + "dependencies": { + "@storybook/csf": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.1.tgz", + "integrity": "sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + } + } + }, + "eslint-plugin-testing-library": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.0.tgz", + "integrity": "sha512-ELY7Gefo+61OfXKlQeXNIDVVLPcvKTeiQOoMZG9TeuWa7Ln4dUNRv8JdRWBQI9Mbb427XGlVB1aa1QPZxBJM8Q==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "^5.58.0" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true + }, + "espree": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", + "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "estree-to-babel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/estree-to-babel/-/estree-to-babel-3.2.1.tgz", + "integrity": "sha512-YNF+mZ/Wu2FU/gvmzuWtYc8rloubL7wfXCTgouFrnjGVXPA/EeYYA7pupXWrb3Iv1cTBeSSxxJIbK23l4MRNqg==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.6", + "@babel/types": "^7.2.0", + "c8": "^7.6.0" + } + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "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" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.0.tgz", + "integrity": "sha512-AV+HaBtnDJ2YEUhPPo25HyUHBLaetM+y/Dq6pEC8VPQyt1dK+k8MfGkMy46djy2bddcqESc1kl4/K1uLWSfk9g==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.6.0", + "@types/node": "*", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-util": "^29.6.0" + } + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "dev": true, + "requires": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", + "dev": true, + "requires": { + "@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" + } + }, + "fast-json-parse": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", + "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "fetch-retry": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-5.0.6.tgz", + "integrity": "sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "file-system-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/file-system-cache/-/file-system-cache-2.3.0.tgz", + "integrity": "sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==", + "dev": true, + "requires": { + "fs-extra": "11.1.1", + "ramda": "0.29.0" + } + }, + "filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "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==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + } + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "flow-parser": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.211.0.tgz", + "integrity": "sha512-Ftqkqisn4MA8u+1I7KGYz35y/RtLsRETsK4qrH6KkDUjxnC4mgq3CcXbckHpGyfTErqMyVhJnlJ56feEn9Cn7A==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, + "fork-ts-checker-webpack-plugin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", + "integrity": "sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "fs-monkey": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", + "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", + "dev": true + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "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==", + "dev": true + }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dev": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "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==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, + "get-npm-tarball-url": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/get-npm-tarball-url/-/get-npm-tarball-url-2.0.3.tgz", + "integrity": "sha512-R/PW6RqyaBQNWYaSyfrh54/qtcnOp22FHCCiRhSSZj0FP3KQWCsxxt0DzIdVTbwTqe9CtQfvl/FPD4UIPt4pqw==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true + }, + "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==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "get-tsconfig": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.0.tgz", + "integrity": "sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw==", + "dev": true, + "requires": { + "resolve-pkg-maps": "^1.0.0" + } + }, + "giget": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.1.2.tgz", + "integrity": "sha512-HsLoS07HiQ5oqvObOI+Qb2tyZH4Gj5nYGfF9qQcZNrPw+uEFhdXtgJr01aO2pWadGHucajYDLxxbtQkm97ON2A==", + "dev": true, + "requires": { + "colorette": "^2.0.19", + "defu": "^6.1.2", + "https-proxy-agent": "^5.0.1", + "mri": "^1.2.0", + "node-fetch-native": "^1.0.2", + "pathe": "^1.1.0", + "tar": "^6.1.13" + } + }, + "github-slugger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", + "dev": true + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-promise": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-6.0.3.tgz", + "integrity": "sha512-m+kxywR5j/2Z2V9zvHKfwwL5Gp7gIFEBX+deTB9w2lJB+wSuw9kcS43VfvTAMk8TXL5JCl/cCjsR+tgNVspGyA==", + "dev": true, + "requires": { + "@types/glob": "^8.0.0" + }, + "dependencies": { + "@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "dev": true, + "requires": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + } + } + }, + "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==", + "dev": true + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "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" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "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==", + "dev": true + }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "graphql": { + "version": "16.7.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz", + "integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==" + }, + "gunzip-maybe": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", + "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==", + "dev": true, + "requires": { + "browserify-zlib": "^0.1.4", + "is-deflate": "^1.0.0", + "is-gzip": "^1.0.0", + "peek-stream": "^1.1.0", + "pumpify": "^1.3.3", + "through2": "^2.0.3" + } + }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "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==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "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==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "headers-polyfill": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.1.2.tgz", + "integrity": "sha512-tWCK4biJ6hcLqTviLXVR9DTRfYGQMXEIUj3gwJ2rZ5wO/at3XtkI4g8mCvFdUF9l1KMBNCfmNAdnahm1cgavQA==" + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "requires": { + "react-is": "^16.7.0" + } + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, + "html-entities": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "requires": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + } + } + }, + "html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true + }, + "html-webpack-plugin": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", + "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==", + "dev": true, + "requires": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + } + }, + "htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "requires": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "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==", + "dev": true + }, + "husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "inquirer": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true + }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "dev": true + }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "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==" + }, + "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==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "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==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-deflate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz", + "integrity": "sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==", + "dev": true + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-gzip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", + "integrity": "sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==", + "dev": true + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" + }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true + }, + "is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "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==", + "dev": true + }, + "is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "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==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + }, + "dependencies": { + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + } + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "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==", + "requires": { + "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-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "dev": true, + "requires": { + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" + } + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "dev": true, + "requires": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.0.tgz", + "integrity": "sha512-do1J9gGrQ68E4UfMz/4OM71p9qCqQxu32N/9ZfeYFSSlx0uUOuxeyZxtJZNaUTW12ZA11ERhmBjBhy1Ho96R4g==", + "dev": true, + "requires": { + "@jest/core": "^29.6.0", + "@jest/types": "^29.6.0", + "import-local": "^3.0.2", + "jest-cli": "^29.6.0" + } + }, + "jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + } + } + }, + "jest-circus": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.0.tgz", + "integrity": "sha512-LtG45qEKhse2Ws5zNR4DnZATReLGQXzBZGZnJ0DU37p6d4wDhu41vvczCQ3Ou+llR6CRYDBshsubV7H4jZvIkw==", + "dev": true, + "requires": { + "@jest/environment": "^29.6.0", + "@jest/expect": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.6.0", + "jest-matcher-utils": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-runtime": "^29.6.0", + "jest-snapshot": "^29.6.0", + "jest-util": "^29.6.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.6.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-cli": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.0.tgz", + "integrity": "sha512-WvZIaanK/abkw6s01924DQ2QLwM5Q4Y4iPbSDb9Zg6smyXGqqcPQ7ft9X8D7B0jICz312eSzM6UlQNxuZJBrMw==", + "dev": true, + "requires": { + "@jest/core": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/types": "^29.6.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.6.0", + "jest-util": "^29.6.0", + "jest-validate": "^29.6.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } + }, + "jest-config": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.0.tgz", + "integrity": "sha512-fKA4jM91PDqWVkMpb1FVKxIuhg3hC6hgaen57cr1rRZkR96dCatvJZsk3ik7/GNu9ERj9wgAspOmyvkFoGsZhA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.6.0", + "@jest/types": "^29.6.0", + "babel-jest": "^29.6.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.6.0", + "jest-environment-node": "^29.6.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.0", + "jest-runner": "^29.6.0", + "jest-util": "^29.6.0", + "jest-validate": "^29.6.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.6.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-diff": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.0.tgz", + "integrity": "sha512-ZRm7cd2m9YyZ0N3iMyuo1iUiprxQ/MFpYWXzEEj7hjzL3WnDffKW8192XBDcrAI8j7hnrM1wed3bL/oEnYF/8w==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.0.tgz", + "integrity": "sha512-d0Jem4RBAlFUyV6JSXPSHVUpNo5RleSj+iJEy1G3+ZCrzHDjWs/1jUfrbnJKHdJdAx5BCEce/Ju379WqHhQk4w==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.6.0", + "pretty-format": "^29.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-environment-jsdom": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.6.0.tgz", + "integrity": "sha512-/cOhoyv+uMbOh4nQPyqtkPas/uUxr5AbK6TPqMMFyj1qEJURY78RhqgBjOFIX02+Lvu5V0RWLq2qKY1dHubFOQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.6.0", + "@jest/fake-timers": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.6.0", + "jest-util": "^29.6.0", + "jsdom": "^20.0.0" + }, + "dependencies": { + "jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + } + } + } + }, + "jest-environment-node": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.0.tgz", + "integrity": "sha512-BOf5Q2/nFCdBOnyBM5c5/6DbdQYgc+0gyUQ8l8qhUAB8O7pM+4QJXIXJsRZJaxd5SHV6y5VArTVhOfogoqcP8Q==", + "dev": true, + "requires": { + "@jest/environment": "^29.6.0", + "@jest/fake-timers": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-mock": "^29.6.0", + "jest-util": "^29.6.0" + }, + "dependencies": { + "jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + } + } + } + }, + "jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true + }, + "jest-haste-map": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.0.tgz", + "integrity": "sha512-dY1DKufptj7hcJSuhpqlYPGcnN3XjlOy/g0jinpRTMsbb40ivZHiuIPzeminOZkrek8C+oDxC54ILGO3vMLojg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.0", + "jest-worker": "^29.6.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.0.tgz", + "integrity": "sha512-oiQHH1SnKmZIwwPnpOrXTq4kHBk3lKGY/07DpnH0sAu+x7J8rXlbLDROZsU6vy9GwB0hPiZeZpu6YlJ48QoKcA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.6.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-leak-detector": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.0.tgz", + "integrity": "sha512-JdV6EZOPxHR1gd6ccxjNowuROkT2jtGU5G/g58RcJX1xe5mrtLj0g6/ZkyMoXF4cs+tTkHMFX6pcIrB1QPQwCw==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } + } + }, + "jest-matcher-utils": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.0.tgz", + "integrity": "sha512-oSlqfGN+sbkB2Q5um/zL7z80w84FEAcLKzXBZIPyRk2F2Srg1ubhrHVKW68JCvb2+xKzAeGw35b+6gciS24PHw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.6.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.0.tgz", + "integrity": "sha512-mkCp56cETbpoNtsaeWVy6SKzk228mMi9FPHSObaRIhbR2Ujw9PqjW/yqVHD2tN1bHbC8ol6h3UEo7dOPmIYwIA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-mock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "dev": true, + "requires": { + "@jest/types": "^27.5.1", + "@types/node": "*" + }, + "dependencies": { + "@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", + "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true + }, + "jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true + }, + "jest-resolve": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.0.tgz", + "integrity": "sha512-+hrpY4LzAONoZA/rvB6rnZLkOSA6UgJLpdCWrOZNSgGxWMumzRLu7dLUSCabAHzoHIDQ9qXfr3th1zYNJ0E8sQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.6.0", + "jest-validate": "^29.6.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-resolve-dependencies": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.0.tgz", + "integrity": "sha512-eOfPog9K3hJdJk/3i6O6bQhXS+3uXhMDkLJGX+xmMPp7T1d/zdcFofbDnHgNoEkhD/mSimC5IagLEP7lpLLu/A==", + "dev": true, + "requires": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.6.0" + } + }, + "jest-runner": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.0.tgz", + "integrity": "sha512-4fZuGV2lOxS2BiqEG9/AI8E6O+jo+QZjMVcgi1x5E6aDql0Gd/EFIbUQ0pSS09y8cya1vJB/qC2xsE468jqtSg==", + "dev": true, + "requires": { + "@jest/console": "^29.6.0", + "@jest/environment": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/transform": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.6.0", + "jest-haste-map": "^29.6.0", + "jest-leak-detector": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-resolve": "^29.6.0", + "jest-runtime": "^29.6.0", + "jest-util": "^29.6.0", + "jest-watcher": "^29.6.0", + "jest-worker": "^29.6.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.0.tgz", + "integrity": "sha512-oiQHH1SnKmZIwwPnpOrXTq4kHBk3lKGY/07DpnH0sAu+x7J8rXlbLDROZsU6vy9GwB0hPiZeZpu6YlJ48QoKcA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.6.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-runtime": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.0.tgz", + "integrity": "sha512-5FavYo3EeXLHIvnJf+r7Cj0buePAbe4mzRB9oeVxDS0uVmouSBjWeGgyRjZkw7ArxOoZI8gO6f8SGMJ2HFlwwg==", + "dev": true, + "requires": { + "@jest/environment": "^29.6.0", + "@jest/fake-timers": "^29.6.0", + "@jest/globals": "^29.6.0", + "@jest/source-map": "^29.6.0", + "@jest/test-result": "^29.6.0", + "@jest/transform": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-mock": "^29.6.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.0", + "jest-snapshot": "^29.6.0", + "jest-util": "^29.6.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-mock": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", + "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "jest-util": "^29.6.0" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-snapshot": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.0.tgz", + "integrity": "sha512-H3kUE9NwWDEDoutcOSS921IqdlkdjgnMdj1oMyxAHNflscdLc9dB8OudZHV6kj4OHJxbMxL8CdI5DlwYrs4wQg==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.6.0", + "@jest/transform": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.6.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.6.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.0", + "jest-message-util": "^29.6.0", + "jest-util": "^29.6.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.6.0", + "semver": "^7.5.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "jest-util": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.0.tgz", + "integrity": "sha512-S0USx9YwcvEm4pQ5suisVm/RVxBmi0GFR7ocJhIeaCuW5AXnAnffXbaVKvIFodyZNOc9ygzVtTxmBf40HsHXaA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-validate": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.0.tgz", + "integrity": "sha512-MLTrAJsb1+W7svbeZ+A7pAnyXMaQrjvPDKCy7OlfsfB6TMVc69v7WjUWfiR6r3snULFWZASiKgvNVDuATta1dg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", + "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-watcher": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.0.tgz", + "integrity": "sha512-LdsQqFNX60mRdRRe+zsELnYRH1yX6KL+ukbh+u6WSQeTheZZe1TlLJNKRQiZ7e0VbvMkywmMWL/KV35noOJCcw==", + "dev": true, + "requires": { + "@jest/test-result": "^29.6.0", + "@jest/types": "^29.6.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.6.0", + "string-length": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jscodeshift": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", + "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", + "dev": true, + "requires": { + "@babel/core": "^7.13.16", + "@babel/parser": "^7.13.16", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/preset-flow": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", + "@babel/register": "^7.13.16", + "babel-core": "^7.0.0-bridge.0", + "chalk": "^4.1.2", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "neo-async": "^2.5.0", + "node-dir": "^0.1.17", + "recast": "^0.21.0", + "temp": "^0.8.4", + "write-file-atomic": "^2.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "dev": true, + "requires": { + "tslib": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "recast": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", + "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", + "dev": true, + "requires": { + "ast-types": "0.15.2", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } + } + }, + "jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "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==", + "dev": true + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, + "launch-editor": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", + "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "dev": true, + "requires": { + "picocolors": "^1.0.0", + "shell-quote": "^1.7.3" + } + }, + "lazy-universal-dotenv": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-4.0.0.tgz", + "integrity": "sha512-aXpZJRnTkpK6gQ/z4nk+ZBLd/Qdp118cvPruLSIQzQNRhKwEcdXCOzXuF55VDqIiuAaY3UGZ10DJtvZzDcvsxg==", + "dev": true, + "requires": { + "app-root-dir": "^1.0.2", + "dotenv": "^16.0.0", + "dotenv-expand": "^10.0.0" + } + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "lint-staged": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.3.tgz", + "integrity": "sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg==", + "dev": true, + "requires": { + "chalk": "5.2.0", + "cli-truncate": "^3.1.0", + "commander": "^10.0.0", + "debug": "^4.3.4", + "execa": "^7.0.0", + "lilconfig": "2.1.0", + "listr2": "^5.0.7", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.3", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.2.2" + }, + "dependencies": { + "chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true + }, + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + }, + "execa": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", + "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", + "dev": true, + "requires": { + "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" + } + }, + "human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "dev": true + } + } + }, + "listr2": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", + "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", + "dev": true, + "requires": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.19", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.8.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + } + } + }, + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true + }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "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==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "map-or-similar": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", + "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", + "dev": true + }, + "markdown-to-jsx": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.2.1.tgz", + "integrity": "sha512-9HrdzBAo0+sFz9ZYAGT5fB8ilzTW+q6lPocRxrIesMO+aB40V9MgFfbfMXxlGjf22OpRy+IXlvVaQenicdpgbg==", + "dev": true + }, + "mdast-util-definitions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", + "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", + "dev": true, + "requires": { + "unist-util-visit": "^2.0.0" + } + }, + "mdast-util-to-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", + "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true + }, + "memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.4" + } + }, + "memoizerific": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", + "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", + "dev": true, + "requires": { + "map-or-similar": "^1.5.0" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "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==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "msw": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/msw/-/msw-1.2.3.tgz", + "integrity": "sha512-Fqy/TaLKR32x4IkMwudJHJysBzVM/v/lSoMPS9f3QaHLOmb3xHN9YurSUnRt+2eEvNXLjVPij1wMBQtLmTbKsg==", + "requires": { + "@mswjs/cookies": "^0.2.2", + "@mswjs/interceptors": "^0.17.5", + "@open-draft/until": "^1.0.3", + "@types/cookie": "^0.4.1", + "@types/js-levenshtein": "^1.1.1", + "chalk": "4.1.1", + "chokidar": "^3.4.2", + "cookie": "^0.4.2", + "graphql": "^15.0.0 || ^16.0.0", + "headers-polyfill": "^3.1.2", + "inquirer": "^8.2.0", + "is-node-process": "^1.2.0", + "js-levenshtein": "^1.1.6", + "node-fetch": "^2.6.7", + "outvariant": "^1.4.0", + "path-to-regexp": "^6.2.0", + "strict-event-emitter": "^0.4.3", + "type-fest": "^2.19.0", + "yargs": "^17.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } + } + }, + "msw-storybook-addon": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/msw-storybook-addon/-/msw-storybook-addon-1.8.0.tgz", + "integrity": "sha512-dw3vZwqjixmiur0vouRSOax7wPSu9Og2Hspy9JZFHf49bZRjwDiLF0Pfn2NXEkGviYJOJiGxS1ejoTiUwoSg4A==", + "dev": true, + "requires": { + "is-node-process": "^1.0.1" + } + }, + "multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "requires": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + } + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true + }, + "node-dir": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", + "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "dev": true, + "requires": { + "minimatch": "^3.0.2" + } + }, + "node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-fetch-native": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.2.0.tgz", + "integrity": "sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ==", + "dev": true + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "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==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dev": true, + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "nwsapi": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.6.tgz", + "integrity": "sha512-vSZ4miHQ4FojLjmz2+ux4B0/XA16jfwt/LBzIUftDpRd8tujHFkXjMyLwjS08fIZCzesj2z7gJukOKJwqebJAQ==", + "dev": true + }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "objectorarray": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/objectorarray/-/objectorarray-1.0.5.tgz", + "integrity": "sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==", + "dev": true + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@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" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" + }, + "outvariant": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", + "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true + }, + "param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + }, + "dependencies": { + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + } + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "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==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "peek-stream": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", + "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "duplexify": "^3.5.0", + "through2": "^2.0.3" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "polished": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", + "integrity": "sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.17.8" + } + }, + "postcss": { + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "dev": true, + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true + }, + "postcss-modules-local-by-default": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true + }, + "pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "requires": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + } + } + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "dependencies": { + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + } + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, + "puppeteer-core": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-2.1.1.tgz", + "integrity": "sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w==", + "dev": true, + "requires": { + "@types/mime-types": "^2.1.0", + "debug": "^4.1.0", + "extract-zip": "^1.6.6", + "https-proxy-agent": "^4.0.0", + "mime": "^2.0.3", + "mime-types": "^2.1.25", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^2.6.1", + "ws": "^6.1.0" + }, + "dependencies": { + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "requires": { + "agent-base": "5", + "debug": "4" + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ws": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "ramda": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", + "integrity": "sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + } + } + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-colorful": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", + "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", + "dev": true + }, + "react-docgen": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-5.4.3.tgz", + "integrity": "sha512-xlLJyOlnfr8lLEEeaDZ+X2J/KJoe6Nr9AzxnkdQWush5hz2ZSu66w6iLMOScMmxoSHWpWMn+k3v5ZiyCfcWsOA==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@babel/generator": "^7.12.11", + "@babel/runtime": "^7.7.6", + "ast-types": "^0.14.2", + "commander": "^2.19.0", + "doctrine": "^3.0.0", + "estree-to-babel": "^3.1.0", + "neo-async": "^2.6.1", + "node-dir": "^0.1.10", + "strip-indent": "^3.0.0" + }, + "dependencies": { + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + } + } + }, + "react-docgen-typescript": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", + "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", + "dev": true + }, + "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==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "react-element-to-jsx-string": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz", + "integrity": "sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==", + "dev": true, + "requires": { + "@base2/pretty-print-object": "1.0.1", + "is-plain-object": "5.0.0", + "react-is": "18.1.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + }, + "react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + } + } + }, + "react-inspector": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", + "integrity": "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==", + "dev": true + }, + "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==", + "dev": true + }, + "react-refresh": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", + "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", + "dev": true + }, + "react-router": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.1.tgz", + "integrity": "sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g==", + "requires": { + "@remix-run/router": "1.7.1" + } + }, + "react-router-dom": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.1.tgz", + "integrity": "sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw==", + "requires": { + "@remix-run/router": "1.7.1", + "react-router": "6.14.1" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "recast": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.2.tgz", + "integrity": "sha512-Qv6cPfVZyMOtPszK6PgW70pUgm7gPlFitAPf0Q69rlOA0zLw2XdDcNmPbVGYicFGT9O8I7TZ/0ryJD+6COvIPw==", + "dev": true, + "requires": { + "assert": "^2.0.0", + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "dependencies": { + "ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "requires": { + "tslib": "^2.0.1" + } + } + } + }, + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "requires": { + "resolve": "^1.20.0" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "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==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + } + }, + "regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "requires": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true + }, + "remark-external-links": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/remark-external-links/-/remark-external-links-8.0.0.tgz", + "integrity": "sha512-5vPSX0kHoSsqtdftSHhIYofVINC8qmp0nctkeU9YoJwV3YfiBRiI6cbFRJ0oI/1F9xS+bopXG0m2KS8VFscuKA==", + "dev": true, + "requires": { + "extend": "^3.0.0", + "is-absolute-url": "^3.0.0", + "mdast-util-definitions": "^4.0.0", + "space-separated-tokens": "^1.0.0", + "unist-util-visit": "^2.0.0" + } + }, + "remark-slug": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/remark-slug/-/remark-slug-6.1.0.tgz", + "integrity": "sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ==", + "dev": true, + "requires": { + "github-slugger": "^1.0.0", + "mdast-util-to-string": "^1.0.0", + "unist-util-visit": "^2.0.0" + } + }, + "renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "requires": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "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==", + "dev": true + }, + "resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "requires": { + "tslib": "^2.1.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dev": true, + "requires": { + "node-forge": "^1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-favicon": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.5.0.tgz", + "integrity": "sha512-FMW2RvqNr03x+C0WxTyu6sOv21oOjkq5j8tjquWccwa6ScNyGFOGJVpuS1NmTVGBAHS07xnSKotgf2ehQmf9iA==", + "dev": true, + "requires": { + "etag": "~1.8.1", + "fresh": "0.5.2", + "ms": "2.1.1", + "parseurl": "~1.3.2", + "safe-buffer": "5.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + } + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true + }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + } + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "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==" + }, + "simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true + } + } + }, + "sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "requires": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "dev": true + }, + "spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "requires": { + "internal-slot": "^1.0.4" + } + }, + "store2": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.2.tgz", + "integrity": "sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==", + "dev": true + }, + "storybook": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.0.26.tgz", + "integrity": "sha512-N6+/QBIahTnOJ3mQFNh+PIimjw+yUUoBlnMq8kE1Rg6QFi8ErEK8xte6uppiTh+7ShpqeLhp9ipuDV6DwJ9Aqg==", + "dev": true, + "requires": { + "@storybook/cli": "7.0.26" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "strict-event-emitter": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", + "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + } + } + }, + "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==", + "dev": true, + "requires": { + "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" + } + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "style-loader": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", + "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", + "dev": true + }, + "styled-components": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.0.2.tgz", + "integrity": "sha512-CRWTuYme0W4zVqzXpODByyocgVbBpRoXmaEgPGb67dvweV1igp7Ik4Z5C9e83wZ2l2hPg/XKV7cjuNxhRlC7Mg==", + "dev": true, + "requires": { + "@babel/cli": "^7.21.0", + "@babel/core": "^7.21.0", + "@babel/helper-module-imports": "^7.18.6", + "@babel/plugin-external-helpers": "^7.18.6", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@babel/traverse": "^7.21.2", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/unitless": "^0.8.0", + "@types/stylis": "^4.0.2", + "css-to-react-native": "^3.2.0", + "csstype": "^3.1.2", + "postcss": "^8.4.23", + "shallowequal": "^1.1.0", + "stylis": "^4.3.0", + "tslib": "^2.5.0" + } + }, + "stylis": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", + "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "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==", + "dev": true + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "synchronous-promise": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.17.tgz", + "integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "tar": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + }, + "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + } + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "telejson": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/telejson/-/telejson-7.1.0.tgz", + "integrity": "sha512-jFJO4P5gPebZAERPkJsqMAQ0IMA1Hi0AoSfxpnUaV6j6R2SZqlpkbS20U6dEUtA3RUYt2Ak/mTlkQzHH9Rv/hA==", + "dev": true, + "requires": { + "memoizerific": "^1.11.3" + } + }, + "temp": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", + "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "dev": true, + "requires": { + "rimraf": "~2.6.2" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true + }, + "tempy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", + "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", + "dev": true, + "requires": { + "del": "^6.0.0", + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "dependencies": { + "del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dev": true, + "requires": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true + } + } + }, + "terser": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.2.tgz", + "integrity": "sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + }, + "terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "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==", + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + } + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "tsconfig-paths-webpack-plugin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.1.tgz", + "integrity": "sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^4.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "requires": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + } + } + }, + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, + "typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "dev": true + }, + "unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + } + }, + "unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "unplugin": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-0.10.2.tgz", + "integrity": "sha512-6rk7GUa4ICYjae5PrAllvcDeuT8pA9+j5J5EkxbMFaV+SalHhxZ7X2dohMzu6C3XzsMT+6jwR/+pwPNR3uK9MA==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "chokidar": "^3.5.3", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.4.5" + } + }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use-resize-observer": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", + "integrity": "sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==", + "dev": true, + "requires": { + "@juggle/resize-observer": "^3.3.1" + } + }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" + }, + "util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, + "w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "requires": { + "xml-name-validator": "^4.0.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "requires": { + "defaults": "^1.0.3" + } + }, + "web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "requires": { + "@zxing/text-encoding": "0.9.0", + "util": "^0.12.3" + } + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "webpack": { + "version": "5.88.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz", + "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + } + } + }, + "webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + } + }, + "webpack-dev-server": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "dev": true, + "requires": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + } + }, + "webpack-hot-middleware": { + "version": "2.25.4", + "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.25.4.tgz", + "integrity": "sha512-IRmTspuHM06aZh98OhBJtqLpeWFM8FXJS5UYpKYxCJzyFoyWj1w6VGFfomZU7OPA55dMLrQK0pRT1eQ3PACr4w==", + "dev": true, + "requires": { + "ansi-html-community": "0.0.8", + "html-entities": "^2.1.0", + "strip-ansi": "^6.0.0" + } + }, + "webpack-merge": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", + "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "webpack-virtual-modules": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.6.tgz", + "integrity": "sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==", + "dev": true + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", + "dev": true + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "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" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "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==", + "requires": { + "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" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true + }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 000000000..6f62a83e7 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,87 @@ +{ + "name": "votogether", + "version": "1.0.0", + "description": "", + "main": "index.js", + "keywords": [], + "author": "", + "license": "ISC", + "scripts": { + "dev": "webpack-dev-server --config webpack.dev.js --open --hot", + "build": "webpack --config webpack.prod.js", + "start": "webpack --config webpack.dev.js", + "lint": "eslint --cache .", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build", + "test": "jest", + "prepare": "cd .. && husky install frontend/.husky", + "mac-local-ip": "ifconfig | grep \"inet \" | grep -v 127.0.0.1" + }, + "dependencies": { + "@tanstack/react-query": "^4.29.19", + "dotenv": "^16.3.1", + "msw": "^1.2.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.14.1" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest", + "plugin:storybook/recommended" + ] + }, + "devDependencies": { + "@babel/preset-env": "^7.22.6", + "@babel/preset-react": "^7.22.5", + "@babel/preset-typescript": "^7.22.5", + "@storybook/addon-essentials": "^7.0.26", + "@storybook/addon-interactions": "^7.0.26", + "@storybook/addon-links": "^7.0.26", + "@storybook/blocks": "^7.0.26", + "@storybook/react": "^7.0.26", + "@storybook/react-webpack5": "^7.0.26", + "@storybook/testing-library": "^0.0.14-next.2", + "@testing-library/react": "^14.0.0", + "@types/jest": "^29.5.2", + "@types/react": "^18.2.14", + "@types/react-dom": "^18.2.6", + "@types/styled-components": "^5.1.26", + "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^11.0.0", + "dotenv-webpack": "^8.0.1", + "esbuild-loader": "^4.0.2", + "eslint": "^8.44.0", + "eslint-config-prettier": "^8.8.0", + "eslint-config-react-app": "^7.0.1", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-storybook": "^0.6.12", + "fork-ts-checker-webpack-plugin": "^8.0.0", + "html-webpack-plugin": "^5.5.3", + "husky": "^8.0.3", + "jest": "^29.6.0", + "jest-environment-jsdom": "^29.6.0", + "lint-staged": "^13.2.3", + "msw-storybook-addon": "^1.8.0", + "prettier": "^2.8.8", + "storybook": "^7.0.26", + "styled-components": "^6.0.2", + "tsconfig-paths-webpack-plugin": "^4.0.1", + "typescript": "^5.1.6", + "webpack": "^5.88.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1", + "webpack-merge": "^5.9.0", + "whatwg-fetch": "^3.6.2" + }, + "msw": { + "workerDirectory": "public" + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx}": [ + "eslint --fix", + "prettier --write" + ] + } +} diff --git a/frontend/public/icons/android-icon-144x144.png b/frontend/public/icons/android-icon-144x144.png new file mode 100644 index 000000000..18d54d458 Binary files /dev/null and b/frontend/public/icons/android-icon-144x144.png differ diff --git a/frontend/public/icons/android-icon-192x192.png b/frontend/public/icons/android-icon-192x192.png new file mode 100644 index 000000000..ab1b2295b Binary files /dev/null and b/frontend/public/icons/android-icon-192x192.png differ diff --git a/frontend/public/icons/android-icon-36x36.png b/frontend/public/icons/android-icon-36x36.png new file mode 100644 index 000000000..be4ce59ab Binary files /dev/null and b/frontend/public/icons/android-icon-36x36.png differ diff --git a/frontend/public/icons/android-icon-48x48.png b/frontend/public/icons/android-icon-48x48.png new file mode 100644 index 000000000..76660dba7 Binary files /dev/null and b/frontend/public/icons/android-icon-48x48.png differ diff --git a/frontend/public/icons/android-icon-512x512.png b/frontend/public/icons/android-icon-512x512.png new file mode 100644 index 000000000..811e033c0 Binary files /dev/null and b/frontend/public/icons/android-icon-512x512.png differ diff --git a/frontend/public/icons/android-icon-72x72.png b/frontend/public/icons/android-icon-72x72.png new file mode 100644 index 000000000..af6a321a5 Binary files /dev/null and b/frontend/public/icons/android-icon-72x72.png differ diff --git a/frontend/public/icons/android-icon-96x96.png b/frontend/public/icons/android-icon-96x96.png new file mode 100644 index 000000000..6a57d703e Binary files /dev/null and b/frontend/public/icons/android-icon-96x96.png differ diff --git a/frontend/public/icons/apple-icon-114x114.png b/frontend/public/icons/apple-icon-114x114.png new file mode 100644 index 000000000..30f994210 Binary files /dev/null and b/frontend/public/icons/apple-icon-114x114.png differ diff --git a/frontend/public/icons/apple-icon-120x120.png b/frontend/public/icons/apple-icon-120x120.png new file mode 100644 index 000000000..cd2015521 Binary files /dev/null and b/frontend/public/icons/apple-icon-120x120.png differ diff --git a/frontend/public/icons/apple-icon-144x144.png b/frontend/public/icons/apple-icon-144x144.png new file mode 100644 index 000000000..18d54d458 Binary files /dev/null and b/frontend/public/icons/apple-icon-144x144.png differ diff --git a/frontend/public/icons/apple-icon-152x152.png b/frontend/public/icons/apple-icon-152x152.png new file mode 100644 index 000000000..141c05895 Binary files /dev/null and b/frontend/public/icons/apple-icon-152x152.png differ diff --git a/frontend/public/icons/apple-icon-180x180.png b/frontend/public/icons/apple-icon-180x180.png new file mode 100644 index 000000000..7a5865c4b Binary files /dev/null and b/frontend/public/icons/apple-icon-180x180.png differ diff --git a/frontend/public/icons/apple-icon-57x57.png b/frontend/public/icons/apple-icon-57x57.png new file mode 100644 index 000000000..25c7ad77a Binary files /dev/null and b/frontend/public/icons/apple-icon-57x57.png differ diff --git a/frontend/public/icons/apple-icon-60x60.png b/frontend/public/icons/apple-icon-60x60.png new file mode 100644 index 000000000..dc2a5f2a5 Binary files /dev/null and b/frontend/public/icons/apple-icon-60x60.png differ diff --git a/frontend/public/icons/apple-icon-72x72.png b/frontend/public/icons/apple-icon-72x72.png new file mode 100644 index 000000000..af6a321a5 Binary files /dev/null and b/frontend/public/icons/apple-icon-72x72.png differ diff --git a/frontend/public/icons/apple-icon-76x76.png b/frontend/public/icons/apple-icon-76x76.png new file mode 100644 index 000000000..c9759b7df Binary files /dev/null and b/frontend/public/icons/apple-icon-76x76.png differ diff --git a/frontend/public/icons/apple-icon-precomposed.png b/frontend/public/icons/apple-icon-precomposed.png new file mode 100644 index 000000000..dc54f1c01 Binary files /dev/null and b/frontend/public/icons/apple-icon-precomposed.png differ diff --git a/frontend/public/icons/apple-icon.png b/frontend/public/icons/apple-icon.png new file mode 100644 index 000000000..dc54f1c01 Binary files /dev/null and b/frontend/public/icons/apple-icon.png differ diff --git a/frontend/public/icons/browserconfig.xml b/frontend/public/icons/browserconfig.xml new file mode 100644 index 000000000..7d453d3bb --- /dev/null +++ b/frontend/public/icons/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff diff --git a/frontend/public/icons/favicon-16x16.png b/frontend/public/icons/favicon-16x16.png new file mode 100644 index 000000000..90f3664f4 Binary files /dev/null and b/frontend/public/icons/favicon-16x16.png differ diff --git a/frontend/public/icons/favicon-32x32.png b/frontend/public/icons/favicon-32x32.png new file mode 100644 index 000000000..532626f91 Binary files /dev/null and b/frontend/public/icons/favicon-32x32.png differ diff --git a/frontend/public/icons/favicon-96x96.png b/frontend/public/icons/favicon-96x96.png new file mode 100644 index 000000000..e2ed53caf Binary files /dev/null and b/frontend/public/icons/favicon-96x96.png differ diff --git a/frontend/public/icons/favicon.ico b/frontend/public/icons/favicon.ico new file mode 100644 index 000000000..8fb8577e9 Binary files /dev/null and b/frontend/public/icons/favicon.ico differ diff --git a/frontend/public/icons/manifest.json b/frontend/public/icons/manifest.json new file mode 100644 index 000000000..216c581fc --- /dev/null +++ b/frontend/public/icons/manifest.json @@ -0,0 +1,59 @@ +{ + "name": "VoTogether", + "short_name": "VoTogether", + "icons": [ + { + "src": "./android-icon-36x36.png", + "sizes": "36x36", + "type": "image/png", + "density": "0.75", + "purpose": "any maskable" + }, + { + "src": "./android-icon-48x48.png", + "sizes": "48x48", + "type": "image/png", + "density": "1.0", + "purpose": "any maskable" + }, + { + "src": "./android-icon-72x72.png", + "sizes": "72x72", + "type": "image/png", + "density": "1.5", + "purpose": "any maskable" + }, + { + "src": "./android-icon-96x96.png", + "sizes": "96x96", + "type": "image/png", + "density": "2.0", + "purpose": "any maskable" + }, + { + "src": "./android-icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "density": "3.0", + "purpose": "any maskable" + }, + { + "src": "./android-icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "density": "4.0", + "purpose": "any maskable" + }, + { + "src": "/images/android-icon-512x512.png", + "type": "image/png", + "sizes": "512x512", + "density": "5.0", + "purpose": "any maskable" + } + ], + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#ffffff" +} diff --git a/frontend/public/icons/ms-icon-144x144.png b/frontend/public/icons/ms-icon-144x144.png new file mode 100644 index 000000000..e59180e0e Binary files /dev/null and b/frontend/public/icons/ms-icon-144x144.png differ diff --git a/frontend/public/icons/ms-icon-150x150.png b/frontend/public/icons/ms-icon-150x150.png new file mode 100644 index 000000000..796162e69 Binary files /dev/null and b/frontend/public/icons/ms-icon-150x150.png differ diff --git a/frontend/public/icons/ms-icon-310x310.png b/frontend/public/icons/ms-icon-310x310.png new file mode 100644 index 000000000..1fed9b2f1 Binary files /dev/null and b/frontend/public/icons/ms-icon-310x310.png differ diff --git a/frontend/public/icons/ms-icon-70x70.png b/frontend/public/icons/ms-icon-70x70.png new file mode 100644 index 000000000..28eb8f8aa Binary files /dev/null and b/frontend/public/icons/ms-icon-70x70.png differ diff --git a/frontend/public/icons/splash-screen-768x1004.png b/frontend/public/icons/splash-screen-768x1004.png new file mode 100644 index 000000000..a0d11a991 Binary files /dev/null and b/frontend/public/icons/splash-screen-768x1004.png differ diff --git a/frontend/public/icons/thumbnail.png b/frontend/public/icons/thumbnail.png new file mode 100644 index 000000000..83fb7922b Binary files /dev/null and b/frontend/public/icons/thumbnail.png differ diff --git a/frontend/public/index.html b/frontend/public/index.html new file mode 100644 index 000000000..868c6195e --- /dev/null +++ b/frontend/public/index.html @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VoTogether + + +
+ + + + diff --git a/frontend/public/mockServiceWorker.js b/frontend/public/mockServiceWorker.js new file mode 100644 index 000000000..36a992745 --- /dev/null +++ b/frontend/public/mockServiceWorker.js @@ -0,0 +1,303 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (1.2.3). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70' +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + const accept = request.headers.get('accept') || '' + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return + } + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = Math.random().toString(16).slice(2) + + event.respondWith( + handleRequest(event, requestId).catch((error) => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url, + ) + return + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}`, + ) + }), + ) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const clonedResponse = response.clone() + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: + clonedResponse.body === null ? null : await clonedResponse.text(), + headers: Object.fromEntries(clonedResponse.headers.entries()), + redirected: clonedResponse.redirected, + }, + }) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + const clonedRequest = request.clone() + + function passthrough() { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const headers = Object.fromEntries(clonedRequest.headers.entries()) + + // Remove MSW-specific request headers so the bypassed requests + // comply with the server's CORS preflight check. + // Operate with the headers as an object because request "Headers" + // are immutable. + delete headers['x-msw-bypass'] + + return fetch(clonedRequest, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Bypass requests with the explicit bypass header. + // Such requests can be issued by "ctx.fetch()". + if (request.headers.get('x-msw-bypass') === 'true') { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.text(), + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'MOCK_NOT_FOUND': { + return passthrough() + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.data + const networkError = new Error(message) + networkError.name = name + + // Rejecting a "respondWith" promise emulates a network error. + throw networkError + } + } + + return passthrough() +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [channel.port2]) + }) +} + +function sleep(timeMs) { + return new Promise((resolve) => { + setTimeout(resolve, timeMs) + }) +} + +async function respondWithMock(response) { + await sleep(response.delay) + return new Response(response.body, response) +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 000000000..bb8b11cc5 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,40 @@ +import { RouterProvider } from 'react-router-dom'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ThemeProvider } from 'styled-components'; + +import { AuthProvider } from '@hooks/context/auth'; +import PostOptionProvider from '@hooks/context/postOption'; + +import router from '@routes/router'; + +import ErrorBoundaryForTopClass from '@pages/ErrorBoundaryForTopClass'; + +import ChannelTalk from '@components/ChannelTalk'; + +import { GlobalStyle } from '@styles/globalStyle'; +import { theme } from '@styles/theme'; + +const queryClient = new QueryClient(); + +ChannelTalk.loadScript(); +ChannelTalk.boot({ + pluginKey: process.env.VOTOGETHER_CHANNEL_TALK_KEY, +}); + +const App = () => ( + + + + + + + + + + + + +); + +export default App; diff --git a/frontend/src/api/categoryList.ts b/frontend/src/api/categoryList.ts new file mode 100644 index 000000000..20fc5776f --- /dev/null +++ b/frontend/src/api/categoryList.ts @@ -0,0 +1,33 @@ +import { CategoryResponse } from '@type/category'; + +import { deleteFetch, getFetch, postFetch } from '@utils/fetch'; + +export const transformCategoryListResponse = (categoryList: CategoryResponse[]) => { + return categoryList.map(category => ({ + id: category.id, + name: category.name, + isFavorite: category.isFavorite, + })); +}; + +const BASE_URL = process.env.VOTOGETHER_BASE_URL; + +export const getUserCategoryList = async () => { + const categoryList = await getFetch(`${BASE_URL}/categories`); + + return transformCategoryListResponse(categoryList); +}; + +export const getGuestCategoryList = async () => { + const categoryList = await getFetch(`${BASE_URL}/categories/guest`); + + return transformCategoryListResponse(categoryList); +}; + +export const addFavoriteCategory = async (categoryId: number) => { + await postFetch(`${BASE_URL}/categories/${categoryId}/like`, ''); +}; + +export const removeFavoriteCategory = async (categoryId: number) => { + await deleteFetch(`${BASE_URL}/categories/${categoryId}/like`); +}; diff --git a/frontend/src/api/comment.ts b/frontend/src/api/comment.ts new file mode 100644 index 000000000..50745ea0e --- /dev/null +++ b/frontend/src/api/comment.ts @@ -0,0 +1,39 @@ +import { Comment, CommentRequest, CommentResponse } from '@type/comment'; + +import { getFetch, postFetch, putFetch, deleteFetch } from '@utils/fetch'; + +const BASE_URL = process.env.VOTOGETHER_BASE_URL ?? ''; + +export const transformCommentListResponse = (commentList: CommentResponse[]): Comment[] => { + return commentList.map(comment => ({ + id: comment.id, + content: comment.content, + createdAt: comment.createdAt, + member: comment.member, + isEdit: comment.createdAt !== comment.updatedAt, + })); +}; + +export const getCommentList = async (postId: number): Promise => { + const commentList = await getFetch(`${BASE_URL}/posts/${postId}/comments`); + + return transformCommentListResponse(commentList); +}; + +export const createComment = async (postId: number, newComment: CommentRequest) => { + return await postFetch(`${BASE_URL}/posts/${postId}/comments`, newComment); +}; + +export const editComment = async ( + postId: number, + commentId: number, + updatedComment: CommentRequest +) => { + return await putFetch(`${BASE_URL}/posts/${postId}/comments/${commentId}`, { + content: updatedComment.content, + }); +}; + +export const deleteComment = async (postId: number, commentId: number) => { + return await deleteFetch(`${BASE_URL}/posts/${postId}/comments/${commentId}`); +}; diff --git a/frontend/src/api/example.ts b/frontend/src/api/example.ts new file mode 100644 index 000000000..c92a190ac --- /dev/null +++ b/frontend/src/api/example.ts @@ -0,0 +1,37 @@ +// 장바구니을 가져오는 예시 코드 + +/** + * 게시물 get: api/cart + * 게시물 Cart: api/cart + */ +import { deleteFetch, getFetch, patchFetch, postFetch, putFetch } from '@utils/fetch'; + +export interface Cart { + id: number; + text: string; +} + +export const getCartList = async () => { + return await getFetch('api/cart'); +}; + +export const createCart = async () => { + return await postFetch('api/cart', { id: 12, text: '생성' }); +}; + +export const editCart = async () => { + return await putFetch<{ id: number }>('api/cart', { id: 12 }); +}; + +// remove or delete +export const deleteCart = async () => { + return await deleteFetch('api/cart/1'); +}; + +/** + * + * patch와 put은 edit을 접두어로 붙힌다. 어색하다면 바꾼다. + */ +export const editOption = async () => { + return await patchFetch('api/cart/1/213123'); +}; diff --git a/frontend/src/api/post.ts b/frontend/src/api/post.ts new file mode 100644 index 000000000..87574912a --- /dev/null +++ b/frontend/src/api/post.ts @@ -0,0 +1,82 @@ +import { PostInfo, PostInfoResponse } from '@type/post'; + +import { + getFetch, + patchFetch, + postFetch, + multiPutFetch, + multiPostFetch, + deleteFetch, +} from '@utils/fetch'; + +const BASE_URL = process.env.VOTOGETHER_BASE_URL ?? ''; + +export const transformPostResponse = (post: PostInfoResponse): PostInfo => { + return { + category: post.categories.map(category => ({ id: category.id, name: category.name })), + content: post.content, + deadline: post.deadline, + imageUrl: post.imageUrl, + postId: post.postId, + createTime: post.createdAt, + title: post.title, + voteInfo: { + allPeopleCount: post.voteInfo.totalVoteCount, + selectedOptionId: post.voteInfo.selectedOptionId, + options: post.voteInfo.options.map(option => ({ + id: option.optionId, + text: option.content, + peopleCount: option.voteCount, + percent: option.votePercent, + imageUrl: option.imageUrl, + })), + }, + writer: { + id: post.writer.id, + nickname: post.writer.nickname, + }, + }; +}; + +export const votePost = async (postId: number, optionId: number) => { + return await postFetch(`${BASE_URL}/posts/${postId}/options/${optionId}`, ''); +}; + +export interface OptionData { + originOptionId: number; + newOptionId: number; +} + +export const changeVotedOption = async (postId: number, optionData: OptionData) => { + return await patchFetch( + `${BASE_URL}/posts/${postId}/options?source=${optionData.originOptionId}&target=${optionData.newOptionId}` + ); +}; + +export const getPost = async (postId: number): Promise => { + const post = await getFetch(`${BASE_URL}/posts/${postId}`); + + return transformPostResponse(post); +}; + +export const getPostForGuest = async (postId: number): Promise => { + const post = await getFetch(`${BASE_URL}/posts/${postId}/guest`); + + return transformPostResponse(post); +}; + +export const createPost = async (newPost: FormData) => { + return await multiPostFetch(`${BASE_URL}/posts`, newPost); +}; + +export const editPost = async (postId: number, updatedPost: FormData) => { + return await multiPutFetch(`${BASE_URL}/posts/${postId}`, updatedPost); +}; + +export const deletePost = async (postId: number) => { + return await deleteFetch(`${BASE_URL}/posts/${postId}`); +}; + +export const setEarlyClosePost = async (postId: number) => { + return await patchFetch(`${BASE_URL}/posts/${postId}/close`); +}; diff --git a/frontend/src/api/postList.ts b/frontend/src/api/postList.ts new file mode 100644 index 000000000..e92920c90 --- /dev/null +++ b/frontend/src/api/postList.ts @@ -0,0 +1,57 @@ +import { PostInfoResponse, PostListByOptionalOption, PostListByRequiredOption } from '@type/post'; + +import { + REQUEST_STATUS_OPTION, + REQUEST_SORTING_OPTION, + REQUEST_POST_KIND_URL, + POST_TYPE, + SEARCH_KEYWORD, +} from '@constants/post'; + +import { getFetch } from '@utils/fetch'; + +import { transformPostResponse } from './post'; + +const BASE_URL = process.env.VOTOGETHER_BASE_URL; + +export const makePostListUrl = ( + requiredOption: PostListByRequiredOption, + optionalOption: PostListByOptionalOption +) => { + const { pageNumber, postSorting, postStatus, postType, isLoggedIn } = requiredOption; + const { categoryId, keyword } = optionalOption; + + const requestedStatus = REQUEST_STATUS_OPTION[postStatus]; + const requestedSorting = REQUEST_SORTING_OPTION[postSorting]; + + const POST_BASE_URL = `${BASE_URL}/${REQUEST_POST_KIND_URL[postType]}${ + isLoggedIn ? '' : '/guest' + }`; + const OPTION_URL = `postClosingType=${requestedStatus}&postSortType=${requestedSorting}&page=${pageNumber}`; + + if (categoryId > 0 && postType === POST_TYPE.CATEGORY) { + return `${POST_BASE_URL}?${OPTION_URL}&category=${categoryId}`; + } + + if (postType === POST_TYPE.SEARCH) { + return `${POST_BASE_URL}?${SEARCH_KEYWORD}=${keyword}&${OPTION_URL}`; + } + + return `${POST_BASE_URL}?${OPTION_URL}`; +}; + +export const getPostList = async ( + requiredOption: PostListByRequiredOption, + optionalOption: PostListByOptionalOption +) => { + const { pageNumber } = requiredOption; + + const postListUrl = makePostListUrl(requiredOption, optionalOption); + + const postList = await getFetch(postListUrl); + + return { + pageNumber, + postList: postList.map(post => transformPostResponse(post)), + }; +}; diff --git a/frontend/src/api/ranking.ts b/frontend/src/api/ranking.ts new file mode 100644 index 000000000..fc47f2a55 --- /dev/null +++ b/frontend/src/api/ranking.ts @@ -0,0 +1,19 @@ +import { PassionUser, RankingPost } from '@type/ranking'; + +import { getFetch } from '@utils/fetch'; + +const BASE_URL = process.env.VOTOGETHER_BASE_URL; + +export const getUserRanking = async (isLoggedIn: boolean) => { + if (!isLoggedIn) return null; + + return await getFetch(`${BASE_URL}/members/me/ranking/passion`); +}; + +export const getPassionUserRanking = async () => { + return await getFetch(`${BASE_URL}/members/ranking/passion/guest`); +}; + +export const getPopularPostRanking = async () => { + return await getFetch(`${BASE_URL}/posts/ranking/popular/guest`); +}; diff --git a/frontend/src/api/report.ts b/frontend/src/api/report.ts new file mode 100644 index 000000000..bfb0353fc --- /dev/null +++ b/frontend/src/api/report.ts @@ -0,0 +1,9 @@ +import { ReportRequest } from '@type/report'; + +import { postFetch } from '@utils/fetch'; + +const BASE_URL = process.env.VOTOGETHER_BASE_URL; + +export const reportContent = async (reportData: ReportRequest) => { + return await postFetch(`${BASE_URL}/report`, reportData); +}; diff --git a/frontend/src/api/token.ts b/frontend/src/api/token.ts new file mode 100644 index 000000000..0bb33e9b2 --- /dev/null +++ b/frontend/src/api/token.ts @@ -0,0 +1,19 @@ +interface SilentLoginToken { + accessToken: string; +} + +const BASE_URL = process.env.VOTOGETHER_BASE_URL ?? ''; + +export const postTokens = async (accessToken: string): Promise => { + const response = await fetch(`${BASE_URL}/auth/silent-login`, { + method: 'POST', + body: accessToken, + credentials: 'include', + }); + + if (!response.ok) { + throw new Error('error'); + } + + return await response.json(); +}; diff --git a/frontend/src/api/userInfo.ts b/frontend/src/api/userInfo.ts new file mode 100644 index 000000000..38676654e --- /dev/null +++ b/frontend/src/api/userInfo.ts @@ -0,0 +1,46 @@ +import type { + UserInfoResponse, + User, + ModifyNicknameRequest, + UpdateUserInfoRequest, +} from '@type/user'; + +import { deleteFetch, getFetch, patchFetch } from '@utils/fetch'; + +export const transformUserInfoResponse = (userInfo: UserInfoResponse): User => { + const { nickname, gender, birthYear, postCount, voteCount } = userInfo; + + return { + nickname, + gender, + birthYear, + postCount, + voteCount, + }; +}; + +const BASE_URL = process.env.VOTOGETHER_BASE_URL; + +export const getUserInfo = async (isLoggedIn: boolean): Promise => { + if (!isLoggedIn) return null; + + const userInfo = await getFetch(`${BASE_URL}/members/me`); + + return transformUserInfoResponse(userInfo); +}; + +export const modifyNickname = async (nickname: string) => { + await patchFetch(`${BASE_URL}/members/me/nickname`, { nickname }); +}; + +export const withdrawalMembership = async () => { + await deleteFetch(`${BASE_URL}/members/me/delete`); +}; + +export const updateUserInfo = async (userInfo: UpdateUserInfoRequest) => { + await patchFetch(`${BASE_URL}/members/me/detail`, userInfo); +}; + +export const logoutUser = async () => { + await fetch('/auth/logout', { method: 'DELETE' }); +}; diff --git a/frontend/src/api/voteResult.ts b/frontend/src/api/voteResult.ts new file mode 100644 index 000000000..e3960c0f5 --- /dev/null +++ b/frontend/src/api/voteResult.ts @@ -0,0 +1,19 @@ +import { VoteResultResponse } from '@components/VoteStatistics/type'; + +import { getFetch } from '@utils/fetch'; + +const BASE_URL = process.env.VOTOGETHER_BASE_URL; + +export const getPostStatistics = async (postId: number): Promise => { + return await getFetch(`${BASE_URL}/posts/${postId}/options`); +}; + +export const getOptionStatistics = async ({ + postId, + optionId, +}: { + postId: number; + optionId: number; +}): Promise => { + return await getFetch(`${BASE_URL}/posts/${postId}/options/${optionId}`); +}; diff --git a/frontend/src/assets.d.ts b/frontend/src/assets.d.ts new file mode 100644 index 000000000..976cf508f --- /dev/null +++ b/frontend/src/assets.d.ts @@ -0,0 +1,9 @@ +declare module '*.svg' { + const content: any; + export default content; +} + +declare module '*.png' { + const content: any; + export default content; +} diff --git a/frontend/src/assets/arrow-up-on-square.svg b/frontend/src/assets/arrow-up-on-square.svg new file mode 100644 index 000000000..c6eca45db --- /dev/null +++ b/frontend/src/assets/arrow-up-on-square.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/back.svg b/frontend/src/assets/back.svg new file mode 100644 index 000000000..5dc080c2d --- /dev/null +++ b/frontend/src/assets/back.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/category.svg b/frontend/src/assets/category.svg new file mode 100644 index 000000000..99b7ca244 --- /dev/null +++ b/frontend/src/assets/category.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/assets/chevron-down.svg b/frontend/src/assets/chevron-down.svg new file mode 100644 index 000000000..ac802d948 --- /dev/null +++ b/frontend/src/assets/chevron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/chevron-up.svg b/frontend/src/assets/chevron-up.svg new file mode 100644 index 000000000..48bf4b209 --- /dev/null +++ b/frontend/src/assets/chevron-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/chevron_up_primary.svg b/frontend/src/assets/chevron_up_primary.svg new file mode 100644 index 000000000..d64606409 --- /dev/null +++ b/frontend/src/assets/chevron_up_primary.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/ellipsis-horizontal.svg b/frontend/src/assets/ellipsis-horizontal.svg new file mode 100644 index 000000000..83abe485b --- /dev/null +++ b/frontend/src/assets/ellipsis-horizontal.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/first-rank.svg b/frontend/src/assets/first-rank.svg new file mode 100644 index 000000000..92513f745 --- /dev/null +++ b/frontend/src/assets/first-rank.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/kakao_login.svg b/frontend/src/assets/kakao_login.svg new file mode 100644 index 000000000..cbdb3098f --- /dev/null +++ b/frontend/src/assets/kakao_login.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/kakao_login_large.svg b/frontend/src/assets/kakao_login_large.svg new file mode 100644 index 000000000..fc16cc083 --- /dev/null +++ b/frontend/src/assets/kakao_login_large.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/kakao_login_medium_wide.svg b/frontend/src/assets/kakao_login_medium_wide.svg new file mode 100644 index 000000000..cbdb3098f --- /dev/null +++ b/frontend/src/assets/kakao_login_medium_wide.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg new file mode 100644 index 000000000..fbf18d653 --- /dev/null +++ b/frontend/src/assets/logo.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/photo_white.svg b/frontend/src/assets/photo_white.svg new file mode 100644 index 000000000..330feedd6 --- /dev/null +++ b/frontend/src/assets/photo_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/projectName.svg b/frontend/src/assets/projectName.svg new file mode 100644 index 000000000..5eca542a2 --- /dev/null +++ b/frontend/src/assets/projectName.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/ranking.svg b/frontend/src/assets/ranking.svg new file mode 100644 index 000000000..4b1b5f5cf --- /dev/null +++ b/frontend/src/assets/ranking.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/retry.svg b/frontend/src/assets/retry.svg new file mode 100644 index 000000000..fbc0b60ca --- /dev/null +++ b/frontend/src/assets/retry.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/search_black.svg b/frontend/src/assets/search_black.svg new file mode 100644 index 000000000..a61f87924 --- /dev/null +++ b/frontend/src/assets/search_black.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/search_white.svg b/frontend/src/assets/search_white.svg new file mode 100644 index 000000000..eb227cbe5 --- /dev/null +++ b/frontend/src/assets/search_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/second-rank.svg b/frontend/src/assets/second-rank.svg new file mode 100644 index 000000000..c91f6f43a --- /dev/null +++ b/frontend/src/assets/second-rank.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/star-filled.svg b/frontend/src/assets/star-filled.svg new file mode 100644 index 000000000..2a9f62fbb --- /dev/null +++ b/frontend/src/assets/star-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/star-lined.svg b/frontend/src/assets/star-lined.svg new file mode 100644 index 000000000..ac8cdaab5 --- /dev/null +++ b/frontend/src/assets/star-lined.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/stroke-logo.svg b/frontend/src/assets/stroke-logo.svg new file mode 100644 index 000000000..436c0c327 --- /dev/null +++ b/frontend/src/assets/stroke-logo.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/third-rank.svg b/frontend/src/assets/third-rank.svg new file mode 100644 index 000000000..6da147c62 --- /dev/null +++ b/frontend/src/assets/third-rank.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/user.svg b/frontend/src/assets/user.svg new file mode 100644 index 000000000..66ba4b665 --- /dev/null +++ b/frontend/src/assets/user.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/votogether_home.png b/frontend/src/assets/votogether_home.png new file mode 100644 index 000000000..320c25aba Binary files /dev/null and b/frontend/src/assets/votogether_home.png differ diff --git a/frontend/src/assets/votogether_write.png b/frontend/src/assets/votogether_write.png new file mode 100644 index 000000000..df6ff3d6f Binary files /dev/null and b/frontend/src/assets/votogether_write.png differ diff --git a/frontend/src/assets/x_mark_black.svg b/frontend/src/assets/x_mark_black.svg new file mode 100644 index 000000000..457d8e626 --- /dev/null +++ b/frontend/src/assets/x_mark_black.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/x_mark_white.svg b/frontend/src/assets/x_mark_white.svg new file mode 100644 index 000000000..11b81b1ea --- /dev/null +++ b/frontend/src/assets/x_mark_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/components/ChannelTalk/index.tsx b/frontend/src/components/ChannelTalk/index.tsx new file mode 100644 index 000000000..60ebb97c5 --- /dev/null +++ b/frontend/src/components/ChannelTalk/index.tsx @@ -0,0 +1,201 @@ +declare global { + interface Window { + ChannelIO?: IChannelIO; + ChannelIOInitialized?: boolean; + } +} + +interface IChannelIO { + c?: (...args: any) => void; + q?: [methodName: string, ...args: any[]][]; + (...args: any): void; +} + +interface BootOption { + appearance?: string; + customLauncherSelector?: string; + hideChannelButtonOnBoot?: boolean; + hidePopup?: boolean; + language?: string; + memberHash?: string; + memberId?: string; + pluginKey: string; + profile?: Profile; + trackDefaultEvent?: boolean; + trackUtmSource?: boolean; + unsubscribe?: boolean; + unsubscribeEmail?: boolean; + unsubscribeTexting?: boolean; + zIndex?: number; +} + +interface Callback { + (error: Error | null, user: CallbackUser | null): void; +} + +interface CallbackUser { + alert: number; + avatarUrl: string; + id: string; + language: string; + memberId: string; + name?: string; + profile?: Profile | null; + tags?: string[] | null; + unsubscribeEmail: boolean; + unsubscribeTexting: boolean; +} + +interface UpdateUserInfo { + language?: string; + profile?: Profile | null; + profileOnce?: Profile; + tags?: string[] | null; + unsubscribeEmail?: boolean; + unsubscribeTexting?: boolean; +} + +interface Profile { + [key: string]: string | number | boolean | null | undefined; +} + +interface FollowUpProfile { + name?: string | null; + mobileNumber?: string | null; + email?: string | null; +} + +interface EventProperty { + [key: string]: string | number | boolean | null | undefined; +} + +type Appearance = 'light' | 'dark' | 'system' | null; + +class ChannelTalk { + constructor() { + this.loadScript(); + } + + loadScript() { + (function () { + let w = window; + if (w.ChannelIO) { + return w.console.error('ChannelIO script included twice.'); + } + let ch: IChannelIO = function () { + ch.c?.(arguments); + }; + ch.q = []; + ch.c = function (args) { + ch.q?.push(args); + }; + w.ChannelIO = ch; + function l() { + if (w.ChannelIOInitialized) { + return; + } + w.ChannelIOInitialized = true; + let s = document.createElement('script'); + s.type = 'text/javascript'; + s.async = true; + s.src = 'https://cdn.channel.io/plugin/ch-plugin-web.js'; + let x = document.getElementsByTagName('script')[0]; + if (x.parentNode) { + x.parentNode.insertBefore(s, x); + } + } + if (document.readyState === 'complete') { + l(); + } else { + w.addEventListener('DOMContentLoaded', l); + w.addEventListener('load', l); + } + })(); + } + + boot(option: BootOption, callback?: Callback) { + window.ChannelIO?.('boot', option, callback); + } + + shutdown() { + window.ChannelIO?.('shutdown'); + } + + showMessenger() { + window.ChannelIO?.('showMessenger'); + } + + hideMessenger() { + window.ChannelIO?.('hideMessenger'); + } + + openChat(chatId?: string | number, message?: string) { + window.ChannelIO?.('openChat', chatId, message); + } + + track(eventName: string, eventProperty?: EventProperty) { + window.ChannelIO?.('track', eventName, eventProperty); + } + + onShowMessenger(callback: () => void) { + window.ChannelIO?.('onShowMessenger', callback); + } + + onHideMessenger(callback: () => void) { + window.ChannelIO?.('onHideMessenger', callback); + } + + onBadgeChanged(callback: (unread: number, alert: number) => void) { + window.ChannelIO?.('onBadgeChanged', callback); + } + + onChatCreated(callback: () => void) { + window.ChannelIO?.('onChatCreated', callback); + } + + onFollowUpChanged(callback: (profile: FollowUpProfile) => void) { + window.ChannelIO?.('onFollowUpChanged', callback); + } + + onUrlClicked(callback: (url: string) => void) { + window.ChannelIO?.('onUrlClicked', callback); + } + + clearCallbacks() { + window.ChannelIO?.('clearCallbacks'); + } + + updateUser(userInfo: UpdateUserInfo, callback?: Callback) { + window.ChannelIO?.('updateUser', userInfo, callback); + } + + addTags(tags: string[], callback?: Callback) { + window.ChannelIO?.('addTags', tags, callback); + } + + removeTags(tags: string[], callback?: Callback) { + window.ChannelIO?.('removeTags', tags, callback); + } + + setPage(page: string) { + window.ChannelIO?.('setPage', page); + } + + resetPage() { + window.ChannelIO?.('resetPage'); + } + + showChannelButton() { + window.ChannelIO?.('showChannelButton'); + } + + hideChannelButton() { + window.ChannelIO?.('hideChannelButton'); + } + + setAppearance(appearance: Appearance) { + window.ChannelIO?.('setAppearance', appearance); + } +} + +export default new ChannelTalk(); diff --git a/frontend/src/components/Example/Example.stories.tsx b/frontend/src/components/Example/Example.stories.tsx new file mode 100644 index 000000000..21edfe314 --- /dev/null +++ b/frontend/src/components/Example/Example.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import Example from '.'; + +const meta: Meta = { + component: Example, +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + render: () => , +}; diff --git a/frontend/src/components/Example/index.tsx b/frontend/src/components/Example/index.tsx new file mode 100644 index 000000000..be41b5b1b --- /dev/null +++ b/frontend/src/components/Example/index.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +import * as S from './style'; + +export default function Example() { + return 버튼; +} diff --git a/frontend/src/components/Example/style.ts b/frontend/src/components/Example/style.ts new file mode 100644 index 000000000..83b743789 --- /dev/null +++ b/frontend/src/components/Example/style.ts @@ -0,0 +1,8 @@ +import { styled } from 'styled-components'; + +export const Button = styled.button` + width: 80px; + color: red; + background: black; + font-size: 1rem; +`; diff --git a/frontend/src/components/PostForm/CategoryWrapper/index.tsx b/frontend/src/components/PostForm/CategoryWrapper/index.tsx new file mode 100644 index 000000000..6362d8714 --- /dev/null +++ b/frontend/src/components/PostForm/CategoryWrapper/index.tsx @@ -0,0 +1,35 @@ +import { useContext } from 'react'; + +import { AuthContext } from '@hooks/context/auth'; +import { useCategoryList } from '@hooks/query/category/useCategoryList'; + +import MultiSelect from '@components/common/MultiSelect'; +import { Option } from '@components/common/MultiSelect/types'; + +import { changeCategoryToOption } from '@utils/post/changeCategoryToOption'; + +interface CategoryWrapperProps { + multiSelectHook: { + selectedOptionList: Option[]; + handleOptionAdd: (newItem: Option) => void; + handleOptionDelete: (optionId: number) => void; + }; +} + +export default function CategoryWrapper({ multiSelectHook }: CategoryWrapperProps) { + const { selectedOptionList, handleOptionAdd, handleOptionDelete } = multiSelectHook; + const { isLoggedIn } = useContext(AuthContext).loggedInfo; + const { data: categoryList } = useCategoryList(isLoggedIn); + + const categoryOptionList = changeCategoryToOption(categoryList ?? []); + + return ( + + ); +} diff --git a/frontend/src/components/PostForm/ContentImageSection/index.tsx b/frontend/src/components/PostForm/ContentImageSection/index.tsx new file mode 100644 index 000000000..a66c0e93f --- /dev/null +++ b/frontend/src/components/PostForm/ContentImageSection/index.tsx @@ -0,0 +1,58 @@ +import { ChangeEvent, MouseEvent, MutableRefObject } from 'react'; + +import { Size } from '@type/style'; + +import OptionCancelButton from '@components/optionList/WritingVoteOptionList/WritingVoteOption/OptionCancelButton'; + +import * as S from './style'; + +interface ContentImageSectionProps { + size: Size; + contentImageHook: { + contentImage: string; + contentInputRef: MutableRefObject; + removeImage: () => void; + handleUploadImage: (event: ChangeEvent) => void; + }; +} +export default function ContentImageSection({ contentImageHook, size }: ContentImageSectionProps) { + const { contentImage, contentInputRef, removeImage, handleUploadImage } = contentImageHook; + + const handleButtonClick = (e: MouseEvent) => { + e.preventDefault(); + contentInputRef.current && contentInputRef.current.click(); + }; + + return ( + <> + {contentImage && ( + + + + + + + )} + { + + + 본문에 사진 넣기 + + + + } + + ); +} diff --git a/frontend/src/components/PostForm/ContentImageSection/style.ts b/frontend/src/components/PostForm/ContentImageSection/style.ts new file mode 100644 index 000000000..ba5e9ddad --- /dev/null +++ b/frontend/src/components/PostForm/ContentImageSection/style.ts @@ -0,0 +1,66 @@ +import { styled } from 'styled-components'; + +import { Size } from '@type/style'; + +export const ContentImageContainer = styled.div` + display: grid; + grid-template-columns: 40px auto; +`; + +const IMAGE_SIZE = { + sm: '25%', + md: '50%', + lg: '100%', +}; + +export const ContentImageWrapper = styled.div<{ $size: Size }>` + width: ${props => IMAGE_SIZE[props.$size]}; + height: 100%; + + position: relative; +`; + +export const ContentImage = styled.img` + width: 100%; + border-radius: 4px; + + aspect-ratio: 1/1; + object-fit: cover; +`; + +export const FileInputContainer = styled.div` + width: 100%; + border-radius: 50%; +`; + +export const FileInput = styled.input` + visibility: hidden; +`; + +export const Button = styled.button<{ $isVisible: boolean }>` + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + border: 2px solid var(--primary-color); + border-radius: 5px; + padding: 5px 0; + + background-color: var(--primary-color); + + visibility: ${props => (props.$isVisible ? 'hidden' : '')}; + cursor: pointer; +`; + +export const Label = styled.label` + width: 100%; + height: 100%; + + color: var(--white); + + font: var(--text-body); + text-align: center; + + cursor: pointer; +`; diff --git a/frontend/src/components/PostForm/PostForm.stories.tsx b/frontend/src/components/PostForm/PostForm.stories.tsx new file mode 100644 index 000000000..4e636c5dd --- /dev/null +++ b/frontend/src/components/PostForm/PostForm.stories.tsx @@ -0,0 +1,76 @@ +import type { Meta } from '@storybook/react'; + +import { PostInfo } from '@type/post'; + +import { useCreatePost } from '@hooks/query/post/useCreatePost'; +import { useEditPost } from '@hooks/query/post/useEditPost'; + +import PostForm from '.'; + +const meta: Meta = { + component: PostForm, +}; + +export default meta; + +const MOCK_DATA: PostInfo = { + postId: 1, + title: '당신의 최애 동물에 투표하세요!', + writer: { id: 15216, nickname: 'jero' }, + content: + '한자로 견(犬)·구(狗) 등으로 표기한다. 포유류 중 가장 오래된 가축으로 거의 전세계에서 사육되며 약 400여 품종이 있다. 개는 이리·자칼(jackal) 등이 조상이라고 하는데, 이는 개와 교배하여 계대(繼代) 번식의 가능성이 있는 새끼를 낳을 수 있다는 것을 뜻한다. 즉 개에 이들의 혈액이 혼혈될 가능성이 있다는 것이다. 그러나 두개골이나 치아의 구조를 보면 개는 혼합된 것이 아니며, 또 그들 중의 어느 것에서 생긴 것이라고도 여겨지지 않는다. 아마도 개는 오스트레일리아에 야생하는 딩고(dingo)나 남아시아에 반야생상태로 서식하는 개와 흡사한, 절멸된 야생종에서 생긴 것으로 추측된다.', + imageUrl: '', + category: [ + { id: 13215, name: '음식' }, + { id: 13217, name: '게임' }, + { id: 13219, name: '연예' }, + ], + createTime: '2023-07-18 12:40', + deadline: '2023-08-15 12:40', + voteInfo: { + selectedOptionId: 1, + allPeopleCount: 0, + options: [ + { + id: Math.floor(Math.random() * 100000), + text: '햄스터가 세상을 구한다.', + imageUrl: '', + peopleCount: 0, + percent: 20, + }, + { + id: Math.floor(Math.random() * 100000), + text: '강아지가 세상을 구한다.', + imageUrl: '', + peopleCount: 0, + percent: 10, + }, + { + id: Math.floor(Math.random() * 100000), + text: '고양이가 세상을 구한다.', + imageUrl: 'https://source.unsplash.com/random', + peopleCount: 0, + percent: 10, + }, + ], + }, +}; + +export const NewPost = () => { + const { mutate } = useCreatePost(); + return ( + <> + + + ); +}; + +export const OldPost = () => { + const examplePostId = 1; + const { mutate } = useEditPost(examplePostId); + return ( + <> + + + ); +}; diff --git a/frontend/src/components/PostForm/constants.ts b/frontend/src/components/PostForm/constants.ts new file mode 100644 index 000000000..595ecad57 --- /dev/null +++ b/frontend/src/components/PostForm/constants.ts @@ -0,0 +1,5 @@ +export type DeadlineOption = '10분' | '30분' | '1시간' | '6시간' | '1일'; + +export const DEADLINE_OPTION: DeadlineOption[] = ['10분', '30분', '1시간', '6시간', '1일']; + +export const MAX_FILE_SIZE = 1500000; diff --git a/frontend/src/components/PostForm/index.tsx b/frontend/src/components/PostForm/index.tsx new file mode 100644 index 000000000..390aad3b4 --- /dev/null +++ b/frontend/src/components/PostForm/index.tsx @@ -0,0 +1,335 @@ +import type { UseMutateFunction } from '@tanstack/react-query'; + +import React, { HTMLAttributes, useState } from 'react'; +import { Navigate, useNavigate } from 'react-router-dom'; + +import { PostInfo } from '@type/post'; + +import { useContentImage } from '@hooks/useContentImage'; +import { useMultiSelect } from '@hooks/useMultiSelect'; +import { useText } from '@hooks/useText'; +import { useToast } from '@hooks/useToast'; +import { useToggle } from '@hooks/useToggle'; +import { useWritingOption } from '@hooks/useWritingOption'; + +import ErrorBoundary from '@pages/ErrorBoundary'; + +import Modal from '@components/common/Modal'; +import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; +import SquareButton from '@components/common/SquareButton'; +import TimePickerOptionList from '@components/common/TimePickerOptionList'; +import Toast from '@components/common/Toast'; +import WritingVoteOptionList from '@components/optionList/WritingVoteOptionList'; + +import { PATH } from '@constants/path'; +import { + CONTENT_PLACEHOLDER, + POST_DEADLINE_POLICY, + POST_TITLE_POLICY, +} from '@constants/policyMessage'; +import { CATEGORY_COUNT_LIMIT, POST_CONTENT, POST_TITLE } from '@constants/post'; + +import { calculateDeadlineTime } from '@utils/post/calculateDeadlineTime'; +import { checkWriter } from '@utils/post/checkWriter'; +import { + convertImageUrlToServerUrl, + convertServerUrlToImageUrl, +} from '@utils/post/convertImageUrlToServerUrl'; +import { addTimeToDate, formatTimeWithOption } from '@utils/post/formatTime'; +import { getDeadlineTime } from '@utils/post/getDeadlineTime'; +import { getSelectedTimeOption } from '@utils/post/getSelectedTimeOption'; +import { checkIrreplaceableTime } from '@utils/time'; + +import CategoryWrapper from './CategoryWrapper'; +import { DEADLINE_OPTION, DeadlineOption } from './constants'; +import ContentImagePart from './ContentImageSection'; +import * as S from './style'; + +interface PostFormProps extends HTMLAttributes { + data?: PostInfo; + mutate: UseMutateFunction; +} + +export default function PostForm({ data, mutate }: PostFormProps) { + const { + postId, + title, + content, + category: categoryIds, + createTime, + deadline, + voteInfo: serverVoteInfo, + imageUrl: serverImageUrl, + writer, + } = data ?? {}; + + const navigate = useNavigate(); + const contentImageHook = useContentImage( + serverImageUrl && convertImageUrlToServerUrl(serverImageUrl) + ); + const writingOptionHook = useWritingOption( + serverVoteInfo?.options.map(option => ({ + ...option, + imageUrl: option.imageUrl ? convertImageUrlToServerUrl(option.imageUrl) : '', + })) + ); + + const { isToastOpen, openToast, toastMessage } = useToast(); + const [selectTimeOption, setSelectTimeOption] = useState( + getSelectedTimeOption(calculateDeadlineTime(createTime, deadline)) + ); + const { isOpen, openComponent, closeComponent } = useToggle(); + const [time, setTime] = useState(calculateDeadlineTime(createTime, deadline)); + const baseTime = createTime ? new Date(createTime) : new Date(); + const closeModal = () => { + if (data && checkIrreplaceableTime(time, data.createTime)) { + openToast('마감시간 지정 조건을 다시 확인해주세요.'); + const updatedTime = { + day: 0, + hour: 0, + minute: 0, + }; + setTime(updatedTime); + setSelectTimeOption(null); + } + + setSelectTimeOption(Object.values(time).every(time => time === 0) ? null : '사용자지정'); + closeComponent(); + }; + + const { text: writingTitle, handleTextChange: handleTitleChange } = useText(title ?? ''); + const { text: writingContent, handleTextChange: handleContentChange } = useText(content ?? ''); + const multiSelectHook = useMultiSelect(categoryIds ?? [], CATEGORY_COUNT_LIMIT); + + const handleDeadlineButtonClick = (option: DeadlineOption) => { + const targetTime = formatTimeWithOption(option); + + if (data && checkIrreplaceableTime(targetTime, data.createTime)) + return openToast('마감시간 지정 조건을 다시 확인해주세요.'); + setSelectTimeOption(option); + setTime(targetTime); + }; + + const handleResetButton = () => { + if (window.confirm('정말 초기화하시겠습니까?')) { + const updatedTime = { + day: 0, + hour: 0, + minute: 0, + }; + setTime(updatedTime); + } + }; + + const handlePostFormSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const formData = new FormData(); + + const writingOptionList = writingOptionHook.optionList.map(({ text, imageUrl }, index) => { + return { content: text, imageUrl: convertServerUrlToImageUrl(imageUrl) }; + }); + + const imageUrlList = [ + convertServerUrlToImageUrl(contentImageHook.contentImage), + ...writingOptionList.map(option => option.imageUrl), + ]; + + //예외처리 + const { selectedOptionList } = multiSelectHook; + if (selectedOptionList.length < 1) return openToast('카테고리를 최소 1개 골라주세요.'); + if (selectedOptionList.length > 3) return openToast('카테고리를 최대 3개 골라주세요.'); + if (writingTitle.trim() === '') return openToast('제목은 필수로 입력해야 합니다.'); + if (writingContent.trim() === '') return openToast('내용은 필수로 입력해야 합니다.'); + if (writingOptionList.length < 2) return openToast('선택지는 최소 2개 입력해주세요.'); + if (writingOptionList.length > 5) return openToast('선택지는 최대 5개 입력할 수 있습니다.'); + if (writingOptionList.some(option => option.content.trim() === '')) + return openToast('선택지에 글을 입력해주세요.'); + if (Object.values(time).reduce((a, b) => a + b, 0) < 1) + return openToast('시간은 필수로 입력해야 합니다.'); + + if (e.target instanceof HTMLFormElement) { + const optionImageFileInputs = + e.target.querySelectorAll('input[type="file"]'); + const fileInputList: HTMLInputElement[] = [...optionImageFileInputs]; + const contentImageFileList: File[] = []; + const optionImageFileList: File[] = []; + fileInputList.forEach((item, index) => { + if (!item.files) return; + + if (index === 0) { + //사진url이 ""거나 undefined이거나 기존 url과 동일한 url이면 true + !imageUrlList[index] || imageUrlList[index] === serverImageUrl + ? contentImageFileList.push(new File(['없는사진'], '없는사진.jpg')) + : contentImageFileList.push(item.files[0]); + } else { + //사진url이 ""거나 undefined이거나 기존 url과 동일한 url이면 true + !imageUrlList[index] || + imageUrlList[index] === serverVoteInfo?.options[index - 1].imageUrl + ? optionImageFileList.push(new File(['없는사진'], '없는사진.jpg')) + : optionImageFileList.push(item.files[0]); + } + }); + + contentImageFileList.map(file => formData.append('contentImages', file)); + optionImageFileList.map(file => formData.append('optionImages', file)); + + const updatedPostTexts = { + categoryIds: selectedOptionList.map(option => option.id), + title: writingTitle, + imageUrl: convertServerUrlToImageUrl(contentImageHook.contentImage), + content: writingContent, + postOptions: writingOptionList, + deadline: addTimeToDate(time, baseTime), + // 글 수정의 경우 작성시간을 기준으로 마감시간 옵션을 더한다. + // 마감시간 옵션을 선택 안했다면 기존의 마감 시간을 유지한다. + }; + formData.append('request', JSON.stringify(updatedPostTexts)); + + mutate(formData); + } + }; + + if (postId && writer && !checkWriter(writer.id)) return ; + + return ( + <> + + + navigate('/')}>취소 + + 저장 + + + +
+ + + + + + ) => + handleTitleChange(e, POST_TITLE) + } + placeholder={POST_TITLE_POLICY.DEFAULT} + maxLength={POST_TITLE.MAX_LENGTH} + minLength={POST_TITLE.MIN_LENGTH} + required + /> + ) => + handleContentChange(e, POST_CONTENT) + } + placeholder={CONTENT_PLACEHOLDER} + maxLength={POST_CONTENT.MAX_LENGTH} + minLength={POST_CONTENT.MIN_LENGTH} + required + /> + + + + + + + + + + + {getDeadlineTime({ hour: time.hour, day: time.day, minute: time.minute })} + {data && ( + + 현재 시간으로부터 글 작성일({createTime})로부터 3일 이내 ( + {addTimeToDate({ day: 3, hour: 0, minute: 0 }, baseTime)})까지만 선택 + 가능합니다. + + )} + {data && ( + + * 작성일시로부터 마감시간이 계산됩니다.{' '} + + )} + {data && ( + * 기존 마감 시간은 {deadline}입니다. + )} + + + {DEADLINE_OPTION.map(option => ( + handleDeadlineButtonClick(option)} + theme={selectTimeOption === option ? 'fill' : 'blank'} + > + {option} + + ))} + { + + 사용자 지정 + + } + + + + + 저장 + + + + + {isOpen && ( + + <> + +

마감 시간 선택

+ + X + +
+ + + {POST_DEADLINE_POLICY.DEFAULT} + + + + + 초기화 + + + + +
+ )} +
+ {isToastOpen && ( + + {toastMessage} + + )} + + ); +} diff --git a/frontend/src/components/PostForm/style.ts b/frontend/src/components/PostForm/style.ts new file mode 100644 index 000000000..95b3540b9 --- /dev/null +++ b/frontend/src/components/PostForm/style.ts @@ -0,0 +1,248 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const HeaderWrapper = styled.div` + width: 100%; + + position: fixed; + left: 0; + + z-index: ${theme.zIndex.header}; + + @media (min-width: ${theme.breakpoint.sm}) { + display: none; + } +`; + +export const HeaderButton = styled.button` + width: 30px; + + color: white; + + cursor: pointer; +`; + +export const Wrapper = styled.div` + display: grid; + grid-template-columns: 1fr; + gap: 20px; + + padding: 70px 10px 20px 10px; + + @media (min-width: ${theme.breakpoint.sm}) { + grid-template-columns: 2fr 1fr; + gap: 30px; + + padding: 30px 40px 20px 40px; + } + + @media (min-width: ${theme.breakpoint.md}) { + grid-template-columns: 1fr 300px; + padding: 30px 80px 20px 80px; + } + + @media (min-width: ${theme.breakpoint.lg}) { + grid-template-columns: 1fr 400px; + } +`; + +export const LeftSide = styled.div<{ $hasImage: boolean }>` + display: flex; + flex-direction: column; + + @media (min-width: ${theme.breakpoint.sm}) { + display: grid; + grid-template-rows: max-content max-content minmax(max-content, 1fr) max-content; + + height: calc(100vh - 130px); + + overflow-y: ${({ $hasImage }) => $hasImage && 'scroll'}; + } +`; + +export const Title = styled.input` + border: 1px solid #e6e6e6; + border-radius: 6px; + padding: 10px; + margin-top: 10px; + + color: gray; + + font: var(--text-title); + + &:focus { + outline: none !important; + border-color: var(--slate); + } + + @media (min-width: ${theme.breakpoint.md}) { + font-size: 2.4rem; + } + + @media (min-width: ${theme.breakpoint.lg}) { + font-size: 2.8rem; + line-height: 3.6rem; + } +`; + +export const Content = styled.textarea` + min-height: 300px; + border: 1px solid #e6e6e6; + border-radius: 6px; + padding: 10px; + margin-top: 7px; + margin-bottom: 10px; + + color: gray; + + resize: none; + + font: var(--text-caption); + font-family: 'Raleway', sans-serif; + + &:focus { + outline: none !important; + border-color: var(--slate); + } + + @media (min-width: ${theme.breakpoint.md}) { + font-size: 1.8rem; + line-height: 2.4rem; + } + + @media (min-width: ${theme.breakpoint.lg}) { + font-size: 2rem; + line-height: 2.8rem; + } +`; + +export const ContentImagePartWrapper = styled.div<{ $hasImage: boolean }>` + justify-self: ${props => props.$hasImage && 'center'}; + height: 100%; + + @media (min-width: ${theme.breakpoint.sm}) { + max-width: ${({ $hasImage }) => $hasImage && '800px'}; + width: ${({ $hasImage }) => $hasImage && '80%'}; + } +`; + +export const RightSide = styled.div` + display: grid; + grid-template-rows: auto max-content max-content; + + @media (min-width: ${theme.breakpoint.sm}) { + height: calc(100vh - 130px); + } +`; + +export const OptionListWrapper = styled.div` + width: 100%; + padding-bottom: 10px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + + overflow-x: hidden; + + @media (min-width: ${theme.breakpoint.sm}) { + overflow-y: auto; + } +`; + +export const Deadline = styled.div` + font: var(--text-body); + font-weight: bold; + text-align: center; + + @media (min-width: ${theme.breakpoint.sm}) { + margin: 10px 0; + } +`; + +export const DeadlineDescription = styled.div` + display: flex; + flex-direction: column; + + margin: 10px 0; + + @media (min-width: ${theme.breakpoint.sm}) { + margin: 10px 0; + min-height: 40px; + } +`; + +export const Description = styled.div` + color: gray; + + font: var(--text-small); +`; + +export const ButtonWrapper = styled.div` + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(2, 46px); + gap: 10px; + + margin-bottom: 30px; +`; + +export const SaveButtonWrapper = styled.div` + display: none; + + visibility: hidden; + + @media (min-width: ${theme.breakpoint.sm}) { + display: flex; + + width: 100%; + height: 60px; + + visibility: visible; + } +`; + +export const ModalHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + width: 100%; + border-bottom: 1px solid #f6f6f6; + padding: 10px; + + font: var(--text-body); + font-weight: bold; +`; + +export const CloseButton = styled.button` + width: 25px; + height: 20px; + + background: white; + + font: var(--text-body); + + cursor: pointer; +`; + +export const ModalBody = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: start; + align-items: center; + gap: 10px; + + padding: 10px 0; + + font: var(--text-caption); +`; + +export const ResetButtonWrapper = styled.div` + display: flex; + + justify-content: center; + align-items: center; + + width: 50%; + height: 40px; +`; diff --git a/frontend/src/components/ReportModal/ReportModal.stories.tsx b/frontend/src/components/ReportModal/ReportModal.stories.tsx new file mode 100644 index 000000000..88f08690f --- /dev/null +++ b/frontend/src/components/ReportModal/ReportModal.stories.tsx @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import ReportModal from '.'; + +const meta: Meta = { + component: ReportModal, +}; + +export default meta; +type Story = StoryObj; + +export const Nickname: Story = { + render: () => ( + {}} handleReportClick={() => {}} /> + ), +}; + +export const Comment: Story = { + render: () => ( + {}} handleReportClick={() => {}} /> + ), +}; + +export const Post: Story = { + render: () => ( + {}} handleReportClick={() => {}} /> + ), +}; diff --git a/frontend/src/components/ReportModal/constants.ts b/frontend/src/components/ReportModal/constants.ts new file mode 100644 index 000000000..445bb3f6a --- /dev/null +++ b/frontend/src/components/ReportModal/constants.ts @@ -0,0 +1,16 @@ +import { ReportInfo, ReportType } from '@type/report'; + +export const REPORT_MESSAGE = { + BEHAVIOR: '부적절한 언행/혐오/차별적 표현이 포함되어있습니다.', + SPAMMING: '도배성 내용이 포함되어있습니다.', + ADVERTISING: '광고성 내용이 포함되어있습니다.', + SENSUALITY: '음란성 내용이 포함되어 있습니다.', + SPECIFIC_PERSON: '특정인이 거론되어있습니다.', + PRIVACY: '개인정보가 포함되어있습니다.', +}; + +export const REPORT_TYPE: Record = { + POST: { name: '게시글 신고', reportMessageList: REPORT_MESSAGE }, + COMMENT: { name: '댓글 신고', reportMessageList: REPORT_MESSAGE }, + NICKNAME: { name: '닉네임 신고', reportMessageList: REPORT_MESSAGE }, +}; diff --git a/frontend/src/components/ReportModal/index.tsx b/frontend/src/components/ReportModal/index.tsx new file mode 100644 index 000000000..7b1a22862 --- /dev/null +++ b/frontend/src/components/ReportModal/index.tsx @@ -0,0 +1,50 @@ +import { ReportType } from '@type/report'; + +import { useSelect } from '@hooks/useSelect'; + +import Select from '@components/common/Select'; +import TwoButtonModal from '@components/common/TwoButtonModal'; + +import { REPORT_TYPE } from './constants'; + +interface UserReportModalProps { + reportType: ReportType; + handleCancelClick: () => void; + handleReportClick: (reason: string) => void; +} + +export default function ReportModal({ + reportType, + handleCancelClick, + handleReportClick, +}: UserReportModalProps) { + const { name, reportMessageList } = REPORT_TYPE[reportType]; + const defaultReportMessage = Object.keys(reportMessageList)[0]; + const { selectedOption, handleOptionChange } = useSelect(defaultReportMessage); + + const handlePrimaryButtonClick = () => { + handleReportClick(selectedOption); + handleCancelClick(); + }; + + return ( + + + {radioMode[mode]} + + ); + })} + + + {currentRadioMode === 'gender' && ( + <> + + + + )} + + {currentRadioMode === 'all' && } + {currentRadioMode === 'gender' && } + + ); +} diff --git a/frontend/src/components/VoteStatistics/style.ts b/frontend/src/components/VoteStatistics/style.ts new file mode 100644 index 000000000..39615cece --- /dev/null +++ b/frontend/src/components/VoteStatistics/style.ts @@ -0,0 +1,52 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +import { GENDER_COLOR } from './GraphStyle'; +import { Gender } from './type'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + + font: var(--text-small); + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-caption); + } +`; + +export const CategoryWrapper = styled.fieldset` + display: flex; + gap: 10px; +`; + +export const RadioLabel = styled.label` + display: flex; + gap: 5px; +`; + +export const GenderExplain = styled.span` + display: flex; + gap: 10px; + + height: 20px; + + & > * { + display: flex; + align-items: center; + gap: 3px; + + line-height: initial; + } +`; + +export const ColorIcon = styled.span<{ $gender: Gender }>` + width: 12px; + height: 12px; + border-radius: 50%; + + background-color: ${props => GENDER_COLOR[props.$gender]}; +`; diff --git a/frontend/src/components/VoteStatistics/type.ts b/frontend/src/components/VoteStatistics/type.ts new file mode 100644 index 000000000..de33933d6 --- /dev/null +++ b/frontend/src/components/VoteStatistics/type.ts @@ -0,0 +1,47 @@ +import { Size } from '@type/style'; + +export interface GraphProps { + ageGroup: VoteDetailResult[]; + size: Size; +} + +export const AGE_OPTION = [ + '10대 미만', + '10대', + '20대', + '30대', + '40대', + '50대', + '60대 이상', +] as const; + +export type AgeCategory = (typeof AGE_OPTION)[number]; + +export interface VoteDetailResult { + name: AgeCategory; + total: number; + female: number; + male: number; +} + +export interface VoteResult { + ageGroup: VoteDetailResult[]; + total: number; + female: number; + male: number; +} + +export interface VoteDetailResultResponse { + ageGroup: AgeCategory; + voteCount: number; + femaleCount: number; + maleCount: number; +} +export interface VoteResultResponse { + ageGroup: VoteDetailResultResponse[]; + totalVoteCount: number; + totalFemaleCount: number; + totalMaleCount: number; +} + +export type Gender = 'FEMALE' | 'MALE'; diff --git a/frontend/src/components/VoteStatistics/util.ts b/frontend/src/components/VoteStatistics/util.ts new file mode 100644 index 000000000..003fbb104 --- /dev/null +++ b/frontend/src/components/VoteStatistics/util.ts @@ -0,0 +1,22 @@ +import { VoteResult, VoteResultResponse, VoteDetailResultResponse, VoteDetailResult } from './type'; + +const transDetailVoteStatisticsFormat = ( + voteResult: VoteDetailResultResponse +): VoteDetailResult => { + const { ageGroup, femaleCount, maleCount, voteCount } = voteResult; + + return { name: ageGroup, female: femaleCount, male: maleCount, total: voteCount }; +}; + +export const transVoteStatisticsFormat = (voteResult: VoteResultResponse): VoteResult => { + const { ageGroup, totalFemaleCount, totalMaleCount, totalVoteCount } = voteResult; + + const newAgeGroup = ageGroup.map(ageResult => transDetailVoteStatisticsFormat(ageResult)); + + return { + ageGroup: newAgeGroup, + female: totalFemaleCount, + male: totalMaleCount, + total: totalVoteCount, + }; +}; diff --git a/frontend/src/components/comment/CommentList/CommentItem/CommentItem.stories.tsx b/frontend/src/components/comment/CommentList/CommentItem/CommentItem.stories.tsx new file mode 100644 index 000000000..f802d0fc8 --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentItem/CommentItem.stories.tsx @@ -0,0 +1,32 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { MOCK_TRANSFORMED_COMMENT_LIST } from '@mocks/mockData/comment'; + +import { COMMENT_USER } from '../constants'; + +import CommentItem from '.'; + +const meta: Meta = { + component: CommentItem, +}; + +export default meta; +type Story = StoryObj; + +export const GuestUser: Story = { + render: () => ( + + ), +}; + +export const WriterUser: Story = { + render: () => ( + + ), +}; + +export const NotWriterUser: Story = { + render: () => ( + + ), +}; diff --git a/frontend/src/components/comment/CommentList/CommentItem/CommentMenu/CommentMenu.stories.tsx b/frontend/src/components/comment/CommentList/CommentItem/CommentMenu/CommentMenu.stories.tsx new file mode 100644 index 000000000..338865652 --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentItem/CommentMenu/CommentMenu.stories.tsx @@ -0,0 +1,20 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { COMMENT_MENU } from '../../constants'; + +import CommentMenu from '.'; + +const meta: Meta = { + component: CommentMenu, +}; + +export default meta; +type Story = StoryObj; + +export const WriterUser: Story = { + render: () => {}} />, +}; + +export const NotWriterUser: Story = { + render: () => {}} />, +}; diff --git a/frontend/src/components/comment/CommentList/CommentItem/CommentMenu/index.tsx b/frontend/src/components/comment/CommentList/CommentItem/CommentMenu/index.tsx new file mode 100644 index 000000000..cde3ae431 --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentItem/CommentMenu/index.tsx @@ -0,0 +1,32 @@ +import { MouseEvent } from 'react'; + +import { type CommentAction, type CommentMenuItem } from '@components/comment/CommentList/types'; + +import * as S from './style'; + +interface CommentMenuProps { + menuList: CommentMenuItem[]; + handleMenuClick: (menu: CommentAction) => void; +} + +export default function CommentMenu({ menuList, handleMenuClick }: CommentMenuProps) { + return ( + + {menuList.map(({ content, color, action }) => ( + { + event.stopPropagation(); + handleMenuClick(action); + }} + > + {content} + + ))} + + ); +} diff --git a/frontend/src/components/comment/CommentList/CommentItem/CommentMenu/style.ts b/frontend/src/components/comment/CommentList/CommentItem/CommentMenu/style.ts new file mode 100644 index 000000000..26658404f --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentItem/CommentMenu/style.ts @@ -0,0 +1,45 @@ +import { styled } from 'styled-components'; + +import { type CommentMenuItem } from '../../types'; + +const COLOR_PALETTE: Record = { + red: 'var(--primary-color)', + black: '#727171', +}; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + width: max-content; + border: 1px solid rgba(0, 0, 0, 0.4); + border-radius: 6px; + + background-color: var(--white); + + font: var(--text-caption); +`; + +export const Menu = styled.button<{ $color: CommentMenuItem['color'] }>` + padding: 10px 15px; + border-bottom: 1px solid rgba(0, 0, 0, 0.4); + + color: ${({ $color }) => COLOR_PALETTE[$color]}; + background-color: white; + + cursor: pointer; + + &:hover { + background-color: var(--gray); + } + + &:first-child { + border-radius: 6px 6px 0 0; + } + + &:last-child { + border-radius: 0 0 6px 6px; + border-bottom: none; + } +`; diff --git a/frontend/src/components/comment/CommentList/CommentItem/index.tsx b/frontend/src/components/comment/CommentList/CommentItem/index.tsx new file mode 100644 index 000000000..fd9049ee0 --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentItem/index.tsx @@ -0,0 +1,172 @@ +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; + +import { Comment } from '@type/comment'; +import { ReportRequest } from '@type/report'; + +import { useDeleteComment } from '@hooks/query/comment/useDeleteComment'; +import { useToast } from '@hooks/useToast'; +import { useToggle } from '@hooks/useToggle'; + +import { reportContent } from '@api/report'; + +import CommentTextForm from '@components/comment/CommentList/CommentTextForm'; +import DeleteModal from '@components/common/DeleteModal'; +import Toast from '@components/common/Toast'; +import ReportModal from '@components/ReportModal'; + +import ellipsis from '@assets/ellipsis-horizontal.svg'; + +import { COMMENT_ACTION, COMMENT_MENU, COMMENT_USER, COMMENT_USER_MENU } from '../constants'; +import { type CommentAction, type CommentUser } from '../types'; + +import CommentMenu from './CommentMenu'; +import * as S from './style'; + +interface CommentItemProps { + comment: Comment; + userType: CommentUser; +} + +export default function CommentItem({ comment, userType }: CommentItemProps) { + const { isOpen, toggleComponent, closeComponent } = useToggle(); + const { isToastOpen, openToast, toastMessage } = useToast(); + const { id, member, content, createdAt, isEdit } = comment; + const [action, setAction] = useState(null); + + const params = useParams() as { postId: string }; + const postId = Number(params.postId); + + const { mutate, isError, error } = useDeleteComment(postId, id); + + const handleMenuClick = (menu: CommentAction) => { + closeComponent(); + setAction(menu); + }; + + const handleCommentReportClick = async (reason: string) => { + const reportData: ReportRequest = { type: 'COMMENT', id, reason }; + + await reportContent(reportData) + .then(res => { + openToast('댓글을 신고했습니다.'); + }) + .catch(e => { + if (e instanceof Error) { + const errorResposne = JSON.parse(e.message); + openToast(errorResposne.message); + return; + } + openToast('댓글 신고가 실패했습니다.'); + }); + }; + + const handleNicknameReportClick = async (reason: string) => { + const reportData: ReportRequest = { type: 'NICKNAME', id: member.id, reason }; + + await reportContent(reportData) + .then(res => { + openToast('작성자 닉네임을 신고했습니다.'); + }) + .catch(e => { + if (e instanceof Error) { + const errorResposne = JSON.parse(e.message); + openToast(errorResposne.message); + return; + } + openToast('작성자 닉네임 신고가 실패했습니다.'); + }); + }; + + const handleCancelClick = () => { + setAction(null); + }; + + const handleDeleteClick = () => { + mutate(); + }; + + useEffect(() => { + if (isError && error instanceof Error) { + const errorResponse = JSON.parse(error.message); + openToast(errorResponse.message); + return; + } + }, [isError, error]); + + const USER_TYPE = COMMENT_USER_MENU[userType]; + + const isAllowedMenu = userType !== COMMENT_USER.GUEST && action !== COMMENT_ACTION.EDIT; + + return ( + + + + {member.nickname} + + {createdAt} + {isEdit && (수정됨)} + + + {isAllowedMenu && ( + + + {isOpen && ( + + + + )} + + )} + + {action === COMMENT_ACTION.EDIT ? ( + + + + ) : ( + {content} + )} + {action === COMMENT_ACTION.DELETE && ( + + )} + {action === COMMENT_ACTION.USER_REPORT && ( + + )} + {action === COMMENT_ACTION.COMMENT_REPORT && ( + + )} + {isToastOpen && ( + + {toastMessage} + + )} + + ); +} diff --git a/frontend/src/components/comment/CommentList/CommentItem/style.ts b/frontend/src/components/comment/CommentList/CommentItem/style.ts new file mode 100644 index 000000000..38b63cb28 --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentItem/style.ts @@ -0,0 +1,87 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + + padding-bottom: 25px; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); + + font: var(--text-caption); + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-body); + } +`; + +export const Header = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + height: 24px; + margin-bottom: 7px; +`; + +export const Nickname = styled.span` + font-weight: 600; +`; + +export const UserContainer = styled.div` + display: flex; + align-items: end; +`; + +export const SubTitleContainer = styled.div` + display: flex; + align-items: center; + + margin-left: 15px; +`; + +export const SubTitle = styled.span` + font: var(--text-small); + font-weight: 400; + + color: var(--text-dark-gray); + + &:nth-child(2) { + margin-left: 6px; + } + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-caption); + } +`; + +export const MenuWrapper = styled.div` + position: absolute; + right: 0%; +`; + +export const Description = styled.p` + font-weight: 400; + line-height: 24px; + + white-space: pre-wrap; +`; + +export const MenuContainer = styled.div` + width: 24px; + position: relative; + + color: #888; + + cursor: pointer; +`; + +export const TextFormWrapper = styled.div` + margin-top: 12px; +`; + +export const Image = styled.img` + width: 100%; + height: 100%; +`; diff --git a/frontend/src/components/comment/CommentList/CommentList.stories.tsx b/frontend/src/components/comment/CommentList/CommentList.stories.tsx new file mode 100644 index 000000000..cc3c2e5fc --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentList.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import CommentList from '.'; + +const meta: Meta = { + component: CommentList, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/components/comment/CommentList/CommentLoginSection/CommentLoginSection.stories.tsx b/frontend/src/components/comment/CommentList/CommentLoginSection/CommentLoginSection.stories.tsx new file mode 100644 index 000000000..56938f6b9 --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentLoginSection/CommentLoginSection.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import CommentLoginSection from '.'; + +const meta: Meta = { + component: CommentLoginSection, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/components/comment/CommentList/CommentLoginSection/index.tsx b/frontend/src/components/comment/CommentList/CommentLoginSection/index.tsx new file mode 100644 index 000000000..92916fd84 --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentLoginSection/index.tsx @@ -0,0 +1,21 @@ +import { PATH } from '@constants/path'; + +import kakaoLogin from '@assets/kakao_login_large.svg'; + +import * as S from './style'; + +interface CommentLoginSectionProps { + name: string; +} + +export default function CommentLoginSectionSection({ name }: CommentLoginSectionProps) { + return ( + + 대화에 참여하려면 회원가입 + 로그인하여 {name}님의 이야기에 대해 피드백을 제공해 보세요 + + + + + ); +} diff --git a/frontend/src/components/comment/CommentList/CommentLoginSection/style.ts b/frontend/src/components/comment/CommentList/CommentLoginSection/style.ts new file mode 100644 index 000000000..f09a29b45 --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentLoginSection/style.ts @@ -0,0 +1,48 @@ +import { Link } from 'react-router-dom'; + +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.section` + display: flex; + flex-direction: column; + + @media (min-width: ${theme.breakpoint.sm}) { + padding: 0 20px; + + font-size: 2.4rem; + } +`; + +export const Title = styled.span` + font-size: 2.2rem; + font-weight: 600; + + @media (min-width: ${theme.breakpoint.sm}) { + font-size: 2.4rem; + } +`; + +export const SubTitle = styled.p` + margin-top: 10px; + + font: var(--text-caption); + font-weight: 400; + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-body); + } +`; + +export const LoginLink = styled(Link)` + display: flex; + justify-content: center; + + margin-top: 40px; +`; + +export const Image = styled.img` + width: 300px; + height: 45px; +`; diff --git a/frontend/src/components/comment/CommentList/CommentTextForm/CommentTextForm.stories.tsx b/frontend/src/components/comment/CommentList/CommentTextForm/CommentTextForm.stories.tsx new file mode 100644 index 000000000..dac625494 --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentTextForm/CommentTextForm.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { MOCK_TRANSFORMED_COMMENT_LIST } from '@mocks/mockData/comment'; + +import CommentTextForm from '.'; + +const meta: Meta = { + component: CommentTextForm, +}; + +export default meta; +type Story = StoryObj; + +const initialComment = { + id: -1, + member: { + id: -1, + nickname: '', + }, + content: '', + createdAt: '', + isEdit: false, +}; + +export const InitForm: Story = { + render: () => , +}; + +export const EditForm: Story = { + render: () => ( + {}} + /> + ), +}; diff --git a/frontend/src/components/comment/CommentList/CommentTextForm/index.tsx b/frontend/src/components/comment/CommentList/CommentTextForm/index.tsx new file mode 100644 index 000000000..dc0a66553 --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentTextForm/index.tsx @@ -0,0 +1,111 @@ +import { ChangeEvent, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; + +import { Comment } from '@type/comment'; + +import { useCreateComment } from '@hooks/query/comment/useCreateComment'; +import { useEditComment } from '@hooks/query/comment/useEditComment'; +import { useText } from '@hooks/useText'; +import { useToast } from '@hooks/useToast'; + +import SquareButton from '@components/common/SquareButton'; +import Toast from '@components/common/Toast'; + +import { COMMENT } from '@constants/comment'; + +import * as S from './style'; +interface CommentTextFormProps { + commentId: number; + initialComment: Comment; + handleCancelClick?: () => void; +} + +export default function CommentTextForm({ + commentId, + initialComment, + handleCancelClick, +}: CommentTextFormProps) { + const { text: content, handleTextChange, resetText } = useText(initialComment.content); + const { isToastOpen, openToast, toastMessage } = useToast(); + + const params = useParams() as { postId: string }; + const postId = Number(params.postId); + + const isEdit = initialComment.id !== -1; + + const { + mutate: createComment, + isSuccess: isCreateSuccess, + isError: isCreateError, + error: createError, + } = useCreateComment(postId); + const { + mutate: editComment, + isSuccess: isEditSuccess, + isError: isEditError, + error: editError, + } = useEditComment(postId, commentId); + + const updateComment = isEdit + ? () => { + editComment({ ...initialComment, content }); + } + : () => { + createComment({ content }); + }; + + useEffect(() => { + isCreateSuccess && resetText(); + }, [isCreateSuccess]); + + useEffect(() => { + isEditSuccess && handleCancelClick && handleCancelClick(); + }, [isEditSuccess]); + + useEffect(() => { + isCreateError && createError instanceof Error && openToast(createError.message); + }, [isCreateError, createError]); + + useEffect(() => { + isEditError && editError instanceof Error && openToast(editError.message); + }, [isEditError, editError]); + + return ( + + ) => handleTextChange(e, COMMENT)} + /> + + {isEdit && ( + + + 취소 + + + )} + + updateComment()} + theme="blank" + type="button" + > + 저장 + + + + {isToastOpen && ( + + {toastMessage} + + )} + + ); +} diff --git a/frontend/src/components/comment/CommentList/CommentTextForm/style.ts b/frontend/src/components/comment/CommentList/CommentTextForm/style.ts new file mode 100644 index 000000000..d19035595 --- /dev/null +++ b/frontend/src/components/comment/CommentList/CommentTextForm/style.ts @@ -0,0 +1,50 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + display: flex; + flex-direction: column; +`; + +export const TextArea = styled.textarea` + height: 120px; + padding: 12px; + border: 1px solid var(--primary-color); + border-radius: 6px; + + font: var(--text-caption); + font-weight: 400; + line-height: 2.4rem; + + resize: none; + + @media (min-width: ${theme.breakpoint.sm}) { + height: 160px; + + font: var(--text-body); + } +`; + +export const ButtonContainer = styled.div` + display: flex; + justify-content: end; + gap: 20px; + + padding-top: 20px; +`; + +export const ButtonWrapper = styled.div` + width: 60px; + height: 40px; + + font: var(--text-caption); + font-weight: 600; + + @media (min-width: ${theme.breakpoint.sm}) { + width: 74px; + height: 46px; + + font: var(--text-body); + } +`; diff --git a/frontend/src/components/comment/CommentList/constants.ts b/frontend/src/components/comment/CommentList/constants.ts new file mode 100644 index 000000000..d3a135691 --- /dev/null +++ b/frontend/src/components/comment/CommentList/constants.ts @@ -0,0 +1,31 @@ +import { CommentMenu, CommentMenuItem, CommentUser } from './types'; + +export const COMMENT_USER = { + GUEST: 'GUEST', + NOT_WRITER: 'NOT_WRITER', + WRITER: 'WRITER', +} as const; + +export const COMMENT_ACTION = { + DELETE: 'delete', + USER_REPORT: 'userReport', + COMMENT_REPORT: 'commentReport', + EDIT: 'edit', +} as const; + +export const COMMENT_USER_MENU: Record = { + [COMMENT_USER.GUEST]: COMMENT_USER.NOT_WRITER, + [COMMENT_USER.NOT_WRITER]: COMMENT_USER.NOT_WRITER, + [COMMENT_USER.WRITER]: COMMENT_USER.WRITER, +} as const; + +export const COMMENT_MENU: Record = { + [COMMENT_USER.NOT_WRITER]: [ + { color: 'black', content: '닉네임 신고', action: COMMENT_ACTION.USER_REPORT }, + { color: 'black', content: '댓글 신고', action: COMMENT_ACTION.COMMENT_REPORT }, + ], + [COMMENT_USER.WRITER]: [ + { content: '수정', color: 'black', action: COMMENT_ACTION.EDIT }, + { content: '삭제', color: 'red', action: COMMENT_ACTION.DELETE }, + ], +}; diff --git a/frontend/src/components/comment/CommentList/index.tsx b/frontend/src/components/comment/CommentList/index.tsx new file mode 100644 index 000000000..5ac8a8402 --- /dev/null +++ b/frontend/src/components/comment/CommentList/index.tsx @@ -0,0 +1,117 @@ +import { useContext, useRef, Fragment } from 'react'; + +import { AuthContext } from '@hooks/context/auth'; +import { useCommentList } from '@hooks/query/comment/useCommentList'; +import { useMoreComment } from '@hooks/useMoreComment'; + +import SquareButton from '@components/common/SquareButton'; + +import { smoothScrollToTop } from '@utils/scrollToTop'; + +import CommentItem from './CommentItem'; +import CommentLoginSection from './CommentLoginSection'; +import CommentTextForm from './CommentTextForm'; +import { COMMENT_USER } from './constants'; +import * as S from './style'; + +interface CommentListProps { + postId: number; + postWriterName: string; +} + +const initialComment = { + id: -1, + member: { + id: -1, + nickname: '', + }, + content: '', + createdAt: '', + isEdit: false, +}; + +export default function CommentList({ postId, postWriterName }: CommentListProps) { + const inputRef = useRef(null); + const { data: commentList } = useCommentList(postId); + const { loggedInfo } = useContext(AuthContext); + const { isLoggedIn, id: memberId } = loggedInfo; + + const isGuest = !isLoggedIn; + + const { slicedCommentList, handleMoreComment, hasMoreComment } = useMoreComment( + commentList ?? [] + ); + + const getUserType = (writerId: number) => { + if (isGuest) { + return COMMENT_USER.GUEST; + } + + if (writerId === memberId) { + return COMMENT_USER.WRITER; + } + + return COMMENT_USER.NOT_WRITER; + }; + + return ( + + + {isGuest ? ( + + ) : ( + + )} + + + {slicedCommentList.map((comment, index) => { + if (index % 10 === 9) { + return ( + + + + + ); + } + return ( + + ); + })} + + {hasMoreComment && ( + + { + if (!inputRef.current) return; + + handleMoreComment(); + inputRef.current.focus(); + inputRef.current.ariaLabel = '더보기 버튼을 눌러 댓글이 추가되었습니다'; + }} + theme="fill" + aria-label="댓글 더보기" + > + 더보기 + + + )} + + + + TOP + + + + + ); +} diff --git a/frontend/src/components/comment/CommentList/style.ts b/frontend/src/components/comment/CommentList/style.ts new file mode 100644 index 000000000..ce0c786c8 --- /dev/null +++ b/frontend/src/components/comment/CommentList/style.ts @@ -0,0 +1,61 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div``; + +export const TextOrLoginWrapper = styled.div` + margin-top: 30px; + padding: 40px 0; + border-top: 1px solid rgba(0, 0, 0, 0.2); +`; + +export const ListContainer = styled.div` + display: flex; + flex-direction: column; + gap: 25px; + + padding-top: 25px; + border-top: 1px solid rgba(0, 0, 0, 0.2); + + position: relative; +`; + +export const MoreButtonWrapper = styled.div` + width: 80px; + height: 46px; + margin: 25px auto 0 auto; + + @media (min-width: ${theme.breakpoint.sm}) { + width: 190px; + } +`; + +export const ButtonContainer = styled.div` + display: flex; + justify-content: end; + + padding: 20px 0; + + @media (min-width: ${theme.breakpoint.sm}) { + padding: 50px 0; + } +`; + +export const TopButtonWrapper = styled.div` + width: 55px; + height: 40px; + + @media (min-width: ${theme.breakpoint.sm}) { + width: 64px; + height: 46px; + } +`; + +export const HiddenInput = styled.input` + position: absolute; + bottom: 0; + + color: white; + z-index: -1; +`; diff --git a/frontend/src/components/comment/CommentList/types.ts b/frontend/src/components/comment/CommentList/types.ts new file mode 100644 index 000000000..b082af694 --- /dev/null +++ b/frontend/src/components/comment/CommentList/types.ts @@ -0,0 +1,13 @@ +import { COMMENT_ACTION, COMMENT_USER } from './constants'; + +export type CommentAction = (typeof COMMENT_ACTION)[keyof typeof COMMENT_ACTION]; + +export type CommentUser = (typeof COMMENT_USER)[keyof typeof COMMENT_USER]; + +export type CommentMenu = Exclude; + +export interface CommentMenuItem { + content: string; + color: 'black' | 'red'; + action: CommentAction; +} diff --git a/frontend/src/components/common/Accordion/Accordion.stories.tsx b/frontend/src/components/common/Accordion/Accordion.stories.tsx new file mode 100644 index 000000000..831985f76 --- /dev/null +++ b/frontend/src/components/common/Accordion/Accordion.stories.tsx @@ -0,0 +1,112 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { styled } from 'styled-components'; + +import { useToggle } from '@hooks/useToggle'; + +import Modal from '../Modal'; +import SquareButton from '../SquareButton'; + +import Accordion from '.'; + +const meta: Meta = { + component: Accordion, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => ( + + Hello This is Content! + + ), +}; + +export const NicknameChange: Story = { + render: () => ( + + + + + 변경 + + + + ), +}; + +export const DeleteUserAccount = () => { + const { isOpen, openComponent, closeComponent } = useToggle(); + return ( + + + + 회원 탈퇴 + + + {isOpen && ( + + + 정말 탈퇴하시겠어요? + + 탈퇴 버튼 클릭 시,

계정은 삭제되며 복구되지 않아요. +
+ + + 탈퇴 + + + 취소 + + +
+
+ )} +
+ ); +}; + +const ButtonWrapper = styled.div` + width: 90px; + height: 50px; +`; + +const Input = styled.input` + width: 80%; + border: 1px solid #f2f2f2; + padding: 20px; +`; + +const ModalBody = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 30px; + + width: 90%; + margin: 40px 20px 0px 16px; + + font: var(--text-caption); +`; + +const ModalTitle = styled.div` + font: var(--text-title); +`; + +const ModalDescription = styled.div` + font: var(--text-body); +`; + +const ButtonListWrapper = styled.div` + display: flex; + justify-content: space-around; + gap: 20px; + + width: 90%; + height: 50px; + + margin-top: 20px; +`; diff --git a/frontend/src/components/common/Accordion/index.tsx b/frontend/src/components/common/Accordion/index.tsx new file mode 100644 index 000000000..bf17e66ef --- /dev/null +++ b/frontend/src/components/common/Accordion/index.tsx @@ -0,0 +1,36 @@ +import { PropsWithChildren, useState } from 'react'; + +import chevronDown from '@assets/chevron-down.svg'; +import chevronUp from '@assets/chevron-up.svg'; + +import * as S from './style'; + +interface AccordionProps extends PropsWithChildren { + title: string; + ariaLabel?: string; +} + +export default function Accordion({ title, ariaLabel = '메뉴', children }: AccordionProps) { + const [isOpen, setIsOpen] = useState(false); + + const toggleAccordion = () => { + setIsOpen(!isOpen); + }; + + return ( + + + {title} + + + + {children} + + + ); +} diff --git a/frontend/src/components/common/Accordion/style.ts b/frontend/src/components/common/Accordion/style.ts new file mode 100644 index 000000000..622f043e0 --- /dev/null +++ b/frontend/src/components/common/Accordion/style.ts @@ -0,0 +1,62 @@ +import styled, { keyframes } from 'styled-components'; + +export const Wrapper = styled.div` + width: 100%; + + font: var(--text-caption); +`; + +export const Title = styled.div` + display: flex; + justify-content: space-between; + + border: 1px solid #f2f2f2; + border-radius: 7px 7px 0 0; + padding: 16px; + + background-color: #ffffff; + + &:hover { + background-color: #f2f2f2; + } + cursor: pointer; +`; + +export const Content = styled.div<{ $isOpen: boolean }>` + display: ${props => (props.$isOpen ? 'block' : 'none')}; + justify-content: space-between; + + border: 1px solid #f2f2f2; + border-radius: 0 0 7px 7px; + padding: 16px; + + opacity: ${props => (props.$isOpen ? 1 : 0)}; + animation: ${props => (props.$isOpen ? fadeIn : fadeOut)} 0.2s ease-in-out; +`; + +export const Image = styled.img<{ $isOpen: boolean }>` + width: 20px; + height: 20px; +`; + +const fadeIn = keyframes` + from { + opacity: 0; + height: 0; + } + to { + opacity: 1; + height: auto; + } +`; + +const fadeOut = keyframes` + from { + opacity: 1; + height: auto; + } + to { + opacity: 0; + height: 0; + } +`; diff --git a/frontend/src/components/common/AddButton/AddButton.stories.tsx b/frontend/src/components/common/AddButton/AddButton.stories.tsx new file mode 100644 index 000000000..580c6a070 --- /dev/null +++ b/frontend/src/components/common/AddButton/AddButton.stories.tsx @@ -0,0 +1,22 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import AddButton from '.'; + +const meta: Meta = { + component: AddButton, +}; + +export default meta; +type Story = StoryObj; + +export const SizeS: Story = { + render: () => , +}; + +export const SizeM: Story = { + render: () => , +}; + +export const SizeL: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/AddButton/index.tsx b/frontend/src/components/common/AddButton/index.tsx new file mode 100644 index 000000000..e1639ec9d --- /dev/null +++ b/frontend/src/components/common/AddButton/index.tsx @@ -0,0 +1,17 @@ +import { ButtonHTMLAttributes } from 'react'; + +import { Size } from '@type/style'; + +import * as S from './style'; + +interface AddButtonProps extends ButtonHTMLAttributes { + size: Size; +} + +export default function AddButton({ size, ...rest }: AddButtonProps) { + return ( + + + + + ); +} diff --git a/frontend/src/components/common/AddButton/style.ts b/frontend/src/components/common/AddButton/style.ts new file mode 100644 index 000000000..9ba6f36a8 --- /dev/null +++ b/frontend/src/components/common/AddButton/style.ts @@ -0,0 +1,28 @@ +import { styled } from 'styled-components'; + +import { Size } from '@type/style'; + +interface ButtonProps { + size: Size; +} + +const SIZE = { + sm: { button: '25px', font: '13px' }, + md: { button: '40px', font: '30px' }, + lg: { button: '60px', font: '50px' }, +}; + +export const Button = styled.button` + display: block; + + width: ${props => SIZE[props.size].button}; + height: ${props => SIZE[props.size].button}; + border-radius: 50%; + + background-color: var(--primary-color); + color: var(--white); + + font-size: ${props => SIZE[props.size].font}; + + cursor: pointer; +`; diff --git a/frontend/src/components/common/AppInstallPrompt/BookMarkPrompt/BookMarkPrompt.stories.tsx b/frontend/src/components/common/AppInstallPrompt/BookMarkPrompt/BookMarkPrompt.stories.tsx new file mode 100644 index 000000000..a90c524e3 --- /dev/null +++ b/frontend/src/components/common/AppInstallPrompt/BookMarkPrompt/BookMarkPrompt.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import BookMarkPrompt from '.'; + +const meta: Meta = { + component: BookMarkPrompt, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => {}} />, +}; diff --git a/frontend/src/components/common/AppInstallPrompt/BookMarkPrompt/index.tsx b/frontend/src/components/common/AppInstallPrompt/BookMarkPrompt/index.tsx new file mode 100644 index 000000000..9aa9348e5 --- /dev/null +++ b/frontend/src/components/common/AppInstallPrompt/BookMarkPrompt/index.tsx @@ -0,0 +1,38 @@ +import arrowUp from '@assets/arrow-up-on-square.svg'; +import logo from '@assets/logo.svg'; +import cancel from '@assets/x_mark_black.svg'; + +import * as S from './style'; + +interface BookMarkPromptProps { + handleCancelClick: () => void; +} + +export default function BookMarkPrompt({ handleCancelClick }: BookMarkPromptProps) { + return ( + + + + + + + VoTogether + + + + + + VoTogether는 앱처럼 원활히 사용할 수 있습니다. 설치하시겠습니까? + + + + + + 브라우저 메뉴바에서 모양 버튼을 눌러 + "홈 화면에 추가하기"를 통해 설치를 할 수 있습니다. + + + + + ); +} diff --git a/frontend/src/components/common/AppInstallPrompt/BookMarkPrompt/style.ts b/frontend/src/components/common/AppInstallPrompt/BookMarkPrompt/style.ts new file mode 100644 index 000000000..327c9db5f --- /dev/null +++ b/frontend/src/components/common/AppInstallPrompt/BookMarkPrompt/style.ts @@ -0,0 +1,91 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + + width: 100vw; + height: 240px; + border-top: 1px solid rgba(0, 0, 0, 0.3); + + position: fixed; + bottom: 0; + left: 0; + + background-color: white; + + z-index: ${theme.zIndex.modal}; +`; + +export const Content = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + + width: max-content; + padding: 30px 20px; +`; + +export const Header = styled.div` + display: flex; + justify-content: space-between; + + margin-bottom: 50px; +`; + +export const LogoImage = styled.img` + border-radius: 16px; + + width: 80px; + height: 80px; +`; + +export const HeaderContent = styled.div` + display: flex; + flex-direction: column; + + margin-left: 24px; +`; + +export const HeaderTop = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + margin-bottom: 10px; +`; + +export const Title = styled.span` + font-size: 2.4rem; + font-weight: 700; +`; + +export const Description = styled.p` + font-size: 1.6rem; + font-weight: 700; +`; + +export const CancelButton = styled.button` + padding: 10px; + + position: relative; + bottom: 10px; + left: 10px; + + cursor: pointer; +`; + +export const IconImage = styled.img` + width: 24px; + height: 24px; +`; + +export const DescriptionWrapper = styled.div` + display: flex; + align-items: center; + align-self: end; + gap: 8px; +`; diff --git a/frontend/src/components/common/AppInstallPrompt/InstallPrompt/InstallPrompt.stories.tsx b/frontend/src/components/common/AppInstallPrompt/InstallPrompt/InstallPrompt.stories.tsx new file mode 100644 index 000000000..5e17ead42 --- /dev/null +++ b/frontend/src/components/common/AppInstallPrompt/InstallPrompt/InstallPrompt.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import InstallPrompt from '.'; + +const meta: Meta = { + component: InstallPrompt, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => {}} handleCancelClick={() => {}} />, +}; diff --git a/frontend/src/components/common/AppInstallPrompt/InstallPrompt/index.tsx b/frontend/src/components/common/AppInstallPrompt/InstallPrompt/index.tsx new file mode 100644 index 000000000..e8e220369 --- /dev/null +++ b/frontend/src/components/common/AppInstallPrompt/InstallPrompt/index.tsx @@ -0,0 +1,39 @@ +import logo from '@assets/logo.svg'; +import cancel from '@assets/x_mark_black.svg'; + +import * as S from './style'; + +interface InstallPromptProps { + handleInstallClick: () => void; + handleCancelClick: () => void; +} + +export default function InstallPrompt({ + handleInstallClick, + handleCancelClick, +}: InstallPromptProps) { + return ( + + + + + + + VoTogether + + + + + + VoTogether는 앱처럼 원활히 사용할 수 있습니다. 설치하시겠습니까? + + + + + 웹으로 볼게요 + 홈 화면에 추가 + + + + ); +} diff --git a/frontend/src/components/common/AppInstallPrompt/InstallPrompt/style.ts b/frontend/src/components/common/AppInstallPrompt/InstallPrompt/style.ts new file mode 100644 index 000000000..0a6be7fe5 --- /dev/null +++ b/frontend/src/components/common/AppInstallPrompt/InstallPrompt/style.ts @@ -0,0 +1,114 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + + width: 100vw; + height: 240px; + border-top: 1px solid rgba(0, 0, 0, 0.3); + + position: fixed; + bottom: 0; + left: 0; + + background-color: white; + + z-index: ${theme.zIndex.modal}; +`; + +export const Content = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + + width: max-content; + padding: 30px 20px; +`; + +export const Header = styled.div` + display: flex; + justify-content: space-between; + + margin-bottom: 50px; +`; + +export const LogoImage = styled.img` + border-radius: 16px; + + width: 80px; + height: 80px; +`; + +export const HeaderContent = styled.div` + display: flex; + flex-direction: column; + + margin-left: 24px; +`; + +export const HeaderTop = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + margin-bottom: 10px; +`; + +export const Title = styled.span` + font-size: 2.4rem; + font-weight: 700; +`; + +export const Description = styled.p` + font-size: 1.6rem; + font-weight: 700; +`; + +export const CancelButton = styled.button` + padding: 10px; + + position: relative; + bottom: 10px; + left: 10px; + + cursor: pointer; +`; + +export const IconImage = styled.img` + width: 24px; + height: 24px; +`; + +export const ButtonContainer = styled.div` + display: flex; + justify-content: space-between; + gap: 10px; +`; + +export const UserButton = styled.button` + align-self: end; + + border-radius: 6px; + + width: 100%; + height: 40px; + + font-size: 1.6rem; + font-weight: 500; + + color: white; + + cursor: pointer; + + &:nth-child(1) { + background-color: #888888; + } + + &:nth-child(2) { + background-color: #5383ed; + } +`; diff --git a/frontend/src/components/common/AppInstallPrompt/index.tsx b/frontend/src/components/common/AppInstallPrompt/index.tsx new file mode 100644 index 000000000..2a8044071 --- /dev/null +++ b/frontend/src/components/common/AppInstallPrompt/index.tsx @@ -0,0 +1,71 @@ +import { Fragment, useEffect, useState } from 'react'; + +import { getCookie, setCookie } from '@utils/cookie'; + +import { BeforeInstallPromptEvent } from '../../../../window'; + +import BookMarkPrompt from './BookMarkPrompt'; +import InstallPrompt from './InstallPrompt'; + +const isBookMarkPromptActive = () => { + const isActive = JSON.parse(getCookie().isAppInstallVisible || 'true'); + + if (isActive) { + return true; + } + + return null; +}; + +export default function AppInstallPrompt() { + const isDeviceIOS = /iPad|iPhone|iPod/.test(window.navigator.userAgent); + const [bookMarkPrompt, setBookMarkPrompt] = useState( + isDeviceIOS ? isBookMarkPromptActive() : null + ); + const [deferredPrompt, setDeferredPrompt] = useState(null); + + const handleInstallClick = () => { + if (deferredPrompt) { + deferredPrompt.prompt(); + + deferredPrompt.userChoice.then(() => { + setDeferredPrompt(null); + }); + } + }; + + const handleCancelClick = () => { + setCookie({ key: 'isAppInstallVisible', value: 'false', maxAge: 7 * 24 * 60 * 60 }); + setBookMarkPrompt(null); + setDeferredPrompt(null); + }; + + const handleBeforeInstallPrompt = (event: BeforeInstallPromptEvent) => { + event.preventDefault(); + const isVisible = JSON.parse(getCookie().isAppInstallVisible || 'true'); + + if (!isVisible) return; + + setDeferredPrompt(event); + }; + + useEffect(() => { + window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt); + + return () => { + window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt); + }; + }, []); + + return ( + + {deferredPrompt && ( + + )} + {bookMarkPrompt && } + + ); +} diff --git a/frontend/src/components/common/Dashboard/CategorySection/index.tsx b/frontend/src/components/common/Dashboard/CategorySection/index.tsx new file mode 100644 index 000000000..0c0af29db --- /dev/null +++ b/frontend/src/components/common/Dashboard/CategorySection/index.tsx @@ -0,0 +1,47 @@ +import { useContext } from 'react'; + +import { AuthContext } from '@hooks/context/auth'; +import { useCategoryList } from '@hooks/query/category/useCategoryList'; +import { usePostRequestInfo } from '@hooks/usePostRequestInfo'; + +import { getSelectedState } from '@utils/post/getSelectedState'; + +import CategoryToggle from '../CategoryToggle'; + +import * as S from './style'; + +export default function CategorySection() { + const { loggedInfo } = useContext(AuthContext); + const { userInfo, isLoggedIn } = loggedInfo; + + const { data: categoryList } = useCategoryList(isLoggedIn); + + const categoryListFallback = categoryList ?? []; + + const { postOptionalOption, postType } = usePostRequestInfo(); + const { categoryId } = postOptionalOption; + + const selectedState = getSelectedState({ + categoryId, + categoryList: categoryListFallback, + postType, + }); + + const favoriteCategory = categoryListFallback.filter(category => category.isFavorite === true); + const allCategory = categoryListFallback.filter(category => category.isFavorite === false); + + return ( + <> + + + {selectedState} + + + + {userInfo && } + + + + + ); +} diff --git a/frontend/src/components/common/Dashboard/CategorySection/style.ts b/frontend/src/components/common/Dashboard/CategorySection/style.ts new file mode 100644 index 000000000..9f9ddf988 --- /dev/null +++ b/frontend/src/components/common/Dashboard/CategorySection/style.ts @@ -0,0 +1,58 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const ContentContainer = styled.div` + display: flex; + flex-direction: column; + align-items: start; + + width: 100%; + height: calc(100vh - 320px); + + margin-bottom: 85px; + + overflow-y: scroll; + + -ms-overflow-style: none; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + + @media (min-width: ${theme.breakpoint.md}) { + height: calc(100vh - 400px); + } +`; + +export const SelectedStateWrapper = styled.div` + display: flex; + align-items: center; + gap: 12px; + justify-self: start; + + width: 100%; + border-bottom: 2px solid var(--gray); + padding-bottom: 20px; +`; + +export const Circle = styled.div` + width: 12px; + height: 12px; + border-radius: 50%; + + background-color: var(--red); +`; + +export const SelectedStateText = styled.span` + font: var(--text-body); +`; + +export const CategoryToggleContainer = styled.div` + display: flex; + flex-direction: column; + gap: 20px; + + padding-top: 20px; +`; diff --git a/frontend/src/components/common/Dashboard/CategoryToggle/CategoryToggle.stories.tsx b/frontend/src/components/common/Dashboard/CategoryToggle/CategoryToggle.stories.tsx new file mode 100644 index 000000000..afc35d9c6 --- /dev/null +++ b/frontend/src/components/common/Dashboard/CategoryToggle/CategoryToggle.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Category } from '@type/category'; + +import CategoryToggle from '.'; + +const meta: Meta = { + component: CategoryToggle, +}; + +export default meta; +type Story = StoryObj; + +const MOCK_CATEGORIES: Category[] = [ + { id: 12312, name: '음식', isFavorite: false }, + { id: 12, name: '연애', isFavorite: false }, + { id: 13, name: '패션', isFavorite: false }, + { id: 14, name: '금융', isFavorite: false }, +]; + +export const Default: Story = { + render: () => , +}; + +export const Closed: Story = { + render: () => ( + + ), +}; diff --git a/frontend/src/components/common/Dashboard/CategoryToggle/index.tsx b/frontend/src/components/common/Dashboard/CategoryToggle/index.tsx new file mode 100644 index 000000000..b4037376b --- /dev/null +++ b/frontend/src/components/common/Dashboard/CategoryToggle/index.tsx @@ -0,0 +1,81 @@ +import React, { useContext, useEffect, useState } from 'react'; + +import { Category } from '@type/category'; + +import { AuthContext } from '@hooks/context/auth'; +import { useCategoryFavoriteToggle } from '@hooks/query/category/useCategoryFavoriteToggle'; +import { useToast } from '@hooks/useToast'; + +import Toast from '@components/common/Toast'; + +import chevronDown from '@assets/chevron-down.svg'; +import chevronUp from '@assets/chevron-up.svg'; +import starFilled from '@assets/star-filled.svg'; +import startLined from '@assets/star-lined.svg'; + +import * as S from './style'; + +interface CategoryToggleProps { + title: string; + categoryList: Category[]; + isInitialOpen?: boolean; +} + +export default function CategoryToggle({ + title, + categoryList, + isInitialOpen = true, +}: CategoryToggleProps) { + const [isToggleOpen, setIsToggleOpen] = useState(isInitialOpen); + const { isToastOpen, openToast, toastMessage } = useToast(); + const { mutate, isError, error } = useCategoryFavoriteToggle(); + + const { loggedInfo } = useContext(AuthContext); + + const handleToggleClick = () => { + setIsToggleOpen(prevIsToggleOpen => !prevIsToggleOpen); + }; + + useEffect(() => { + if (isError && error instanceof Error) { + if (!loggedInfo.isLoggedIn) { + openToast('즐겨찾기는 로그인 후 이용할 수 있습니다.'); + return; + } + const errorResponse = JSON.parse(error.message); + openToast(errorResponse.message); + return; + } + }, [isError, error]); + + return ( + + + + {title} + + {isToggleOpen && ( + + {categoryList.length === 0 && 현재 카테고리가 없습니다} + {categoryList.map(({ id, name, isFavorite }) => ( + + mutate({ id, isFavorite })}> + + + {name} + + ))} + + )} + {isToastOpen && ( + + {toastMessage} + + )} + + ); +} diff --git a/frontend/src/components/common/Dashboard/CategoryToggle/style.ts b/frontend/src/components/common/Dashboard/CategoryToggle/style.ts new file mode 100644 index 000000000..47c785dc5 --- /dev/null +++ b/frontend/src/components/common/Dashboard/CategoryToggle/style.ts @@ -0,0 +1,61 @@ +import { Link } from 'react-router-dom'; + +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + font: var(--text-caption); + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-body); + } +`; + +export const TitleContainer = styled.button` + display: flex; + align-items: center; + + font: inherit; + + cursor: pointer; +`; + +export const TriangleImage = styled.img` + width: 16px; + height: 16px; + margin-right: 8px; +`; + +export const CategoryList = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + + padding: 16px 12px; +`; + +export const CategoryItem = styled.div` + display: flex; + align-items: center; +`; + +export const Circle = styled.button` + width: 17px; + height: 17px; + margin-right: 12px; + + cursor: pointer; +`; + +export const Caption = styled.span` + font: var(--text-caption); + + color: var(--dark-gray); +`; + +export const CategoryNameLink = styled(Link)` + text-decoration: none; + + color: inherit; +`; diff --git a/frontend/src/components/common/Dashboard/Dashboard.stories.tsx b/frontend/src/components/common/Dashboard/Dashboard.stories.tsx new file mode 100644 index 000000000..50484010b --- /dev/null +++ b/frontend/src/components/common/Dashboard/Dashboard.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import Dashboard from '.'; + +const meta: Meta = { + component: Dashboard, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/Dashboard/GuestProfile/GuestProfile.stories.tsx b/frontend/src/components/common/Dashboard/GuestProfile/GuestProfile.stories.tsx new file mode 100644 index 000000000..9920f2a20 --- /dev/null +++ b/frontend/src/components/common/Dashboard/GuestProfile/GuestProfile.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import GuestProfile from '.'; + +const meta: Meta = { + component: GuestProfile, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/Dashboard/GuestProfile/index.tsx b/frontend/src/components/common/Dashboard/GuestProfile/index.tsx new file mode 100644 index 000000000..9e1e15663 --- /dev/null +++ b/frontend/src/components/common/Dashboard/GuestProfile/index.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +import { BASE_PATH } from '@constants/path'; + +import kakaoLogin from '@assets/kakao_login.svg'; + +import * as S from './style'; + +export default function GuestProfile() { + return ( + + + + + 로그인 후 이용할 수 있습니다 + + ); +} diff --git a/frontend/src/components/common/Dashboard/GuestProfile/style.ts b/frontend/src/components/common/Dashboard/GuestProfile/style.ts new file mode 100644 index 000000000..c9eeb1814 --- /dev/null +++ b/frontend/src/components/common/Dashboard/GuestProfile/style.ts @@ -0,0 +1,20 @@ +import { styled } from 'styled-components'; + +import { ProfileContainer } from '../profileStyle'; + +export const Container = styled(ProfileContainer)` + align-items: center; +`; + +export const Image = styled.img` + width: 183px; + height: 40px; +`; + +export const TextCard = styled.span` + margin-top: 20px; + + font: var(--text-caption); + + color: var(--dark-gray); +`; diff --git a/frontend/src/components/common/Dashboard/UserProfile/UserProfile.stories.tsx b/frontend/src/components/common/Dashboard/UserProfile/UserProfile.stories.tsx new file mode 100644 index 000000000..677cf0c73 --- /dev/null +++ b/frontend/src/components/common/Dashboard/UserProfile/UserProfile.stories.tsx @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { User } from '@type/user'; + +import UserProfile from '.'; + +const meta: Meta = { + component: UserProfile, +}; + +export default meta; +type Story = StoryObj; + +const MOCK_USER_INFO: User = { + nickname: '우아한 코끼리', + gender: 'MALE', + birthYear: 1989, + postCount: 4, + voteCount: 128, +}; + +export const NoBadge: Story = { + render: () => , +}; + +export const Badge: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/Dashboard/UserProfile/index.tsx b/frontend/src/components/common/Dashboard/UserProfile/index.tsx new file mode 100644 index 000000000..1fdf26a05 --- /dev/null +++ b/frontend/src/components/common/Dashboard/UserProfile/index.tsx @@ -0,0 +1,31 @@ +import { User } from '@type/user'; + +import { PATH } from '@constants/path'; + +import * as PS from '../profileStyle'; + +import * as S from './style'; + +interface UserProfileProps { + userInfo: User; +} + +export default function UserProfile({ userInfo }: UserProfileProps) { + const { nickname, postCount, voteCount } = userInfo; + + return ( + + {nickname} + + + 작성글 + {postCount} + + + 투표수 + {voteCount} + + + + ); +} diff --git a/frontend/src/components/common/Dashboard/UserProfile/style.ts b/frontend/src/components/common/Dashboard/UserProfile/style.ts new file mode 100644 index 000000000..ff3c04804 --- /dev/null +++ b/frontend/src/components/common/Dashboard/UserProfile/style.ts @@ -0,0 +1,44 @@ +import { Link } from 'react-router-dom'; + +import { styled } from 'styled-components'; + +export const Badge = styled.span` + margin-bottom: 7px; +`; + +export const NickName = styled.span` + margin-bottom: 12px; + margin-left: 5px; + + font: var(--text-title); + + color: var(--red); +`; + +export const UserInfoContainer = styled.div` + display: flex; + justify-content: space-around; +`; + +export const TextCardContainer = styled.div` + display: flex; + flex-direction: column; +`; + +export const TextCardLink = styled(Link)` + display: flex; + flex-direction: column; + + text-decoration: none; + + color: initial; +`; + +export const TextCardTitle = styled.span` + font: var(--text-caption); +`; + +export const TextCardContent = styled.span` + font: var(--text-caption); + text-align: center; +`; diff --git a/frontend/src/components/common/Dashboard/index.tsx b/frontend/src/components/common/Dashboard/index.tsx new file mode 100644 index 000000000..1f00c417d --- /dev/null +++ b/frontend/src/components/common/Dashboard/index.tsx @@ -0,0 +1,48 @@ +import { Suspense, useContext } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { AuthContext } from '@hooks/context/auth'; + +import ErrorBoundary from '@pages/ErrorBoundary'; + +import Skeleton from '../Skeleton'; +import SquareButton from '../SquareButton'; + +import CategorySection from './CategorySection'; +import GuestProfile from './GuestProfile'; +import * as S from './style'; +import UserProfile from './UserProfile'; + +export default function Dashboard() { + const navigate = useNavigate(); + const { loggedInfo, clearLoggedInfo } = useContext(AuthContext); + const { userInfo } = loggedInfo; + + const handleLogoutClick = () => { + clearLoggedInfo(); + + navigate('/'); + }; + + return ( + + + {userInfo ? : } + + + + }> + + + + + {userInfo && ( + + + 로그아웃 + + + )} + + ); +} diff --git a/frontend/src/components/common/Dashboard/profileStyle.ts b/frontend/src/components/common/Dashboard/profileStyle.ts new file mode 100644 index 000000000..ff27f8293 --- /dev/null +++ b/frontend/src/components/common/Dashboard/profileStyle.ts @@ -0,0 +1,16 @@ +import { styled } from 'styled-components'; + +export const ProfileContainer = styled.section` + display: flex; + flex-direction: column; + justify-content: space-around; + + width: 100%; + height: 130px; + padding: 16px 12px; + border-radius: 4px; + + font: var(--text-body); + + background-color: var(--gray); +`; diff --git a/frontend/src/components/common/Dashboard/style.ts b/frontend/src/components/common/Dashboard/style.ts new file mode 100644 index 000000000..f5d85df0d --- /dev/null +++ b/frontend/src/components/common/Dashboard/style.ts @@ -0,0 +1,31 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + width: 225px; + height: 100vh; + padding: 20px; + border-right: 2px solid var(--gray); + + @media (min-width: ${theme.breakpoint.sm}) { + height: 100%; + } +`; + +export const CategorySectionWrapper = styled.div` + width: 100%; + margin-top: 32px; +`; + +export const ButtonWrapper = styled.div` + width: 90px; + height: 40px; + + position: absolute; + bottom: 30px; +`; diff --git a/frontend/src/components/common/DeleteModal/DeleteModal.stories.tsx b/frontend/src/components/common/DeleteModal/DeleteModal.stories.tsx new file mode 100644 index 000000000..01e008e72 --- /dev/null +++ b/frontend/src/components/common/DeleteModal/DeleteModal.stories.tsx @@ -0,0 +1,22 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import DeleteModal from '.'; + +const meta: Meta = { + component: DeleteModal, +}; + +export default meta; +type Story = StoryObj; + +export const Post: Story = { + render: () => ( + {}} handleDeleteClick={() => {}} /> + ), +}; + +export const User: Story = { + render: () => ( + {}} handleDeleteClick={() => {}} /> + ), +}; diff --git a/frontend/src/components/common/DeleteModal/index.tsx b/frontend/src/components/common/DeleteModal/index.tsx new file mode 100644 index 000000000..5d8dc51d1 --- /dev/null +++ b/frontend/src/components/common/DeleteModal/index.tsx @@ -0,0 +1,46 @@ +import TwoButtonModal from '../../common/TwoButtonModal'; + +import * as S from './style'; + +export type TargetForDelete = 'MEMBERSHIP' | 'POST' | 'COMMENT'; + +const TARGET_FOR_DELETE: Record = { + MEMBERSHIP: '계정을', + POST: '게시글을', + COMMENT: '댓글을', +}; + +interface DeleteModalProps { + target: TargetForDelete; + handleCancelClick: () => void; + handleDeleteClick: () => void; +} + +export default function DeleteModal({ + target, + handleCancelClick, + handleDeleteClick, +}: DeleteModalProps) { + const handlePrimaryButtonClick = () => { + handleDeleteClick(); + handleCancelClick(); + }; + + return ( + + {`${TARGET_FOR_DELETE[target]} 삭제하시겠습니까?\n${TARGET_FOR_DELETE[target]} 삭제하면 취소할 수 없습니다.`} + + ); +} diff --git a/frontend/src/components/common/DeleteModal/style.ts b/frontend/src/components/common/DeleteModal/style.ts new file mode 100644 index 000000000..18a1d0b4c --- /dev/null +++ b/frontend/src/components/common/DeleteModal/style.ts @@ -0,0 +1,14 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Description = styled.p` + color: #67727e; + + font: var(--text-caption); + white-space: pre-wrap; + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-body); + } +`; diff --git a/frontend/src/components/common/Drawer/Drawer.stories.tsx b/frontend/src/components/common/Drawer/Drawer.stories.tsx new file mode 100644 index 000000000..f37c1d6bc --- /dev/null +++ b/frontend/src/components/common/Drawer/Drawer.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta } from '@storybook/react'; + +import { useDrawer } from '@hooks/useDrawer'; + +import Dashboard from '../Dashboard'; +import NarrowMainHeader from '../NarrowMainHeader'; + +import Drawer from '.'; + +const meta: Meta = { + component: Drawer, +}; + +export default meta; + +export const LeftSideBar = () => { + const { drawerRef, openDrawer, closeDrawer } = useDrawer('left'); + + return ( +
+ + + + +
+ ); +}; + +export const RightSideBar = () => { + const { drawerRef, openDrawer, closeDrawer } = useDrawer('right'); + + return ( +
+ + + + +
+ ); +}; diff --git a/frontend/src/components/common/Drawer/index.tsx b/frontend/src/components/common/Drawer/index.tsx new file mode 100644 index 000000000..dff587262 --- /dev/null +++ b/frontend/src/components/common/Drawer/index.tsx @@ -0,0 +1,62 @@ +import React, { + ForwardedRef, + KeyboardEvent, + MouseEvent, + PropsWithChildren, + forwardRef, +} from 'react'; + +import * as S from './style'; + +interface DrawerProps extends PropsWithChildren { + handleDrawerClose: () => void; + width: string; + placement: 'left' | 'right'; +} + +const ARIA_MESSAGE = + '사용자 정보 및 카테고리 정보가 있는 사이드바가 열렸습니다. 사이드바 닫기 버튼을 누르거나 ESC를 누르면 닫을 수 있습니다.'; + +export default forwardRef(function Drawer( + { handleDrawerClose, width, placement, children }: DrawerProps, + ref: ForwardedRef +) { + const handleCloseClick = (event: MouseEvent) => { + const modalBoundary = event.currentTarget.getBoundingClientRect(); + + if ( + modalBoundary.left > event.clientX || + modalBoundary.right < event.clientX || + modalBoundary.top > event.clientY || + modalBoundary.bottom < event.clientY + ) { + handleDrawerClose(); + } + }; + + const handleKeyDown = (event: KeyboardEvent) => { + event.preventDefault(); + + if (event.currentTarget.open && event.key === 'Escape') { + handleDrawerClose(); + } + }; + + return ( + + 사이드바 닫기버튼 + {children} +
+ + ); +}); diff --git a/frontend/src/components/common/Drawer/style.ts b/frontend/src/components/common/Drawer/style.ts new file mode 100644 index 000000000..144e526dd --- /dev/null +++ b/frontend/src/components/common/Drawer/style.ts @@ -0,0 +1,34 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const CloseButton = styled.button` + position: absolute; + top: 0; + right: 99999px; +`; + +export const Dialog = styled.dialog<{ + $width: string; + $placement: 'left' | 'right'; +}>` + width: ${({ $width }) => $width}; + min-height: 100%; + + position: fixed; + top: 0; + left: ${({ $placement }) => ($placement === 'left' ? '0' : 'auto')}; + right: ${({ $placement }) => ($placement === 'right' ? '0' : 'auto')}; + + overflow: hidden; + + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + + z-index: ${theme.zIndex.modal}; + + &::backdrop { + background-color: rgba(0, 0, 0, 0.35); + } +`; diff --git a/frontend/src/components/common/ErrorMessage/ErrorMessage.stories.tsx b/frontend/src/components/common/ErrorMessage/ErrorMessage.stories.tsx new file mode 100644 index 000000000..3b77283d2 --- /dev/null +++ b/frontend/src/components/common/ErrorMessage/ErrorMessage.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import ErrorMessage from '.'; + +const meta: Meta = { + component: ErrorMessage, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => {}} />, +}; diff --git a/frontend/src/components/common/ErrorMessage/index.tsx b/frontend/src/components/common/ErrorMessage/index.tsx new file mode 100644 index 000000000..69196e750 --- /dev/null +++ b/frontend/src/components/common/ErrorMessage/index.tsx @@ -0,0 +1,21 @@ +// import IconButton from '../IconButton'; +// import SquareButton from '../SquareButton'; + +import * as S from './style'; + +export default function ErrorMessage({ errorHandler }: { errorHandler?: () => void }) { + return ( + + ⚠ 잠시 후 다시 시도해주세요. + 요청하신 데이터를 불러오는데 실패했습니다. + {/* + + + + 다시 시도 + + + */} + + ); +} diff --git a/frontend/src/components/common/ErrorMessage/style.ts b/frontend/src/components/common/ErrorMessage/style.ts new file mode 100644 index 000000000..fae76a2b0 --- /dev/null +++ b/frontend/src/components/common/ErrorMessage/style.ts @@ -0,0 +1,67 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + + position: relative; +`; + +export const HeaderWrapper = styled.div` + width: 100%; + + position: fixed; + + z-index: ${theme.zIndex.header}; + + @media (min-width: ${theme.breakpoint.md}) { + display: none; + } +`; + +export const Title = styled.h1` + width: 90%; + margin-top: 60px; + + font-size: 20px; + font-weight: bold; + + text-align: center; +`; + +export const Description = styled.p` + width: 90%; + margin: 20px 0; + + font: var(--text-body); + text-align: center; +`; + +export const Direction = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; +`; + +export const RetryText = styled.p` + display: flex; + justify-content: space-around; + gap: 10px; + + padding: 12px; + + font: var(--text-body); + font-weight: bold; +`; + +export const ButtonWrapper = styled.div` + width: 120px; + height: 50px; +`; diff --git a/frontend/src/components/common/HeaderTextButton/HeaderTextButton.stories.tsx b/frontend/src/components/common/HeaderTextButton/HeaderTextButton.stories.tsx new file mode 100644 index 000000000..cca319fbd --- /dev/null +++ b/frontend/src/components/common/HeaderTextButton/HeaderTextButton.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import HeaderTextButton from '.'; + +const meta: Meta = { + component: HeaderTextButton, +}; + +export default meta; +type Story = StoryObj; + +export const DefaultButton: Story = { + render: () => 확 인, +}; diff --git a/frontend/src/components/common/HeaderTextButton/index.tsx b/frontend/src/components/common/HeaderTextButton/index.tsx new file mode 100644 index 000000000..6d5c0c73b --- /dev/null +++ b/frontend/src/components/common/HeaderTextButton/index.tsx @@ -0,0 +1,14 @@ +import { ButtonHTMLAttributes } from 'react'; + +import * as S from './style'; + +interface HeaderTextButtonProps extends ButtonHTMLAttributes { + children: string; +} + +/* 헤더에 포함되어 취소, 확인, 신고 등 사용될 버튼 + * props로 s/m/l 크기를 받음 + */ +export default function HeaderTextButton({ children, ...rest }: HeaderTextButtonProps) { + return {children}; +} diff --git a/frontend/src/components/common/HeaderTextButton/style.ts b/frontend/src/components/common/HeaderTextButton/style.ts new file mode 100644 index 000000000..d58bc9d15 --- /dev/null +++ b/frontend/src/components/common/HeaderTextButton/style.ts @@ -0,0 +1,11 @@ +import { styled } from 'styled-components'; + +export const Button = styled.button` + background-color: rgba(0, 0, 0, 0); + color: var(--white); + + font: var(--text-caption); + font-weight: 500; + + cursor: pointer; +`; diff --git a/frontend/src/components/common/IconButton/IconButton.stories.tsx b/frontend/src/components/common/IconButton/IconButton.stories.tsx new file mode 100644 index 000000000..29fc59a37 --- /dev/null +++ b/frontend/src/components/common/IconButton/IconButton.stories.tsx @@ -0,0 +1,46 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import IconButton from '.'; + +const meta: Meta = { + component: IconButton, +}; + +export default meta; +type Story = StoryObj; + +export const Category: Story = { + render: () => ( +
+ +
+ ), +}; + +export const Back: Story = { + render: () => ( +
+ +
+ ), +}; + +export const Search: Story = { + render: () => ( +
+ +
+ ), +}; + +export const Retry: Story = { + render: () => , +}; + +export const UserInfo: Story = { + render: () => , +}; + +export const Ranking: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/IconButton/index.tsx b/frontend/src/components/common/IconButton/index.tsx new file mode 100644 index 000000000..379e669dd --- /dev/null +++ b/frontend/src/components/common/IconButton/index.tsx @@ -0,0 +1,72 @@ +import { ButtonHTMLAttributes } from 'react'; + +import backIcon from '@assets/back.svg'; +import categoryIcon from '@assets/category.svg'; +import ranking from '@assets/ranking.svg'; +import retryIcon from '@assets/retry.svg'; +import searchIcon from '@assets/search_white.svg'; +import userInfo from '@assets/user.svg'; + +import * as S from './style'; + +type IconCategory = 'category' | 'back' | 'search' | 'retry' | 'userInfo' | 'ranking'; + +interface IconInfo { + name: string; + url: string; + isRoundBackground: boolean; +} + +const ICON_CATEGORY: Record = { + category: { + name: '카테고리', + url: categoryIcon, + isRoundBackground: false, + }, + back: { + name: '뒤로가기', + url: backIcon, + isRoundBackground: false, + }, + search: { + name: '검색', + url: searchIcon, + isRoundBackground: false, + }, + retry: { + name: '다시시도', + url: retryIcon, + isRoundBackground: false, + }, + userInfo: { + name: '사용자 페이지 이동', + url: userInfo, + isRoundBackground: true, + }, + ranking: { + name: '랭킹 페이지 이동', + url: ranking, + isRoundBackground: false, + }, +}; + +interface IconButtonProps extends ButtonHTMLAttributes { + category: IconCategory; +} + +/* 뒤로가기, 카테고리 열기 등에 사용될 아이콘 버튼 + */ +export default function IconButton({ category, ...rest }: IconButtonProps) { + const src = ICON_CATEGORY[category].url; + const ariaLabelText = ICON_CATEGORY[category].name; + + return ( + + {`${ariaLabelText} + + ); +} diff --git a/frontend/src/components/common/IconButton/style.ts b/frontend/src/components/common/IconButton/style.ts new file mode 100644 index 000000000..1317df709 --- /dev/null +++ b/frontend/src/components/common/IconButton/style.ts @@ -0,0 +1,11 @@ +import { styled } from 'styled-components'; + +export const Button = styled.button<{ $isRoundBackground: boolean }>` + width: 35px; + height: 35px; + border-radius: 50%; + + background-color: ${props => (props.$isRoundBackground ? 'var(--gray)' : 'rgba(0, 0, 0, 0)')}; + + cursor: pointer; +`; diff --git a/frontend/src/components/common/Layout/Layout.stories.tsx b/frontend/src/components/common/Layout/Layout.stories.tsx new file mode 100644 index 000000000..a1cb29464 --- /dev/null +++ b/frontend/src/components/common/Layout/Layout.stories.tsx @@ -0,0 +1,46 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import Skeleton from '../Skeleton'; + +import Layout from '.'; + +const meta: Meta = { + component: Layout, +}; + +export default meta; +type Story = StoryObj; + +export const VisibleCategory: Story = { + render: () => ( + + + + + + + + + + + + + ), +}; + +export const HiddenCategory: Story = { + render: () => ( + + + + + + + + + + + + + ), +}; diff --git a/frontend/src/components/common/Layout/index.tsx b/frontend/src/components/common/Layout/index.tsx new file mode 100644 index 000000000..3b229f28d --- /dev/null +++ b/frontend/src/components/common/Layout/index.tsx @@ -0,0 +1,30 @@ +import { PropsWithChildren } from 'react'; + +import Dashboard from '@components/common/Dashboard'; +import WideHeader from '@components/common/WideHeader'; + +import * as S from './style'; + +interface LayoutProps extends PropsWithChildren { + isSidebarVisible: boolean; +} + +export default function Layout({ children, isSidebarVisible }: LayoutProps) { + return ( + + + + + + {isSidebarVisible && ( + + + + )} + + {children} + + + + ); +} diff --git a/frontend/src/components/common/Layout/style.ts b/frontend/src/components/common/Layout/style.ts new file mode 100644 index 000000000..8907814ee --- /dev/null +++ b/frontend/src/components/common/Layout/style.ts @@ -0,0 +1,61 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + height: 100vh; +`; + +export const ContentContainer = styled.div` + display: flex; + justify-content: space-between; + + @media (min-width: ${theme.breakpoint.sm}) { + padding-top: 70px; + } +`; + +export const WideHeaderWrapper = styled.div` + width: 100%; + + position: fixed; + top: 0; + + z-index: ${theme.zIndex.header}; + + @media (max-width: ${theme.breakpoint.sm}) { + display: none; + visibility: hidden; + } +`; + +export const DashboardWrapper = styled.aside` + width: 225px; + height: 90vh; + + position: fixed; + left: 0; + + @media (max-width: ${theme.breakpoint.sm}) { + display: none; + visibility: hidden; + } +`; + +export const MainContainer = styled.main<{ $isSidebarVisible: boolean }>` + display: flex; + justify-content: center; + + margin-top: 15px; + width: 100%; + + @media (min-width: ${theme.breakpoint.sm}) { + margin-top: 0; + padding-left: ${({ $isSidebarVisible }) => $isSidebarVisible && '225px'}; + } +`; + +export const ChildrenWrapper = styled.div<{ $isSidebarVisible: boolean }>` + width: 100%; + max-width: ${({ $isSidebarVisible }) => $isSidebarVisible && '700px'}; +`; diff --git a/frontend/src/components/common/LoadingSpinner/LoadingSpinner.stories.tsx b/frontend/src/components/common/LoadingSpinner/LoadingSpinner.stories.tsx new file mode 100644 index 000000000..59d436cb9 --- /dev/null +++ b/frontend/src/components/common/LoadingSpinner/LoadingSpinner.stories.tsx @@ -0,0 +1,22 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import LoadingSpinner from '.'; + +const meta: Meta = { + component: LoadingSpinner, +}; + +export default meta; +type Story = StoryObj; + +export const SizeS: Story = { + render: () => , +}; + +export const SizeM: Story = { + render: () => , +}; + +export const SizeL: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/LoadingSpinner/index.tsx b/frontend/src/components/common/LoadingSpinner/index.tsx new file mode 100644 index 000000000..1deed191f --- /dev/null +++ b/frontend/src/components/common/LoadingSpinner/index.tsx @@ -0,0 +1,17 @@ +import { Size } from '@type/style'; + +import * as S from './style'; + +interface LoadingSpinnerProps { + size: Size; +} + +export default function LoadingSpinner({ size }: LoadingSpinnerProps) { + return ( + + + + + + ); +} diff --git a/frontend/src/components/common/LoadingSpinner/style.ts b/frontend/src/components/common/LoadingSpinner/style.ts new file mode 100644 index 000000000..d6b9bfc2a --- /dev/null +++ b/frontend/src/components/common/LoadingSpinner/style.ts @@ -0,0 +1,44 @@ +import { keyframes, styled } from 'styled-components'; + +import { Size } from '@type/style'; + +interface LoadingSpinnerProps { + $size: Size; +} + +const SIZE = { + sm: '10px', + md: '15px', + lg: '30px', +}; + +const Animation = keyframes` +to { + transform: translate(0, -15px); +} +`; + +export const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + + & > :nth-child(2) { + animation-delay: 0.1s; + margin: 0 ${props => SIZE[props.$size]}; + } + + & > :nth-child(3) { + animation-delay: 0.2s; + } +`; + +export const Unit = styled.div` + width: ${props => SIZE[props.$size]}; + height: ${props => SIZE[props.$size]}; + border-radius: 50%; + + background-color: #747474; + + animation: ${Animation} 0.5s ease-in-out infinite alternate; +`; diff --git a/frontend/src/components/common/LogoButton/LogoButton.stories.tsx b/frontend/src/components/common/LogoButton/LogoButton.stories.tsx new file mode 100644 index 000000000..a174f863f --- /dev/null +++ b/frontend/src/components/common/LogoButton/LogoButton.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import LogoButton from '.'; + +const meta: Meta = { + component: LogoButton, + decorators: [ + storyFn =>
{storyFn()}
, + ], +}; + +export default meta; +type Story = StoryObj; + +export const Icon: Story = { + render: () => , +}; + +export const Text: Story = { + render: () => , +}; + +export const Full: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/LogoButton/index.tsx b/frontend/src/components/common/LogoButton/index.tsx new file mode 100644 index 000000000..eee09b238 --- /dev/null +++ b/frontend/src/components/common/LogoButton/index.tsx @@ -0,0 +1,47 @@ +import { ButtonHTMLAttributes } from 'react'; + +import logo from '@assets/logo.svg'; +import votogether from '@assets/projectName.svg'; + +import * as S from './style'; + +type Content = 'icon' | 'text' | 'full'; + +const contentCategory: Record = { + icon: { + name: '보투게더 로고 아이콘', + url: logo, + }, + text: { + name: '보투게더 아이콘', + url: votogether, + }, + full: { + name: '보투게더 아이콘', + url: '', + }, +}; + +interface LogoButtonProps extends ButtonHTMLAttributes { + content: Content; +} + +export default function LogoButton({ content, ...rest }: LogoButtonProps) { + const src = contentCategory[content].url; + const ariaLabelText = contentCategory[content].name; + + if (content === 'full') { + return ( + + 로고 아이콘 + 보투게더 아이콘 + + ); + } + + return ( + + 보투게더 아이콘 + + ); +} diff --git a/frontend/src/components/common/LogoButton/style.ts b/frontend/src/components/common/LogoButton/style.ts new file mode 100644 index 000000000..8ebdbf50d --- /dev/null +++ b/frontend/src/components/common/LogoButton/style.ts @@ -0,0 +1,24 @@ +import { styled } from 'styled-components'; + +type Content = 'icon' | 'text' | 'full'; + +export const Button = styled.button<{ content: Content }>` + display: flex; + align-items: center; + gap: 10px; + + background-color: rgba(0, 0, 0, 0); + + height: 100%; + + cursor: pointer; + + & :first-child { + height: 100%; + border-radius: 5px; + } + + & :last-child { + height: ${props => props.content !== 'icon' && '60%'}; + } +`; diff --git a/frontend/src/components/common/Modal/Modal.stories.tsx b/frontend/src/components/common/Modal/Modal.stories.tsx new file mode 100644 index 000000000..2a4870dbf --- /dev/null +++ b/frontend/src/components/common/Modal/Modal.stories.tsx @@ -0,0 +1,255 @@ +import type { Meta } from '@storybook/react'; + +import { useEffect, useState } from 'react'; + +import { styled } from 'styled-components'; + +import SquareButton from '../SquareButton'; +import TimePickerOptionList from '../TimePickerOptionList'; + +import Modal from '.'; + +const meta: Meta = { + component: Modal, + decorators: [storyFn =>
{storyFn()}
], +}; + +export default meta; + +export const Default = () => { + const [isOpen, setIsOpen] = useState(false); + + const openModal = () => { + setIsOpen(true); + }; + + const closeModal = () => { + setIsOpen(false); + }; + + return ( + <> + + Open Modal + + {isOpen && ( + +

This is Default Modal

+
+ )} + + ); +}; + +export const Wide = () => { + const [isOpen, setIsOpen] = useState(false); + + const openModal = () => { + setIsOpen(true); + }; + + const closeModal = () => { + setIsOpen(false); + }; + + return ( + <> + + Open Modal + + {isOpen && ( + +

This is Wide Modal

+
+ )} + + ); +}; + +export const WithCloseButton = () => { + const [isOpen, setIsOpen] = useState(false); + + const openModal = () => { + setIsOpen(true); + }; + + const closeModal = () => { + setIsOpen(false); + }; + + return ( + <> + + Open Modal + + {isOpen && ( + + <> + +

Modal Title

+ X +
+ + This is Description + This is Content + + +
+ )} + + ); +}; + +export const CloseByESC = () => { + const [isOpen, setIsOpen] = useState(false); + + const openModal = () => { + setIsOpen(true); + }; + + const closeModal = () => { + setIsOpen(false); + }; + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + closeModal(); + } + }; + + if (isOpen) { + document.addEventListener('keydown', handleKeyDown); + } + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [isOpen]); + + return ( + <> + + Open Modal + + {isOpen && ( + +

Close This Modal by ESC

+
+ )} + + ); +}; + +export const WithTimePicker = () => { + const [time, setTime] = useState({ + day: 2, + hour: 7, + minute: 58, + }); + const [isOpen, setIsOpen] = useState(false); + + const openModal = () => { + setIsOpen(true); + }; + + const closeModal = () => { + setIsOpen(false); + }; + + const handleResetBUtton = () => { + if (window.confirm('정말 초기화하시겠습니까?')) { + const updatedTime = { + day: 0, + hour: 0, + minute: 0, + }; + setTime(updatedTime); + } + }; + + return ( + <> + + 사용자 지정 + + {isOpen && ( + + <> + +

마감 시간 선택

+ X +
+ + 최대 3일을 넘을 수 없습니다. + + + + 초기화 + + + + +
+ )} + + ); +}; + +const Header = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + width: 100%; + border-bottom: 1px solid #f6f6f6; + padding: 10px; + + font: var(--text-body); + font-weight: bold; +`; + +const Body = styled.div` + display: flex; + flex-direction: column; + justify-content: start; + align-items: center; + gap: 10px; + + padding: 10px 0; + font: var(--text-caption); +`; + +const Description = styled.div` + color: gray; + + font: var(--text-small); +`; + +const CloseButton = styled.button` + width: 25px; + height: 20px; + + background: white; + + font: var(--text-body); + + cursor: pointer; +`; + +const ButtonWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + + width: 50%; + height: 40px; +`; + +const S = { + Header, + Body, + Description, + CloseButton, + ButtonWrapper, +}; diff --git a/frontend/src/components/common/Modal/index.tsx b/frontend/src/components/common/Modal/index.tsx new file mode 100644 index 000000000..945199f26 --- /dev/null +++ b/frontend/src/components/common/Modal/index.tsx @@ -0,0 +1,41 @@ +import React, { useEffect, useRef } from 'react'; + +import { Size } from '@type/style'; + +import * as S from './style'; + +interface ModalProps { + onModalClose: () => void; + children: React.JSX.Element; + size: Size; +} + +export default function Modal({ onModalClose, children, size }: ModalProps) { + const BackDropRef = useRef(null); + + useEffect(() => { + const handler = (e: MouseEvent) => { + if (e.target === BackDropRef.current) { + onModalClose(); + } + }; + + document.addEventListener('click', handler); + + return () => document.removeEventListener('click', handler); + }, [BackDropRef, onModalClose]); + + return ( + + + + + {children} + + + ); +} diff --git a/frontend/src/components/common/Modal/style.ts b/frontend/src/components/common/Modal/style.ts new file mode 100644 index 000000000..a3b6eb9a3 --- /dev/null +++ b/frontend/src/components/common/Modal/style.ts @@ -0,0 +1,51 @@ +import { styled } from 'styled-components'; + +import { Size } from '@type/style'; + +const MODAL_SIZE: Record = { + sm: '290px', + md: '590px', + lg: '700px', +}; + +export const All = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +export const Backdrop = styled.div` + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + + background: rgba(0, 0, 0, 0.35); +`; + +export const Container = styled.div<{ size: Size }>` + display: grid; + grid-template-rows: 1fr 6fr; + + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + width: ${props => MODAL_SIZE[props.size]}; + height: 290px; + border-radius: 12px; + border: 2px solid #f6f6f6; + padding: 5px; + + background-color: white; + + box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5); +`; + +export const HiddenCloseButton = styled.button` + position: absolute; + left: -10000px; + top: -10000px; +`; diff --git a/frontend/src/components/common/MultiSelect/MultiSelect.stories.tsx b/frontend/src/components/common/MultiSelect/MultiSelect.stories.tsx new file mode 100644 index 000000000..9d6be11bd --- /dev/null +++ b/frontend/src/components/common/MultiSelect/MultiSelect.stories.tsx @@ -0,0 +1,110 @@ +import type { Meta } from '@storybook/react'; + +import { useMultiSelect } from '@hooks/useMultiSelect'; + +import MultiSelect from '.'; + +const meta: Meta = { + component: MultiSelect, + decorators: [storyFn => storyFn()], +}; + +export default meta; + +const MOCK_OPTION_LIST = [ + { id: 1, name: '옵션1' }, + { id: 2, name: '옵션2' }, + { id: 5, name: '옵션3' }, + { id: 7, name: '옵션4' }, + { id: 9, name: '옵션5' }, + { id: 10, name: '옵션6' }, + { id: 11, name: '옵션7' }, + { id: 13, name: '옵션8' }, + { id: 15, name: '옵션9' }, + { id: 16, name: '매우 긴----------~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~옵션10' }, +]; + +const MOCK_CATEGORY_LIST = [ + { id: 1, name: '음식' }, + { id: 2, name: '패션' }, + { id: 5, name: '금융' }, + { id: 7, name: '게임' }, + { id: 9, name: '개발' }, + { id: 10, name: '연애' }, + { id: 11, name: '취미' }, + { id: 13, name: '주식' }, + { id: 15, name: '연예' }, + { id: 16, name: '정치' }, +]; + +export const NotSelected = () => { + const { selectedOptionList, handleOptionAdd, handleOptionDelete } = useMultiSelect([]); + + return ( + + ); +}; + +export const Selected = () => { + const initialSelectedOptionList = MOCK_OPTION_LIST.filter( + option => option.name === '옵션1' || option.name === '옵션7' + ); + + const { selectedOptionList, handleOptionAdd, handleOptionDelete } = + useMultiSelect(initialSelectedOptionList); + + return ( + + ); +}; + +export const CategoryNotSelected = () => { + const CATEGORY_COUNT_LIMIT = 3; + + const { selectedOptionList, handleOptionAdd, handleOptionDelete } = useMultiSelect( + [], + CATEGORY_COUNT_LIMIT + ); + + return ( + + ); +}; + +export const CategorySelected = () => { + const CATEGORY_COUNT_LIMIT = 3; + const initialSelectedOptionList = MOCK_CATEGORY_LIST.filter( + option => option.id === 7 || option.id === 9 + ); + + const { selectedOptionList, handleOptionAdd, handleOptionDelete } = useMultiSelect( + initialSelectedOptionList, + CATEGORY_COUNT_LIMIT + ); + + return ( + + ); +}; diff --git a/frontend/src/components/common/MultiSelect/index.tsx b/frontend/src/components/common/MultiSelect/index.tsx new file mode 100644 index 000000000..bf47f35d4 --- /dev/null +++ b/frontend/src/components/common/MultiSelect/index.tsx @@ -0,0 +1,103 @@ +import type { Option } from './types'; + +import React, { useState, useEffect, useRef } from 'react'; + +import OptionCancelButton from '@components/optionList/WritingVoteOptionList/WritingVoteOption/OptionCancelButton'; + +import chevronDown from '@assets/chevron-down.svg'; +import chevronUp from '@assets/chevron-up.svg'; + +import * as S from './style'; + +interface MultiSelectProps { + selectedOptionList: Option[]; + optionList: Option[]; + handleOptionAdd: (newItem: Option) => void; + handleOptionDelete: (optionId: number) => void; + placeholder?: string; +} + +export default function MultiSelect({ + selectedOptionList, + optionList, + handleOptionAdd, + handleOptionDelete, + placeholder = '여러 개의 옵션을 선택해주세요', +}: MultiSelectProps) { + const [isOpen, setIsOpen] = useState(false); + const wrapperRef = useRef(null); + + const filteredOptionList = optionList.filter( + option => !selectedOptionList.some(selected => selected.id === option.id) + ); + + const handleToggleWrapper = () => { + setIsOpen(!isOpen); + }; + + const handleOutsideClick = (event: MouseEvent) => { + if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + useEffect(() => { + document.addEventListener('click', handleOutsideClick); + return () => { + document.removeEventListener('click', handleOutsideClick); + }; + }, []); + + return ( + + option.name) + .join(', ')}`} + aria-live="polite" + > + + {selectedOptionList.length === 0 && ( + {placeholder} + )} + {selectedOptionList.map(({ id, name }) => ( + e.stopPropagation()}> + {name} + { + e.stopPropagation(); + handleOptionDelete(id); + }} + /> + + ))} + + + + + + {filteredOptionList.length > 0 && ( + + {filteredOptionList.map(({ id, name }) => ( + + ))} + + )} + + ); +} diff --git a/frontend/src/components/common/MultiSelect/style.ts b/frontend/src/components/common/MultiSelect/style.ts new file mode 100644 index 000000000..686b24f73 --- /dev/null +++ b/frontend/src/components/common/MultiSelect/style.ts @@ -0,0 +1,98 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + position: relative; + + font: var(--text-caption); + + & > * { + background-color: var(--white); + } + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-body); + } +`; + +export const Wrapper = styled.div` + display: grid; + grid-template-columns: 1fr 20px; + align-items: center; + + height: auto; + border: 1px solid var(--slate); + border-radius: 6px; + padding: 7px; + + position: relative; + + cursor: pointer; +`; + +export const SelectedOptionListContainer = styled.div` + display: flex; + flex-grow: 1; + flex-wrap: wrap; +`; + +export const SelectIcon = styled.button` + width: 20px; +`; + +export const DropDown = styled.div<{ + $isOpened: boolean; +}>` + width: 100%; + border: 1px solid #e4e5e7; + border-radius: 6px; + margin-top: 10px; + + position: absolute; + + opacity: ${({ $isOpened }) => ($isOpened ? 1 : 0)}; + visibility: ${({ $isOpened }) => ($isOpened ? 'visible' : 'hidden')}; + + transition: all 0.2s linear 0.1s; + + & > button { + display: block; + + width: 100%; + list-style: none; + padding: 10px 0px 10px 15px; + border-bottom: 1px solid #e4e5e7; + + text-align: left; + + &:hover { + background-color: #ffefd5; + } + &:last-child { + border-bottom: none; + } + } +`; + +export const SelectedOption = styled.span` + display: flex; + justify-content: space-between; + align-items: center; + + border-radius: 4px; + padding: 10px; + margin: 2px 4px 2px 2px; + & > span { + margin-right: 8px; + } + + background: #e8f7f6; +`; + +export const Image = styled.img<{ $isSelected: boolean }>` + width: 20px; + height: 20px; + border-left: 1px solid var(--slate); + padding-left: 8px; +`; diff --git a/frontend/src/components/common/MultiSelect/types.ts b/frontend/src/components/common/MultiSelect/types.ts new file mode 100644 index 000000000..86767fe61 --- /dev/null +++ b/frontend/src/components/common/MultiSelect/types.ts @@ -0,0 +1,4 @@ +export interface Option { + id: number; + name: string; +} diff --git a/frontend/src/components/common/NarrowMainHeader/NarrowMainHeader.stories.tsx b/frontend/src/components/common/NarrowMainHeader/NarrowMainHeader.stories.tsx new file mode 100644 index 000000000..33986112a --- /dev/null +++ b/frontend/src/components/common/NarrowMainHeader/NarrowMainHeader.stories.tsx @@ -0,0 +1,15 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import NarrowMainHeader from '.'; + +const meta: Meta = { + component: NarrowMainHeader, + decorators: [storyFn =>
{storyFn()}
], +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + render: () => {}} />, +}; diff --git a/frontend/src/components/common/NarrowMainHeader/index.tsx b/frontend/src/components/common/NarrowMainHeader/index.tsx new file mode 100644 index 000000000..0773cb0d2 --- /dev/null +++ b/frontend/src/components/common/NarrowMainHeader/index.tsx @@ -0,0 +1,54 @@ +import { MouseEvent } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { useToggle } from '@hooks/useToggle'; + +import { PATH } from '@constants/path'; + +import IconButton from '../IconButton'; +import LogoButton from '../LogoButton'; +import SearchBar from '../SearchBar'; + +import * as S from './style'; + +interface NarrowMainHeaderProps { + handleMenuOpenClick: () => void; +} + +export default function NarrowMainHeader({ handleMenuOpenClick }: NarrowMainHeaderProps) { + const { + isOpen: isSearchInputOpen, + openComponent: openSearchInput, + closeComponent: closeSearchInput, + } = useToggle(); + + const navigate = useNavigate(); + + const movePostListPage = () => { + navigate('/'); + }; + + const moveUserInfoPage = () => { + navigate(PATH.USER_INFO); + }; + + const moveRankingPage = () => { + navigate(PATH.RANKING); + }; + + return isSearchInputOpen ? ( + + event.stopPropagation()}> + + + + ) : ( + + + + + + + + ); +} diff --git a/frontend/src/components/common/NarrowMainHeader/style.ts b/frontend/src/components/common/NarrowMainHeader/style.ts new file mode 100644 index 000000000..a71e0a16d --- /dev/null +++ b/frontend/src/components/common/NarrowMainHeader/style.ts @@ -0,0 +1,29 @@ +import { styled } from 'styled-components'; + +export const Container = styled.header` + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + + width: 100%; + height: 55px; + padding: 0 20px; + + position: fixed; + top: 0; + + background-color: var(--header); + + & > :nth-child(2) { + margin-right: auto; + height: 60%; + } +`; + +export const Background = styled.div` + width: 100%; + height: 100vh; + + cursor: pointer; +`; diff --git a/frontend/src/components/common/NarrowTemplateHeader/NarrowTemplateHeader.stories.tsx b/frontend/src/components/common/NarrowTemplateHeader/NarrowTemplateHeader.stories.tsx new file mode 100644 index 000000000..8a95c5871 --- /dev/null +++ b/frontend/src/components/common/NarrowTemplateHeader/NarrowTemplateHeader.stories.tsx @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import NarrowTemplateHeader from '.'; + +const meta: Meta = { + component: NarrowTemplateHeader, + decorators: [storyFn =>
{storyFn()}
], +}; + +export default meta; +type Story = StoryObj; + +export const BothSideHeader: Story = { + render: () => ( + +
예시
+
예시
+
+ ), +}; + +export const ThreeComponentHeaderLeft: Story = { + render: () => ( + +
+
예시
+
예시
+
+
예시
+
+ ), +}; + +export const ThreeComponentHeaderRight: Story = { + render: () => ( + +
예시
+
+
예시
+
예시
+
+
+ ), +}; diff --git a/frontend/src/components/common/NarrowTemplateHeader/index.tsx b/frontend/src/components/common/NarrowTemplateHeader/index.tsx new file mode 100644 index 000000000..fa1da668c --- /dev/null +++ b/frontend/src/components/common/NarrowTemplateHeader/index.tsx @@ -0,0 +1,7 @@ +import { PropsWithChildren } from 'react'; + +import * as S from './style'; + +export default function NarrowTemplateHeader({ children }: PropsWithChildren) { + return {children}; +} diff --git a/frontend/src/components/common/NarrowTemplateHeader/style.ts b/frontend/src/components/common/NarrowTemplateHeader/style.ts new file mode 100644 index 000000000..790cd3fe9 --- /dev/null +++ b/frontend/src/components/common/NarrowTemplateHeader/style.ts @@ -0,0 +1,17 @@ +import { styled } from 'styled-components'; + +export const Container = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + + width: 100%; + height: 55px; + padding: 0 20px; + + position: fixed; + top: 0; + + background-color: var(--header); +`; diff --git a/frontend/src/components/common/Post/Post.stories.tsx b/frontend/src/components/common/Post/Post.stories.tsx new file mode 100644 index 000000000..1d63ce099 --- /dev/null +++ b/frontend/src/components/common/Post/Post.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { MOCK_NOT_VOTE_POST, MOCK_VOTE_POST } from './mockData'; + +import Post from '.'; + +const meta: Meta = { + component: Post, + decorators: [storyFn =>
{storyFn()}
], +}; + +export default meta; +type Story = StoryObj; + +export const PreviewNotVotedPost: Story = { + render: () => , +}; + +export const PreviewVotedPost: Story = { + render: () => , +}; + +export const NotVotedPost: Story = { + render: () => , +}; + +export const VotedPost: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/Post/index.tsx b/frontend/src/components/common/Post/index.tsx new file mode 100644 index 000000000..4d78c9509 --- /dev/null +++ b/frontend/src/components/common/Post/index.tsx @@ -0,0 +1,185 @@ +import { MouseEvent, useContext, useEffect } from 'react'; + +import { PostInfo } from '@type/post'; + +import { AuthContext } from '@hooks/context/auth'; +import { useCreateVote } from '@hooks/query/post/useCreateVote'; +import { useEditVote } from '@hooks/query/post/useEditVote'; +import { useToast } from '@hooks/useToast'; + +import WrittenVoteOptionList from '@components/optionList/WrittenVoteOptionList'; + +import { PATH } from '@constants/path'; +import { POST } from '@constants/vote'; + +import { convertImageUrlToServerUrl } from '@utils/post/convertImageUrlToServerUrl'; +import { checkClosedPost, convertTimeToWord } from '@utils/time'; + +import photoIcon from '@assets/photo_white.svg'; + +import Toast from '../Toast'; + +import * as S from './style'; + +interface PostProps { + postInfo: PostInfo; + isPreview: boolean; +} + +export default function Post({ postInfo, isPreview }: PostProps) { + const { postId, category, imageUrl, title, writer, createTime, deadline, content, voteInfo } = + postInfo; + const { loggedInfo } = useContext(AuthContext); + const { isToastOpen, openToast, toastMessage } = useToast(); + + const { + mutate: createVote, + isError: isCreateError, + error: createError, + } = useCreateVote({ isPreview, postId }); + const { + mutate: editVote, + isError: isEditError, + error: editError, + } = useEditVote({ isPreview, postId }); + + const isActive = !checkClosedPost(deadline); + + const isStatisticsVisible = + writer.id === loggedInfo.id || !isActive || voteInfo.selectedOptionId !== POST.NOT_VOTE; + + const handleVoteClick = (newOptionId: number) => { + if (!loggedInfo.isLoggedIn) { + openToast('투표는 로그인 후에 이용하실 수 있습니다.'); + return; + } + + if (!isActive) { + openToast('마감된 게시글에는 투표를 할 수 없습니다.'); + return; + } + + if (writer.nickname === loggedInfo.userInfo?.nickname) { + openToast('내가 쓴 글에는 투표를 할 수 없습니다.'); + return; + } + + if (voteInfo.selectedOptionId === newOptionId) return; + + if (voteInfo.selectedOptionId === POST.NOT_VOTE) { + createVote(newOptionId); + return; + } + + editVote({ + originOptionId: voteInfo.selectedOptionId, + newOptionId, + }); + }; + + const handleLinkClick = (e: MouseEvent) => { + if (!isPreview) e.preventDefault(); + }; + + useEffect(() => { + if (isCreateError && createError instanceof Error) { + openToast(createError.message); + } + }, [isCreateError, createError]); + + useEffect(() => { + if (isEditError && editError instanceof Error) { + openToast(editError.message); + } + }, [isEditError, editError]); + + const checkIncludeImage = () => { + if (imageUrl !== '') return true; + + return voteInfo.options.map(option => option.imageUrl).some(url => url !== ''); + }; + + const isPreviewTabIndex = isPreview ? undefined : 0; + + return ( + + + category.name).join('|')}`} + > + {category.map(category => category.name).join(' | ')} + + {isPreview && checkIncludeImage() && ( + + + + )} + + + {title} + + + + {writer.nickname} + + + + {convertTimeToWord(createTime)} + + + {isActive ? convertTimeToWord(deadline) : '마감 완료'} + + + + + {content} + + {!isPreview && imageUrl && ( + + )} + + + {isToastOpen && ( + + {toastMessage} + + )} + + ); +} diff --git a/frontend/src/components/common/Post/mockData.ts b/frontend/src/components/common/Post/mockData.ts new file mode 100644 index 000000000..0395adeb1 --- /dev/null +++ b/frontend/src/components/common/Post/mockData.ts @@ -0,0 +1,127 @@ +import { PostInfo } from '@type/post'; + +export const MOCK_NOT_VOTE_POST: PostInfo = { + postId: 1111111, + title: + '어느 곳에서 정보를 찾아야 할지도 막막한 사람들을 위한, 심심풀이로 나의 취향과 남의 취향을 비교해보고 싶은 사람들을 위한 프로젝트', + writer: { + id: 12121221, + nickname: '우아한 잔치국수', + }, + content: + '이는 사람들에게 재미와 정보, 두 가지를 줄 수 있습니다. 사람들은 MBTI, 밸런스게임처럼 나와 같은 사람들을 찾고, 나와 다른 사람들과 비교하는 것을 즐깁니다. 이를 컨텐츠화하여 보다 빠르게 질문하고 답변하며, 사람들의 반응을 확인할 수 있다면 사람들은 충분한 즐거움을 느낄 것입니다. 또한 20대가 많은 대학가에 창업을 하고 싶지만 20대의 의견을 모르겠을 때, 확실한 답은 아닐지라도 어느 정도의 가이드를 줄 수 있을 것입니다. 질문자에게 제공되는 성별/나이대별 투표 결과 정보는 질문자가 하고자 하는 의사결정의 근거가 될 수 있을 것입니다.', + imageUrl: '', + category: [ + { + id: 76767, + name: '개발', + }, + { + id: 74632, + name: '연애', + }, + { + id: 71347, + name: '상담', + }, + ], + createTime: '2023-07-12 12:40', + deadline: '2023-07-20 18:40', + voteInfo: { + selectedOptionId: 0, + allPeopleCount: 100, + options: [ + { + id: 12312, + text: '당선', + peopleCount: -1, + percent: -1, + imageUrl: '', + }, + { + id: 12314, + text: 'votogether', + peopleCount: -1, + percent: -1, + imageUrl: '', + }, + { + id: 123152, + text: '블라인드와 같은 SNS의 형식을 차용합니다. 누군가는 글을 쓰고, 누군가는 반응합니다. 다만, 댓글은 없습니다. 투표로 자신의 의견을 표현하고 이를 사람들에게 보여줍니다.', + peopleCount: -1, + percent: -1, + imageUrl: '', + }, + { + id: 123122, + text: 'fun from choice, 오늘도 즐거운 한 표 ', + imageUrl: 'https://source.unsplash.com/random', + peopleCount: -1, + percent: -1, + }, + ], + }, +}; + +export const MOCK_VOTE_POST: PostInfo = { + postId: 1111112, + title: + '어느 곳에서 정보를 찾아야 할지도 막막한 사람들을 위한, 심심풀이로 나의 취향과 남의 취향을 비교해보고 싶은 사람들을 위한 프로젝트', + writer: { + id: 12121221, + nickname: '우아한 잔치국수', + }, + content: + '이는 사람들에게 재미와 정보, 두 가지를 줄 수 있습니다. 사람들은 MBTI, 밸런스게임처럼 나와 같은 사람들을 찾고, 나와 다른 사람들과 비교하는 것을 즐깁니다. 이를 컨텐츠화하여 보다 빠르게 질문하고 답변하며, 사람들의 반응을 확인할 수 있다면 사람들은 충분한 즐거움을 느낄 것입니다. 또한 20대가 많은 대학가에 창업을 하고 싶지만 20대의 의견을 모르겠을 때, 확실한 답은 아닐지라도 어느 정도의 가이드를 줄 수 있을 것입니다. 질문자에게 제공되는 성별/나이대별 투표 결과 정보는 질문자가 하고자 하는 의사결정의 근거가 될 수 있을 것입니다.', + imageUrl: '', + category: [ + { + id: 76767, + name: '개발', + }, + { + id: 74632, + name: '연애', + }, + { + id: 71347, + name: '상담', + }, + ], + createTime: '2023-07-12 12:40', + deadline: '2023-07-21 18:40', + voteInfo: { + selectedOptionId: 12312, + allPeopleCount: 123, + options: [ + { + id: 12312, + text: '당선', + peopleCount: 30, + imageUrl: '', + percent: 30, + }, + { + id: 12314, + text: 'votogether', + peopleCount: 40, + imageUrl: '', + percent: 40, + }, + { + id: 123152, + text: '인스타그램, 블라인드와 같은 SNS의 형식을 차용합니다. 누군가는 글을 쓰고, 누군가는 반응합니다. 다만, 댓글은 없습니다. 투표로 자신의 의견을 표현하고 이를 사람들에게 보여줍니다.', + peopleCount: 20, + imageUrl: '', + percent: 20, + }, + { + id: 123122, + text: 'fun from choice, 오늘도 즐거운 한 표 ', + imageUrl: 'https://source.unsplash.com/random', + peopleCount: 10, + percent: 10, + }, + ], + }, +}; diff --git a/frontend/src/components/common/Post/style.ts b/frontend/src/components/common/Post/style.ts new file mode 100644 index 000000000..df7a5c798 --- /dev/null +++ b/frontend/src/components/common/Post/style.ts @@ -0,0 +1,134 @@ +import { Link } from 'react-router-dom'; + +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.li` + width: 100%; + + position: relative; + + font: var(--text-small); + letter-spacing: 0.5px; + line-height: 1.5; + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-caption); + } +`; + +export const Category = styled.span` + font: var(--text-small); + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-caption); + } +`; + +export const ImageIconWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + + width: 15px; + height: 15px; + border-radius: 50%; + + position: absolute; + right: 25px; + top: 0; + + background-color: var(--header); +`; + +export const ImageIcon = styled.img` + width: 13px; + height: 13px; +`; + +export const ActivateState = styled.div<{ $isActive: boolean }>` + width: 15px; + height: 15px; + border-radius: 50%; + + position: absolute; + right: 0; + top: 0; + + background-color: ${({ $isActive }) => ($isActive ? 'var(--active-post)' : 'var(--dark-gray)')}; +`; + +export const Title = styled.p<{ $isPreview: boolean }>` + display: -webkit-box; + + font: var(--text-title); + text-overflow: ellipsis; + word-break: break-word; + + overflow: hidden; + + -webkit-line-clamp: ${props => props.$isPreview && '2'}; + -webkit-box-orient: vertical; + + @media (min-width: ${theme.breakpoint.sm}) { + font-size: 2.2rem; + } +`; + +export const Wrapper = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + + font: var(--text-small); + + & > :nth-child(2) { + margin-left: 10px; + } + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-caption); + } +`; + +export const Content = styled.p<{ $isPreview: boolean }>` + display: -webkit-box; + + margin: 10px 0; + + font: var(--text-caption); + text-overflow: ellipsis; + word-break: break-word; + white-space: pre-wrap; + + overflow: hidden; + + -webkit-line-clamp: ${props => props.$isPreview && '10'}; + -webkit-box-orient: vertical; + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-body); + } +`; + +export const DetailLink = styled(Link)<{ $isPreview: boolean }>` + display: flex; + flex-direction: column; + gap: 10px; + + pointer-events: ${({ $isPreview }) => !$isPreview && 'none'}; +`; + +export const Image = styled.img` + width: 100%; + border-radius: 4px; + margin-bottom: 10px; + + aspect-ratio: 1/1; + object-fit: cover; + + @media (min-width: ${theme.breakpoint.md}) { + margin-bottom: 20px; + } +`; diff --git a/frontend/src/components/common/PostMenu/PostMenu.stories.tsx b/frontend/src/components/common/PostMenu/PostMenu.stories.tsx new file mode 100644 index 000000000..8caeabbb8 --- /dev/null +++ b/frontend/src/components/common/PostMenu/PostMenu.stories.tsx @@ -0,0 +1,21 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { PostMenuItem } from '@type/menu'; + +import PostMenu from '.'; + +const meta: Meta = { + component: PostMenu, +}; + +export default meta; +type Story = StoryObj; + +const menuList: PostMenuItem[] = [ + { color: 'black', content: '닉네임 신고', action: 'NICKNAME_REPORT' }, + { color: 'black', content: '게시글 신고', action: 'POST_REPORT' }, +]; + +export const Default: Story = { + render: () => {}} />, +}; diff --git a/frontend/src/components/common/PostMenu/index.tsx b/frontend/src/components/common/PostMenu/index.tsx new file mode 100644 index 000000000..cc9cd296b --- /dev/null +++ b/frontend/src/components/common/PostMenu/index.tsx @@ -0,0 +1,30 @@ +import { MouseEvent } from 'react'; + +import { PostAction, PostMenuItem } from '@type/menu'; + +import * as S from './style'; + +interface PostMenuProps { + menuList: PostMenuItem[]; + handleMenuClick: (menu: PostAction) => void; +} + +export default function PostMenu({ menuList, handleMenuClick }: PostMenuProps) { + return ( + + {menuList.map(({ content, color, action }) => ( + { + event.stopPropagation(); + handleMenuClick(action); + }} + > + {content} + + ))} + + ); +} diff --git a/frontend/src/components/common/PostMenu/style.ts b/frontend/src/components/common/PostMenu/style.ts new file mode 100644 index 000000000..7fb0b182b --- /dev/null +++ b/frontend/src/components/common/PostMenu/style.ts @@ -0,0 +1,45 @@ +import { styled } from 'styled-components'; + +import { MenuColor } from '@type/menu'; + +const COLOR_PALETTE: Record = { + red: 'var(--primary-color)', + black: '#727171', +}; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + width: max-content; + border: 1px solid rgba(0, 0, 0, 0.4); + border-radius: 6px; + + background-color: var(--white); + + font: var(--text-caption); +`; + +export const Menu = styled.button<{ $color: MenuColor }>` + padding: 10px 15px; + border-bottom: 1px solid rgba(0, 0, 0, 0.4); + + color: ${({ $color }) => COLOR_PALETTE[$color]}; + background-color: white; + + cursor: pointer; + + &:hover { + background-color: var(--gray); + } + + &:first-child { + border-radius: 6px 6px 0 0; + } + + &:last-child { + border-radius: 0 0 6px 6px; + border-bottom: none; + } +`; diff --git a/frontend/src/components/common/ScrollToTop/index.tsx b/frontend/src/components/common/ScrollToTop/index.tsx new file mode 100644 index 000000000..91c3b45bb --- /dev/null +++ b/frontend/src/components/common/ScrollToTop/index.tsx @@ -0,0 +1,14 @@ +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +import { defaultScrollToTop } from '@utils/scrollToTop'; + +export default function ScrollToTop() { + const { pathname } = useLocation(); + + useEffect(() => { + defaultScrollToTop(); + }, [pathname]); + + return null; +} diff --git a/frontend/src/components/common/SearchBar/SearchBar.stories.tsx b/frontend/src/components/common/SearchBar/SearchBar.stories.tsx new file mode 100644 index 000000000..f9c14b8b4 --- /dev/null +++ b/frontend/src/components/common/SearchBar/SearchBar.stories.tsx @@ -0,0 +1,26 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import SearchBar from '.'; + +const meta: Meta = { + component: SearchBar, +}; + +export default meta; +type Story = StoryObj; + +export const SizeSm: Story = { + render: () => , +}; + +export const SizeMd: Story = { + render: () => , +}; + +export const SizeLg: Story = { + render: () => , +}; + +export const SizeFree: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/SearchBar/index.tsx b/frontend/src/components/common/SearchBar/index.tsx new file mode 100644 index 000000000..17e9861ff --- /dev/null +++ b/frontend/src/components/common/SearchBar/index.tsx @@ -0,0 +1,48 @@ +import { HTMLAttributes } from 'react'; + +import { Size } from '@type/style'; + +import { useCurrentKeyword } from '@hooks/useCurrentKeyword'; +import { useSearch } from '@hooks/useSearch'; + +import { PATH } from '@constants/path'; +import { SEARCH_KEYWORD, SEARCH_KEYWORD_MAX_LENGTH } from '@constants/post'; + +import searchIcon from '@assets/search_black.svg'; + +import * as S from './style'; + +interface SearchBarProps extends HTMLAttributes { + size: Size | 'free'; + isOpen?: boolean; +} + +export default function SearchBar({ size, isOpen, ...rest }: SearchBarProps) { + const { currentKeyword } = useCurrentKeyword(); + const { keyword, handleKeywordChange, handleSearchSubmit, searchInputRef } = + useSearch(currentKeyword); + + return ( + + + + 검색버튼 + + {isOpen && ( + + 검색창을 닫으려면 검색창 외부를 클릭해주세요. + + )} + + ); +} diff --git a/frontend/src/components/common/SearchBar/style.ts b/frontend/src/components/common/SearchBar/style.ts new file mode 100644 index 000000000..c3a7a258a --- /dev/null +++ b/frontend/src/components/common/SearchBar/style.ts @@ -0,0 +1,52 @@ +import { styled } from 'styled-components'; + +import { Size } from '@type/style'; + +interface SearchBarProps { + size: Size | 'free'; +} + +const formSize = { + sm: '170px', + md: '250px', + lg: '400px', +}; + +export const Form = styled.form` + display: flex; + align-items: center; + justify-content: space-between; + gap: 5px; + + width: ${props => (props.size === 'free' ? '100%' : formSize[props.size])}; + height: 36px; + padding: 5px 10px; + border-radius: 5px; + + background-color: #cccccc; + color: red; + + font-size: 1rem; +`; + +export const Input = styled.input` + width: 100%; + height: 100%; + outline: 0; + + background-color: rgba(0, 0, 0, 0); + + font: var(--text-caption); + letter-spacing: 1px; +`; + +export const Button = styled.button` + background-color: rgba(0, 0, 0, 0); + + cursor: pointer; +`; + +export const ScreenReaderDirection = styled.p` + position: absolute; + left: -9999px; +`; diff --git a/frontend/src/components/common/Select/Select.stories.tsx b/frontend/src/components/common/Select/Select.stories.tsx new file mode 100644 index 000000000..f55a5fa1f --- /dev/null +++ b/frontend/src/components/common/Select/Select.stories.tsx @@ -0,0 +1,82 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { useState } from 'react'; + +import Select from '.'; + +const meta: Meta = { + component: Select, + decorators: [storyFn =>
{storyFn()}
], +}; + +export default meta; +type Story = StoryObj; + +const postStatus = ['all', 'progress', 'closed'] as const; +const sortingOption = ['popular', 'latest', 'longLong'] as const; + +type PostStatusType = (typeof postStatus)[number]; +type SortingOptionType = (typeof sortingOption)[number]; + +const MOCK_STATUS_OPTION: Record = { + all: '전체', + progress: '진행중', + closed: '마감완료', +}; + +const MOCK_SORTING_OPTION: Record = { + popular: '인기순', + latest: '최신순', + longLong: '엄청나게 긴 옵션', +}; + +export const PostStatus: Story = { + render: () => ( + + aria-label="게시글 진행 상태 선택" + selectedOption="진행중" + optionList={MOCK_STATUS_OPTION} + handleOptionChange={() => {}} + /> + ), +}; + +export const Sorting: Story = { + render: () => ( + {}} + /> + ), +}; + +export const SelectExample = () => { + const [selectedOption, setSelectedOption] = useState('popular'); + + const handelOptionChange = (option: SortingOptionType) => { + setSelectedOption(option); + }; + + return ( + + aria-label="게시글 정렬 방법 선택" + selectedOption={MOCK_SORTING_OPTION[selectedOption]} + optionList={MOCK_SORTING_OPTION} + handleOptionChange={handelOptionChange} + /> + ); +}; diff --git a/frontend/src/components/common/Select/constants.ts b/frontend/src/components/common/Select/constants.ts new file mode 100644 index 000000000..e95387f4a --- /dev/null +++ b/frontend/src/components/common/Select/constants.ts @@ -0,0 +1,3 @@ +export const SELECT_SELECTED = 'selected'; +export const SELECT_DISABLED = 'disabled'; +export const SELECT_DEFAULT = 'default'; diff --git a/frontend/src/components/common/Select/index.tsx b/frontend/src/components/common/Select/index.tsx new file mode 100644 index 000000000..061dc571f --- /dev/null +++ b/frontend/src/components/common/Select/index.tsx @@ -0,0 +1,77 @@ +import React, { useState } from 'react'; + +import chevronDown from '@assets/chevron-down.svg'; +import chevronUp from '@assets/chevron-up.svg'; + +import { SELECT_DEFAULT, SELECT_DISABLED, SELECT_SELECTED } from './constants'; +import * as S from './style'; + +export interface SelectProps + extends React.ButtonHTMLAttributes { + selectedOption: string; + optionList: Record; + handleOptionChange: (option: T) => void; + isDisabled?: boolean; +} + +export default function Select({ + selectedOption, + optionList, + handleOptionChange, + isDisabled = false, + ...rest +}: SelectProps) { + const optionKeyList = Object.keys(optionList) as T[]; + const [isOpen, setIsOpen] = useState(false); + + const toggleOpen = () => { + if (isDisabled) return; + setIsOpen(prev => !prev); + }; + + const handleSelectClick = (option: T) => { + handleOptionChange(option); + setIsOpen(false); + }; + + const getSelectStatus = () => { + if (isDisabled) { + return SELECT_DISABLED; + } + + if (isOpen) { + return SELECT_SELECTED; + } + + return SELECT_DEFAULT; + }; + + return ( + + + {selectedOption} + + + {isOpen && ( + + 이 요소를 닫으려면 한번 더 클릭해주세요. + + )} + {isOpen && ( + + + {optionKeyList.map((optionKey: T) => ( + handleSelectClick(optionKey)} + > + {optionList[optionKey]} + + ))} + + + )} + + ); +} diff --git a/frontend/src/components/common/Select/style.ts b/frontend/src/components/common/Select/style.ts new file mode 100644 index 000000000..01c4650e2 --- /dev/null +++ b/frontend/src/components/common/Select/style.ts @@ -0,0 +1,91 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +import { SELECT_DEFAULT, SELECT_DISABLED, SELECT_SELECTED } from './constants'; + +export const Container = styled.div` + font: var(--text-caption); + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-body); + } +`; + +const SELECTED_CSS_OPTION = { + selected: { + border: '2px solid #60a5fa', + color: 'var(--slate)', + cursor: 'pointer', + }, + disabled: { + border: '1px solid var(--slate)', + color: 'var(--slate)', + cursor: 'not-allowed', + }, + default: { + border: '1px solid var(--slate)', + color: '', + cursor: 'pointer', + }, +}; + +type Status = typeof SELECT_DEFAULT | typeof SELECT_DISABLED | typeof SELECT_SELECTED; + +export const SelectedContainer = styled.button<{ $status: Status }>` + display: flex; + justify-content: space-between; + align-items: center; + + width: 100%; + padding: 8px; + border: ${({ $status }) => SELECTED_CSS_OPTION[$status].border}; + border-radius: 4px; + + font: inherit; + + color: ${({ $status }) => SELECTED_CSS_OPTION[$status].color}; + + cursor: ${({ $status }) => SELECTED_CSS_OPTION[$status].cursor}; +`; + +export const OptionListParent = styled.div` + position: relative; + + z-index: ${theme.zIndex.select}; +`; + +export const OptionListContainer = styled.div` + display: flex; + flex-direction: column; + + width: 100%; + border: 1px solid var(--slate); + border-radius: 4px; + + position: absolute; + top: 4px; + + background-color: var(--white); +`; + +export const OptionContainer = styled.div` + padding: 8px; + + cursor: pointer; + &:hover { + background-color: rgba(0, 0, 0, 0.1); + } +`; + +export const Image = styled.img<{ $isSelected: boolean }>` + width: 20px; + height: 20px; + border-left: 1px solid var(--slate); + padding-left: 8px; +`; + +export const ScreenReaderDirection = styled.p` + position: absolute; + left: -9999px; +`; diff --git a/frontend/src/components/common/Skeleton/Skeleton.stories.tsx b/frontend/src/components/common/Skeleton/Skeleton.stories.tsx new file mode 100644 index 000000000..8d502b741 --- /dev/null +++ b/frontend/src/components/common/Skeleton/Skeleton.stories.tsx @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import Skeleton from '.'; + +const meta: Meta = { + component: Skeleton, +}; + +export default meta; +type Story = StoryObj; + +export const FirstBoxLarge: Story = { + render: () => , +}; + +export const FirstBoxSmall: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/Skeleton/index.tsx b/frontend/src/components/common/Skeleton/index.tsx new file mode 100644 index 000000000..2b23579a6 --- /dev/null +++ b/frontend/src/components/common/Skeleton/index.tsx @@ -0,0 +1,15 @@ +import * as S from './style'; + +interface SkeletonProps { + isLarge: boolean; +} + +export default function Skeleton({ isLarge }: SkeletonProps) { + return ( + + + + + + ); +} diff --git a/frontend/src/components/common/Skeleton/style.ts b/frontend/src/components/common/Skeleton/style.ts new file mode 100644 index 000000000..98f6cef01 --- /dev/null +++ b/frontend/src/components/common/Skeleton/style.ts @@ -0,0 +1,47 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + gap: 9px; + + @media (min-width: ${theme.breakpoint.sm}) { + gap: 12px; + } +`; + +const Box = styled.div` + border-radius: 4px; + + background-color: #eee; + background: linear-gradient(110deg, #ececec 8%, #f5f5f5 18%, #ececec 33%); + background-size: 200% 100%; + + animation: 1.7s ${theme.animation.skeletonGradientWave} linear infinite; +`; + +export const FirstBox = styled(Box)<{ $isLarge: boolean }>` + height: ${props => (props.$isLarge ? '40vh' : '30vh')}; + + @media (min-width: ${theme.breakpoint.sm}) { + height: ${props => (props.$isLarge ? '44vh' : '34vh')}; + } +`; + +export const SecondBox = styled(Box)` + height: 4vh; + + @media (min-width: ${theme.breakpoint.sm}) { + height: 6vh; + } +`; + +export const ThirdBox = styled(Box)` + height: 2vh; + + @media (min-width: ${theme.breakpoint.sm}) { + height: 4vh; + } +`; diff --git a/frontend/src/components/common/SnackBar/SnackBar.stories.tsx b/frontend/src/components/common/SnackBar/SnackBar.stories.tsx new file mode 100644 index 000000000..bc51714cb --- /dev/null +++ b/frontend/src/components/common/SnackBar/SnackBar.stories.tsx @@ -0,0 +1,176 @@ +import type { Meta } from '@storybook/react'; + +import { useToggle } from '@hooks/useToggle'; + +import SnackBar from '.'; + +const meta: Meta = { + component: SnackBar, +}; + +export default meta; + +const buttonStyle = { display: 'block', margin: '10px', background: 'gray', cursor: 'pointer' }; + +export const SizeCase = () => { + const { + isOpen: isSmOpen, + openComponent: openSmComponent, + closeComponent: closeSmComponent, + } = useToggle(); + const { + isOpen: isMdOpen, + openComponent: openMdComponent, + closeComponent: closeMdComponent, + } = useToggle(); + const { + isOpen: isLgOpen, + openComponent: openLgComponent, + closeComponent: closeLgComponent, + } = useToggle(); + const { + isOpen: isFreeOpen, + openComponent: openFreeComponent, + closeComponent: closeFreeComponent, + } = useToggle(); + + const handleSmButtonClick = () => { + openSmComponent(); + }; + + const handleMdButtonClick = () => { + openMdComponent(); + }; + + const handleLgButtonClick = () => { + openLgComponent(); + }; + + const handleFreeButtonClick = () => { + openFreeComponent(); + }; + + const handleSmCancelClick = () => { + closeSmComponent(); + }; + + const handleMdCancelClick = () => { + closeMdComponent(); + }; + + const handleLgCancelClick = () => { + closeLgComponent(); + }; + + const handleFreeCancelClick = () => { + closeFreeComponent(); + }; + + return ( + <> + + {isSmOpen && ( + + 게시물이 삭제되었습니다. + + + )} + + {isMdOpen && ( + + 게시물이 삭제되었습니다. + + + )} + + {isLgOpen && ( + + 게시물이 삭제되었습니다. + + + + )} + + {isFreeOpen && ( + + 게시물이 삭제되었습니다. + + + )} + + ); +}; + +export const PositionCase = () => { + const { + isOpen: isTopOpen, + openComponent: openTopComponent, + closeComponent: closeTopComponent, + } = useToggle(); + const { + isOpen: isBottomOpen, + openComponent: openBottomComponent, + closeComponent: closeBottomComponent, + } = useToggle(); + + const handleTopButtonClick = () => { + openTopComponent(); + }; + + const handleBottomButtonClick = () => { + openBottomComponent(); + }; + + const handleTopCancelClick = () => { + closeTopComponent(); + }; + + const handleBottomCancelClick = () => { + closeBottomComponent(); + }; + + return ( + <> + + {isTopOpen && ( + + 게시물이 삭제되었습니다. + + + )} + + {isBottomOpen && ( + + 게시물이 삭제되었습니다. + + + )} + + ); +}; diff --git a/frontend/src/components/common/SnackBar/index.tsx b/frontend/src/components/common/SnackBar/index.tsx new file mode 100644 index 000000000..508df52e3 --- /dev/null +++ b/frontend/src/components/common/SnackBar/index.tsx @@ -0,0 +1,21 @@ +import { ReactNode } from 'react'; + +import { Size } from '@type/style'; + +import * as S from './style'; + +interface SnackBarProps { + children: ReactNode; + size: Size | 'free'; + position: 'top' | 'bottom'; +} + +export default function SnackBar({ children, size, position }: SnackBarProps) { + return ( + + + {children} + + + ); +} diff --git a/frontend/src/components/common/SnackBar/style.ts b/frontend/src/components/common/SnackBar/style.ts new file mode 100644 index 000000000..3d8fa959f --- /dev/null +++ b/frontend/src/components/common/SnackBar/style.ts @@ -0,0 +1,51 @@ +import { keyframes, styled } from 'styled-components'; + +import { Size } from '@type/style'; + +import { theme } from '@styles/theme'; + +import { POSITION, SQUARE_SIZE } from '../ToastNSnackBarStyle'; + +const fadeInAnimation = keyframes` + 0%{ + opacity: 0; + } + 100% { + opacity: 1; + } +`; + +export const Wrapper = styled.div<{ $position: 'top' | 'bottom' }>` + display: grid; + grid-template-columns: 1fr; + grid-template-rows: ${props => POSITION[props.$position]}; + align-items: end; + justify-items: center; + + width: 100vw; + height: 100vh; + + position: fixed; + top: 0; + left: 0; +`; + +export const Content = styled.div<{ $size: Size | 'free'; $isOpen: boolean }>` + display: flex; + align-items: center; + justify-content: center; + + width: ${props => SQUARE_SIZE[props.$size].width}; + height: ${props => SQUARE_SIZE[props.$size].height}; + border: 2px solid var(--primary-color); + border-radius: 4px; + + background-color: var(--white); + + font: var(--text-caption); + letter-spacing: 1px; + + animation: ${fadeInAnimation} ease-in-out 0.3s; + + z-index: ${theme.zIndex.modal}; +`; diff --git a/frontend/src/components/common/SquareButton/SquareButton.stories.tsx b/frontend/src/components/common/SquareButton/SquareButton.stories.tsx new file mode 100644 index 000000000..843b5337c --- /dev/null +++ b/frontend/src/components/common/SquareButton/SquareButton.stories.tsx @@ -0,0 +1,23 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import SquareButton from '.'; + +const meta: Meta = { + component: SquareButton, + decorators: [storyFn =>
{storyFn()}
], +}; + +export default meta; +type Story = StoryObj; + +export const ColorBlank: Story = { + render: () => 확 인, +}; + +export const ColorFill: Story = { + render: () => 버 튼, +}; + +export const ColorGray: Story = { + render: () => 버 튼, +}; diff --git a/frontend/src/components/common/SquareButton/index.tsx b/frontend/src/components/common/SquareButton/index.tsx new file mode 100644 index 000000000..40fb9f991 --- /dev/null +++ b/frontend/src/components/common/SquareButton/index.tsx @@ -0,0 +1,20 @@ +import { ButtonHTMLAttributes, ReactNode } from 'react'; + +import * as S from './style'; + +interface SquareButtonProps extends ButtonHTMLAttributes { + theme: 'blank' | 'fill' | 'gray'; + children: ReactNode; +} + +/* 마감시간, 확인, 취소 등 사용될 버튼 + * 부모에서 크기를 조절, 내용(children) 전달 + * props로 테마를 받음 + */ +export default function SquareButton({ theme, children, ...rest }: SquareButtonProps) { + return ( + + {children} + + ); +} diff --git a/frontend/src/components/common/SquareButton/style.ts b/frontend/src/components/common/SquareButton/style.ts new file mode 100644 index 000000000..a94729808 --- /dev/null +++ b/frontend/src/components/common/SquareButton/style.ts @@ -0,0 +1,45 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +interface ButtonProps { + $theme: 'blank' | 'fill' | 'gray'; +} + +const BORDER_THEME: Record = { + fill: 'var(--primary-color)', + blank: 'var(--primary-color)', + gray: '#67727E', +}; + +const TEXT_THEME: Record = { + fill: 'white', + blank: 'var(--primary-color)', + gray: 'white', +}; + +const BACKGROUND_THEME: Record = { + fill: 'var(--primary-color)', + blank: 'white', + gray: '#67727E', +}; + +export const Button = styled.button` + display: block; + + width: 100%; + height: 100%; + border: 2px solid ${({ $theme }) => BORDER_THEME[$theme]}; + border-radius: 8px; + + color: ${({ $theme }) => TEXT_THEME[$theme]}; + background-color: ${({ $theme }) => BACKGROUND_THEME[$theme]}; + + font: var(--text-caption); + + cursor: pointer; + + @media (min-width: ${theme.breakpoint.sm}) { + font: var(--text-body); + } +`; diff --git a/frontend/src/components/common/TagButton/TagButton.stories.tsx b/frontend/src/components/common/TagButton/TagButton.stories.tsx new file mode 100644 index 000000000..462311e62 --- /dev/null +++ b/frontend/src/components/common/TagButton/TagButton.stories.tsx @@ -0,0 +1,23 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import TagButton from '.'; + +const meta: Meta = { + component: TagButton, + decorators: [storyFn =>
{storyFn()}
], +}; + +export default meta; +type Story = StoryObj; + +export const SizeS: Story = { + render: () => 조기마감, +}; + +export const sizeM: Story = { + render: () => 조기마감, +}; + +export const sizeL: Story = { + render: () => 조기마감, +}; diff --git a/frontend/src/components/common/TagButton/index.tsx b/frontend/src/components/common/TagButton/index.tsx new file mode 100644 index 000000000..ee14da1ae --- /dev/null +++ b/frontend/src/components/common/TagButton/index.tsx @@ -0,0 +1,17 @@ +import { ButtonHTMLAttributes } from 'react'; + +import { Size } from '@type/style'; + +import * as S from './style'; + +interface TagButtonProps extends ButtonHTMLAttributes { + size: Size; +} + +export default function TagButton({ size, ...rest }: TagButtonProps) { + return ( + + {rest.children} + + ); +} diff --git a/frontend/src/components/common/TagButton/style.ts b/frontend/src/components/common/TagButton/style.ts new file mode 100644 index 000000000..e2860e248 --- /dev/null +++ b/frontend/src/components/common/TagButton/style.ts @@ -0,0 +1,28 @@ +import { styled } from 'styled-components'; + +import { Size } from '@type/style'; + +interface ButtonProps { + $size: Size; +} + +const SIZE = { + sm: { width: '80px', height: '40px', fontSize: '14px' }, + md: { width: '100px', height: '50px', fontSize: '20px' }, + lg: { width: '120px', height: '60px', fontSize: '24px' }, +}; + +export const Button = styled.button` + display: block; + + width: ${props => SIZE[props.$size].width}; + height: ${props => SIZE[props.$size].height}; + border-radius: 0 0 5px 5px; + + background-color: var(--primary-color); + color: var(--white); + + font-size: ${props => SIZE[props.$size].fontSize}; + + cursor: pointer; +`; diff --git a/frontend/src/components/common/TimePickerOptionList/TimePickerOption/TimePickerOption.stories.tsx b/frontend/src/components/common/TimePickerOptionList/TimePickerOption/TimePickerOption.stories.tsx new file mode 100644 index 000000000..c0cedaf48 --- /dev/null +++ b/frontend/src/components/common/TimePickerOptionList/TimePickerOption/TimePickerOption.stories.tsx @@ -0,0 +1,33 @@ +import type { Meta } from '@storybook/react'; + +import { useState } from 'react'; + +import TimePickerOption from '.'; + +const meta: Meta = { + component: TimePickerOption, +}; + +export default meta; + +export const Default = () => { + const [time, setTime] = useState({ + day: 0, + hour: 5, + minute: 0, + }); + + const updateTime = (option: string, updatedTime: number) => { + setTime(prev => ({ + ...prev, + [option]: updatedTime, + })); + }; + + return ( + <> + +

시간 단위: {time.hour}

+ + ); +}; diff --git a/frontend/src/components/common/TimePickerOptionList/TimePickerOption/constants.ts b/frontend/src/components/common/TimePickerOptionList/TimePickerOption/constants.ts new file mode 100644 index 000000000..db46ae862 --- /dev/null +++ b/frontend/src/components/common/TimePickerOptionList/TimePickerOption/constants.ts @@ -0,0 +1,13 @@ +export const TIMEBOX_CHILD_HEIGHT = 33; + +export const TIME_UNIT: { [key: string]: number } = { + day: 3, + hour: 24, + minute: 60, +}; + +export const TIME_KOREAN: { [key: string]: string } = { + day: '일', + hour: '시간', + minute: '분', +}; diff --git a/frontend/src/components/common/TimePickerOptionList/TimePickerOption/index.tsx b/frontend/src/components/common/TimePickerOptionList/TimePickerOption/index.tsx new file mode 100644 index 000000000..7ba762860 --- /dev/null +++ b/frontend/src/components/common/TimePickerOptionList/TimePickerOption/index.tsx @@ -0,0 +1,91 @@ +import React, { useEffect, useRef } from 'react'; + +import { TIME_KOREAN, TIME_UNIT, TIMEBOX_CHILD_HEIGHT } from './constants'; +import * as S from './style'; + +interface TimePickerOptionProps { + currentTime: number; + option: string; + handlePickTime: (option: string, updatedTime: number) => void; +} + +export default function TimePickerOption({ + currentTime, + option, + handlePickTime, +}: TimePickerOptionProps) { + const timeUnit = TIME_UNIT[option]; + const timeBoxRef = useRef(null); + const timeBoxChildRef = useRef(null); + + const handleScroll = () => { + const timeBox = timeBoxRef.current; + + if (!timeBox) return; + + const pickedTimeIndex = Math.round(timeBox.scrollTop / TIMEBOX_CHILD_HEIGHT); + + if (pickedTimeIndex >= 0 && pickedTimeIndex < timeUnit) { + handlePickTime(option, pickedTimeIndex); + } + }; + + const handleWheel = (event: React.WheelEvent) => { + const timeBox = timeBoxRef.current; + + if (!timeBox) return; + + if (event.deltaY > 0) { + timeBox.scrollTop += TIMEBOX_CHILD_HEIGHT; + } + if (event.deltaY < 0) { + timeBox.scrollTop -= TIMEBOX_CHILD_HEIGHT; + } + }; + + useEffect(() => { + const timeBox = timeBoxRef.current; + + if (!timeBox) return; + + const cancelWheel = (e: WheelEvent) => e.preventDefault(); + timeBox.addEventListener('wheel', cancelWheel, { passive: false }); + + return () => timeBox.removeEventListener('wheel', cancelWheel); + }, []); + + useEffect(() => { + const timeBox = timeBoxRef.current; + const timeBoxChild = timeBoxChildRef.current; + if (!timeBox || !timeBoxChild) return; + + timeBox.scrollTop = timeBoxChild.offsetHeight * currentTime; + }, []); + + return ( + + + + + {Array.from({ length: timeUnit }).map((_, index) => ( + + {index} + + ))} + + + + ); +} diff --git a/frontend/src/components/common/TimePickerOptionList/TimePickerOption/style.ts b/frontend/src/components/common/TimePickerOptionList/TimePickerOption/style.ts new file mode 100644 index 000000000..c94a2613d --- /dev/null +++ b/frontend/src/components/common/TimePickerOptionList/TimePickerOption/style.ts @@ -0,0 +1,60 @@ +import { styled } from 'styled-components'; + +import { TIMEBOX_CHILD_HEIGHT } from './constants'; + +export const Container = styled.div` + width: 33.3%; + height: 99px; + + position: relative; +`; + +export const TimeBox = styled.div` + width: 100%; + height: ${`${TIMEBOX_CHILD_HEIGHT * 3}px`}; + + position: absolute; + + background-color: rgba(0, 0, 0, 0); + + text-align: center; + + overflow: auto; + + &::-webkit-scrollbar { + display: none; + } + + -ms-overflow-style: none; + scrollbar-width: none; +`; + +export const PickedTimeOverlay = styled.div` + width: 100%; + height: 33px; + + position: absolute; + top: 33%; + + background-color: rgba(128, 128, 128, 0.2); + + z-index: -1; +`; + +export const Time = styled.div<{ $isPicked: boolean }>` + display: flex; + justify-content: center; + align-items: center; + + width: 100%; + height: ${`${TIMEBOX_CHILD_HEIGHT}px`}; + + color: ${props => !props.$isPicked && 'gray'}; + + font: var(--text-body); + font-weight: ${props => (props.$isPicked ? 'bold' : 'light')}; +`; + +export const Empty = styled.div` + height: ${`${TIMEBOX_CHILD_HEIGHT}px`}; +`; diff --git a/frontend/src/components/common/TimePickerOptionList/TimePickerOptionList.stories.tsx b/frontend/src/components/common/TimePickerOptionList/TimePickerOptionList.stories.tsx new file mode 100644 index 000000000..c248c3ec4 --- /dev/null +++ b/frontend/src/components/common/TimePickerOptionList/TimePickerOptionList.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta } from '@storybook/react'; + +import { useState } from 'react'; + +import TimePickerOptionList from '.'; + +const meta: Meta = { + component: TimePickerOptionList, +}; + +export default meta; + +export const Default = () => { + const [time, setTime] = useState({ + day: 1, + hour: 3, + minute: 25, + }); + + return ( + <> + + + ); +}; diff --git a/frontend/src/components/common/TimePickerOptionList/index.tsx b/frontend/src/components/common/TimePickerOptionList/index.tsx new file mode 100644 index 000000000..7969dedf2 --- /dev/null +++ b/frontend/src/components/common/TimePickerOptionList/index.tsx @@ -0,0 +1,46 @@ +import React, { Dispatch } from 'react'; + +import * as S from './style'; +import TimePickerOption from './TimePickerOption'; + +interface Time { + day: number; + hour: number; + minute: number; +} + +interface TimePickerOptionListProps { + time: Time; + setTime: Dispatch>; +} + +export default function TimePickerOptionList({ time, setTime }: TimePickerOptionListProps) { + const { day, hour, minute } = time; + + const updateTime = (option: string, updatedTime: number) => { + setTime(prev => ({ + ...prev, + [option]: updatedTime, + })); + }; + + return ( + + + {Object.entries(time).map(([key, value]) => ( + + ))} + + +

{day}일

+

{hour}시

+

{minute}분

후 마감 +
+
+ ); +} diff --git a/frontend/src/components/common/TimePickerOptionList/style.ts b/frontend/src/components/common/TimePickerOptionList/style.ts new file mode 100644 index 000000000..34bd4a401 --- /dev/null +++ b/frontend/src/components/common/TimePickerOptionList/style.ts @@ -0,0 +1,30 @@ +import { styled } from 'styled-components'; + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: start; + align-items: center; + gap: 15px; + + width: 200px; +`; + +export const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + + width: 100%; + border: 1px solid gray; +`; + +export const PickedTimeText = styled.p` + display: flex; + justify-content: start; + align-items: center; + gap: 20px; + + font: var(--text-caption); + font-weight: bold; +`; diff --git a/frontend/src/components/common/Toast/Toast.stories.tsx b/frontend/src/components/common/Toast/Toast.stories.tsx new file mode 100644 index 000000000..7671acb37 --- /dev/null +++ b/frontend/src/components/common/Toast/Toast.stories.tsx @@ -0,0 +1,111 @@ +import type { Meta } from '@storybook/react'; + +import { useToast } from '@hooks/useToast'; + +import Toast from '.'; + +const meta: Meta = { + component: Toast, +}; + +export default meta; + +export const SizeCase = () => { + const { isToastOpen: isSmOpen, openToast: openSmComponent, toastMessage: smMessage } = useToast(); + const { isToastOpen: isMdOpen, openToast: openMdComponent, toastMessage: mdMessage } = useToast(); + const { isToastOpen: isLgOpen, openToast: openLgComponent, toastMessage: lgMessage } = useToast(); + const { + isToastOpen: isFreeOpen, + openToast: openFreeComponent, + toastMessage: freeMessage, + } = useToast(); + + return ( + <> + + {isSmOpen && ( + + {smMessage} + + )} + + {isMdOpen && ( + + {mdMessage} + + )} + + {isLgOpen && ( + + {lgMessage} + + )} + + {isFreeOpen && ( + + {freeMessage} + + )} + + ); +}; + +export const PositionCase = () => { + const { + isToastOpen: isTopOpen, + openToast: openTopComponent, + toastMessage: topMessage, + } = useToast(); + const { + isToastOpen: isBottomOpen, + openToast: openBottomComponent, + toastMessage: bottomMessage, + } = useToast(); + + return ( + <> + + {isTopOpen && ( + + {topMessage} + + )} + + {isBottomOpen && ( + + {bottomMessage} + + )} + + ); +}; diff --git a/frontend/src/components/common/Toast/index.tsx b/frontend/src/components/common/Toast/index.tsx new file mode 100644 index 000000000..5e9160d99 --- /dev/null +++ b/frontend/src/components/common/Toast/index.tsx @@ -0,0 +1,19 @@ +import { Size } from '@type/style'; + +import * as S from './style'; + +interface ToastProps { + children: string; + size: Size | 'free'; + position: 'top' | 'bottom'; +} + +export default function Toast({ children, size, position }: ToastProps) { + return ( + + + {children} + + + ); +} diff --git a/frontend/src/components/common/Toast/style.ts b/frontend/src/components/common/Toast/style.ts new file mode 100644 index 000000000..347cb6ce6 --- /dev/null +++ b/frontend/src/components/common/Toast/style.ts @@ -0,0 +1,44 @@ +import { keyframes, styled } from 'styled-components'; + +import { Size } from '@type/style'; + +import { TOAST_TIME } from '@constants/animation'; + +import { theme } from '@styles/theme'; + +import { POSITION, SQUARE_SIZE } from '../ToastNSnackBarStyle'; + +export const fadeInOutAnimation = keyframes` + 0%, 100%{ + opacity: 0; + } + 10%, 90% { + opacity: 1; + } +`; + +export const Wrapper = styled.div<{ $position: 'top' | 'bottom' }>` + position: fixed; + + top: ${props => POSITION[props.$position]}; +`; + +export const Content = styled.div<{ $size: Size | 'free' }>` + display: flex; + align-items: center; + justify-content: center; + + width: ${props => SQUARE_SIZE[props.$size].width}; + height: ${props => SQUARE_SIZE[props.$size].height}; + border-radius: 4px; + + background-color: rgba(0, 0, 0, 0.5); + color: var(--white); + + font: var(--text-caption); + letter-spacing: 1px; + + animation: ${fadeInOutAnimation} ${TOAST_TIME}s linear infinite; + + z-index: ${theme.zIndex.modal}; +`; diff --git a/frontend/src/components/common/ToastNSnackBarStyle.ts b/frontend/src/components/common/ToastNSnackBarStyle.ts new file mode 100644 index 000000000..78d5c1abe --- /dev/null +++ b/frontend/src/components/common/ToastNSnackBarStyle.ts @@ -0,0 +1,11 @@ +export const POSITION = { + top: '25%', + bottom: '85%', +}; + +export const SQUARE_SIZE = { + sm: { width: '250px', height: '40px' }, + md: { width: '400px', height: '40px' }, + lg: { width: '500px', height: '45px' }, + free: { width: '80%', height: '50px' }, +}; diff --git a/frontend/src/components/common/ToggleSwitch/ToggleSwitch.stories.tsx b/frontend/src/components/common/ToggleSwitch/ToggleSwitch.stories.tsx new file mode 100644 index 000000000..361c448e6 --- /dev/null +++ b/frontend/src/components/common/ToggleSwitch/ToggleSwitch.stories.tsx @@ -0,0 +1,65 @@ +import type { Meta } from '@storybook/react'; + +import { useToggleSwitch } from '@hooks/useToggleSwitch'; + +import ToggleSwitch from '.'; + +const meta: Meta = { + component: ToggleSwitch, +}; + +export default meta; + +export const SizeS = () => { + const { selectedButton, firstButton, secondButton } = useToggleSwitch('first', 'second'); + + return ( + + ); +}; + +export const SizeM = () => { + const { selectedButton, firstButton, secondButton } = useToggleSwitch('first', 'second'); + + return ( + + ); +}; + +export const SizeL = () => { + const { selectedButton, firstButton, secondButton } = useToggleSwitch('first', 'second'); + + return ( + + ); +}; + +export const SizeFree = () => { + const { selectedButton, firstButton, secondButton } = useToggleSwitch('first', 'second'); + + return ( +
+ +
+ ); +}; diff --git a/frontend/src/components/common/ToggleSwitch/index.tsx b/frontend/src/components/common/ToggleSwitch/index.tsx new file mode 100644 index 000000000..243c89a08 --- /dev/null +++ b/frontend/src/components/common/ToggleSwitch/index.tsx @@ -0,0 +1,48 @@ +import { Size } from '@type/style'; + +import * as S from './style'; + +interface ButtonInfo { + text: string; + event: () => void; +} + +interface ToggleSwitchProps { + size: Size | 'free'; + selectedButton: string; + firstButton: ButtonInfo; + secondButton: ButtonInfo; +} + +export default function ToggleSwitch({ + size, + selectedButton, + firstButton, + secondButton, +}: ToggleSwitchProps) { + const handleFirstButtonClick = () => { + if (selectedButton === firstButton.text) return; + + firstButton.event(); + }; + + const handleSecondButtonClick = () => { + if (selectedButton === secondButton.text) return; + + secondButton.event(); + }; + + return ( + + + {firstButton.text} + + + {secondButton.text} + + + ); +} diff --git a/frontend/src/components/common/ToggleSwitch/style.ts b/frontend/src/components/common/ToggleSwitch/style.ts new file mode 100644 index 000000000..c82f8bef0 --- /dev/null +++ b/frontend/src/components/common/ToggleSwitch/style.ts @@ -0,0 +1,36 @@ +import { styled } from 'styled-components'; + +import { Size } from '@type/style'; + +const SIZE = { + sm: { height: '40px' }, + md: { height: '60px' }, + lg: { height: '80px' }, + free: { height: '100%' }, +}; + +export const Wrapper = styled.div<{ $size: Size | 'free' }>` + display: grid; + grid-template-columns: 1fr 1fr; + align-items: center; + justify-items: center; + + height: ${props => SIZE[props.$size].height}; + border: 1.5px solid var(--primary-color); + border-radius: 4px; +`; + +export const Content = styled.button<{ $isSelected: boolean }>` + border-radius: 4px; + + height: calc(100% - 10px); + width: calc(100% - 10px); + + background-color: ${props => props.$isSelected && 'var(--primary-color)'}; + color: ${props => (props.$isSelected ? 'white' : 'var(--primary-color)')}; + + font: var(--text-caption); + letter-spacing: 1px; + + cursor: pointer; +`; diff --git a/frontend/src/components/common/TwoButtonModal/index.tsx b/frontend/src/components/common/TwoButtonModal/index.tsx new file mode 100644 index 000000000..51e70b75e --- /dev/null +++ b/frontend/src/components/common/TwoButtonModal/index.tsx @@ -0,0 +1,63 @@ +import { PropsWithChildren, useEffect, useRef } from 'react'; + +import SquareButton from '@components/common/SquareButton'; + +import * as S from './style'; + +interface ButtonProps { + text: string; + handleClick: () => void; +} + +interface CommentModalProps extends PropsWithChildren { + title: string; + primaryButton: ButtonProps; + secondaryButton: ButtonProps; +} + +export default function TwoButtonModal({ + title, + primaryButton, + secondaryButton, + children, +}: CommentModalProps) { + const BackDropRef = useRef(null); + + const { text: primaryText, handleClick: primaryClick } = primaryButton; + const { text: secondaryText, handleClick: secondaryClick } = secondaryButton; + + useEffect(() => { + const handler = (e: MouseEvent) => { + if (e.target === BackDropRef.current) { + secondaryClick(); + } + }; + + document.addEventListener('click', handler); + + return () => document.removeEventListener('click', handler); + }, [BackDropRef, secondaryClick]); + + return ( + + + + {title} + + {children} + + + + {secondaryText} + + + + + {primaryText} + + + + + + ); +} diff --git a/frontend/src/components/common/TwoButtonModal/style.ts b/frontend/src/components/common/TwoButtonModal/style.ts new file mode 100644 index 000000000..2e25d5ae3 --- /dev/null +++ b/frontend/src/components/common/TwoButtonModal/style.ts @@ -0,0 +1,65 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + + width: 100vw; + height: 100vh; + padding: 0 12px; + + position: fixed; + top: 0; + left: 0; + + background-color: rgba(0, 0, 0, 0.5); + + z-index: ${theme.zIndex.modal}; + + @media (min-width: ${theme.breakpoint.sm}) { + padding: 0; + } +`; + +export const ModalContainer = styled.div` + display: flex; + flex-direction: column; + + width: 100%; + max-width: 400px; + padding: 30px; + border-radius: 8px; + + background-color: white; +`; + +export const Title = styled.span` + margin-bottom: 32px; + + color: #334253; + + font: var(--text-title); + font-weight: 500; + + @media (min-width: ${theme.breakpoint.sm}) { + font-size: 2.4rem; + } +`; + +export const ButtonContainer = styled.div` + display: flex; + gap: 12px; + + @media (min-width: ${theme.breakpoint.sm}) { + gap: 14px; + } +`; + +export const ButtonWrapper = styled.div` + width: 100%; + height: 48px; + margin-top: 44px; +`; diff --git a/frontend/src/components/common/UpButton/UpButton.stories.tsx b/frontend/src/components/common/UpButton/UpButton.stories.tsx new file mode 100644 index 000000000..6b041e2ad --- /dev/null +++ b/frontend/src/components/common/UpButton/UpButton.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import UpButton from '.'; + +const meta: Meta = { + component: UpButton, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/UpButton/index.tsx b/frontend/src/components/common/UpButton/index.tsx new file mode 100644 index 000000000..29d7d6db7 --- /dev/null +++ b/frontend/src/components/common/UpButton/index.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import chevronUp from '@assets/chevron_up_primary.svg'; + +import * as S from './style'; + +interface UpButtonProps extends React.ButtonHTMLAttributes {} + +export default function UpButton({ ...rest }: UpButtonProps) { + return ( + + 페이지 최상단으로 스크롤 올리기 + + ); +} diff --git a/frontend/src/components/common/UpButton/style.ts b/frontend/src/components/common/UpButton/style.ts new file mode 100644 index 000000000..9ecf51165 --- /dev/null +++ b/frontend/src/components/common/UpButton/style.ts @@ -0,0 +1,12 @@ +import { styled } from 'styled-components'; + +export const Button = styled.button` + width: 60px; + height: 60px; + border: 2px solid var(--primary-color); + border-radius: 50%; + + background-color: var(--white); + + cursor: pointer; +`; diff --git a/frontend/src/components/common/WideHeader/WideHeader.stories.tsx b/frontend/src/components/common/WideHeader/WideHeader.stories.tsx new file mode 100644 index 000000000..8a5573d46 --- /dev/null +++ b/frontend/src/components/common/WideHeader/WideHeader.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import WideHeader from '.'; + +const meta: Meta = { + component: WideHeader, +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + render: () => , +}; diff --git a/frontend/src/components/common/WideHeader/index.tsx b/frontend/src/components/common/WideHeader/index.tsx new file mode 100644 index 000000000..f28b3543a --- /dev/null +++ b/frontend/src/components/common/WideHeader/index.tsx @@ -0,0 +1,38 @@ +import { useNavigate } from 'react-router-dom'; + +import { PATH } from '@constants/path'; + +import IconButton from '../IconButton'; +import LogoButton from '../LogoButton'; +import SearchBar from '../SearchBar'; + +import * as S from './style'; + +export default function WideHeader() { + const navigate = useNavigate(); + + const movePostListPage = () => { + navigate('/'); + }; + + const moveUserInfoPage = () => { + navigate(PATH.USER_INFO); + }; + + const moveRankingPage = () => { + navigate(PATH.RANKING); + }; + + return ( + + + + + + + + + + + ); +} diff --git a/frontend/src/components/common/WideHeader/style.ts b/frontend/src/components/common/WideHeader/style.ts new file mode 100644 index 000000000..20b390593 --- /dev/null +++ b/frontend/src/components/common/WideHeader/style.ts @@ -0,0 +1,26 @@ +import { styled } from 'styled-components'; + +export const Container = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + width: 100%; + height: 70px; + + position: fixed; + top: 0; + + background-color: var(--header); + + padding: 0 80px; +`; + +export const LogoWrapper = styled.div` + height: 50%; +`; + +export const Wrapper = styled.div` + display: flex; + gap: 15px; +`; diff --git a/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionCancelButton/OptionCancelButton.stories.tsx b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionCancelButton/OptionCancelButton.stories.tsx new file mode 100644 index 000000000..e6444c985 --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionCancelButton/OptionCancelButton.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import OptionCancelButton from '.'; + +const meta: Meta = { + component: OptionCancelButton, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionCancelButton/index.tsx b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionCancelButton/index.tsx new file mode 100644 index 000000000..a28be23b6 --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionCancelButton/index.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import xMarkIcon from '@assets/x_mark_white.svg'; + +import * as S from './style'; + +interface OptionCancelButtonProps extends React.ButtonHTMLAttributes {} + +export default function OptionCancelButton({ ...rest }: OptionCancelButtonProps) { + return ( + + + + ); +} diff --git a/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionCancelButton/style.ts b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionCancelButton/style.ts new file mode 100644 index 000000000..e241c1217 --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionCancelButton/style.ts @@ -0,0 +1,9 @@ +import { styled } from 'styled-components'; + +import { ButtonCssText, IconImage } from '../style'; + +export const Container = styled.button` + ${ButtonCssText} +`; + +export const Image = styled(IconImage)``; diff --git a/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionUploadImageButton/OptionUploadImageButton.stories.tsx b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionUploadImageButton/OptionUploadImageButton.stories.tsx new file mode 100644 index 000000000..d3329025f --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionUploadImageButton/OptionUploadImageButton.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import OptionUploadImageButton from '.'; + +const meta: Meta = { + component: OptionUploadImageButton, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionUploadImageButton/index.tsx b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionUploadImageButton/index.tsx new file mode 100644 index 000000000..7cf34b3d7 --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionUploadImageButton/index.tsx @@ -0,0 +1,35 @@ +import React, { MouseEvent, useRef } from 'react'; + +import photoIcon from '@assets/photo_white.svg'; + +import * as S from './style'; + +interface OptionUploadImageButtonProps extends React.InputHTMLAttributes { + optionId: number; + isImageVisible: boolean; +} + +export default function OptionUploadImageButton({ + optionId, + isImageVisible, + ...rest +}: OptionUploadImageButtonProps) { + const inputRef = useRef(null); + const id = optionId.toString(); + + const handleButtonClick = (e: MouseEvent) => { + e.preventDefault(); + inputRef.current && inputRef.current.click(); + }; + + return ( + + + + + ); +} diff --git a/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionUploadImageButton/style.ts b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionUploadImageButton/style.ts new file mode 100644 index 000000000..8509c65fd --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/OptionUploadImageButton/style.ts @@ -0,0 +1,20 @@ +import { styled } from 'styled-components'; + +import { ButtonCssText, IconImage } from '../style'; + +export const Container = styled.div<{ $isVisible: boolean }>` + width: 24px; + height: 24px; + border-radius: 50%; + visibility: ${props => props.$isVisible && 'hidden'}; +`; + +export const Label = styled.label` + ${ButtonCssText} +`; + +export const FileInput = styled.input` + visibility: hidden; +`; + +export const Image = styled(IconImage)``; diff --git a/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/WritingVoteOption.stories.tsx b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/WritingVoteOption.stories.tsx new file mode 100644 index 000000000..7ce3b3f9d --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/WritingVoteOption.stories.tsx @@ -0,0 +1,61 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import WritingVoteOption from '.'; + +const meta: Meta = { + component: WritingVoteOption, +}; + +export default meta; +type Story = StoryObj; + +export const IsDeletable: Story = { + render: () => ( + {}} + handleDeleteOptionClick={() => {}} + handleRemoveImageClick={() => {}} + handleUploadImage={() => {}} + optionId={Math.floor(Math.random() * 100000)} + text="방학 때 강릉으로 강아지와 기차여행을 하려했지 + 만 장마가 와서 취소했어요. 여행을 별로 좋" + isDeletable={true} + /> + ), +}; + +export const IsNotDeletable: Story = { + render: () => ( + {}} + handleDeleteOptionClick={() => {}} + handleRemoveImageClick={() => {}} + handleUploadImage={() => {}} + optionId={Math.floor(Math.random() * 100000)} + text="방학 때 강릉으로 강아지와 기차여행을 하려했지 + 만 장마가 와서 취소했어요. 여행을 별로 좋" + isDeletable={false} + /> + ), +}; + +export const ShowImage: Story = { + render: () => ( + {}} + handleDeleteOptionClick={() => {}} + handleRemoveImageClick={() => {}} + handleUploadImage={() => {}} + optionId={Math.floor(Math.random() * 100000)} + text="방학 때 강릉으로 강아지와 기차여행을 하려했지 + 만 장마가 와서 취소했어요. 여행을 별로 좋" + isDeletable={true} + imageUrl="https://source.unsplash.com/random" + /> + ), +}; diff --git a/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/index.tsx b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/index.tsx new file mode 100644 index 000000000..d3bfa6eba --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/index.tsx @@ -0,0 +1,68 @@ +import { ChangeEvent } from 'react'; + +import { POST_OPTION_POLICY } from '@constants/policyMessage'; + +import OptionCancelButton from './OptionCancelButton'; +import OptionUploadImageButton from './OptionUploadImageButton'; +import * as S from './style'; + +interface WritingVoteOptionProps { + optionId: number; + text: string; + isDeletable: boolean; + ariaLabel: string; + handleUpdateOptionChange: (event: ChangeEvent) => void; + handleDeleteOptionClick: () => void; + handleRemoveImageClick: () => void; + handleUploadImage: (event: ChangeEvent) => void; + imageUrl: string; +} + +const MAX_WRITING_LENGTH = 50; + +export default function WritingVoteOption({ + optionId, + text, + isDeletable, + ariaLabel, + handleUpdateOptionChange, + handleDeleteOptionClick, + handleRemoveImageClick, + handleUploadImage, + imageUrl, +}: WritingVoteOptionProps) { + return ( + + + {isDeletable && ( + + )} + + + + ) => handleUpdateOptionChange(e)} + placeholder={POST_OPTION_POLICY.DEFAULT} + maxLength={MAX_WRITING_LENGTH} + /> + + 0} + optionId={optionId} + onChange={handleUploadImage} + /> + + {imageUrl && ( + + + + + + + )} + + + ); +} diff --git a/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/style.ts b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/style.ts new file mode 100644 index 000000000..27b301835 --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOption/style.ts @@ -0,0 +1,100 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.li` + display: flex; + gap: 10px; +`; + +export const OptionContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + width: 100%; + padding: 15px; + border-radius: 4px; + + background-color: #e6e6e6; +`; + +export const ContentContainer = styled.div` + display: flex; + justify-content: space-between; + gap: 10px; + + width: 100%; +`; + +export const ContentTextArea = styled.textarea` + width: 100%; + height: 90px; + padding: 8px; + + font: var(--text-caption); + line-height: 2.4rem; + + background-color: #e6e6e6; + + resize: none; + + @media (min-width: ${theme.breakpoint.md}) { + font: var(--text-body); + } +`; + +export const ImageContainer = styled.div` + width: 80%; + margin-top: 20px; + + position: relative; +`; + +export const Image = styled.img` + width: 100%; + border-radius: 4px; + + aspect-ratio: 1/1; + object-fit: cover; +`; + +export const ImageCancelWrapper = styled.div` + position: absolute; + top: 10px; + right: 10px; +`; + +export const CancelButtonWrapper = styled.div` + width: 34px; + height: 100%; +`; + +export const ButtonCssText = ` +display: flex; +justify-content: center; +align-items: center; + +width: 24px; +height: 24px; +border-radius: 50%; + +background-color: #bebebe; + +cursor: pointer; + +@media (min-width: ${theme.breakpoint.md}) { + width:20px; + height:20px; +} +`; + +export const IconImage = styled.img` + width: 14px; + height: 14px; + + @media (min-width: ${theme.breakpoint.md}) { + width: 16px; + height: 16px; + } +`; diff --git a/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOptionList.stories.tsx b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOptionList.stories.tsx new file mode 100644 index 000000000..487cc1cca --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/WritingVoteOptionList.stories.tsx @@ -0,0 +1,73 @@ +import type { Meta } from '@storybook/react'; + +import { styled } from 'styled-components'; + +import { useWritingOption } from '@hooks/useWritingOption'; + +import WritingVoteOptionList from '.'; + +const meta: Meta = { + component: WritingVoteOptionList, +}; + +export default meta; + +const ListWrapper = styled.div` + width: 100%; + max-width: 320px; +`; + +const MOCK_MAX_VOTE_OPTION = [ + { id: 1234123, text: '', imageUrl: '' }, + { id: 1234177, text: '', imageUrl: '' }, + { + id: 1234221, + text: '방학 때 강릉으로 강아지와 기차여행을 하려했지만 장마가 와서 취소했어요. 여행을 별로 좋', + imageUrl: '', + }, + { + id: 1834221, + text: '방학 때 강릉으로 강아지와 기차여행을 하려했지만 장마가 와서 취소했어요. 여행을 별로 좋', + imageUrl: 'https://source.unsplash.com/random', + }, + { + id: 1234451, + text: '', + imageUrl: 'https://source.unsplash.com/random', + }, +]; + +const MOCK_MIN_VOTE_OPTION = [ + { id: 123741, text: '', imageUrl: '' }, + { id: 123415, text: '', imageUrl: '' }, +]; + +export const DefaultOptionList = () => { + const writingOptionHook = useWritingOption(); + + return ( + + + + ); +}; + +export const MaxCountOptionList = () => { + const writingOptionHook = useWritingOption(MOCK_MAX_VOTE_OPTION); + + return ( + + + + ); +}; + +export const MinCountOptionList = () => { + const writingOptionHook = useWritingOption(MOCK_MIN_VOTE_OPTION); + + return ( + + + + ); +}; diff --git a/frontend/src/components/optionList/WritingVoteOptionList/index.tsx b/frontend/src/components/optionList/WritingVoteOptionList/index.tsx new file mode 100644 index 000000000..a579ee04a --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/index.tsx @@ -0,0 +1,56 @@ +import { ChangeEvent } from 'react'; + +import { WritingVoteOptionType } from '@hooks/useWritingOption'; + +import AddButton from '@components/common/AddButton'; + +import * as S from './style'; +import WritingVoteOption from './WritingVoteOption'; + +const MINIMUM_COUNT = 2; +const MAXIMUM_COUNT = 5; + +interface WritingVoteOptionListProps { + writingOptionHook: { + optionList: WritingVoteOptionType[]; + addOption: () => void; + writingOption: ( + optionId: number + ) => (event: ChangeEvent) => void; + deleteOption: (optionId: number) => void; + removeImage: (optionId: number) => void; + handleUploadImage: (event: ChangeEvent, optionId: number) => void; + }; +} + +export default function WritingVoteOptionList({ writingOptionHook }: WritingVoteOptionListProps) { + const { optionList, addOption, writingOption, deleteOption, removeImage, handleUploadImage } = + writingOptionHook; + const isDeletable = optionList.length > MINIMUM_COUNT; + + return ( + + {optionList.map((optionItem, index) => ( + deleteOption(optionItem.id)} + handleRemoveImageClick={() => removeImage(optionItem.id)} + handleUploadImage={(event: ChangeEvent) => + handleUploadImage(event, optionItem.id) + } + imageUrl={optionItem.imageUrl} + /> + ))} + {optionList.length < MAXIMUM_COUNT && ( + + + + )} + + ); +} diff --git a/frontend/src/components/optionList/WritingVoteOptionList/style.ts b/frontend/src/components/optionList/WritingVoteOptionList/style.ts new file mode 100644 index 000000000..2c4fbcb4d --- /dev/null +++ b/frontend/src/components/optionList/WritingVoteOptionList/style.ts @@ -0,0 +1,15 @@ +import { styled } from 'styled-components'; + +export const Container = styled.ul` + display: flex; + flex-direction: column; + + gap: 20px; +`; + +export const AddButtonWrapper = styled.div` + display: flex; + justify-content: center; + + position: relative; +`; diff --git a/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/ProgressBar/ProgressBar.stories.tsx b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/ProgressBar/ProgressBar.stories.tsx new file mode 100644 index 000000000..03c0080d7 --- /dev/null +++ b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/ProgressBar/ProgressBar.stories.tsx @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import ProgressBar from '.'; + +const meta: Meta = { + component: ProgressBar, +}; + +export default meta; +type Story = StoryObj; + +export const Selected: Story = { + render: () => , +}; + +export const NotSelected: Story = { + render: () => , +}; diff --git a/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/ProgressBar/index.tsx b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/ProgressBar/index.tsx new file mode 100644 index 000000000..332c6403d --- /dev/null +++ b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/ProgressBar/index.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import * as S from './style'; + +interface ProgressBarProps { + percent: number; + isSelected: boolean; +} + +export default function ProgressBar({ percent, isSelected }: ProgressBarProps) { + return ( + + + + ); +} diff --git a/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/ProgressBar/style.ts b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/ProgressBar/style.ts new file mode 100644 index 000000000..7f34ac03e --- /dev/null +++ b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/ProgressBar/style.ts @@ -0,0 +1,18 @@ +import { styled } from 'styled-components'; + +export const Container = styled.div` + border-radius: 4px; + + height: 8px; + + background-color: rgba(0, 0, 0, 0.15); +`; + +export const Bar = styled.div<{ progress: number; $isSelected: boolean }>` + border-radius: 4px; + + width: ${({ progress }) => `${progress}%`}; + height: 8px; + + background-color: ${({ $isSelected }) => ($isSelected ? 'var(--primary-color)' : '#9F9F9F')}; +`; diff --git a/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/WrittenVoteOption.stories.tsx b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/WrittenVoteOption.stories.tsx new file mode 100644 index 000000000..d79eb3953 --- /dev/null +++ b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/WrittenVoteOption.stories.tsx @@ -0,0 +1,178 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { styled } from 'styled-components'; + +import WrittenVoteOption from '.'; + +const meta: Meta = { + component: WrittenVoteOption, +}; + +export default meta; +type Story = StoryObj; + +const WrittenVoteWrapper = styled.div` + max-width: 460px; +`; + +export const Select: Story = { + render: () => ( + + {}} + isPreview={true} + text="자유를 찾게 냅둔다" + peopleCount={2} + percent={70.9} + isSelected={true} + isStatisticsVisible={true} + imageUrl="" + /> + + ), +}; + +export const NotSelectAndLongText: Story = { + render: () => ( + + {}} + isPreview={true} + text="또는 JavaScript로 컴포넌트의 텍스트를 가져와서 원하는 길이로 자르고, 생략 부호를" + percent={80} + peopleCount={6} + isSelected={false} + isStatisticsVisible={true} + imageUrl="" + /> + + ), +}; + +export const ImageAndSelect: Story = { + render: () => ( + + {}} + isPreview={true} + imageUrl="https://source.unsplash.com/random" + text="또는 JavaScript로 컴포넌트의 텍스트를 가져와서 원하는 길이로 자르고, 생략 부호를" + percent={80} + peopleCount={6} + isSelected={true} + isStatisticsVisible={true} + /> + + ), +}; + +export const NotVote: Story = { + render: () => ( + + {}} + isPreview={true} + text="또는 JavaScript로 컴포넌트의 텍스트를 가져와서 원하는 길이로 자르고, 생략 부호를" + percent={0} + peopleCount={0} + isSelected={false} + isStatisticsVisible={false} + imageUrl="" + /> + + ), +}; + +export const ImageAndNotVote: Story = { + render: () => ( + + {}} + isPreview={true} + imageUrl="https://source.unsplash.com/random" + text="또는 JavaScript로 컴포넌트의 텍스트를 가져와서 원하는 길이로 자르고, 생략 부호를" + percent={0} + peopleCount={0} + isSelected={false} + isStatisticsVisible={false} + /> + + ), +}; + +export const PreviewContent: Story = { + render: () => ( + + {}} + isPreview={true} + imageUrl="https://source.unsplash.com/random" + text="isVote는 변수명으로서는 영문법상으로 볼 때는 어색하진 않습니다. is는 보통 boolean 타입을 나타낼 때 사용되는 접두사이며, Vote는 투표를 의미하는 명사입니다. 따라서 isVote는 투표 여부를 나타내는지를 의미하는 변수명으로 적합합니다. 그러나 개인적인 취향에 따라 다른 변수명을 선호할 수도 있습니다. 예를 들면 hasVoted와 같이 투표를 했는지 여부를 나타내는 변수명을 사용하는 것도 가능합니다. 중요한 것은 코드의 가독성과 일관성을 유지하는 것이며, 개발자들과의 커뮤니케이션을 원활하게 하기 위해 명확하고 이해하기 쉬운 변수명을 선택하는 것이 좋습니다." + percent={0} + peopleCount={0} + isSelected={false} + isStatisticsVisible={false} + /> + + ), +}; + +export const DetailContent: Story = { + render: () => ( + + {}} + isPreview={false} + imageUrl="https://source.unsplash.com/random" + text="isVote는 변수명으로서는 영문법상으로 볼 때는 어색하진 않습니다. is는 보통 boolean 타입을 나타낼 때 사용되는 접두사이며, Vote는 투표를 의미하는 명사입니다. 따라서 isVote는 투표 여부를 나타내는지를 의미하는 변수명으로 적합합니다. 그러나 개인적인 취향에 따라 다른 변수명을 선호할 수도 있습니다. 예를 들면 hasVoted와 같이 투표를 했는지 여부를 나타내는 변수명을 사용하는 것도 가능합니다. 중요한 것은 코드의 가독성과 일관성을 유지하는 것이며, 개발자들과의 커뮤니케이션을 원활하게 하기 위해 명확하고 이해하기 쉬운 변수명을 선택하는 것이 좋습니다." + percent={0} + peopleCount={0} + isSelected={false} + isStatisticsVisible={false} + /> + + ), +}; + +export const NoImageAndDetailContent: Story = { + render: () => ( + + {}} + isPreview={false} + text="isVote는 변수명으로서는 영문법상으로 볼 때는 어색하진 않습니다. is는 보통 boolean 타입을 나타낼 때 사용되는 접두사이며, Vote는 투표를 의미하는 명사입니다. 따라서 isVote는 투표 여부를 나타내는지를 의미하는 변수명으로 적합합니다. 그러나 개인적인 취향에 따라 다른 변수명을 선호할 수도 있습니다. 예를 들면 hasVoted와 같이 투표를 했는지 여부를 나타내는 변수명을 사용하는 것도 가능합니다. 중요한 것은 코드의 가독성과 일관성을 유지하는 것이며, 개발자들과의 커뮤니케이션을 원활하게 하기 위해 명확하고 이해하기 쉬운 변수명을 선택하는 것이 좋습니다." + percent={60} + peopleCount={8} + isSelected={true} + isStatisticsVisible={true} + imageUrl="" + /> + + ), +}; + +export const ImageAndSelectAndDetailContent: Story = { + render: () => ( + + {}} + isPreview={false} + imageUrl="https://source.unsplash.com/random" + text="isVote는 변수명으로서는 영문법상으로 볼 때는 어색하진 않습니다. is는 보통 boolean 타입을 나타낼 때 사용되는 접두사이며, Vote는 투표를 의미하는 명사입니다. 따라서 isVote는 투표 여부를 나타내는지를 의미하는 변수명으로 적합합니다. 그러나 개인적인 취향에 따라 다른 변수명을 선호할 수도 있습니다. 예를 들면 hasVoted와 같이 투표를 했는지 여부를 나타내는 변수명을 사용하는 것도 가능합니다. 중요한 것은 코드의 가독성과 일관성을 유지하는 것이며, 개발자들과의 커뮤니케이션을 원활하게 하기 위해 명확하고 이해하기 쉬운 변수명을 선택하는 것이 좋습니다." + percent={60} + peopleCount={8} + isSelected={true} + isStatisticsVisible={true} + /> + + ), +}; diff --git a/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/index.tsx b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/index.tsx new file mode 100644 index 000000000..fa6f6223d --- /dev/null +++ b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/index.tsx @@ -0,0 +1,57 @@ +import { convertImageUrlToServerUrl } from '@utils/post/convertImageUrlToServerUrl'; + +import ProgressBar from './ProgressBar'; +import * as S from './style'; + +interface WrittenVoteOptionProps { + handleVoteClick: () => void; + text: string; + isStatisticsVisible: boolean; + peopleCount: number; + percent: number; + isSelected: boolean; + isPreview: boolean; + imageUrl: string; + ariaLabel: string; +} + +export default function WrittenVoteOption({ + handleVoteClick, + text, + isStatisticsVisible, + peopleCount, + percent, + isSelected, + isPreview, + imageUrl, + ariaLabel, +}: WrittenVoteOptionProps) { + return ( + + {!isPreview && imageUrl && ( + + )} + {isPreview ? ( + {text} + ) : ( + {text} + )} + {isStatisticsVisible && ( + <> + + + + + + + + + )} + + ); +} diff --git a/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/style.ts b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/style.ts new file mode 100644 index 000000000..a600844f1 --- /dev/null +++ b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOption/style.ts @@ -0,0 +1,106 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.button<{ $isSelected: boolean }>` + display: flex; + flex-direction: column; + align-items: stretch; + + border: ${({ $isSelected }) => + $isSelected ? '2px solid var(--primary-color)' : '1px solid rgba(0, 0, 0, 0.1)'}; + border-radius: 4px; + padding: 10px 15px; + + color: #5b5b5b; + + text-align: left; + + cursor: pointer; + + @media (min-width: ${theme.breakpoint.md}) { + padding: 15px 25px; + } +`; + +export const Image = styled.img` + border-radius: 4px; + margin-bottom: 10px; + + width: 100%; + + aspect-ratio: 1/1; + object-fit: cover; + + @media (min-width: ${theme.breakpoint.md}) { + margin-bottom: 20px; + } +`; + +export const PreviewContent = styled.p` + display: -webkit-box; + + font: var(--text-caption); + font-weight: 500; + text-overflow: ellipsis; + word-break: break-word; + + overflow: hidden; + + -webkit-line-clamp: 2; // 원하는 라인수 + -webkit-box-orient: vertical; + + @media (min-width: ${theme.breakpoint.md}) { + font: var(--text-body); + } +`; + +export const DetailContent = styled.p` + font: var(--text-caption); + font-weight: 500; + + @media (min-width: ${theme.breakpoint.md}) { + font: var(--text-body); + } +`; + +export const ProgressContainer = styled.div` + margin-top: 12px; + + @media (min-width: ${theme.breakpoint.md}) { + margin-top: 18px; + } +`; + +export const TextContainer = styled.div` + margin-top: 8px; + + text-align: end; + font-weight: 500; + + @media (min-width: ${theme.breakpoint.md}) { + margin-top: 12px; + + font: var(--text-body); + } +`; + +export const PeopleText = styled.span` + font: var(--text-caption); + + @media (min-width: ${theme.breakpoint.md}) { + font: var(--text-body); + } +`; + +export const PercentText = styled.span` + margin-left: 4px; + + color: var(--text-dark-gray); + + font: var(--text-small); + + @media (min-width: ${theme.breakpoint.md}) { + font: var(--text-caption); + } +`; diff --git a/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOptionList.stories.tsx b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOptionList.stories.tsx new file mode 100644 index 000000000..69357d64e --- /dev/null +++ b/frontend/src/components/optionList/WrittenVoteOptionList/WrittenVoteOptionList.stories.tsx @@ -0,0 +1,142 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { styled } from 'styled-components'; + +import WrittenVoteOptionList from '.'; + +const meta: Meta = { + component: WrittenVoteOptionList, +}; + +export default meta; +type Story = StoryObj; + +const WrittenVoteWrapper = styled.div` + max-width: 460px; +`; + +const MOCK_NOT_VOTED_DATA = { + selectedOptionId: 0, + allPeopleCount: 123, + options: [ + { + id: 12312, + text: '또는 JavaScript로 컴포넌트의 텍스트를 가져와서 원하는 길이로 자르고, 생략 부호를', + peopleCount: -1, + percent: -1, + imageUrl: '', + }, + { + id: 12314, + text: '자유를 찾게 냅둔다', + peopleCount: -1, + imageUrl: '', + percent: -1, + }, + { + id: 123152, + text: 'isVote는 변수명으로서는 영문법상으로 볼 때는 어색하진 않습니다. is는 보통 boolean 타입을 나타낼 때 사용되는 접두사이며, Vote는 투표를 의미하는 명사입니다. 따라서 isVote는 투표 여부를 나타내는지를 의미하는 변수명으로 적합합니다. 그러나 개인적인 취향에 따라 다른 변수명을 선호할 수도 있습니다. 예를 들면 hasVoted와 같이 투표를 했는지 여부를 나타내는 변수명을 사용하는 것도 가능합니다. 중요한 것은 코드의 가독성과 일관성을 유지하는 것이며, 개발자들과의 커뮤니케이션을 원활하게 하기 위해 명확하고 이해하기 쉬운 변수명을 선택하는 것이 좋습니다.', + peopleCount: -1, + percent: -1, + imageUrl: '', + }, + { + id: 123122, + text: 'isVote는 변수명으로서는 영문법상으로 볼 때는 어색하진 않습니다. is는 보통 boolean 타입을 나타낼 때 사용되는 접두사이며, Vote는 투표를 의미하는 명사입니다. 따라서 isVote는 투표 여부를 나타내는지를 의미하는 변수명으로 적합합니다. 그러나 개인적인 취향에 따라 다른 변수명을 선호할 수도 있습니다. 예를 들면 hasVoted와 같이 투표를 했는지 여부를 나타내는 변수명을 사용하는 것도 가능합니다. 중요한 것은 코드의 가독성과 일관성을 유지하는 것이며, 개발자들과의 커뮤니케이션을 원활하게 하기 위해 명확하고 이해하기 쉬운 변수명을 선택하는 것이 좋습니다.', + imageUrl: 'https://source.unsplash.com/random', + peopleCount: -1, + percent: -1, + }, + ], +}; + +const MOCK_VOTED_DATA = { + selectedOptionId: 123122, + allPeopleCount: 123, + options: [ + { + id: 12312, + text: '또는 JavaScript로 컴포넌트의 텍스트를 가져와서 원하는 길이로 자르고, 생략 부호를', + peopleCount: 7, + imageUrl: '', + percent: 8, + }, + { + id: 12314, + text: '자유를 찾게 냅둔다', + peopleCount: 12, + imageUrl: '', + percent: 15, + }, + { + id: 123152, + text: 'isVote는 변수명으로서는 영문법상으로 볼 때는 어색하진 않습니다. is는 보통 boolean 타입을 나타낼 때 사용되는 접두사이며, Vote는 투표를 의미하는 명사입니다. 따라서 isVote는 투표 여부를 나타내는지를 의미하는 변수명으로 적합합니다. 그러나 개인적인 취향에 따라 다른 변수명을 선호할 수도 있습니다. 예를 들면 hasVoted와 같이 투표를 했는지 여부를 나타내는 변수명을 사용하는 것도 가능합니다. 중요한 것은 코드의 가독성과 일관성을 유지하는 것이며, 개발자들과의 커뮤니케이션을 원활하게 하기 위해 명확하고 이해하기 쉬운 변수명을 선택하는 것이 좋습니다.', + imageUrl: '', + peopleCount: 0, + percent: 0, + }, + { + id: 123122, + text: 'isVote는 변수명으로서는 영문법상으로 볼 때는 어색하진 않습니다. is는 보통 boolean 타입을 나타낼 때 사용되는 접두사이며, Vote는 투표를 의미하는 명사입니다. 따라서 isVote는 투표 여부를 나타내는지를 의미하는 변수명으로 적합합니다. 그러나 개인적인 취향에 따라 다른 변수명을 선호할 수도 있습니다. 예를 들면 hasVoted와 같이 투표를 했는지 여부를 나타내는 변수명을 사용하는 것도 가능합니다. 중요한 것은 코드의 가독성과 일관성을 유지하는 것이며, 개발자들과의 커뮤니케이션을 원활하게 하기 위해 명확하고 이해하기 쉬운 변수명을 선택하는 것이 좋습니다.', + imageUrl: 'https://source.unsplash.com/random', + peopleCount: 85, + percent: 60, + }, + ], +}; + +export const PreviewNotVoted: Story = { + render: () => ( + + {}} + isPreview={true} + voteOptionList={MOCK_NOT_VOTED_DATA.options} + /> + + ), +}; + +export const PreviewVoted: Story = { + render: () => ( + + {}} + isPreview={true} + voteOptionList={MOCK_VOTED_DATA.options} + /> + + ), +}; + +export const DetailNotVoted: Story = { + render: () => ( + + {}} + isPreview={false} + voteOptionList={MOCK_NOT_VOTED_DATA.options} + /> + + ), +}; + +export const DetailVoted: Story = { + render: () => ( + + {}} + isPreview={false} + voteOptionList={MOCK_VOTED_DATA.options} + /> + + ), +}; diff --git a/frontend/src/components/optionList/WrittenVoteOptionList/index.tsx b/frontend/src/components/optionList/WrittenVoteOptionList/index.tsx new file mode 100644 index 000000000..70f13c2a6 --- /dev/null +++ b/frontend/src/components/optionList/WrittenVoteOptionList/index.tsx @@ -0,0 +1,47 @@ +import { WrittenVoteOptionType } from '@type/post'; + +import * as S from './style'; +import WrittenVoteOption from './WrittenVoteOption'; + +interface WrittenVoteOptionListProps { + isPreview: boolean; + isStatisticsVisible: boolean; + selectedOptionId: number; + voteOptionList: WrittenVoteOptionType[]; + handleVoteClick: (newOptionId: number) => void; +} + +export default function WrittenVoteOptionList({ + isPreview, + isStatisticsVisible, + voteOptionList, + selectedOptionId, + handleVoteClick, +}: WrittenVoteOptionListProps) { + return ( + + {voteOptionList.map((voteOption, index) => { + const isSelected = selectedOptionId === voteOption.id; + + return ( + handleVoteClick(voteOption.id)} + /> + ); + })} + + ); +} diff --git a/frontend/src/components/optionList/WrittenVoteOptionList/style.ts b/frontend/src/components/optionList/WrittenVoteOptionList/style.ts new file mode 100644 index 000000000..67d312abb --- /dev/null +++ b/frontend/src/components/optionList/WrittenVoteOptionList/style.ts @@ -0,0 +1,15 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const VoteOptionListContainer = styled.div` + display: flex; + flex-direction: column; + gap: 12px; + + width: 100%; + + @media (min-width: ${theme.breakpoint.md}) { + gap: 18px; + } +`; diff --git a/frontend/src/components/post/EmptyPostList/EmptyPostList.stories.tsx b/frontend/src/components/post/EmptyPostList/EmptyPostList.stories.tsx new file mode 100644 index 000000000..c47202e39 --- /dev/null +++ b/frontend/src/components/post/EmptyPostList/EmptyPostList.stories.tsx @@ -0,0 +1,26 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import EmptyPostList from '.'; + +const meta: Meta = { + component: EmptyPostList, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; + +export const AllKeyword: Story = { + render: () => , +}; + +export const ClosedKeyword: Story = { + render: () => , +}; + +export const ProgressKeyword: Story = { + render: () => , +}; diff --git a/frontend/src/components/post/EmptyPostList/index.tsx b/frontend/src/components/post/EmptyPostList/index.tsx new file mode 100644 index 000000000..6adef194e --- /dev/null +++ b/frontend/src/components/post/EmptyPostList/index.tsx @@ -0,0 +1,37 @@ +import { PostStatus } from '../PostListPage/types'; + +import * as S from './style'; + +interface EmptyPostListProps { + status: PostStatus; + keyword?: string; +} + +export default function EmptyPostList({ keyword, status }: EmptyPostListProps) { + const statusText = status === 'progress' ? "'진행중'을" : "'마감완료'를"; + const direction = `현재 옵션인 ${statusText} '전체'로 변경해보세요.`; + + if (keyword) { + return ( + +
+ '{keyword}' + 와(과) 일치하는 검색결과가 없습니다. +
+ + {status !== 'all' && {direction}} + 모든 단어의 철자가 정확한지 확인하세요. + 다른 검색어를 사용해 보세요. + 더 일반적인 검색어를 사용해 보세요. + 키워드 수를 줄여보세요. + +
+ ); + } + + return ( + + 해당 되는 조건의 게시글이 없습니다. + + ); +} diff --git a/frontend/src/components/post/EmptyPostList/style.ts b/frontend/src/components/post/EmptyPostList/style.ts new file mode 100644 index 000000000..58bdef64e --- /dev/null +++ b/frontend/src/components/post/EmptyPostList/style.ts @@ -0,0 +1,46 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.main` + display: flex; + flex-direction: column; + align-items: center; + + padding-top: 30px; +`; + +export const Title = styled.span` + font: var(--text-body); + font-weight: 600; + + @media (min-width: ${theme.breakpoint.md}) { + font: var(--text-subtitle); + } +`; + +export const Keyword = styled(Title)` + color: #ff3c3c; +`; + +export const TextCardContainer = styled.ul` + display: flex; + flex-direction: column; + gap: 5px; + + padding: 30px 15px; + + font: var(--text-caption); + + @media (min-width: ${theme.breakpoint.md}) { + font: var(--text-body); + } +`; + +export const TextCard = styled.li` + list-style: disc; + + p { + font-weight: bold; + } +`; diff --git a/frontend/src/components/post/PostList/PostList.stories.tsx b/frontend/src/components/post/PostList/PostList.stories.tsx new file mode 100644 index 000000000..687b46407 --- /dev/null +++ b/frontend/src/components/post/PostList/PostList.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import PostList from '.'; + +const meta: Meta = { + component: PostList, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/components/post/PostList/index.tsx b/frontend/src/components/post/PostList/index.tsx new file mode 100644 index 000000000..616d90f1a --- /dev/null +++ b/frontend/src/components/post/PostList/index.tsx @@ -0,0 +1,120 @@ +import React, { useContext, useEffect, useRef } from 'react'; +import { Link } from 'react-router-dom'; + +import { AuthContext } from '@hooks/context/auth'; +import { PostOptionContext } from '@hooks/context/postOption'; +import { usePostList } from '@hooks/query/usePostList'; +import { useIntersectionObserver } from '@hooks/useIntersectionObserver'; +import { usePostRequestInfo } from '@hooks/usePostRequestInfo'; +import { useSelect } from '@hooks/useSelect'; + +import Post from '@components/common/Post'; +import Select from '@components/common/Select'; +import Skeleton from '@components/common/Skeleton'; +import { SORTING_OPTION, STATUS_OPTION } from '@components/post/PostListPage/constants'; +import type { PostSorting, PostStatus } from '@components/post/PostListPage/types'; + +import { PATH } from '@constants/path'; + +import EmptyPostList from '../EmptyPostList'; + +import * as S from './style'; + +export default function PostList() { + const topButtonRef = useRef(null); + const { postType, postOptionalOption } = usePostRequestInfo(); + const { loggedInfo } = useContext(AuthContext); + const { targetRef, isIntersecting } = useIntersectionObserver({ + root: null, + rootMargin: '', + thresholds: 0.1, + }); + + const { postOption, setPostOption } = useContext(PostOptionContext); + + const { selectedOption: selectedStatusOption, handleOptionChange: handleStatusOptionChange } = + useSelect(postOption.status); + const { selectedOption: selectedSortingOption, handleOptionChange: handleSortingOptionChange } = + useSelect(postOption.sorting); + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isPostListEmpty } = usePostList( + { + postType, + postSorting: selectedSortingOption, + postStatus: selectedStatusOption, + isLoggedIn: loggedInfo.isLoggedIn, + }, + postOptionalOption + ); + + const focusTopContent = () => { + if (!topButtonRef.current) return; + + topButtonRef.current.focus(); + }; + + useEffect(() => { + if (isIntersecting && hasNextPage) { + fetchNextPage(); + } + }, [isIntersecting, fetchNextPage, hasNextPage]); + + return ( + + + + + + aria-label={`마감 여부로 게시글 정렬 선택, 현재 옵션은 ${STATUS_OPTION[selectedStatusOption]}`} + handleOptionChange={(value: PostStatus) => { + setPostOption({ + ...postOption, + status: value, + }); + handleStatusOptionChange(value); + }} + optionList={STATUS_OPTION} + selectedOption={STATUS_OPTION[selectedStatusOption]} + /> + + + + aria-label={`인기순/최신순으로 게시글 정렬 선택, 현재 옵션은 ${SORTING_OPTION[selectedSortingOption]}`} + handleOptionChange={(value: PostSorting) => { + setPostOption({ + ...postOption, + sorting: value, + }); + handleSortingOptionChange(value); + }} + optionList={SORTING_OPTION} + selectedOption={SORTING_OPTION[selectedSortingOption]} + /> + + + + + {isPostListEmpty && ( + + )} + {data?.pages.map((postListInfo, pageIndex) => ( + + {postListInfo.postList.map((post, index) => { + if (index === 7) { + return ( +
+ +
+ ); + } + return ; + })} + + +
+ ))} + {isFetchingNextPage && } +
+
+ ); +} diff --git a/frontend/src/components/post/PostList/style.ts b/frontend/src/components/post/PostList/style.ts new file mode 100644 index 000000000..0680cd1df --- /dev/null +++ b/frontend/src/components/post/PostList/style.ts @@ -0,0 +1,48 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + display: flex; + flex-direction: column; +`; + +export const SelectContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + position: relative; + padding: 20px 20px 30px 20px; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); + + @media (min-width: ${theme.breakpoint.sm}) { + padding: 40px 20px; + } +`; + +export const PostListContainer = styled.ul` + display: flex; + flex-direction: column; + gap: 30px; + + padding: 30px 20px; + + > li { + padding-bottom: 30px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + } +`; + +export const SelectWrapper = styled.div` + width: 110px; + + position: absolute; + + &:first-child { + left: 20px; + } + &:last-child { + right: 20px; + } +`; diff --git a/frontend/src/components/post/PostListPage/PostListPage.stories.tsx b/frontend/src/components/post/PostListPage/PostListPage.stories.tsx new file mode 100644 index 000000000..e121731e8 --- /dev/null +++ b/frontend/src/components/post/PostListPage/PostListPage.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import PostListPage from '.'; + +const meta: Meta = { + component: PostListPage, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/components/post/PostListPage/constants.ts b/frontend/src/components/post/PostListPage/constants.ts new file mode 100644 index 000000000..7cb97667f --- /dev/null +++ b/frontend/src/components/post/PostListPage/constants.ts @@ -0,0 +1,14 @@ +import { SORTING, STATUS } from '@constants/post'; + +import { PostSorting, PostStatus } from './types'; + +export const STATUS_OPTION: Record = { + [STATUS.ALL]: '전체', + [STATUS.PROGRESS]: '진행중', + [STATUS.CLOSED]: '마감완료', +}; + +export const SORTING_OPTION: Record = { + [SORTING.POPULAR]: '인기순', + [SORTING.LATEST]: '최신순', +}; diff --git a/frontend/src/components/post/PostListPage/index.tsx b/frontend/src/components/post/PostListPage/index.tsx new file mode 100644 index 000000000..4a2b05d8e --- /dev/null +++ b/frontend/src/components/post/PostListPage/index.tsx @@ -0,0 +1,47 @@ +import { Suspense } from 'react'; + +import { useDrawer } from '@hooks/useDrawer'; + +import ErrorBoundary from '@pages/ErrorBoundary'; + +import AddButton from '@components/common/AddButton'; +import Dashboard from '@components/common/Dashboard'; +import Drawer from '@components/common/Drawer'; +import NarrowMainHeader from '@components/common/NarrowMainHeader'; +import Skeleton from '@components/common/Skeleton'; +import UpButton from '@components/common/UpButton'; +import PostList from '@components/post/PostList'; + +import { PATH } from '@constants/path'; + +import { smoothScrollToTop } from '@utils/scrollToTop'; + +import * as S from './style'; + +export default function PostListPage() { + const { drawerRef, closeDrawer, openDrawer } = useDrawer('left'); + + return ( + + + + + + + + + + + }> + + + + + + + + + + + ); +} diff --git a/frontend/src/components/post/PostListPage/style.ts b/frontend/src/components/post/PostListPage/style.ts new file mode 100644 index 000000000..5f08ad90f --- /dev/null +++ b/frontend/src/components/post/PostListPage/style.ts @@ -0,0 +1,61 @@ +import { Link } from 'react-router-dom'; + +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + padding-top: 55px; + position: relative; + + @media (min-width: ${theme.breakpoint.sm}) { + padding-top: 0px; + } +`; + +export const HeaderWrapper = styled.div` + width: 100%; + + position: fixed; + top: 0; + + z-index: ${theme.zIndex.header}; + + @media (min-width: ${theme.breakpoint.sm}) { + display: none; + visibility: hidden; + } +`; + +export const DrawerWrapper = styled.div` + @media (min-width: ${theme.breakpoint.sm}) { + display: none; + visibility: hidden; + } +`; + +export const ButtonContainer = styled.div` + display: flex; + flex-direction: column; + align-items: end; + gap: 20px; + + width: 62px; + padding-right: 10px; + + position: fixed; + left: 90%; + bottom: 24px; + + @media (max-width: ${theme.breakpoint.sm}) { + left: 83%; + } + + @media (max-width: 281px) { + left: 78%; + } +`; + +export const AddButtonWrapper = styled(Link)` + text-decoration: none; +`; diff --git a/frontend/src/components/post/PostListPage/types.ts b/frontend/src/components/post/PostListPage/types.ts new file mode 100644 index 000000000..f01821751 --- /dev/null +++ b/frontend/src/components/post/PostListPage/types.ts @@ -0,0 +1,9 @@ +import { + REQUEST_POST_KIND_URL, + REQUEST_SORTING_OPTION, + REQUEST_STATUS_OPTION, +} from '@constants/post'; + +export type PostStatus = keyof typeof REQUEST_STATUS_OPTION; +export type PostSorting = keyof typeof REQUEST_SORTING_OPTION; +export type PostRequestKind = keyof typeof REQUEST_POST_KIND_URL; diff --git a/frontend/src/constants/animation.ts b/frontend/src/constants/animation.ts new file mode 100644 index 000000000..c0677c3ab --- /dev/null +++ b/frontend/src/constants/animation.ts @@ -0,0 +1,2 @@ +//초단위 +export const TOAST_TIME = 3; diff --git a/frontend/src/constants/comment.ts b/frontend/src/constants/comment.ts new file mode 100644 index 000000000..71172876f --- /dev/null +++ b/frontend/src/constants/comment.ts @@ -0,0 +1,4 @@ +export const COMMENT = { + MAX_LENGTH: 200, + MIN_LENGTH: 1, +} as const; diff --git a/frontend/src/constants/cookie.ts b/frontend/src/constants/cookie.ts new file mode 100644 index 000000000..ed2f9c3b1 --- /dev/null +++ b/frontend/src/constants/cookie.ts @@ -0,0 +1,3 @@ +import { convertDayToSecond } from '@utils/time'; + +export const ESSENTIAL_MAX_AGE = convertDayToSecond(365); diff --git a/frontend/src/constants/localStorage.ts b/frontend/src/constants/localStorage.ts new file mode 100644 index 000000000..9b7a92ed5 --- /dev/null +++ b/frontend/src/constants/localStorage.ts @@ -0,0 +1 @@ +export const ACCESS_TOKEN_KEY = '81dc9bdb52d04dc20036dbd8313ed055'; diff --git a/frontend/src/constants/path.ts b/frontend/src/constants/path.ts new file mode 100644 index 000000000..405febbbf --- /dev/null +++ b/frontend/src/constants/path.ts @@ -0,0 +1,21 @@ +export const BASE_PATH = { + HOME: '/', + LANDING: '/landing', + LOGIN: '/login', + POST: '/posts', + USER: '/users', + ADMIN: '/admin', + SEARCH: '/search', + RANKING: '/ranking', +}; + +export const PATH = { + ...BASE_PATH, + POST_WRITE: `${BASE_PATH.POST}/write`, + POST_VOTE_RESULT: `${BASE_PATH.POST}/result`, + POST_CATEGORY: `${BASE_PATH.POST}/category`, + USER_POST: `${BASE_PATH.USER}/posts`, + USER_VOTE: `${BASE_PATH.USER}/votes`, + USER_INFO: `${BASE_PATH.USER}/myPage`, + USER_INFO_REGISTER: `${BASE_PATH.USER}/register`, +}; diff --git a/frontend/src/constants/policyMessage.ts b/frontend/src/constants/policyMessage.ts new file mode 100644 index 000000000..1351df7f8 --- /dev/null +++ b/frontend/src/constants/policyMessage.ts @@ -0,0 +1,44 @@ +export const NICKNAME_POLICY = { + LETTER_AMOUNT: '2자에서 15자 이내로 입력해주세요.', + NO_DUPLICATION: '중복된 닉네임은 사용할 수 없습니다.', + LIMIT_CHANGING: '닉네임 변경은 14일간 1회로 제한됩니다.', + LIMIT_LETTER_TYPE: '한글/영어/숫자를 사용해 닉네임을 지어주세요.', + LIMIT_KOREAN: '한글은 완전한 단어만 가능합니다.', +}; + +export const POST_CATEGORY_POLICY = { + AMOUNT: '1개 ~ 3개의 카테고리를 작성해주세요.', +}; + +export const POST_TITLE_POLICY = { + DEFAULT: '제목을 입력해주세요 (100자 이내)', + LETTER_AMOUNT: '100자 이내로 입력해주세요.', +}; + +export const POST_CONTENT_POLICY = { + DEFAULT: '내용을 입력해주세요 (1000자 이내)', + LETTER_AMOUNT: '1000자 이내로 입력해주세요.', + PHOTO_COUNT: '1장의 사진을 업로드 할 수 있습니다.', + PHOTO_SHAPE: '사진은 정사각형으로 잘라져 업로드됩니다.', + PHOTO_CAPACITY: '용량은 1.5MB으로 제한됩니다.', +}; + +export const POST_OPTION_POLICY = { + DEFAULT: '선택지를 입력해주세요 (50자 이내)', + LETTER_AMOUNT: '50자 이내로 입력해주세요.', + AMOUNT: '2개 ~ 5개 선택지를 작성해주세요.', + PHOTO_COUNT: '1장의 사진을 업로드 할 수 있습니다.', + PHOTO_SHAPE: '사진은 정사각형으로 잘라져 업로드됩니다.', + PHOTO_CAPACITY: '용량은 1.5MB으로 제한됩니다.', +}; + +export const POST_DEADLINE_POLICY = { + DEFAULT: '3일 이내로 마감시간을 정해주세요.', +}; + +export const CONTENT_PLACEHOLDER = [ + POST_CONTENT_POLICY.DEFAULT, + POST_CONTENT_POLICY.PHOTO_COUNT, + POST_CONTENT_POLICY.PHOTO_CAPACITY, + POST_CONTENT_POLICY.PHOTO_SHAPE, +].join('\n - '); diff --git a/frontend/src/constants/post.ts b/frontend/src/constants/post.ts new file mode 100644 index 000000000..cca99a1bc --- /dev/null +++ b/frontend/src/constants/post.ts @@ -0,0 +1,69 @@ +export const POST = { + NOT_VOTE: 0, +}; + +export const STATUS = { + ALL: 'all', + PROGRESS: 'progress', + CLOSED: 'closed', +} as const; + +export const SORTING = { + LATEST: 'latest', + POPULAR: 'popular', +} as const; + +export const POST_TYPE = { + ALL: 'posts', + MY_POST: 'myPost', + MY_VOTE: 'myVote', + CATEGORY: 'category', + SEARCH: 'search', +} as const; + +export const REQUEST_STATUS_OPTION = { + [STATUS.ALL]: 'ALL', + [STATUS.PROGRESS]: 'PROGRESS', + [STATUS.CLOSED]: 'CLOSED', +} as const; + +export const REQUEST_SORTING_OPTION = { + [SORTING.LATEST]: 'LATEST', + [SORTING.POPULAR]: 'HOT', +} as const; + +export const REQUEST_POST_KIND_URL = { + [POST_TYPE.ALL]: 'posts', + [POST_TYPE.MY_POST]: 'posts/me', + [POST_TYPE.MY_VOTE]: 'posts/votes/me', + [POST_TYPE.CATEGORY]: 'posts', + [POST_TYPE.SEARCH]: 'posts/search', +} as const; + +export const SEARCH_KEYWORD = 'keyword'; + +export const MAX_FILE_SIZE = 1500000; + +export const POST_TITLE = { + MAX_LENGTH: 100, + MIN_LENGTH: 2, +} as const; + +export const POST_CONTENT = { + MAX_LENGTH: 1000, + MIN_LENGTH: 2, +} as const; + +export const POST_DESCRIPTION_MAX_LENGTH = 1000; + +export const SEARCH_KEYWORD_MAX_LENGTH = 100; + +export const POST_LIST_MAX_LENGTH = 10; + +export const DEFAULT_CATEGORY_ID = 0; + +export const DEFAULT_KEYWORD = ''; + +export const CATEGORY_COUNT_LIMIT = 3; + +export const IMAGE_BASE_URL = `${process.env.VOTOGETHER_BASE_URL.replace(/api\./, '')}/`; diff --git a/frontend/src/constants/queryKey.ts b/frontend/src/constants/queryKey.ts new file mode 100644 index 000000000..5d4821fa8 --- /dev/null +++ b/frontend/src/constants/queryKey.ts @@ -0,0 +1,9 @@ +export const QUERY_KEY = { + POSTS: 'posts', + POST_DETAIL: 'postDetail', + COMMENTS: 'comments', + CATEGORIES: 'categories', + USER_INFO: 'user_info', + PASSION_RANKING: 'passion_ranking', + POPULAR_RANKING: 'popular_ranking', +}; diff --git a/frontend/src/constants/token.ts b/frontend/src/constants/token.ts new file mode 100644 index 000000000..769cb4bbe --- /dev/null +++ b/frontend/src/constants/token.ts @@ -0,0 +1,3 @@ +import { convertDayToSecond } from '@utils/time'; + +export const REFRESH_EXPIRATION_TIME = convertDayToSecond(14); diff --git a/frontend/src/constants/user.ts b/frontend/src/constants/user.ts new file mode 100644 index 000000000..d509d9dfa --- /dev/null +++ b/frontend/src/constants/user.ts @@ -0,0 +1,9 @@ +export const NICKNAME = { + MAX_LENGTH: 15, + MIN_LENGTH: 2, +} as const; + +export const BIRTH_YEAR = { + MAX_LENGTH: new Date().getFullYear(), + MIN_LENGTH: 1900, +} as const; diff --git a/frontend/src/constants/vote.ts b/frontend/src/constants/vote.ts new file mode 100644 index 000000000..143371efb --- /dev/null +++ b/frontend/src/constants/vote.ts @@ -0,0 +1,3 @@ +export const POST = { + NOT_VOTE: 0, +}; diff --git a/frontend/src/hooks/context/auth.tsx b/frontend/src/hooks/context/auth.tsx new file mode 100644 index 000000000..8bdce9111 --- /dev/null +++ b/frontend/src/hooks/context/auth.tsx @@ -0,0 +1,60 @@ +import React, { Dispatch, SetStateAction, createContext, useEffect, useState } from 'react'; + +import { LoggedInfo } from '@type/user'; + +import { useUserInfo } from '@hooks/query/user/useUserInfo'; + +import { logoutUser } from '@api/userInfo'; + +import { ACCESS_TOKEN_KEY } from '@constants/localStorage'; + +import { clearCookie } from '@utils/cookie'; +import { getLocalStorage, removeLocalStorage } from '@utils/localStorage'; +import { decodeToken } from '@utils/token/decodeToken'; + +interface Auth { + loggedInfo: LoggedInfo; + setLoggedInfo: Dispatch>; + clearLoggedInfo: () => void; +} + +const notLoggedInfo: LoggedInfo = { + isLoggedIn: false, +}; + +export const AuthContext = createContext({} as Auth); + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [loggedInfo, setLoggedInfo] = useState(notLoggedInfo); + const { data: userInfo } = useUserInfo(loggedInfo.isLoggedIn); + + const clearLoggedInfo = () => { + removeLocalStorage(ACCESS_TOKEN_KEY); + clearCookie('hasEssentialInfo'); + logoutUser(); + + setLoggedInfo(notLoggedInfo); + }; + + useEffect(() => { + if (userInfo && loggedInfo.isLoggedIn) { + setLoggedInfo(origin => ({ ...origin, userInfo })); + } + }, [loggedInfo.isLoggedIn, userInfo]); + + useEffect(() => { + const accessToken = getLocalStorage(ACCESS_TOKEN_KEY); + + if (accessToken) { + const decodedPayload = decodeToken(accessToken); + const id = decodedPayload.memberId; + setLoggedInfo(origin => ({ ...origin, id, isLoggedIn: true })); + } + }, []); + + return ( + + {children} + + ); +} diff --git a/frontend/src/hooks/context/postOption.tsx b/frontend/src/hooks/context/postOption.tsx new file mode 100644 index 000000000..806f72255 --- /dev/null +++ b/frontend/src/hooks/context/postOption.tsx @@ -0,0 +1,32 @@ +import { Dispatch, PropsWithChildren, SetStateAction, createContext, useState } from 'react'; + +import type { PostSorting, PostStatus } from '@components/post/PostListPage/types'; + +import { SORTING, STATUS } from '@constants/post'; + +export const PostOptionContext = createContext({ + postOption: { sorting: SORTING.LATEST, status: STATUS.PROGRESS }, + setPostOption: () => {}, +}); + +interface PostOption { + status: PostStatus; + sorting: PostSorting; +} +interface PostOptionContextProps { + postOption: PostOption; + setPostOption: Dispatch>; +} + +export default function PostOptionProvider({ children }: PropsWithChildren) { + const [postOption, setPostOption] = useState({ + sorting: SORTING.LATEST, + status: STATUS.ALL, + }); + + return ( + + {children} + + ); +} diff --git a/frontend/src/hooks/query/category/useCategoryFavoriteToggle.ts b/frontend/src/hooks/query/category/useCategoryFavoriteToggle.ts new file mode 100644 index 000000000..13fa830ca --- /dev/null +++ b/frontend/src/hooks/query/category/useCategoryFavoriteToggle.ts @@ -0,0 +1,25 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { Category } from '@type/category'; + +import { addFavoriteCategory, removeFavoriteCategory } from '@api/categoryList'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useCategoryFavoriteToggle = () => { + const queryClient = useQueryClient(); + const { mutate, isLoading, isError, error } = useMutation( + ({ id, isFavorite }: Omit) => + isFavorite ? removeFavoriteCategory(id) : addFavoriteCategory(id), + { + onSuccess: () => { + queryClient.invalidateQueries([QUERY_KEY.CATEGORIES]); + }, + onError: error => { + window.console.log('Category favorite toggle error', error); + }, + } + ); + + return { mutate, isLoading, isError, error }; +}; diff --git a/frontend/src/hooks/query/category/useCategoryList.ts b/frontend/src/hooks/query/category/useCategoryList.ts new file mode 100644 index 000000000..3235ca7d0 --- /dev/null +++ b/frontend/src/hooks/query/category/useCategoryList.ts @@ -0,0 +1,21 @@ +import { useQuery } from '@tanstack/react-query'; + +import { Category } from '@type/category'; + +import { getGuestCategoryList, getUserCategoryList } from '@api/categoryList'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useCategoryList = (isLoggedIn: boolean) => { + const { data, error, isLoading, isError } = useQuery( + [QUERY_KEY.CATEGORIES, isLoggedIn], + isLoggedIn ? getUserCategoryList : getGuestCategoryList, + { + cacheTime: 60 * 60 * 1000, + staleTime: 60 * 60 * 1000, + suspense: true, + } + ); + + return { data, error, isLoading, isError }; +}; diff --git a/frontend/src/hooks/query/comment/useCommentList.ts b/frontend/src/hooks/query/comment/useCommentList.ts new file mode 100644 index 000000000..4b3e1e345 --- /dev/null +++ b/frontend/src/hooks/query/comment/useCommentList.ts @@ -0,0 +1,17 @@ +import { useQuery } from '@tanstack/react-query'; + +import { getCommentList } from '@api/comment'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useCommentList = (postId: number) => { + const { data, error, isLoading } = useQuery( + [QUERY_KEY.POSTS, postId, QUERY_KEY.COMMENTS], + () => getCommentList(postId), + { + suspense: true, + } + ); + + return { data, error, isLoading }; +}; diff --git a/frontend/src/hooks/query/comment/useCreateComment.ts b/frontend/src/hooks/query/comment/useCreateComment.ts new file mode 100644 index 000000000..a8715739b --- /dev/null +++ b/frontend/src/hooks/query/comment/useCreateComment.ts @@ -0,0 +1,24 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { CommentRequest } from '@type/comment'; + +import { createComment } from '@api/comment'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useCreateComment = (postId: number) => { + const queryClient = useQueryClient(); + const { mutate, isSuccess, isLoading, isError, error } = useMutation( + (newComment: CommentRequest) => createComment(postId, newComment), + { + onSuccess: () => { + queryClient.invalidateQueries([QUERY_KEY.POSTS, postId, QUERY_KEY.COMMENTS]); + }, + onError: error => { + window.console.log('createComment error', error); + }, + } + ); + + return { mutate, isSuccess, isLoading, isError, error }; +}; diff --git a/frontend/src/hooks/query/comment/useDeleteComment.ts b/frontend/src/hooks/query/comment/useDeleteComment.ts new file mode 100644 index 000000000..b0d5d9442 --- /dev/null +++ b/frontend/src/hooks/query/comment/useDeleteComment.ts @@ -0,0 +1,22 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { deleteComment } from '@api/comment'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useDeleteComment = (postId: number, commentId: number) => { + const queryClient = useQueryClient(); + const { mutate, isLoading, isError, error } = useMutation( + () => deleteComment(postId, commentId), + { + onSuccess: () => { + queryClient.invalidateQueries([QUERY_KEY.POSTS, postId, QUERY_KEY.COMMENTS]); + }, + onError: error => { + window.console.log('Delete Comment error', error); + }, + } + ); + + return { mutate, isLoading, isError, error }; +}; diff --git a/frontend/src/hooks/query/comment/useEditComment.ts b/frontend/src/hooks/query/comment/useEditComment.ts new file mode 100644 index 000000000..4e4b6789e --- /dev/null +++ b/frontend/src/hooks/query/comment/useEditComment.ts @@ -0,0 +1,24 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { Comment } from '@type/comment'; + +import { editComment } from '@api/comment'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useEditComment = (postId: number, commentId: number) => { + const queryClient = useQueryClient(); + const { mutate, isSuccess, isLoading, isError, error } = useMutation( + (updatedComment: Comment) => editComment(postId, commentId, updatedComment), + { + onSuccess: () => { + queryClient.invalidateQueries([QUERY_KEY.POSTS, postId, QUERY_KEY.COMMENTS]); + }, + onError: error => { + window.console.log('댓글 수정에 실패했습니다. 다시 시도해주세요.', error); + }, + } + ); + + return { mutate, isSuccess, isLoading, isError, error }; +}; diff --git a/frontend/src/hooks/query/post/useCreatePost.ts b/frontend/src/hooks/query/post/useCreatePost.ts new file mode 100644 index 000000000..018ddd1a2 --- /dev/null +++ b/frontend/src/hooks/query/post/useCreatePost.ts @@ -0,0 +1,24 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { createPost } from '@api/post'; + +import { QUERY_KEY } from '@constants/queryKey'; + +const IS_LOGGED_IN = true; + +export const useCreatePost = () => { + const queryClient = useQueryClient(); + const { mutate, isLoading, isSuccess, isError, error } = useMutation( + (post: FormData) => createPost(post), + { + onSuccess: () => { + queryClient.invalidateQueries([QUERY_KEY.USER_INFO, IS_LOGGED_IN]); + }, + onError: error => { + window.console.log('createPost error', error); + }, + } + ); + + return { mutate, isLoading, isSuccess, isError, error }; +}; diff --git a/frontend/src/hooks/query/post/useCreateVote.ts b/frontend/src/hooks/query/post/useCreateVote.ts new file mode 100644 index 000000000..3084048d4 --- /dev/null +++ b/frontend/src/hooks/query/post/useCreateVote.ts @@ -0,0 +1,31 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { votePost } from '@api/post'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useCreateVote = ({ isPreview, postId }: { isPreview: boolean; postId: number }) => { + const queryClient = useQueryClient(); + const LOGGED_IN = true; + + const { mutate, isError, error } = useMutation({ + mutationFn: (optionId: number) => votePost(postId, optionId), + onSuccess: () => { + queryClient.invalidateQueries([QUERY_KEY.USER_INFO, true]); + + if (isPreview) { + queryClient.invalidateQueries({ + predicate: ({ queryKey }) => queryKey[0] === QUERY_KEY.POSTS, + }); + return; + } + + queryClient.invalidateQueries([QUERY_KEY.POST_DETAIL, postId, LOGGED_IN]); + }, + onError: error => { + window.console.log('투표 선택지 생성에 실패했습니다.', error); + }, + }); + + return { mutate, isError, error }; +}; diff --git a/frontend/src/hooks/query/post/useDeletePost.ts b/frontend/src/hooks/query/post/useDeletePost.ts new file mode 100644 index 000000000..8be37d2bb --- /dev/null +++ b/frontend/src/hooks/query/post/useDeletePost.ts @@ -0,0 +1,21 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { deletePost } from '@api/post'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useDeletePost = (postId: number, isLogged: boolean) => { + const queryClient = useQueryClient(); + + const { mutate, isSuccess, isError, error } = useMutation({ + mutationFn: () => deletePost(postId), + onSuccess: () => { + queryClient.invalidateQueries([QUERY_KEY.USER_INFO, isLogged]); + }, + onError: error => { + window.console.log('게시물 삭제에 실패했습니다.', error); + }, + }); + + return { mutate, isSuccess, isError, error }; +}; diff --git a/frontend/src/hooks/query/post/useEarlyClosePost.ts b/frontend/src/hooks/query/post/useEarlyClosePost.ts new file mode 100644 index 000000000..bfd27c61b --- /dev/null +++ b/frontend/src/hooks/query/post/useEarlyClosePost.ts @@ -0,0 +1,22 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { setEarlyClosePost } from '@api/post'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useEarlyClosePost = (postId: number) => { + const queryClient = useQueryClient(); + const LOGGED_IN = true; + + const { mutate, isError, error } = useMutation({ + mutationFn: () => setEarlyClosePost(postId), + onSuccess: () => { + queryClient.invalidateQueries([QUERY_KEY.POST_DETAIL, postId, LOGGED_IN]); + }, + onError: error => { + window.console.log('조기마감에 실패했습니다.', error); + }, + }); + + return { mutate, isError, error }; +}; diff --git a/frontend/src/hooks/query/post/useEditPost.ts b/frontend/src/hooks/query/post/useEditPost.ts new file mode 100644 index 000000000..28c230ca9 --- /dev/null +++ b/frontend/src/hooks/query/post/useEditPost.ts @@ -0,0 +1,22 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { editPost } from '@api/post'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useEditPost = (postId: number) => { + const queryClient = useQueryClient(); + const { mutate, isLoading, isSuccess, isError, error } = useMutation( + (updatedPost: FormData) => editPost(postId, updatedPost), + { + onSuccess: () => { + queryClient.invalidateQueries([QUERY_KEY.POST_DETAIL, postId]); + }, + onError: error => { + window.console.log('editPost error', error); + }, + } + ); + + return { mutate, isLoading, isSuccess, isError, error }; +}; diff --git a/frontend/src/hooks/query/post/useEditVote.ts b/frontend/src/hooks/query/post/useEditVote.ts new file mode 100644 index 000000000..90e3d8e75 --- /dev/null +++ b/frontend/src/hooks/query/post/useEditVote.ts @@ -0,0 +1,29 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { OptionData, changeVotedOption } from '@api/post'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useEditVote = ({ isPreview, postId }: { isPreview: boolean; postId: number }) => { + const queryClient = useQueryClient(); + const LOGGED_IN = true; + + const { mutate, isError, error } = useMutation({ + mutationFn: (optionData: OptionData) => changeVotedOption(postId, optionData), + onSuccess: () => { + if (isPreview) { + queryClient.invalidateQueries({ + predicate: ({ queryKey }) => queryKey[0] === QUERY_KEY.POSTS, + }); + return; + } + + queryClient.invalidateQueries([QUERY_KEY.POST_DETAIL, postId, LOGGED_IN]); + }, + onError: error => { + window.console.log('투표 선택지 생성에 실패했습니다.', error); + }, + }); + + return { mutate, isError, error }; +}; diff --git a/frontend/src/hooks/query/post/usePostDetail.ts b/frontend/src/hooks/query/post/usePostDetail.ts new file mode 100644 index 000000000..643d1256d --- /dev/null +++ b/frontend/src/hooks/query/post/usePostDetail.ts @@ -0,0 +1,21 @@ +import { useQuery } from '@tanstack/react-query'; + +import { PostInfo } from '@type/post'; + +import { getPost, getPostForGuest } from '@api/post'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const usePostDetail = (isLoggedIn: boolean, postId: number) => { + const fetchApi = isLoggedIn ? getPost : getPostForGuest; + + const { data, isError, isLoading, error } = useQuery( + [QUERY_KEY.POST_DETAIL, postId, isLoggedIn], + () => fetchApi(postId), + { + suspense: true, + } + ); + + return { data, isError, isLoading, error }; +}; diff --git a/frontend/src/hooks/query/ranking/usePassionUserRanking.ts b/frontend/src/hooks/query/ranking/usePassionUserRanking.ts new file mode 100644 index 000000000..84a745c5b --- /dev/null +++ b/frontend/src/hooks/query/ranking/usePassionUserRanking.ts @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query'; + +import { PassionUser } from '@type/ranking'; + +import { getPassionUserRanking } from '@api/ranking'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const usePassionUserRanking = () => { + const { data, error, isLoading, isError } = useQuery( + [QUERY_KEY.PASSION_RANKING], + getPassionUserRanking, + { + suspense: true, + } + ); + + return { data, error, isLoading, isError }; +}; diff --git a/frontend/src/hooks/query/ranking/usePopularPostRanking.ts b/frontend/src/hooks/query/ranking/usePopularPostRanking.ts new file mode 100644 index 000000000..a8951aa63 --- /dev/null +++ b/frontend/src/hooks/query/ranking/usePopularPostRanking.ts @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query'; + +import { RankingPost } from '@type/ranking'; + +import { getPopularPostRanking } from '@api/ranking'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const usePopularPostRanking = () => { + const { data, error, isLoading, isError } = useQuery( + [QUERY_KEY.POPULAR_RANKING], + getPopularPostRanking, + { + suspense: true, + } + ); + + return { data, error, isLoading, isError }; +}; diff --git a/frontend/src/hooks/query/ranking/useUserRanking.ts b/frontend/src/hooks/query/ranking/useUserRanking.ts new file mode 100644 index 000000000..71d9b9c2b --- /dev/null +++ b/frontend/src/hooks/query/ranking/useUserRanking.ts @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query'; + +import { PassionUser } from '@type/ranking'; + +import { getUserRanking } from '@api/ranking'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useUserRanking = (isLoggedIn: boolean) => { + const { data, error, isLoading, isError } = useQuery( + [QUERY_KEY.USER_INFO, isLoggedIn, QUERY_KEY.PASSION_RANKING], + () => getUserRanking(isLoggedIn), + { + suspense: true, + } + ); + + return { data, error, isLoading, isError }; +}; diff --git a/frontend/src/hooks/query/useExample.tsx b/frontend/src/hooks/query/useExample.tsx new file mode 100644 index 000000000..804f042aa --- /dev/null +++ b/frontend/src/hooks/query/useExample.tsx @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query'; + +import { Cart, getCartList } from '@api/example'; + +const convertCartList = (cartList: { id: number; text: string }[]) => { + return cartList.map(cart => ({ id: cart.id, content: cart.text })); +}; + +export const useExample = () => { + const { data, error, isLoading } = useQuery(['carts'], getCartList, { + onSuccess: data => { + const updatedData = convertCartList(data); + + return updatedData; + }, + }); + + return { data, error, isLoading }; +}; diff --git a/frontend/src/hooks/query/usePostList.tsx b/frontend/src/hooks/query/usePostList.tsx new file mode 100644 index 000000000..8f8017efc --- /dev/null +++ b/frontend/src/hooks/query/usePostList.tsx @@ -0,0 +1,35 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; + +import { PostList, PostListByOptionalOption, PostListByRequiredOption } from '@type/post'; + +import { getPostList } from '@api/postList'; + +import { POST_LIST_MAX_LENGTH } from '@constants/post'; +import { QUERY_KEY } from '@constants/queryKey'; + +export const usePostList = ( + requiredOption: Omit, + optionalOption: PostListByOptionalOption +) => { + const { postSorting, postStatus, isLoggedIn, postType } = requiredOption; + const { categoryId, keyword } = optionalOption; + + const { data, error, fetchNextPage, hasNextPage, isFetchingNextPage } = + useInfiniteQuery( + [QUERY_KEY.POSTS, postSorting, postStatus, categoryId, keyword, isLoggedIn, postType], + ({ pageParam = 0 }) => + getPostList({ ...requiredOption, pageNumber: pageParam }, optionalOption), + { + getNextPageParam: lastPage => { + if (lastPage.postList.length !== POST_LIST_MAX_LENGTH) return; + + return lastPage.pageNumber + 1; + }, + suspense: true, + } + ); + + const isPostListEmpty = data?.pages[0].postList.length === 0; + + return { data, error, fetchNextPage, hasNextPage, isFetchingNextPage, isPostListEmpty }; +}; diff --git a/frontend/src/hooks/query/user/useModifyUser.ts b/frontend/src/hooks/query/user/useModifyUser.ts new file mode 100644 index 000000000..fff5e6397 --- /dev/null +++ b/frontend/src/hooks/query/user/useModifyUser.ts @@ -0,0 +1,28 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { modifyNickname } from '@api/userInfo'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useModifyUser = () => { + const queryClient = useQueryClient(); + const { mutate, isLoading, isSuccess, isError, error } = useMutation({ + mutationFn: (nickname: string) => modifyNickname(nickname), + onMutate: async newNickname => { + await queryClient.cancelQueries({ queryKey: [QUERY_KEY.USER_INFO] }); + const previousNickname = queryClient.getQueryData([QUERY_KEY.USER_INFO]); + queryClient.setQueryData([QUERY_KEY.USER_INFO], newNickname); + + return { previousNickname, newNickname }; + }, + onError: (error, __, context) => { + queryClient.setQueryData([QUERY_KEY.USER_INFO], context?.previousNickname); + window.console.error('닉네임 변경에 실패했습니다.'); + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEY.USER_INFO] }); + }, + }); + + return { mutate, isLoading, isSuccess, isError, error }; +}; diff --git a/frontend/src/hooks/query/user/useUpdateUserInfo.ts b/frontend/src/hooks/query/user/useUpdateUserInfo.ts new file mode 100644 index 000000000..d127b6572 --- /dev/null +++ b/frontend/src/hooks/query/user/useUpdateUserInfo.ts @@ -0,0 +1,24 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { UpdateUserInfoRequest } from '@type/user'; + +import { updateUserInfo } from '@api/userInfo'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useUpdateUserInfo = () => { + const queryClient = useQueryClient(); + + const LOGGED_IN = true; + const { mutate, isLoading, isSuccess, isError, error } = useMutation({ + mutationFn: async (userInfo: UpdateUserInfoRequest) => await updateUserInfo(userInfo), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEY.USER_INFO, LOGGED_IN] }); + }, + onError: () => { + window.console.error('개인 정보 등록에 실패했습니다.'); + }, + }); + + return { mutate, isLoading, isSuccess, isError, error }; +}; diff --git a/frontend/src/hooks/query/user/useUserInfo.ts b/frontend/src/hooks/query/user/useUserInfo.ts new file mode 100644 index 000000000..255fdc46e --- /dev/null +++ b/frontend/src/hooks/query/user/useUserInfo.ts @@ -0,0 +1,21 @@ +import { useQuery } from '@tanstack/react-query'; + +import { User } from '@type/user'; + +import { getUserInfo } from '@api/userInfo'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useUserInfo = (isLoggedIn: boolean) => { + const { data, error, isLoading, isError } = useQuery( + [QUERY_KEY.USER_INFO, isLoggedIn], + () => getUserInfo(isLoggedIn), + { + cacheTime: 60 * 60 * 1000, + staleTime: 60 * 60 * 1000, + suspense: true, + } + ); + + return { data, error, isLoading, isError }; +}; diff --git a/frontend/src/hooks/query/user/useWithdrawalMembership.ts b/frontend/src/hooks/query/user/useWithdrawalMembership.ts new file mode 100644 index 000000000..923e449d4 --- /dev/null +++ b/frontend/src/hooks/query/user/useWithdrawalMembership.ts @@ -0,0 +1,23 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { withdrawalMembership } from '@api/userInfo'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useWithdrawalMembership = () => { + const queryClient = useQueryClient(); + + const LOGGED_IN = true; + const { mutate, isLoading, isSuccess, isError, error } = useMutation({ + mutationFn: async () => await withdrawalMembership(), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEY.USER_INFO, LOGGED_IN] }); + }, + onError: () => { + window.console.error('회원 탈퇴에 실패했습니다.'); + alert('회원 탈퇴에 실패했습니다.'); + }, + }); + + return { mutate, isLoading, isSuccess, isError, error }; +}; diff --git a/frontend/src/hooks/useContentImage.ts b/frontend/src/hooks/useContentImage.ts new file mode 100644 index 000000000..85dfd196e --- /dev/null +++ b/frontend/src/hooks/useContentImage.ts @@ -0,0 +1,40 @@ +import { ChangeEvent, useRef, useState } from 'react'; + +import { MAX_FILE_SIZE } from '@components/PostForm/constants'; + +export const useContentImage = (imageUrl: string = '') => { + const [contentImage, setContentImage] = useState(imageUrl); + const contentInputRef = useRef(null); + + const removeImage = () => { + setContentImage(''); + if (contentInputRef.current) contentInputRef.current.value = ''; + }; + + const handleUploadImage = (event: ChangeEvent) => { + const { files } = event.target; + + if (!files) return; + + const file = files[0]; + + event.target.setCustomValidity(''); + + if (file.size > MAX_FILE_SIZE) { + event.target.setCustomValidity('사진의 용량은 1.5MB 이하만 가능합니다.'); + event.target.reportValidity(); + + return; + } + + const reader = new FileReader(); + + reader.readAsDataURL(file); + + reader.onloadend = () => { + setContentImage(reader.result?.toString() ?? ''); + }; + }; + + return { contentImage, contentInputRef, removeImage, handleUploadImage }; +}; diff --git a/frontend/src/hooks/useCount.ts b/frontend/src/hooks/useCount.ts new file mode 100644 index 000000000..4e6d90de1 --- /dev/null +++ b/frontend/src/hooks/useCount.ts @@ -0,0 +1,11 @@ +import { useState } from 'react'; + +export const useCount = () => { + const [count, setCount] = useState(0); + + const increase = () => { + setCount(count + 1); + }; + + return { count, increase }; +}; diff --git a/frontend/src/hooks/useCurrentKeyword.ts b/frontend/src/hooks/useCurrentKeyword.ts new file mode 100644 index 000000000..316f6a1ec --- /dev/null +++ b/frontend/src/hooks/useCurrentKeyword.ts @@ -0,0 +1,17 @@ +import { useSearchParams } from 'react-router-dom'; + +import { DEFAULT_KEYWORD, SEARCH_KEYWORD, SEARCH_KEYWORD_MAX_LENGTH } from '@constants/post'; + +import { getTrimmedWord } from '@utils/getTrimmedWord'; + +export const useCurrentKeyword = () => { + const [searchParams] = useSearchParams(); + const currentKeyword = + searchParams.get(SEARCH_KEYWORD)?.toString().slice(0, SEARCH_KEYWORD_MAX_LENGTH) ?? + DEFAULT_KEYWORD; + + return { + currentKeyword: + currentKeyword !== DEFAULT_KEYWORD ? getTrimmedWord(currentKeyword) : currentKeyword, + }; +}; diff --git a/frontend/src/hooks/useDrawer.tsx b/frontend/src/hooks/useDrawer.tsx new file mode 100644 index 000000000..ad40fc2f2 --- /dev/null +++ b/frontend/src/hooks/useDrawer.tsx @@ -0,0 +1,34 @@ +import { useEffect, useRef } from 'react'; + +export const useDrawer = (placement: 'left' | 'right') => { + const drawerRef = useRef(null); + + const openDrawer = () => { + if (!drawerRef.current) return; + + drawerRef.current.showModal(); + drawerRef.current.style.transform = 'translateX(0)'; + }; + + const closeDrawer = () => { + if (!drawerRef.current) return; + + drawerRef.current.style.transform = + placement === 'left' ? 'translateX(-100%)' : 'translateX(100%)'; + + setTimeout(() => { + if (!drawerRef.current) return; + + drawerRef.current.close(); + }, 300); + }; + + useEffect(() => { + if (!drawerRef.current) return; + + drawerRef.current.style.transform = + placement === 'left' ? 'translateX(-100%)' : 'translateX(100%)'; + }, []); + + return { drawerRef, openDrawer, closeDrawer }; +}; diff --git a/frontend/src/hooks/useFetch.ts b/frontend/src/hooks/useFetch.ts new file mode 100644 index 000000000..fd1d4b872 --- /dev/null +++ b/frontend/src/hooks/useFetch.ts @@ -0,0 +1,30 @@ +import { useCallback, useEffect, useState } from 'react'; + +export const useFetch = (fetchFn: () => Promise) => { + const [data, setData] = useState(null); + const [errorMessage, setErrorMessage] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + const refetch = useCallback(() => { + setIsLoading(true); + setData(null); + setErrorMessage(null); + + fetchFn() + .then(res => { + setData(res); + }) + .catch(error => { + setErrorMessage(error.message); + }) + .finally(() => { + setIsLoading(false); + }); + }, [fetchFn]); + + useEffect(() => { + refetch(); + }, []); + + return { data, errorMessage, isLoading, refetch }; +}; diff --git a/frontend/src/hooks/useIntersectionObserver.tsx b/frontend/src/hooks/useIntersectionObserver.tsx new file mode 100644 index 000000000..b67914ab6 --- /dev/null +++ b/frontend/src/hooks/useIntersectionObserver.tsx @@ -0,0 +1,34 @@ +import { useEffect, useRef, useState } from 'react'; + +interface UseIntersectionObserverParams { + root: Element | Document | null; + rootMargin: string; + thresholds: number; +} + +export const useIntersectionObserver = (options: UseIntersectionObserverParams) => { + const [isIntersecting, setIsIntersecting] = useState(false); + const targetRef = useRef(null); + + useEffect(() => { + const target = targetRef.current; + + if (!target) return; + + const observer = new IntersectionObserver(([entry]) => { + setIsIntersecting(entry.isIntersecting); + }, options); + + if (targetRef.current) { + observer.observe(target); + } + + return () => { + if (target) { + observer.unobserve(target); + } + }; + }, [options, targetRef]); + + return { targetRef, isIntersecting }; +}; diff --git a/frontend/src/hooks/useMoreComment.ts b/frontend/src/hooks/useMoreComment.ts new file mode 100644 index 000000000..42657b89f --- /dev/null +++ b/frontend/src/hooks/useMoreComment.ts @@ -0,0 +1,16 @@ +import { useState } from 'react'; + +import { Comment } from '@type/comment'; + +export const useMoreComment = (commentList: Comment[]) => { + const [page, setPage] = useState(1); + const pageSize = page * 10; + + const handleMoreComment = () => { + setPage(prevPage => prevPage + 1); + }; + + const hasMoreComment = commentList.length > pageSize; + + return { slicedCommentList: commentList.slice(0, pageSize), handleMoreComment, hasMoreComment }; +}; diff --git a/frontend/src/hooks/useMultiSelect.ts b/frontend/src/hooks/useMultiSelect.ts new file mode 100644 index 000000000..c032641a5 --- /dev/null +++ b/frontend/src/hooks/useMultiSelect.ts @@ -0,0 +1,21 @@ +import { useState } from 'react'; + +import type { Option } from '@components/common/MultiSelect/types'; + +export const useMultiSelect = (initialSelectedOptionList: Option[], optionCountLimit?: number) => { + const [selectedOptionList, setSelectedOptionList] = useState(initialSelectedOptionList); + + const handleOptionAdd = (newItem: Option) => { + if (optionCountLimit && optionCountLimit === selectedOptionList.length) { + alert(`${optionCountLimit}개까지 선택 가능합니다!`); + return; + } + setSelectedOptionList([...selectedOptionList, newItem]); + }; + + const handleOptionDelete = (optionId: number) => { + setSelectedOptionList(selectedOptionList.filter(option => option.id !== optionId)); + }; + + return { selectedOptionList, handleOptionAdd, handleOptionDelete }; +}; diff --git a/frontend/src/hooks/usePostRequestInfo.ts b/frontend/src/hooks/usePostRequestInfo.ts new file mode 100644 index 000000000..83df6707e --- /dev/null +++ b/frontend/src/hooks/usePostRequestInfo.ts @@ -0,0 +1,40 @@ +import { useLocation, useParams } from 'react-router-dom'; + +import { PostRequestKind } from '@components/post/PostListPage/types'; + +import { PATH } from '@constants/path'; +import { DEFAULT_CATEGORY_ID, POST_TYPE } from '@constants/post'; + +import { getPathFragment } from '@utils/getPathFragment'; + +import { useCurrentKeyword } from './useCurrentKeyword'; + +const REQUEST_URL: Record = { + [PATH.HOME]: POST_TYPE.ALL, + [PATH.POST_CATEGORY]: POST_TYPE.CATEGORY, + [PATH.USER_POST]: POST_TYPE.MY_POST, + [PATH.USER_VOTE]: POST_TYPE.MY_VOTE, + [PATH.SEARCH]: POST_TYPE.SEARCH, +}; + +export const usePostRequestInfo = () => { + const params = useParams<{ categoryId?: string }>(); + const { currentKeyword } = useCurrentKeyword(); + const { pathname } = useLocation(); + + const categoryId = Number(params.categoryId ?? DEFAULT_CATEGORY_ID); + + const convertedPathname = getPathFragment(pathname); + const postType = REQUEST_URL[convertedPathname]; + + const postOptionalOption = { + categoryId, + keyword: currentKeyword, + }; + + if (!postType) { + return { postType: REQUEST_URL[PATH.HOME], postOptionalOption }; + } + + return { postType, postOptionalOption }; +}; diff --git a/frontend/src/hooks/useSearch.ts b/frontend/src/hooks/useSearch.ts new file mode 100644 index 000000000..7ba5d82c9 --- /dev/null +++ b/frontend/src/hooks/useSearch.ts @@ -0,0 +1,42 @@ +import { ChangeEvent, FormEvent, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { SEARCH_KEYWORD_MAX_LENGTH } from '@constants/post'; + +import { getTrimmedWord } from '@utils/getTrimmedWord'; + +import { useText } from './useText'; + +export const useSearch = (initialKeyword = '') => { + const navigate = useNavigate(); + const searchInputRef = useRef(null); + const { text: keyword, setText: setKeyword, handleTextChange } = useText(initialKeyword); + + const handleKeywordChange = (event: ChangeEvent) => { + if (!searchInputRef.current) return; + + handleTextChange(event, { MAX_LENGTH: SEARCH_KEYWORD_MAX_LENGTH, MIN_LENGTH: 0 }); + }; + + const handleSearchSubmit = (event: FormEvent) => { + event.preventDefault(); + + if (!searchInputRef.current) return; + + const trimmedKeyword = getTrimmedWord(keyword); + + if (keyword !== trimmedKeyword) { + setKeyword(trimmedKeyword); + } + + if (trimmedKeyword === '') { + searchInputRef.current.setCustomValidity('검색어를 입력해주세요'); + searchInputRef.current.reportValidity(); + return; + } + + navigate(`/search?keyword=${trimmedKeyword}`); + }; + + return { keyword, handleKeywordChange, handleSearchSubmit, searchInputRef }; +}; diff --git a/frontend/src/hooks/useSelect.tsx b/frontend/src/hooks/useSelect.tsx new file mode 100644 index 000000000..f1e3c2e1d --- /dev/null +++ b/frontend/src/hooks/useSelect.tsx @@ -0,0 +1,11 @@ +import { useState } from 'react'; + +export const useSelect = (initialOption: T) => { + const [selectedOption, setSelectedOption] = useState(initialOption); + + const handleOptionChange = (option: T) => { + setSelectedOption(option); + }; + + return { selectedOption, handleOptionChange }; +}; diff --git a/frontend/src/hooks/useText.ts b/frontend/src/hooks/useText.ts new file mode 100644 index 000000000..6ff6ce6c6 --- /dev/null +++ b/frontend/src/hooks/useText.ts @@ -0,0 +1,30 @@ +import React, { useState } from 'react'; + +export type InputLength = Record<'MAX_LENGTH' | 'MIN_LENGTH', number>; + +export const useText = (originalText: string) => { + const [text, setText] = useState(originalText); + + const handleTextChange = ( + event: React.ChangeEvent, + limit: InputLength + ) => { + const { value } = event.target; + const standard = value.length; + + if (standard > limit.MAX_LENGTH) { + event.target.setCustomValidity(`해당 입력값은 ${limit.MAX_LENGTH}자까지 입력 가능합니다.`); + event.target.reportValidity(); + return; + } + + setText(value); + event.target.setCustomValidity(''); + }; + + const resetText = () => { + setText(''); + }; + + return { text, setText, handleTextChange, resetText }; +}; diff --git a/frontend/src/hooks/useToast.ts b/frontend/src/hooks/useToast.ts new file mode 100644 index 000000000..0d92b748c --- /dev/null +++ b/frontend/src/hooks/useToast.ts @@ -0,0 +1,32 @@ +import { useEffect, useRef, useState } from 'react'; + +import { TOAST_TIME } from '@constants/animation'; + +export const useToast = () => { + const [isToastOpen, setIsToastOpen] = useState(false); + const [toastMessage, setToastMessage] = useState(''); + const timeIdRef = useRef(); + + const clear = () => { + if (timeIdRef.current) { + window.clearTimeout(timeIdRef.current); + } + }; + + const openToast = (message: string) => { + clear(); + + setIsToastOpen(true); + setToastMessage(message); + + timeIdRef.current = window.setTimeout(() => { + setIsToastOpen(false); + }, TOAST_TIME * 1000); + }; + + useEffect(() => { + return clear; + }, []); + + return { isToastOpen, toastMessage, openToast }; +}; diff --git a/frontend/src/hooks/useToggle.tsx b/frontend/src/hooks/useToggle.tsx new file mode 100644 index 000000000..0ea365b33 --- /dev/null +++ b/frontend/src/hooks/useToggle.tsx @@ -0,0 +1,19 @@ +import { useState } from 'react'; + +export const useToggle = () => { + const [isOpen, setIsOpen] = useState(false); + + const openComponent = () => { + setIsOpen(true); + }; + + const closeComponent = () => { + setIsOpen(false); + }; + + const toggleComponent = () => { + setIsOpen(prevIsOpen => !prevIsOpen); + }; + + return { isOpen, openComponent, closeComponent, toggleComponent }; +}; diff --git a/frontend/src/hooks/useToggleSwitch.ts b/frontend/src/hooks/useToggleSwitch.ts new file mode 100644 index 000000000..ceadc36ef --- /dev/null +++ b/frontend/src/hooks/useToggleSwitch.ts @@ -0,0 +1,17 @@ +import { useState } from 'react'; + +export const useToggleSwitch = (firstText: string, secondText: string) => { + const [selectedButton, setSelectedButton] = useState(firstText); + + const firstButton = { + text: firstText, + event: () => setSelectedButton(firstText), + }; + + const secondButton = { + text: secondText, + event: () => setSelectedButton(secondText), + }; + + return { selectedButton, firstButton, secondButton }; +}; diff --git a/frontend/src/hooks/useWritingOption.tsx b/frontend/src/hooks/useWritingOption.tsx new file mode 100644 index 000000000..7211b24ce --- /dev/null +++ b/frontend/src/hooks/useWritingOption.tsx @@ -0,0 +1,116 @@ +import React, { ChangeEvent, useState } from 'react'; + +import { MAX_FILE_SIZE } from '@components/PostForm/constants'; + +const MAX_WRITING_LENGTH = 50; + +export interface WritingVoteOptionType { + id: number; + text: string; + imageUrl: string; +} + +const MIN_COUNT = 2; +const MAX_COUNT = 5; + +const INIT_OPTION_LIST = [ + { id: Math.floor(Math.random() * 100000), text: '', imageUrl: '' }, + { id: Math.floor(Math.random() * 100000), text: '', imageUrl: '' }, +]; + +export const useWritingOption = (initialOptionList: WritingVoteOptionType[] = INIT_OPTION_LIST) => { + const [optionList, setOptionList] = useState(initialOptionList); + + const addOption = () => { + if (optionList.length >= MAX_COUNT) return; + + const updatedOptionList = [ + ...optionList, + { id: Math.floor(Math.random() * 100000), text: '', imageUrl: '' }, + ]; + + setOptionList(updatedOptionList); + }; + + const writingOption = + (optionId: number) => (event: ChangeEvent) => { + const { value } = event.target; + const standard = value.length; + + if (standard === MAX_WRITING_LENGTH) { + event.target.setCustomValidity( + `선택지 내용은 ${MAX_WRITING_LENGTH}자까지 입력 가능합니다.` + ); + event.target.reportValidity(); + return; + } + + const updateOptionList = optionList.map(optionItem => { + return optionItem.id !== optionId + ? optionItem + : { + ...optionItem, + text: value, + }; + }); + + event.target.setCustomValidity(''); + setOptionList(updateOptionList); + }; + + const deleteOption = (optionId: number) => { + if (optionList.length <= MIN_COUNT) return; + + const removedOptionList = optionList.filter(optionItem => optionItem.id !== optionId); + + setOptionList(removedOptionList); + }; + + const removeImage = (optionId: number) => { + const updatedOptionList = optionList.map(optionItem => { + if (optionItem.id === optionId) { + return { ...optionItem, imageUrl: '' }; + } + + return optionItem; + }); + + setOptionList(updatedOptionList); + }; + + const handleUploadImage = (event: React.ChangeEvent, optionId: number) => { + const { files } = event.target; + + if (!files) return; + + const file = files[0]; + + event.target.setCustomValidity(''); + + if (file.size > MAX_FILE_SIZE) { + event.target.setCustomValidity('사진의 용량은 1.5MB 이하만 가능합니다.'); + event.target.reportValidity(); + + return; + } + + const reader = new FileReader(); + + // readAsDataURL 메서드를 통해 파일을 모두 읽고 나면 reader의 loadend 이벤트에서 이미지 미리보기 결과를 확인할 수 있습니다. + reader.readAsDataURL(file); + + reader.onloadend = () => { + const updatedOptionList = optionList.map(optionItem => { + if (optionItem.id === optionId) { + return { ...optionItem, imageUrl: reader.result?.toString() ?? '' }; + } + + return optionItem; + }); + + setOptionList(updatedOptionList); + }; + }; + + return { optionList, addOption, writingOption, deleteOption, removeImage, handleUploadImage }; +}; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx new file mode 100644 index 000000000..45324606d --- /dev/null +++ b/frontend/src/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import ReactDOM from 'react-dom/client'; + +import App from './App'; +import { worker } from './mocks/worker'; + +if (process.env.NODE_ENV === 'development') { + worker.start(); +} + +if ( + navigator.userAgent.search('Trident') !== -1 || + navigator.userAgent.toLowerCase().indexOf('msie') !== -1 +) { + alert('이 브라우저는 지원 중단 되었습니다. 최적의 환경을 위해 브라우저를 업데이트 하세요.'); +} + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render( + + + +); diff --git a/frontend/src/mocks/categoryList.ts b/frontend/src/mocks/categoryList.ts new file mode 100644 index 000000000..90423b109 --- /dev/null +++ b/frontend/src/mocks/categoryList.ts @@ -0,0 +1,25 @@ +import { rest } from 'msw'; + +import { MOCK_CATEGORY_LIST, MOCK_GUEST_CATEGORY_LIST } from '@mocks/mockData/categoryList'; + +export const mockCategoryHandlers = [ + rest.get('/categories', (req, res, ctx) => { + return res(ctx.status(200), ctx.json(MOCK_CATEGORY_LIST)); + }), + + rest.get('/categories/guest', (req, res, ctx) => { + return res(ctx.status(200), ctx.json(MOCK_GUEST_CATEGORY_LIST)); + }), + + rest.post('/categories/:categoryId/like', (req, res, ctx) => { + MOCK_CATEGORY_LIST[1].isFavorite = true; + + return res(ctx.status(201), ctx.json({ message: '카테고리 즐겨찾기 등록 성공' })); + }), + + rest.delete('/categories/:categoryId/like', (req, res, ctx) => { + MOCK_CATEGORY_LIST[0].isFavorite = false; + + return res(ctx.status(204)); + }), +]; diff --git a/frontend/src/mocks/comment.ts b/frontend/src/mocks/comment.ts new file mode 100644 index 000000000..97aba6dd4 --- /dev/null +++ b/frontend/src/mocks/comment.ts @@ -0,0 +1,33 @@ +import { rest } from 'msw'; + +import { MOCK_COMMENT_LIST } from './mockData/comment'; + +export const mockComment = [ + rest.get(`/posts/:postId/comments`, (req, res, ctx) => { + return res(ctx.delay(1000), ctx.status(200), ctx.json(MOCK_COMMENT_LIST)); + }), + + rest.post('/posts/:postId/comments', (req, res, ctx) => { + window.console.log('등록한 댓글 내용', req.body); + + return res( + ctx.delay(1000), + ctx.status(201), + ctx.json({ message: '댓글이 성공적으로 등록되었습니다!!' }) + ); + }), + + rest.put('/posts/:postId/comments/:commentId', (req, res, ctx) => { + window.console.log('수정한 댓글 내용', req.body); + + return res( + ctx.delay(1000), + ctx.status(200), + ctx.json({ message: '댓글이 성공적으로 수정되었습니다!!' }) + ); + }), + + rest.delete('/posts/:postId/comments/:commentId', (req, res, ctx) => { + return res(ctx.delay(1000), ctx.status(204)); + }), +]; diff --git a/frontend/src/mocks/example/get.ts b/frontend/src/mocks/example/get.ts new file mode 100644 index 000000000..7e54b94c4 --- /dev/null +++ b/frontend/src/mocks/example/get.ts @@ -0,0 +1,7 @@ +import { rest } from 'msw'; + +export const example = [ + rest.get('/example', (req, res, ctx) => { + return res(ctx.status(200)); + }), +]; diff --git a/frontend/src/mocks/getVoteDetail.ts b/frontend/src/mocks/getVoteDetail.ts new file mode 100644 index 000000000..c0bf3a485 --- /dev/null +++ b/frontend/src/mocks/getVoteDetail.ts @@ -0,0 +1,18 @@ +import { rest } from 'msw'; + +import { MOCK_POST_INFO } from './mockData/post'; +import { MOCK_VOTE_RESULT } from './mockData/voteResult'; + +export const mockVoteResult = [ + rest.get(`/posts/:postId`, (req, res, ctx) => { + return res(ctx.status(200), ctx.delay(1000), ctx.json(MOCK_POST_INFO)); + }), + + rest.get(`/posts/:postId/options`, (req, res, ctx) => { + return res(ctx.status(200), ctx.delay(1000), ctx.json(MOCK_VOTE_RESULT)); + }), + + rest.get(`/posts/:postId/options/:optionId`, (req, res, ctx) => { + return res(ctx.status(200), ctx.delay(1000), ctx.json(MOCK_VOTE_RESULT)); + }), +]; diff --git a/frontend/src/mocks/handlers.ts b/frontend/src/mocks/handlers.ts new file mode 100644 index 000000000..97b934f5e --- /dev/null +++ b/frontend/src/mocks/handlers.ts @@ -0,0 +1,25 @@ +import { mockCategoryHandlers } from './categoryList'; +import { mockComment } from './comment'; +import { example } from './example/get'; +import { mockVoteResult } from './getVoteDetail'; +import { mockPost } from './post'; +import { mockPostList } from './postList'; +import { mockRanking } from './ranking'; +import { mockReport } from './report'; +import { mockToken } from './token'; +import { mockUserInfo } from './userInfo'; +import { mockVote } from './vote'; + +export const handlers = [ + ...example, + ...mockPostList, + ...mockPost, + ...mockVoteResult, + ...mockVote, + ...mockCategoryHandlers, + ...mockUserInfo, + ...mockComment, + ...mockReport, + ...mockRanking, + ...mockToken, +]; diff --git a/frontend/src/mocks/mockData/category.ts b/frontend/src/mocks/mockData/category.ts new file mode 100644 index 000000000..23a2c1f5d --- /dev/null +++ b/frontend/src/mocks/mockData/category.ts @@ -0,0 +1,8 @@ +import { Category } from '@type/category'; + +export const MOCK_FAVORITE_CATEGORIES: Category[] = [ + { id: 12312, name: '음식', isFavorite: false }, + { id: 12, name: '연애', isFavorite: true }, + { id: 13, name: '패션', isFavorite: true }, + { id: 14, name: '금융', isFavorite: false }, +]; diff --git a/frontend/src/mocks/mockData/categoryList.ts b/frontend/src/mocks/mockData/categoryList.ts new file mode 100644 index 000000000..0e6efb246 --- /dev/null +++ b/frontend/src/mocks/mockData/categoryList.ts @@ -0,0 +1,27 @@ +import { CategoryResponse } from '@type/category'; + +export const MOCK_CATEGORY_LIST: CategoryResponse[] = [ + { id: 1, name: '음식', isFavorite: false }, + { id: 2, name: '연애', isFavorite: true }, + { id: 3, name: '패션', isFavorite: false }, + { id: 4, name: '금융', isFavorite: false }, + { id: 5, name: '여행', isFavorite: false }, + { id: 6, name: '게임', isFavorite: false }, + { id: 7, name: '재테크', isFavorite: false }, + { id: 8, name: '요리', isFavorite: true }, + { id: 9, name: '개발', isFavorite: true }, + { id: 10, name: '전자기기', isFavorite: true }, +]; + +export const MOCK_GUEST_CATEGORY_LIST: CategoryResponse[] = [ + { id: 1, name: '음식', isFavorite: false }, + { id: 2, name: '연애', isFavorite: false }, + { id: 3, name: '패션', isFavorite: false }, + { id: 4, name: '금융', isFavorite: false }, + { id: 5, name: '여행', isFavorite: false }, + { id: 6, name: '게임', isFavorite: false }, + { id: 7, name: '재테크', isFavorite: false }, + { id: 8, name: '요리', isFavorite: false }, + { id: 9, name: '개발', isFavorite: false }, + { id: 10, name: '전자기기', isFavorite: false }, +]; diff --git a/frontend/src/mocks/mockData/comment.ts b/frontend/src/mocks/mockData/comment.ts new file mode 100644 index 000000000..98d68a692 --- /dev/null +++ b/frontend/src/mocks/mockData/comment.ts @@ -0,0 +1,48 @@ +import { Comment, CommentResponse } from '@type/comment'; + +import { transformCommentListResponse } from '@api/comment'; + +export const MOCK_COMMENT_LIST: CommentResponse[] = []; + +const commentList = [ + 'Woah, your project looks awesome! How long have you been coding for? ', + '일하기 싫어서 화장실에 앉아서 보는 중은 아닌데 아 원숭이 김종민보려고 눈뜬거 진짜웃겨ㅠㅠㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ', + '진짜 다보고 나니 눈물이 ㅜㅜ 너무 참아서 눈물이 줄줄 ㅜㅜ 미쳤네요', + 'ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 생일 축하드립니다 🎉🎉🎉 뭔가 예전에 무한도전에서 했던 돌+아이 콘테스트도 조금 생각나요', + '1:08 4:01 4:20 6:04\n제가 계속 보고 싶어서 정리한 타임코드입니다\n역시나 생일파티 콘텐츠는 아무리봐도 안 질리네요\n유병재님 덕분에 오늘도 마음이 풍선해집니다💚❤️', + '진짜ㅋㅋㅋㅋ레전드중 레전드인 컨텐츠인 것 같아요ㅋㅋㅋ큐ㅠㅠㅠ 몇번을 봐도 웃음이 멈추질 않는ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ!!>w ({ + id: Math.floor(Math.random() * 100000), + content: commentList[Math.floor(Math.random() * 12)], + createdAt: '2023.7.27. 07:43', + member: { + id: Math.floor(Math.random() * 100000), + nickname: nicknameList[Math.floor(Math.random() * 8)], + }, + updatedAt: Math.random() > 0.5 ? '2023.7.27. 07:43' : '2023.7.28. 07:43', +}); + +for (let index = 0; index < 50; index++) { + MOCK_COMMENT_LIST.push(getMockComment()); +} + +export const MOCK_TRANSFORMED_COMMENT_LIST: Comment[] = + transformCommentListResponse(MOCK_COMMENT_LIST); diff --git a/frontend/src/mocks/mockData/post.ts b/frontend/src/mocks/mockData/post.ts new file mode 100644 index 000000000..2a12707c4 --- /dev/null +++ b/frontend/src/mocks/mockData/post.ts @@ -0,0 +1,147 @@ +import { PostInfo, PostInfoResponse } from '@type/post'; + +import { transformPostResponse } from '@api/post'; + +const getMockPost = (): PostInfoResponse => ({ + postId: Math.floor(Math.random() * 100000), + title: + '어느 곳에서 정보를 찾아야 할지도 막막한 사람들을 위한, 심심풀이로 나의 취향과 남의 취향을 비교해보고 싶은 사람들을 위한 프로젝트', + writer: { + id: 2, + nickname: '우아한 잔치국수', + }, + content: + '이는 사람들에게 재미와 정보, 두 가지를 줄 수 있습니다. 사람들은 MBTI, 밸런스게임처럼 나와 같은 사람들을 찾고, 나와 다른 사람들과 비교하는 것을 즐깁니다. 이를 컨텐츠화하여 보다 빠르게 질문하고 답변하며, 사람들의 반응을 확인할 수 있다면 사람들은 충분한 즐거움을 느낄 것입니다. 또한 20대가 많은 대학가에 창업을 하고 싶지만 20대의 의견을 모르겠을 때, 확실한 답은 아닐지라도 어느 정도의 가이드를 줄 수 있을 것입니다. 질문자에게 제공되는 성별/나이대별 투표 결과 정보는 질문자가 하고자 하는 의사결정의 근거가 될 수 있을 것입니다.', + imageUrl: Math.random() > 0.7 ? 'https://source.unsplash.com/random' : '', + categories: [ + { + id: 1, + name: '개발', + }, + { + id: 2, + name: '연애', + }, + { + id: 3, + name: '상담', + }, + ], + createdAt: '2023-07-12 12:40', + deadline: '2023-07-13 18:40', + voteInfo: { + selectedOptionId: 9, + totalVoteCount: 123, + options: [ + { + optionId: 6, + content: '당선', + voteCount: 30, + votePercent: 30, + imageUrl: '', + }, + { + optionId: 7, + content: 'votogether', + voteCount: 40, + votePercent: 40, + imageUrl: '', + }, + { + optionId: 8, + content: + '인스타그램, 블라인드와 같은 SNS의 형식을 차용합니다. 누군가는 글을 쓰고, 누군가는 반응합니다. 다만, 댓글은 없습니다. 투표로 자신의 의견을 표현하고 이를 사람들에게 보여줍니다.', + voteCount: 20, + imageUrl: '', + votePercent: 20, + }, + { + optionId: 9, + content: 'fun from choice, 오늘도 즐거운 한 표 ', + imageUrl: 'https://source.unsplash.com/random', + voteCount: 10, + votePercent: 10, + }, + ], + }, +}); + +const getMockGuestPost = (): PostInfoResponse => ({ + postId: Math.floor(Math.random() * 100000), + title: '애국가', + writer: { + id: 2, + nickname: '동해', + }, + content: '동해물과 백두산이 마르고 닳도록', + imageUrl: Math.random() > 0.7 ? 'https://source.unsplash.com/random' : '', + categories: [ + { + id: 1, + name: '코르키', + }, + { + id: 2, + name: '이즈리얼', + }, + { + id: 3, + name: '초가스', + }, + ], + createdAt: '2023-07-12 12:40', + deadline: '2023-07-13 18:40', + voteInfo: { + selectedOptionId: 0, + totalVoteCount: 0, + options: [ + { + optionId: 6, + content: '1절', + voteCount: 0, + votePercent: 0, + imageUrl: '', + }, + { + optionId: 7, + content: '2절', + voteCount: 0, + votePercent: 0, + imageUrl: '', + }, + { + optionId: 8, + content: '3절', + voteCount: 0, + imageUrl: '', + votePercent: 0, + }, + { + optionId: 9, + content: '4절', + imageUrl: 'https://source.unsplash.com/random', + voteCount: 0, + votePercent: 0, + }, + ], + }, +}); + +export const MOCK_POST_LIST: PostInfoResponse[] = []; +export const MOCK_GUEST_POST_LIST: PostInfoResponse[] = []; + +export const MOCK_POST_INFO: PostInfoResponse = getMockPost(); +export const MOCK_GUEST_POST_INFO: PostInfoResponse = getMockGuestPost(); + +for (let index = 0; index < 10; index += 1) { + MOCK_POST_LIST.push(getMockPost()); + MOCK_GUEST_POST_LIST.push(getMockGuestPost()); +} + +export const MOCK_TRANSFORM_POST_LIST: PostInfo[] = MOCK_POST_LIST.map(POST => + transformPostResponse(POST) +); + +export const MOCK_TRANSFORM_GUEST_POST_LIST: PostInfo[] = MOCK_GUEST_POST_LIST.map(POST => + transformPostResponse(POST) +); diff --git a/frontend/src/mocks/mockData/token.ts b/frontend/src/mocks/mockData/token.ts new file mode 100644 index 000000000..ef7ea84a3 --- /dev/null +++ b/frontend/src/mocks/mockData/token.ts @@ -0,0 +1,4 @@ +export const MOCK_TOKEN = { + accessToken: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtZW1iZXJJZCI6NiwiaWF0IjoxNjkzODM2NDgzLCJleHAiOjE2OTM5MjI4ODN9.iEnSoZVT-PLPjFVaDvMRR9dmCWjbARyXm_giEkDFoTw', +}; diff --git a/frontend/src/mocks/mockData/user.ts b/frontend/src/mocks/mockData/user.ts new file mode 100644 index 000000000..a7e0130df --- /dev/null +++ b/frontend/src/mocks/mockData/user.ts @@ -0,0 +1,9 @@ +import { UserInfoResponse } from '@type/user'; + +export const MOCK_USER_INFO: UserInfoResponse = { + nickname: '우아한 코끼리', + gender: 'MALE', + birthYear: 1989, + postCount: 4, + voteCount: 128, +}; diff --git a/frontend/src/mocks/mockData/voteResult.ts b/frontend/src/mocks/mockData/voteResult.ts new file mode 100644 index 000000000..579c4cef4 --- /dev/null +++ b/frontend/src/mocks/mockData/voteResult.ts @@ -0,0 +1,26 @@ +import { VoteDetailResult, VoteResultResponse } from '@components/VoteStatistics/type'; + +export const MOCK_VOTE_RESULT: VoteResultResponse = { + totalVoteCount: 100, + totalFemaleCount: 30, + totalMaleCount: 70, + ageGroup: [ + { voteCount: 10, femaleCount: 10, maleCount: 0, ageGroup: '10대 미만' }, + { voteCount: 20, femaleCount: 10, maleCount: 10, ageGroup: '10대' }, + { voteCount: 10, femaleCount: 2, maleCount: 8, ageGroup: '20대' }, + { voteCount: 20, femaleCount: 16, maleCount: 4, ageGroup: '30대' }, + { voteCount: 40, femaleCount: 30, maleCount: 10, ageGroup: '40대' }, + { voteCount: 2, femaleCount: 1, maleCount: 1, ageGroup: '50대' }, + { voteCount: 3, femaleCount: 2, maleCount: 1, ageGroup: '60대 이상' }, + ], +}; + +export const MOCK_DETAIL_VOTE_RESULT: VoteDetailResult[] = [ + { total: 10, female: 10, male: 0, name: '10대 미만' }, + { total: 20, female: 10, male: 10, name: '10대' }, + { total: 10, female: 2, male: 8, name: '20대' }, + { total: 20, female: 16, male: 4, name: '30대' }, + { total: 40, female: 30, male: 10, name: '40대' }, + { total: 2, female: 1, male: 1, name: '50대' }, + { total: 3, female: 2, male: 1, name: '60대 이상' }, +]; diff --git a/frontend/src/mocks/post.ts b/frontend/src/mocks/post.ts new file mode 100644 index 000000000..2fcf5a881 --- /dev/null +++ b/frontend/src/mocks/post.ts @@ -0,0 +1,62 @@ +import { rest } from 'msw'; + +import { MOCK_GUEST_POST_INFO, MOCK_POST_INFO } from './mockData/post'; + +export const mockPost = [ + rest.get('/posts/:postId', (req, res, ctx) => { + return res(ctx.delay(1000), ctx.status(200), ctx.json(MOCK_POST_INFO)); + }), + + rest.get('/posts/:postId/guest', (req, res, ctx) => { + return res(ctx.delay(1000), ctx.status(200), ctx.json(MOCK_GUEST_POST_INFO)); + }), + + rest.delete('/posts/:postId', (req, res, ctx) => { + return res( + ctx.delay(1000), + ctx.status(200), + ctx.json({ message: '게시글이 성공적으로 삭제되었습니다' }) + ); + }), + + rest.patch('/posts/:postId/close', (req, res, ctx) => { + MOCK_POST_INFO.deadline = '2023-07-13 18:40'; + + return res( + ctx.delay(1000), + ctx.status(200), + ctx.json({ message: '게시글이 성공적으로 조기 마감 되었습니다' }) + ); + }), + + //게시글 작성 + rest.post('/posts', (req, res, ctx) => { + window.console.log('게시글 작성 완료', req.body); + + return res( + ctx.delay(1000), + ctx.status(201), + ctx.json({ message: '게시글이 성공적으로 생성되었습니다' }) + ); + }), + + //게시글 수정 + rest.put('/posts/:postId', (req, res, ctx) => { + window.console.log('게시글 수정 완료되었습니다', req.body); + + return res( + ctx.delay(1000), + ctx.status(200), + ctx.json({ message: '게시글이 성공적으로 수정되었습니다!!' }) + ); + }), + + //게시글 삭제 + rest.delete('/posts/:postId', (req, res, ctx) => { + return res( + ctx.delay(1000), + ctx.status(200), + ctx.json({ message: '게시글이 성공적으로 삭제되었습니다!!' }) + ); + }), +]; diff --git a/frontend/src/mocks/postList.ts b/frontend/src/mocks/postList.ts new file mode 100644 index 000000000..a521efec3 --- /dev/null +++ b/frontend/src/mocks/postList.ts @@ -0,0 +1,81 @@ +import { + DefaultBodyType, + PathParams, + ResponseComposition, + RestContext, + RestRequest, + rest, +} from 'msw'; + +import { MOCK_GUEST_POST_LIST, MOCK_POST_LIST } from '@mocks/mockData/post'; + +export const mockPostList = [ + rest.get('/posts', (req, res, ctx) => { + return createMockPostListResponse(req, res, ctx); + }), + + rest.get('/posts/guest', (req, res, ctx) => { + return createMockGuestPostListResponse(req, res, ctx); + }), + + rest.get('/posts/categories/:categoryId', (req, res, ctx) => { + return createMockPostListResponse(req, res, ctx); + }), + rest.get('/posts/categories/guest/:categoryId', (req, res, ctx) => { + return createMockGuestPostListResponse(req, res, ctx); + }), + + rest.get('/posts/me', (req, res, ctx) => { + return createMockPostListResponse(req, res, ctx); + }), + + rest.get('/posts/votes/me', (req, res, ctx) => { + return createMockPostListResponse(req, res, ctx); + }), + + rest.get('/posts/search', (req, res, ctx) => { + return createMockPostListResponse(req, res, ctx); + }), + + rest.get('/posts/search/guest', (req, res, ctx) => { + return createMockGuestPostListResponse(req, res, ctx); + }), +]; + +const createMockPostListResponse = ( + req: RestRequest>, + res: ResponseComposition, + ctx: RestContext +) => { + const page = Number(req.url.searchParams.get('page')); + + const keyword = req.url.searchParams.get('keyword'); + + if (page === null) return; + + if (keyword === '999') { + return res(ctx.status(200), ctx.json([])); + } + + if (page > 0) { + return res(ctx.status(200), ctx.json(MOCK_POST_LIST), ctx.delay(1000)); + } + + return res(ctx.status(200), ctx.json(MOCK_POST_LIST)); +}; + +const createMockGuestPostListResponse = ( + req: RestRequest>, + res: ResponseComposition, + ctx: RestContext +) => { + const page = Number(req.url.searchParams.get('page')); + + if (page === null) return; + + if (page > 0) { + return res(ctx.status(200), ctx.json(MOCK_GUEST_POST_LIST), ctx.delay(1000)); + } + + return res(ctx.status(200), ctx.json(MOCK_GUEST_POST_LIST)); +}; diff --git a/frontend/src/mocks/ranking.ts b/frontend/src/mocks/ranking.ts new file mode 100644 index 000000000..b6e2eb392 --- /dev/null +++ b/frontend/src/mocks/ranking.ts @@ -0,0 +1,50 @@ +import { rest } from 'msw'; + +import { PassionUser, RankingPost } from '@type/ranking'; + +const userRankingInfo: PassionUser = { + ranking: 1111, + nickname: 'wow', + postCount: 1, + voteCount: 3, + score: 8, +}; + +const rankerInfo: PassionUser = { + ranking: 1, + nickname: 'gil-dong', + postCount: 11, + voteCount: 79, + score: 134, +}; + +const rankerList: PassionUser[] = new Array(10) + .fill(rankerInfo) + .map((ranker, index) => ({ ...ranker, ranking: index + 1 })); + +const rankingPostInfo: RankingPost = { + ranking: 1, + post: { + id: 29, + writer: 'writer', + title: '이것은 엄청나게 많은 투표가 이루어진 대단한 글이지요', + voteCount: 10, + }, +}; +const rankingPostList: RankingPost[] = new Array(10) + .fill(rankingPostInfo) + .map((post, index) => ({ ...post, ranking: index + 1 })); + +export const mockRanking = [ + rest.get('/members/me/ranking', (req, res, ctx) => { + return res(ctx.status(200), ctx.delay(500), ctx.json(userRankingInfo)); + }), + + rest.get('/members/ranking/passion/guest', (req, res, ctx) => { + return res(ctx.status(200), ctx.delay(1000), ctx.json(rankerList)); + }), + + rest.get('/posts/ranking/popular/guest', (req, res, ctx) => { + return res(ctx.status(200), ctx.delay(500), ctx.json(rankingPostList)); + }), +]; diff --git a/frontend/src/mocks/report.ts b/frontend/src/mocks/report.ts new file mode 100644 index 000000000..3eef05833 --- /dev/null +++ b/frontend/src/mocks/report.ts @@ -0,0 +1,7 @@ +import { rest } from 'msw'; + +export const mockReport = [ + rest.post('/report', (req, res, ctx) => { + return res(ctx.status(200)); + }), +]; diff --git a/frontend/src/mocks/token.ts b/frontend/src/mocks/token.ts new file mode 100644 index 000000000..c8a66f093 --- /dev/null +++ b/frontend/src/mocks/token.ts @@ -0,0 +1,9 @@ +import { rest } from 'msw'; + +import { MOCK_TOKEN } from './mockData/token'; + +export const mockToken = [ + rest.post('/auth/silent-login', (req, res, ctx) => { + return res(ctx.status(200), ctx.json(MOCK_TOKEN)); + }), +]; diff --git a/frontend/src/mocks/userInfo.ts b/frontend/src/mocks/userInfo.ts new file mode 100644 index 000000000..daf2c7079 --- /dev/null +++ b/frontend/src/mocks/userInfo.ts @@ -0,0 +1,36 @@ +import { rest } from 'msw'; + +import { MOCK_USER_INFO } from '@mocks/mockData/user'; + +export const mockUserInfo = [ + rest.get('/members/me', (req, res, ctx) => { + return res(ctx.status(200), ctx.json(MOCK_USER_INFO)); + }), + + rest.patch('/members/me/detail', (req, res, ctx) => { + return res(ctx.status(200), ctx.json({ ok: '개인 정보가 성공적으로 저장되었습니다!' })); + }), + + rest.patch('/members/me/nickname', (req, res, ctx) => { + MOCK_USER_INFO.nickname = 'wood'; + + return res(ctx.status(200), ctx.json({ ok: '닉네임이 성공적으로 수정되었습니다!' })); + }), + + rest.delete('/members/me/delete', (req, res, ctx) => { + MOCK_USER_INFO.nickname = 'cancel'; + + return res(ctx.status(204)); + }), + + rest.delete('/auth/logout', (req, res, ctx) => { + const expirationTime = new Date(Date.now() - 1); + + return res( + ctx.status(204), + ctx.cookie('hasEssentialInfo', 'expired', { + expires: expirationTime, + }) + ); + }), +]; diff --git a/frontend/src/mocks/vote.ts b/frontend/src/mocks/vote.ts new file mode 100644 index 000000000..92bd90749 --- /dev/null +++ b/frontend/src/mocks/vote.ts @@ -0,0 +1,19 @@ +import { rest } from 'msw'; + +import { MOCK_POST_INFO } from './mockData/post'; + +export const mockVote = [ + //투표 + rest.post('/posts/:postId/options/:optionId', (req, res, ctx) => { + MOCK_POST_INFO.voteInfo.selectedOptionId = 999; + + return res(ctx.status(200), ctx.json({ message: 'ok' })); + }), + + //선택지 수정 + rest.patch('/posts/:postId/options', (req, res, ctx) => { + MOCK_POST_INFO.voteInfo.selectedOptionId = 888; + + return res(ctx.status(200), ctx.json({ message: 'ok' })); + }), +]; diff --git a/frontend/src/mocks/worker.ts b/frontend/src/mocks/worker.ts new file mode 100644 index 000000000..9c10cad95 --- /dev/null +++ b/frontend/src/mocks/worker.ts @@ -0,0 +1,5 @@ +import { setupWorker } from 'msw'; + +import { handlers } from './handlers'; + +export const worker = setupWorker(...handlers); diff --git a/frontend/src/pages/Error/Error.stories.tsx b/frontend/src/pages/Error/Error.stories.tsx new file mode 100644 index 000000000..a0d197447 --- /dev/null +++ b/frontend/src/pages/Error/Error.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import Error from '.'; + +const meta: Meta = { + component: Error, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/pages/Error/index.tsx b/frontend/src/pages/Error/index.tsx new file mode 100644 index 000000000..4b6583ba3 --- /dev/null +++ b/frontend/src/pages/Error/index.tsx @@ -0,0 +1,39 @@ +import { useNavigate } from 'react-router-dom'; + +import Layout from '@components/common/Layout'; +import LogoButton from '@components/common/LogoButton'; +import SquareButton from '@components/common/SquareButton'; + +import * as S from './style'; + +export default function Error({ message }: { message?: string }) { + const navigate = useNavigate(); + + return ( + + + {message ? message : '요청 중 오류가 발생했습니다.'} + + 오류가 지속되는 경우 votogether2023@gmail.com 로 문의해주세요. + + { + navigate('/'); + }} + > + 홈으로 가기 + + { + window.location.reload(); + }} + > + 새로 고침 + + + + + ); +} diff --git a/frontend/src/pages/Error/style.ts b/frontend/src/pages/Error/style.ts new file mode 100644 index 000000000..3d946c64a --- /dev/null +++ b/frontend/src/pages/Error/style.ts @@ -0,0 +1,47 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 40px; + + position: relative; +`; + +export const HeaderWrapper = styled.div` + width: 100%; + + position: fixed; + + z-index: ${theme.zIndex.header}; +`; + +export const Description = styled.p` + width: 90%; + margin-top: 60px; + + font: var(--text-title); + text-align: center; +`; + +export const Text = styled.p` + width: 90%; + + color: gray; + + font: var(--text-body); + text-align: center; +`; + +export const ButtonWrapper = styled.div` + display: flex; + justify-content: space-between; + gap: 20px; + + width: 280px; + height: 50px; +`; diff --git a/frontend/src/pages/ErrorBoundary.tsx b/frontend/src/pages/ErrorBoundary.tsx new file mode 100644 index 000000000..c5fa1b80f --- /dev/null +++ b/frontend/src/pages/ErrorBoundary.tsx @@ -0,0 +1,38 @@ +import { Component, ErrorInfo, ReactNode } from 'react'; + +import ErrorMessage from '@components/common/ErrorMessage'; + +interface ErrorBoundaryProps { + children: ReactNode; +} + +interface ErrorBoundaryState { + hasError: boolean; + errorMessage: string; +} + +class ErrorBoundary extends Component { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false, errorMessage: '' }; + } + + static getDerivedStateFromError(error: Error) { + return { hasError: true, errorMessage: error.message }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + // You can also log the error to an error reporting service + window.console.log(error, errorInfo); + } + + render() { + if (this.state.hasError) { + return ; + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/frontend/src/pages/ErrorBoundaryForTopClass.tsx b/frontend/src/pages/ErrorBoundaryForTopClass.tsx new file mode 100644 index 000000000..58703fc94 --- /dev/null +++ b/frontend/src/pages/ErrorBoundaryForTopClass.tsx @@ -0,0 +1,77 @@ +import { styled } from 'styled-components'; + +import ErrorMessage from '@components/common/ErrorMessage'; + +import { theme } from '@styles/theme'; + +import ErrorBoundary from './ErrorBoundary'; + +/* 가장 최상단에서 에러가 난 경우 보여줄 에러 바운더리. + * nav를 사용할 수 없고 글로벌 스타일드도 사용할 수 없음. + * 때문에 nav를 제거한 빈 헤더를 정의하고, 에러메세지 컴포넌트를 사용 + */ +class ErrorBoundaryForTopClass extends ErrorBoundary { + render() { + if (this.state.hasError) { + return ( + <> + + + + + + + ); + } + + return this.props.children; + } +} + +const WideTemplateHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + width: 100%; + height: 70px; + + position: fixed; + top: 0; + + //글로벌 스타일 바깥쪽이라 var 적용되지 않음 + background-color: #1f1f1f; + + padding: 0 80px; + + @media (max-width: ${theme.breakpoint.sm}) { + display: none; + visibility: hidden; + } +`; + +const NarrowTemplateHeader = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + + width: 100%; + height: 55px; + padding: 0 20px; + + position: fixed; + top: 0; + + background-color: #1f1f1f; +`; + +const ErrorWrapper = styled.div` + margin-top: 20px; + + @media (min-width: ${theme.breakpoint.sm}) { + margin-top: 50px; + } +`; + +export default ErrorBoundaryForTopClass; diff --git a/frontend/src/pages/ErrorBoundaryWithNarrowHeader.tsx b/frontend/src/pages/ErrorBoundaryWithNarrowHeader.tsx new file mode 100644 index 000000000..5c7fd3b9c --- /dev/null +++ b/frontend/src/pages/ErrorBoundaryWithNarrowHeader.tsx @@ -0,0 +1,21 @@ +import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; + +import Error from './Error'; +import ErrorBoundary from './ErrorBoundary'; + +class ErrorBoundaryWithNarrowHeader extends ErrorBoundary { + render() { + if (this.state.hasError) { + return ( + <> + + + + ); + } + + return this.props.children; + } +} + +export default ErrorBoundaryWithNarrowHeader; diff --git a/frontend/src/pages/Home/index.tsx b/frontend/src/pages/Home/index.tsx new file mode 100644 index 000000000..7590b4bb8 --- /dev/null +++ b/frontend/src/pages/Home/index.tsx @@ -0,0 +1,12 @@ +import AppInstallPrompt from '@components/common/AppInstallPrompt'; +import Layout from '@components/common/Layout'; +import PostListPage from '@components/post/PostListPage'; + +export default function Home() { + return ( + + + + + ); +} diff --git a/frontend/src/pages/MyInfo/DeleteMemberModal/index.tsx b/frontend/src/pages/MyInfo/DeleteMemberModal/index.tsx new file mode 100644 index 000000000..934680d41 --- /dev/null +++ b/frontend/src/pages/MyInfo/DeleteMemberModal/index.tsx @@ -0,0 +1,36 @@ +import Modal from '@components/common/Modal'; +import SquareButton from '@components/common/SquareButton'; + +import * as S from './style'; +interface DeleteMemberModalProps { + handleModalClose: () => void; + handleWithdrawalMembership: () => void; +} + +export default function DeleteMemberModal({ + handleModalClose, + handleWithdrawalMembership, +}: DeleteMemberModalProps) { + return ( + + + 정말 탈퇴하시겠어요? + + 탈퇴 버튼 클릭 시,

계정은 삭제되며 복구되지 않아요. +

+ 작성한 게시글과 투표 기록 등

서비스 사용 내역이 사라지므로

+ 유의해주세요. +

+
+ + + 탈퇴 + + + 취소 + + +
+
+ ); +} diff --git a/frontend/src/pages/MyInfo/DeleteMemberModal/style.ts b/frontend/src/pages/MyInfo/DeleteMemberModal/style.ts new file mode 100644 index 000000000..313f6864f --- /dev/null +++ b/frontend/src/pages/MyInfo/DeleteMemberModal/style.ts @@ -0,0 +1,32 @@ +import { styled } from 'styled-components'; + +export const ModalBody = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 20px; + + width: 90%; + margin: 40px 20px 0px 16px; + + font: var(--text-caption); +`; + +export const ModalTitle = styled.div` + font: var(--text-title); +`; + +export const ModalDescription = styled.div` + color: gray; + font-size: 16px; +`; + +export const ButtonListWrapper = styled.div` + display: flex; + justify-content: space-around; + gap: 20px; + + width: 90%; + height: 50px; +`; diff --git a/frontend/src/pages/MyInfo/MyInfo.stories.tsx b/frontend/src/pages/MyInfo/MyInfo.stories.tsx new file mode 100644 index 000000000..f615a645c --- /dev/null +++ b/frontend/src/pages/MyInfo/MyInfo.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import MyInfo from '.'; + +const meta: Meta = { + component: MyInfo, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/pages/MyInfo/index.tsx b/frontend/src/pages/MyInfo/index.tsx new file mode 100644 index 000000000..d21613ae2 --- /dev/null +++ b/frontend/src/pages/MyInfo/index.tsx @@ -0,0 +1,149 @@ +import { useContext, ChangeEvent, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { AuthContext } from '@hooks/context/auth'; +import { useModifyUser } from '@hooks/query/user/useModifyUser'; +import { useWithdrawalMembership } from '@hooks/query/user/useWithdrawalMembership'; +import { useText } from '@hooks/useText'; +import { useToast } from '@hooks/useToast'; +import { useToggle } from '@hooks/useToggle'; + +import Accordion from '@components/common/Accordion'; +import GuestProfile from '@components/common/Dashboard/GuestProfile'; +import UserProfile from '@components/common/Dashboard/UserProfile'; +import IconButton from '@components/common/IconButton'; +import Layout from '@components/common/Layout'; +import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; +import SquareButton from '@components/common/SquareButton'; +import Toast from '@components/common/Toast'; + +import { NICKNAME_POLICY } from '@constants/policyMessage'; +import { NICKNAME } from '@constants/user'; + +import DeleteMemberModal from './DeleteMemberModal'; +import * as S from './style'; + +export default function MyInfo() { + const navigate = useNavigate(); + + const { + mutate: modifyNickname, + isSuccess: isModifyNicknameSuccess, + isError: isModifyNicknameError, + error: modifyNicknameError, + } = useModifyUser(); + const { + mutate: withdrawalMembership, + isSuccess: isWithdrawalMembershipSuccess, + isError: isWithdrawalMembershipError, + error: withdrawalMembershipError, + } = useWithdrawalMembership(); + + const { isToastOpen, openToast, toastMessage } = useToast(); + + const { isOpen, openComponent, closeComponent } = useToggle(); + const { loggedInfo, clearLoggedInfo } = useContext(AuthContext); + const { text: newNickname, handleTextChange: handleNicknameChange } = useText( + loggedInfo.userInfo?.nickname ?? '' + ); + + const handleModifyNickname = () => { + modifyNickname(newNickname); + }; + + const handleWithdrawalMembership = () => { + withdrawalMembership(); + }; + + useEffect(() => { + if (isModifyNicknameSuccess) { + openToast('닉네임을 성공적으로 변경했습니다.'); + return; + } + }, [isModifyNicknameSuccess]); + + useEffect(() => { + if (isModifyNicknameError && modifyNicknameError instanceof Error) { + const errorResponse = JSON.parse(modifyNicknameError.message); + openToast(errorResponse.message); + return; + } + }, [isModifyNicknameError, modifyNicknameError]); + + useEffect(() => { + if (isWithdrawalMembershipSuccess) { + clearLoggedInfo(); + + navigate('/'); + } + }, [isWithdrawalMembershipSuccess]); + + useEffect(() => { + if (isWithdrawalMembershipError && withdrawalMembershipError instanceof Error) { + const errorResponse = JSON.parse(withdrawalMembershipError.message); + openToast(errorResponse.message); + return; + } + }, [isWithdrawalMembershipError, withdrawalMembershipError]); + + return ( + + + + + { + navigate(-1); + }} + /> + + + + {loggedInfo.userInfo ? : } + + + + +
  • - {NICKNAME_POLICY.LETTER_AMOUNT}
  • +
  • - {NICKNAME_POLICY.LIMIT_LETTER_TYPE}
  • +
  • - {NICKNAME_POLICY.LIMIT_CHANGING}
  • +
  • - {NICKNAME_POLICY.NO_DUPLICATION}
  • +
  • - {NICKNAME_POLICY.LIMIT_KOREAN}
  • +
    + + ) => handleNicknameChange(e, NICKNAME)} + placeholder="새로운 닉네임을 입력해주세요" + /> + + + 변경 + + + +
    + + + + 회원 탈퇴 + + + {isOpen && ( + + )} + +
    + {isToastOpen && ( + + {toastMessage} + + )} +
    +
    + ); +} diff --git a/frontend/src/pages/MyInfo/style.ts b/frontend/src/pages/MyInfo/style.ts new file mode 100644 index 000000000..7efd05cbf --- /dev/null +++ b/frontend/src/pages/MyInfo/style.ts @@ -0,0 +1,98 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 30px; + + padding-top: 55px; + position: relative; + + @media (min-width: 768px) { + padding-top: 20px; + } + + @media (min-width: ${theme.breakpoint.sm}) { + padding-top: 20px; + } +`; + +export const HeaderWrapper = styled.div` + width: 100%; + + position: fixed; + + z-index: ${theme.zIndex.header}; + + @media (min-width: ${theme.breakpoint.sm}) { + display: none; + } +`; + +export const ProfileSection = styled.section` + width: 90%; +`; + +export const UserControlSection = styled.section` + width: 90%; +`; + +export const DescribeUl = styled.ul` + display: flex; + flex-direction: column; + gap: 6px; + + margin: 0 0 20px 5px; +`; + +export const InputWrapper = styled.div` + display: flex; + align-items: center; + gap: 10px; +`; + +export const Input = styled.input` + width: 80%; + border: 1px solid #f2f2f2; + padding: 20px; +`; + +export const ButtonWrapper = styled.div` + width: 90px; + height: 50px; +`; + +export const ModalBody = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 20px; + + width: 90%; + margin: 40px 20px 0px 16px; + + font: var(--text-caption); +`; + +export const ModalTitle = styled.div` + font: var(--text-title); +`; + +export const ModalDescription = styled.div` + color: gray; + font-size: 16px; +`; + +export const ButtonListWrapper = styled.div` + display: flex; + justify-content: space-around; + gap: 20px; + + width: 90%; + height: 50px; +`; diff --git a/frontend/src/pages/NotFound/NotFound.stories.tsx b/frontend/src/pages/NotFound/NotFound.stories.tsx new file mode 100644 index 000000000..e05cb8aef --- /dev/null +++ b/frontend/src/pages/NotFound/NotFound.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import NotFound from '.'; + +const meta: Meta = { + component: NotFound, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/pages/NotFound/index.tsx b/frontend/src/pages/NotFound/index.tsx new file mode 100644 index 000000000..f577faf73 --- /dev/null +++ b/frontend/src/pages/NotFound/index.tsx @@ -0,0 +1,42 @@ +import { useNavigate } from 'react-router-dom'; + +import IconButton from '@components/common/IconButton'; +import Layout from '@components/common/Layout'; +import LogoButton from '@components/common/LogoButton'; +import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; +import SquareButton from '@components/common/SquareButton'; + +import * as S from './style'; + +export default function NotFound() { + const navigate = useNavigate(); + return ( + + + + + { + navigate(-1); + }} + /> + + + 404 + + 요청하신 페이지를 찾을 수 없어요. + + { + navigate('/'); + }} + > + 홈으로 가기 + + + + + ); +} diff --git a/frontend/src/pages/NotFound/style.ts b/frontend/src/pages/NotFound/style.ts new file mode 100644 index 000000000..1d9447d12 --- /dev/null +++ b/frontend/src/pages/NotFound/style.ts @@ -0,0 +1,46 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 20px; + + position: relative; +`; + +export const HeaderWrapper = styled.div` + width: 100%; + + position: fixed; + + z-index: ${theme.zIndex.header}; + + @media (min-width: ${theme.breakpoint.md}) { + display: none; + } +`; + +export const Title = styled.h1` + width: 90%; + margin-top: 60px; + + font-size: 80px; + text-align: center; +`; + +export const Description = styled.p` + width: 90%; + margin: 20px 0; + + font: var(--text-title); + text-align: center; +`; + +export const ButtonWrapper = styled.div` + width: 120px; + height: 50px; +`; diff --git a/frontend/src/pages/Ranking/PassionUser/PassionUser.stories.tsx b/frontend/src/pages/Ranking/PassionUser/PassionUser.stories.tsx new file mode 100644 index 000000000..3edac80fd --- /dev/null +++ b/frontend/src/pages/Ranking/PassionUser/PassionUser.stories.tsx @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import PassionUserRanking from '.'; + +const meta: Meta = { + component: PassionUserRanking, +}; + +export default meta; +type Story = StoryObj; + +export const User: Story = { + render: () => , +}; + +export const Guest: Story = { + render: () => , +}; diff --git a/frontend/src/pages/Ranking/PassionUser/UserRanking/index.tsx b/frontend/src/pages/Ranking/PassionUser/UserRanking/index.tsx new file mode 100644 index 000000000..431290cbe --- /dev/null +++ b/frontend/src/pages/Ranking/PassionUser/UserRanking/index.tsx @@ -0,0 +1,25 @@ +import React, { useContext } from 'react'; + +import { AuthContext } from '@hooks/context/auth'; +import { useUserRanking } from '@hooks/query/ranking/useUserRanking'; + +import * as S from '../style'; + +export default function UserRanking() { + const { loggedInfo } = useContext(AuthContext); + const { isLoggedIn } = loggedInfo; + + const { data: userRanking } = useUserRanking(isLoggedIn); + + return ( + userRanking && ( + + {userRanking.ranking} + {userRanking.nickname} + {userRanking.postCount} + {userRanking.voteCount} + {userRanking.score} + + ) + ); +} diff --git a/frontend/src/pages/Ranking/PassionUser/index.tsx b/frontend/src/pages/Ranking/PassionUser/index.tsx new file mode 100644 index 000000000..5545696db --- /dev/null +++ b/frontend/src/pages/Ranking/PassionUser/index.tsx @@ -0,0 +1,71 @@ +import { Suspense } from 'react'; + +import { usePassionUserRanking } from '@hooks/query/ranking/usePassionUserRanking'; + +import ErrorBoundary from '@pages/ErrorBoundary'; + +import LoadingSpinner from '@components/common/LoadingSpinner'; + +import firstRankIcon from '@assets/first-rank.svg'; +import secondRankIcon from '@assets/second-rank.svg'; +import thirdRankIcon from '@assets/third-rank.svg'; + +import * as S from './style'; +import UserRanking from './UserRanking'; + +const columnNameList = ['등수', '닉네임', '작성글 수', '투표 수', '점수']; + +const rankIconUrl: Record = { + 1: firstRankIcon, + 2: secondRankIcon, + 3: thirdRankIcon, +}; + +export default function PassionUserRanking() { + const { data: rankerList } = usePassionUserRanking(); + + return ( + + + {columnNameList.map(text => ( + {text} + ))} + + {rankerList && + new Array(10).fill(0).map((_, index) => { + const ranker = rankerList[index] ?? { + ranking: '', + nickname: '', + postCount: '', + voteCount: '', + score: '', + }; + + const rankIcon = rankIconUrl[ranker.ranking] && ( + {ranker.ranking.toString()} + ); + + return ( + + {rankIcon ?? ranker.ranking} + {ranker.nickname} + {ranker.postCount} + {ranker.voteCount} + {ranker.score} + + ); + })} + + + + + } + > + + + + + ); +} diff --git a/frontend/src/pages/Ranking/PassionUser/style.ts b/frontend/src/pages/Ranking/PassionUser/style.ts new file mode 100644 index 000000000..2058d7b3b --- /dev/null +++ b/frontend/src/pages/Ranking/PassionUser/style.ts @@ -0,0 +1,50 @@ +import { styled } from 'styled-components'; + +export const Table = styled.table` + width: 100%; + + font: var(--text-caption); + text-align: center; + + & > :nth-child(12) { + margin-top: 20px; + padding: 3px 0; + border-radius: 4px; + + background-color: var(--white); + + font-weight: 500; + } +`; + +export const Tr = styled.tr` + display: grid; + grid-template-columns: 0.5fr 1.5fr 1fr 1fr 1fr; + align-items: center; +`; + +export const Th = styled.th` + padding: 10px 0; + + font: var(--text-body); +`; + +export const RankingTd = styled.td` + padding: 5px 0; + height: auto; + + line-height: 0; +`; + +export const Td = styled.td` + padding: 10px 0; +`; + +export const LoadingSpinnerWrapper = styled.div` + display: flex; + align-items: center; + justify-content: center; + + height: 50px; + padding: 0; +`; diff --git a/frontend/src/pages/Ranking/PopularPost/PopularPost.stories.tsx b/frontend/src/pages/Ranking/PopularPost/PopularPost.stories.tsx new file mode 100644 index 000000000..f6c50d974 --- /dev/null +++ b/frontend/src/pages/Ranking/PopularPost/PopularPost.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import PopularPost from '.'; + +const meta: Meta = { + component: PopularPost, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/pages/Ranking/PopularPost/index.tsx b/frontend/src/pages/Ranking/PopularPost/index.tsx new file mode 100644 index 000000000..76c95f383 --- /dev/null +++ b/frontend/src/pages/Ranking/PopularPost/index.tsx @@ -0,0 +1,50 @@ +import { Link } from 'react-router-dom'; + +import { usePopularPostRanking } from '@hooks/query/ranking/usePopularPostRanking'; + +import { PATH } from '@constants/path'; + +import firstRankIcon from '@assets/first-rank.svg'; +import secondRankIcon from '@assets/second-rank.svg'; +import thirdRankIcon from '@assets/third-rank.svg'; + +import * as S from './style'; + +const rankIconUrl: Record = { + 1: firstRankIcon, + 2: secondRankIcon, + 3: thirdRankIcon, +}; + +const columnNameList = ['등수', '닉네임', '글 제목', '투표 수']; + +export default function PopularPost() { + const { data: rankingPostList } = usePopularPostRanking(); + + return ( + + + {columnNameList.map(text => ( + {text} + ))} + + {rankingPostList && + rankingPostList.map(rankingPost => { + const rankIcon = rankIconUrl[rankingPost.ranking] && ( + {rankingPost.ranking.toString()} + ); + + return ( + + {rankIcon ?? rankingPost.ranking} + {rankingPost.post.writer} + + {rankingPost.post.title} + + {rankingPost.post.voteCount} + + ); + })} + + ); +} diff --git a/frontend/src/pages/Ranking/PopularPost/style.ts b/frontend/src/pages/Ranking/PopularPost/style.ts new file mode 100644 index 000000000..6fa21c1f9 --- /dev/null +++ b/frontend/src/pages/Ranking/PopularPost/style.ts @@ -0,0 +1,45 @@ +import { styled } from 'styled-components'; + +export const Table = styled.table` + width: 100%; + + font: var(--text-caption); + text-align: center; +`; + +export const Tr = styled.tr` + display: grid; + grid-template-columns: 0.5fr 1fr 3fr 1fr; + align-items: center; +`; + +export const Th = styled.th` + padding: 10px 0; + + font: var(--text-body); +`; + +export const RankingTd = styled.td` + padding: 5px 0; + height: auto; + + line-height: 0; +`; + +export const Td = styled.td` + padding: 10px 0; + + > a { + display: -webkit-box; + + text-decoration-line: underline; + text-underline-offset: 0.2em; + text-overflow: ellipsis; + word-break: break-word; + + overflow: hidden; + + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + } +`; diff --git a/frontend/src/pages/Ranking/Ranking.stories.tsx b/frontend/src/pages/Ranking/Ranking.stories.tsx new file mode 100644 index 000000000..c05a7fc6c --- /dev/null +++ b/frontend/src/pages/Ranking/Ranking.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import Ranking from '.'; + +const meta: Meta = { + component: Ranking, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/pages/Ranking/RankingTableStyle.ts b/frontend/src/pages/Ranking/RankingTableStyle.ts new file mode 100644 index 000000000..b6734f4f7 --- /dev/null +++ b/frontend/src/pages/Ranking/RankingTableStyle.ts @@ -0,0 +1,21 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Background = styled.div` + display: flex; + align-items: center; + justify-content: center; + + height: fit-content; + min-height: 500px; + border-radius: 4px; + + background-color: var(--gray); + + padding: 15px 10px; + + @media (min-width: ${theme.breakpoint.sm}) { + padding: 15px 15px; + } +`; diff --git a/frontend/src/pages/Ranking/index.tsx b/frontend/src/pages/Ranking/index.tsx new file mode 100644 index 000000000..3afe86d49 --- /dev/null +++ b/frontend/src/pages/Ranking/index.tsx @@ -0,0 +1,66 @@ +import { Suspense } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { useToggleSwitch } from '@hooks/useToggleSwitch'; + +import ErrorBoundary from '@pages/ErrorBoundary'; + +import IconButton from '@components/common/IconButton'; +import Layout from '@components/common/Layout'; +import LoadingSpinner from '@components/common/LoadingSpinner'; +import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; +import ToggleSwitch from '@components/common/ToggleSwitch'; + +import PassionUserRanking from './PassionUser'; +import PopularPost from './PopularPost'; +import * as RS from './RankingTableStyle'; +import * as S from './style'; + +export default function Ranking() { + const navigate = useNavigate(); + const { selectedButton, firstButton, secondButton } = useToggleSwitch('열정 유저', '인기글 유저'); + + return ( + + + + { + navigate(-1); + }} + /> + + + + 🏆 랭킹 🏆 + + + {selectedButton === '열정 유저' && ( + + + }> + + + + + )} + {selectedButton === '인기글 유저' && ( + + + }> + + + + + )} + + + + ); +} diff --git a/frontend/src/pages/Ranking/style.ts b/frontend/src/pages/Ranking/style.ts new file mode 100644 index 000000000..29672f2b5 --- /dev/null +++ b/frontend/src/pages/Ranking/style.ts @@ -0,0 +1,48 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const HeaderWrapper = styled.div` + width: 100%; + + position: fixed; + + z-index: ${theme.zIndex.header}; + + @media (min-width: ${theme.breakpoint.sm}) { + display: none; + } +`; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + margin-top: 50px; + margin-bottom: 20px; + + & > * { + width: 100%; + } + + @media (min-width: ${theme.breakpoint.sm}) { + margin-top: 30px; + } +`; + +export const PageHeader = styled.div` + margin: 15px; + + text-align: center; + font: var(--text-title); +`; + +export const ContentContainer = styled.div` + display: flex; + flex-direction: column; + gap: 10px; + + margin-top: 20px; + padding: 0 15px; +`; diff --git a/frontend/src/pages/VoteStatisticsPage/OptionStatistics/OptionStatistics.stories.tsx b/frontend/src/pages/VoteStatisticsPage/OptionStatistics/OptionStatistics.stories.tsx new file mode 100644 index 000000000..18d7ec066 --- /dev/null +++ b/frontend/src/pages/VoteStatisticsPage/OptionStatistics/OptionStatistics.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import OptionStatistics from '.'; + +const meta: Meta = { + component: OptionStatistics, +}; + +export default meta; +type Story = StoryObj; + +const MOCK_MAX_VOTE_OPTION = { + id: 2, + text: '', + imageUrl: 'https://source.unsplash.com/random', + peopleCount: 10, + percent: 10, +}; + +export const DefaultPage: Story = { + render: () => ( + + ), +}; diff --git a/frontend/src/pages/VoteStatisticsPage/OptionStatistics/index.tsx b/frontend/src/pages/VoteStatisticsPage/OptionStatistics/index.tsx new file mode 100644 index 000000000..109d720eb --- /dev/null +++ b/frontend/src/pages/VoteStatisticsPage/OptionStatistics/index.tsx @@ -0,0 +1,85 @@ +import { useState } from 'react'; + +import { WrittenVoteOptionType } from '@type/post'; +import { Size } from '@type/style'; + +import { useFetch } from '@hooks/useFetch'; +import { useToast } from '@hooks/useToast'; + +import { getOptionStatistics } from '@api/voteResult'; + +import LoadingSpinner from '@components/common/LoadingSpinner'; +import Toast from '@components/common/Toast'; +import WrittenVoteOption from '@components/optionList/WrittenVoteOptionList/WrittenVoteOption'; +import VoteStatistics from '@components/VoteStatistics'; + +import * as S from './style'; + +interface OptionStatisticsProps { + postId: number; + isSelectedOption: boolean; + voteOption: WrittenVoteOptionType; + size: Size; +} + +export default function OptionStatistics({ + postId, + voteOption, + isSelectedOption, + size, +}: OptionStatisticsProps) { + const [isStatisticsOpen, setIsStatisticsOpen] = useState(false); + const { isToastOpen, openToast, toastMessage } = useToast(); + + const { + data: voteResult, + errorMessage, + isLoading, + } = useFetch(() => getOptionStatistics({ postId, optionId: voteOption.id })); + + const toggleOptionStatistics = () => { + if (!voteResult) return openToast('투표 통계 불러오기를 실패했습니다.'); + + setIsStatisticsOpen(!isStatisticsOpen); + }; + + return ( + + + + {!isStatisticsOpen && ( + + 투표 선택지를 클릭하여 투표 통계를 열어 확인할 수 있습니다. + + )} + {isStatisticsOpen && voteResult && ( + <> + + 투표 선택지를 클릭하여 투표 통계를 닫을 수 있습니다. + + + + )} + {isStatisticsOpen && isLoading && ( + + + + )} + {isStatisticsOpen && errorMessage} + + {isToastOpen && ( + + {toastMessage} + + )} + + ); +} diff --git a/frontend/src/pages/VoteStatisticsPage/OptionStatistics/style.ts b/frontend/src/pages/VoteStatisticsPage/OptionStatistics/style.ts new file mode 100644 index 000000000..12dea1286 --- /dev/null +++ b/frontend/src/pages/VoteStatisticsPage/OptionStatistics/style.ts @@ -0,0 +1,34 @@ +import { styled } from 'styled-components'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + + width: 95%; + border-radius: 10px; + + background-color: #f6f6f6; + + font: var(--text-title); +`; + +export const StatisticsContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + + & > * { + padding: 30px; + } +`; + +export const LoadingWrapper = styled.div` + display: flex; + + height: 100px; +`; + +export const ScreenReaderDirection = styled.p` + position: absolute; + left: -9999px; +`; diff --git a/frontend/src/pages/VoteStatisticsPage/VoteStatistics.stories.tsx b/frontend/src/pages/VoteStatisticsPage/VoteStatistics.stories.tsx new file mode 100644 index 000000000..2b0d82fa5 --- /dev/null +++ b/frontend/src/pages/VoteStatisticsPage/VoteStatistics.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import VoteStatisticsPage from '.'; + +const meta: Meta = { + component: VoteStatisticsPage, +}; + +export default meta; +type Story = StoryObj; + +export const DefaultPage: Story = { + render: () => , +}; diff --git a/frontend/src/pages/VoteStatisticsPage/index.tsx b/frontend/src/pages/VoteStatisticsPage/index.tsx new file mode 100644 index 000000000..a541b684a --- /dev/null +++ b/frontend/src/pages/VoteStatisticsPage/index.tsx @@ -0,0 +1,85 @@ +import { Navigate, useNavigate, useParams } from 'react-router-dom'; + +import { useFetch } from '@hooks/useFetch'; + +import { getPost } from '@api/post'; +import { getPostStatistics } from '@api/voteResult'; + +import ErrorMessage from '@components/common/ErrorMessage'; +import IconButton from '@components/common/IconButton'; +import Layout from '@components/common/Layout'; +import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; +import Skeleton from '@components/common/Skeleton'; +import VoteStatistics from '@components/VoteStatistics'; + +import { PATH } from '@constants/path'; + +import { checkWriter } from '@utils/post/checkWriter'; + +import OptionStatistics from './OptionStatistics'; +import * as S from './style'; + +export default function VoteStatisticsPage() { + const params = useParams() as { postId: string }; + const postId = Number(params.postId); + + const navigate = useNavigate(); + + const { + data: postDetail, + errorMessage: postError, + isLoading: isPostLoading, + } = useFetch(() => getPost(postId)); + + const { + data: voteResultResponse, + errorMessage: voteResultError, + isLoading: isVoteResultLoading, + } = useFetch(() => getPostStatistics(postId)); + + if (postDetail && !checkWriter(postDetail.writer.id)) return ; + + return ( + + + + navigate(-1)} /> + + + + 투표 통계 + {postError && } + {isPostLoading && ( + + + + )} + {postDetail && ( + + {voteResultError && } + {isVoteResultLoading && ( + + + + )} + {voteResultResponse && ( + + )} + {postDetail.voteInfo.options.map(option => { + const { postId, voteInfo } = postDetail; + return ( + + ); + })} + + )} + + + ); +} diff --git a/frontend/src/pages/VoteStatisticsPage/style.ts b/frontend/src/pages/VoteStatisticsPage/style.ts new file mode 100644 index 000000000..329cf2fe7 --- /dev/null +++ b/frontend/src/pages/VoteStatisticsPage/style.ts @@ -0,0 +1,49 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + margin-top: 50px; + margin-bottom: 20px; + + & > * { + width: 100%; + } + + @media (min-width: ${theme.breakpoint.sm}) { + margin-top: 30px; + } +`; + +export const HeaderWrapper = styled.div` + position: fixed; + z-index: ${theme.zIndex.header}; + + @media (min-width: ${theme.breakpoint.sm}) { + display: none; + } +`; + +export const PageHeader = styled.div` + margin: 15px; + + text-align: center; + font: var(--text-title); +`; + +export const OptionContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; +`; + +export const LoadingWrapper = styled.div` + display: flex; + + height: 100px; +`; diff --git a/frontend/src/pages/auth/Login/Login.stories.tsx b/frontend/src/pages/auth/Login/Login.stories.tsx new file mode 100644 index 000000000..8db4caee3 --- /dev/null +++ b/frontend/src/pages/auth/Login/Login.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import Login from '.'; + +const meta: Meta = { + component: Login, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/pages/auth/Login/MobileLogin/MobileLogin.stories.tsx b/frontend/src/pages/auth/Login/MobileLogin/MobileLogin.stories.tsx new file mode 100644 index 000000000..5e1b49c68 --- /dev/null +++ b/frontend/src/pages/auth/Login/MobileLogin/MobileLogin.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import MobileLogin from '.'; + +const meta: Meta = { + component: MobileLogin, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/pages/auth/Login/MobileLogin/index.tsx b/frontend/src/pages/auth/Login/MobileLogin/index.tsx new file mode 100644 index 000000000..781c79532 --- /dev/null +++ b/frontend/src/pages/auth/Login/MobileLogin/index.tsx @@ -0,0 +1,24 @@ +import { useNavigate } from 'react-router-dom'; + +import kakao from '@assets/kakao_login_medium_wide.svg'; +import logo from '@assets/stroke-logo.svg'; + +import * as S from './style'; + +export default function MobileLogin() { + const navigate = useNavigate(); + const CLIENT_ID = `${process.env.VOTOGETHER_REST_API_KEY}`; + const REDIRECT_URI = `${process.env.VOTOGETHER_SERVER_REDIRECT_URL}`; + const kakaoURL = `https://kauth.kakao.com/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code`; + + return ( + + + VOTOGETHER + (window.location.href = kakaoURL)}> + 카카오 로그인 + + navigate('/')}>비회원으로 이용하기 + + ); +} diff --git a/frontend/src/pages/auth/Login/MobileLogin/style.ts b/frontend/src/pages/auth/Login/MobileLogin/style.ts new file mode 100644 index 000000000..809a21590 --- /dev/null +++ b/frontend/src/pages/auth/Login/MobileLogin/style.ts @@ -0,0 +1,38 @@ +import { styled } from 'styled-components'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + height: 100vh; +`; + +export const LogoImage = styled.img` + width: 80px; + height: 80px; + margin-bottom: 36px; +`; + +export const LogoTitle = styled.h1` + margin-bottom: 140px; + + font-size: 3.2rem; + font-weight: 700; +`; + +export const LoginButton = styled.button` + cursor: pointer; +`; + +export const GuestButton = styled.button` + margin-top: 30px; + + color: #9f9f9f; + + font: var(--text-caption); + font-weight: 400; + + cursor: pointer; +`; diff --git a/frontend/src/pages/auth/Login/ServiceIntroductionSection/StartUsingOurService.stories.tsx b/frontend/src/pages/auth/Login/ServiceIntroductionSection/StartUsingOurService.stories.tsx new file mode 100644 index 000000000..286405d5f --- /dev/null +++ b/frontend/src/pages/auth/Login/ServiceIntroductionSection/StartUsingOurService.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import StartUsingOurService from '.'; + +const meta: Meta = { + component: StartUsingOurService, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/pages/auth/Login/ServiceIntroductionSection/index.tsx b/frontend/src/pages/auth/Login/ServiceIntroductionSection/index.tsx new file mode 100644 index 000000000..233a3e45d --- /dev/null +++ b/frontend/src/pages/auth/Login/ServiceIntroductionSection/index.tsx @@ -0,0 +1,31 @@ +import home from '@assets/votogether_home.png'; +import write from '@assets/votogether_write.png'; + +import * as S from './style'; + +export default function ServiceIntroductionSection() { + return ( + + + + FUN FROM CHOICE! + 오늘도 즐거운 한 표! + + + + + 투표를 해보세요! + + + + 글을 쓰며 사람들의 반응을 확인해요! + + + + + + + + + ); +} diff --git a/frontend/src/pages/auth/Login/ServiceIntroductionSection/style.ts b/frontend/src/pages/auth/Login/ServiceIntroductionSection/style.ts new file mode 100644 index 000000000..d24c71539 --- /dev/null +++ b/frontend/src/pages/auth/Login/ServiceIntroductionSection/style.ts @@ -0,0 +1,87 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.section` + display: none; + + @media (min-width: ${theme.breakpoint.lg}) { + display: flex; + } +`; + +export const ContentContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + + width: 100%; + height: 100vh; + padding: 0 150px; + + color: #fff; + background-color: #1f1f1f; +`; + +export const TitleContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + margin-bottom: 65px; +`; + +export const TitleText = styled.span` + font-size: 3.6rem; + font-weight: 700; +`; + +export const IntroduceContainer = styled.div` + display: flex; + justify-content: space-between; +`; + +export const Introduce = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 34px; + + height: max-content; +`; + +export const PhoneImage = styled.img` + height: 200px; + + object-fit: cover; + + @media (min-height: 600px) { + height: 400px; + } + + @media (min-height: 900px) { + height: max-content; + } +`; + +export const Text = styled.span` + font-size: 2rem; + font-weight: 400; +`; + +export const Decorator = styled.div` + width: 16px; + height: 100vh; + + &:nth-child(2) { + background-color: #6d6d6d; + } + + &:nth-child(3) { + background-color: #8d8d8d; + } + + &:last-child { + background-color: #bcbcbc; + } +`; diff --git a/frontend/src/pages/auth/Login/index.tsx b/frontend/src/pages/auth/Login/index.tsx new file mode 100644 index 000000000..f048d18eb --- /dev/null +++ b/frontend/src/pages/auth/Login/index.tsx @@ -0,0 +1,12 @@ +import MobileLogin from './MobileLogin'; +import ServiceIntroductionSection from './ServiceIntroductionSection'; +import * as S from './style'; + +export default function Login() { + return ( + + + + + ); +} diff --git a/frontend/src/pages/auth/Login/style.ts b/frontend/src/pages/auth/Login/style.ts new file mode 100644 index 000000000..b43ccbf70 --- /dev/null +++ b/frontend/src/pages/auth/Login/style.ts @@ -0,0 +1,12 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + display: grid; + grid-template-columns: 1fr; + + @media (min-width: ${theme.breakpoint.lg}) { + grid-template-columns: 1fr minmax(200px, 400px); + } +`; diff --git a/frontend/src/pages/auth/Redirection.tsx b/frontend/src/pages/auth/Redirection.tsx new file mode 100644 index 000000000..871befd5f --- /dev/null +++ b/frontend/src/pages/auth/Redirection.tsx @@ -0,0 +1,77 @@ +import { useContext, useEffect, useState } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; + +import { AuthResponse } from '@type/auth'; + +import { AuthContext } from '@hooks/context/auth'; + +import Error from '@pages/Error'; + +import LoadingSpinner from '@components/common/LoadingSpinner'; + +import { ESSENTIAL_MAX_AGE } from '@constants/cookie'; +import { ACCESS_TOKEN_KEY } from '@constants/localStorage'; + +import { setCookie } from '@utils/cookie'; +import { getFetch } from '@utils/fetch'; +import { setLocalStorage } from '@utils/localStorage'; +import { decodeToken } from '@utils/token/decodeToken'; + +const getAuthInfo = async (url: string): Promise => { + return await getFetch(url); +}; + +export default function Redirection() { + const { loggedInfo, setLoggedInfo } = useContext(AuthContext); + const [errorMessage, setErrorMessage] = useState(''); + const [isLoading, setIsLoading] = useState(true); + const [params] = useSearchParams(); + + const navigate = useNavigate(); + + useEffect(() => { + const authInfoFetch = async () => { + setIsLoading(true); + setErrorMessage(''); + + const code = params.get('code'); + const REGISTER_API_URL = `${process.env.VOTOGETHER_BASE_URL}/auth/kakao/callback?code=${code}`; + try { + const { accessToken, hasEssentialInfo } = await getAuthInfo(REGISTER_API_URL); + setLocalStorage(ACCESS_TOKEN_KEY, accessToken); + + setCookie({ + key: 'hasEssentialInfo', + value: String(hasEssentialInfo), + maxAge: ESSENTIAL_MAX_AGE, + }); + + const decodedPayload = decodeToken(accessToken); + const id = decodedPayload.memberId; + + setLoggedInfo({ + ...loggedInfo, + id, + isLoggedIn: true, + }); + + navigate('/'); + } catch (error) { + setErrorMessage('로그인 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + }; + + authInfoFetch(); + }, [navigate, loggedInfo, setLoggedInfo, params]); + + if (isLoading) + return ( +
    + +
    + ); + + if (errorMessage) return ; +} diff --git a/frontend/src/pages/post/CreatePostPage/index.tsx b/frontend/src/pages/post/CreatePostPage/index.tsx new file mode 100644 index 000000000..c8698091c --- /dev/null +++ b/frontend/src/pages/post/CreatePostPage/index.tsx @@ -0,0 +1,46 @@ +import { useContext, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { PostOptionContext } from '@hooks/context/postOption'; +import { useCreatePost } from '@hooks/query/post/useCreatePost'; +import { useToast } from '@hooks/useToast'; + +import Layout from '@components/common/Layout'; +import Toast from '@components/common/Toast'; +import PostForm from '@components/PostForm'; + +import { SORTING, STATUS } from '@constants/post'; + +export default function CreatePostPage() { + const navigate = useNavigate(); + + const { mutate, isSuccess, isError, error } = useCreatePost(); + const { isToastOpen, openToast, toastMessage } = useToast(); + const { setPostOption } = useContext(PostOptionContext); + + useEffect(() => { + if (isSuccess) { + navigate('/'); + setPostOption({ sorting: SORTING.LATEST, status: STATUS.PROGRESS }); + } + }, [isSuccess, navigate]); + + useEffect(() => { + if (isError && error instanceof Error) { + const errorResponse = JSON.parse(error.message); + openToast(errorResponse.message); + return; + } + }, [isError, error]); + + return ( + + + {isToastOpen && ( + + {toastMessage} + + )} + + ); +} diff --git a/frontend/src/pages/post/EditPostPage/EditPost/index.tsx b/frontend/src/pages/post/EditPostPage/EditPost/index.tsx new file mode 100644 index 000000000..c1461d8d7 --- /dev/null +++ b/frontend/src/pages/post/EditPostPage/EditPost/index.tsx @@ -0,0 +1,50 @@ +import { useContext, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; + +import { PostOptionContext } from '@hooks/context/postOption'; +import { useEditPost } from '@hooks/query/post/useEditPost'; +import { usePostDetail } from '@hooks/query/post/usePostDetail'; +import { useToast } from '@hooks/useToast'; + +import Toast from '@components/common/Toast'; +import PostForm from '@components/PostForm'; + +import { PATH } from '@constants/path'; +import { SORTING, STATUS } from '@constants/post'; + +export default function EditPost() { + const navigate = useNavigate(); + + const { postId } = useParams(); + + const { data } = usePostDetail(true, Number(postId)); + const { mutate, isSuccess, isError, error } = useEditPost(Number(postId)); + const { isToastOpen, openToast, toastMessage } = useToast(); + const { setPostOption } = useContext(PostOptionContext); + + useEffect(() => { + if (isSuccess) { + navigate(`${PATH.POST}/${postId}`); + setPostOption({ sorting: SORTING.LATEST, status: STATUS.PROGRESS }); + } + }, [isSuccess, navigate, postId]); + + useEffect(() => { + if (isError && error instanceof Error) { + const errorResponse = JSON.parse(error.message); + openToast(errorResponse.message); + return; + } + }, [isError, error]); + + return ( + <> + + {isToastOpen && ( + + {toastMessage} + + )} + + ); +} diff --git a/frontend/src/pages/post/EditPostPage/EditPostPage.stories.tsx b/frontend/src/pages/post/EditPostPage/EditPostPage.stories.tsx new file mode 100644 index 000000000..5e4204c01 --- /dev/null +++ b/frontend/src/pages/post/EditPostPage/EditPostPage.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import EditPostPage from '.'; + +const meta: Meta = { + component: EditPostPage, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/pages/post/EditPostPage/index.tsx b/frontend/src/pages/post/EditPostPage/index.tsx new file mode 100644 index 000000000..43ec412ac --- /dev/null +++ b/frontend/src/pages/post/EditPostPage/index.tsx @@ -0,0 +1,31 @@ +import { Suspense } from 'react'; + +import ErrorBoundaryWithNarrowHeader from '@pages/ErrorBoundaryWithNarrowHeader'; + +import Layout from '@components/common/Layout'; +import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; +import Skeleton from '@components/common/Skeleton'; + +import EditPost from './EditPost'; +import * as S from './style'; + +export default function EditPostPage() { + return ( + + + + + + + + + } + > + + + + + ); +} diff --git a/frontend/src/pages/post/EditPostPage/style.ts b/frontend/src/pages/post/EditPostPage/style.ts new file mode 100644 index 000000000..65da09987 --- /dev/null +++ b/frontend/src/pages/post/EditPostPage/style.ts @@ -0,0 +1,23 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const SkeletonWrapper = styled.div` + padding: 50px 10px 20px 10px; + + @media (min-width: ${theme.breakpoint.sm}) { + padding: 30px 40px 20px 40px; + } + + @media (min-width: ${theme.breakpoint.md}) { + padding: 30px 80px 20px 80px; + } +`; + +export const HeaderButton = styled.button` + width: 30px; + + color: white; + + cursor: pointer; +`; diff --git a/frontend/src/pages/post/PostDetail/BottomButtonPart/BottomButtonPart.stories.tsx b/frontend/src/pages/post/PostDetail/BottomButtonPart/BottomButtonPart.stories.tsx new file mode 100644 index 000000000..72551fdb3 --- /dev/null +++ b/frontend/src/pages/post/PostDetail/BottomButtonPart/BottomButtonPart.stories.tsx @@ -0,0 +1,41 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import BottomButtonPart from '.'; + +const meta: Meta = { + component: BottomButtonPart, +}; + +const handleEvent = { + movePage: { + moveWritePostPage: () => {}, + moveVoteStatisticsPage: () => {}, + movePostListPage: () => {}, + }, + controlPost: { + setEarlyClosePost: () => {}, + deletePost: () => {}, + reportPost: (reason: string) => {}, + reportNickname: (reason: string) => {}, + }, + openToast: () => {}, +}; + +export default meta; +type Story = StoryObj; + +export const isWriterAndIsClosedCase: Story = { + render: () => , +}; + +export const isNotWriterAndIsClosedCase: Story = { + render: () => , +}; + +export const isWriterAndIsNotClosedCase: Story = { + render: () => , +}; + +export const isNotWriterAndIsNotClosedCase: Story = { + render: () => , +}; diff --git a/frontend/src/pages/post/PostDetail/BottomButtonPart/index.tsx b/frontend/src/pages/post/PostDetail/BottomButtonPart/index.tsx new file mode 100644 index 000000000..3b8f83436 --- /dev/null +++ b/frontend/src/pages/post/PostDetail/BottomButtonPart/index.tsx @@ -0,0 +1,117 @@ +import { useContext, useState } from 'react'; + +import { AuthContext } from '@hooks/context/auth'; + +import DeleteModal from '@components/common/DeleteModal'; +import SquareButton from '@components/common/SquareButton'; +import ReportModal from '@components/ReportModal'; + +import * as S from './style'; + +type MovePageEvent = 'moveWritePostPage' | 'moveVoteStatisticsPage' | 'movePostListPage'; + +interface PostDetailPageChildProps { + isWriter: boolean; + isClosed: boolean; + handleEvent: { + movePage: Record void>; + controlPost: { + setEarlyClosePost: () => void; + deletePost: () => void; + reportPost: (reason: string) => void; + reportNickname: (reason: string) => void; + }; + openToast: (text: string) => void; + }; +} + +export default function BottomButtonPart({ + isWriter, + isClosed, + handleEvent: { movePage, controlPost, openToast }, +}: PostDetailPageChildProps) { + const { loggedInfo } = useContext(AuthContext); + const { moveWritePostPage, moveVoteStatisticsPage } = movePage; + const { setEarlyClosePost, deletePost, reportPost, reportNickname } = controlPost; + + const [action, setAction] = useState(null); + + const handleActionButtonClick = (action: string) => { + if (!loggedInfo.isLoggedIn) { + openToast('로그인 후에 기능을 이용해주세요.'); + return; + } + + setAction(action); + }; + + const handleCancelClick = () => { + setAction(null); + }; + + return ( + + {!isWriter ? ( + <> + handleActionButtonClick('POST_REPORT')}> + 게시물 신고 + + handleActionButtonClick('NICKNAME_REPORT')}> + 작성자 닉네임 신고 + + + ) : !isClosed ? ( + <> + + 조기마감 + + + 수 정 + + + handleActionButtonClick('DELETE')} + > + 삭 제 + + + ) : ( + <> + + 통계보기 + + handleActionButtonClick('DELETE')} + > + 삭 제 + + + )} + {action === 'DELETE' && ( + + )} + {action === 'POST_REPORT' && ( + + )} + {action === 'NICKNAME_REPORT' && ( + + )} + + ); +} diff --git a/frontend/src/pages/post/PostDetail/BottomButtonPart/style.ts b/frontend/src/pages/post/PostDetail/BottomButtonPart/style.ts new file mode 100644 index 000000000..fd2c0bd91 --- /dev/null +++ b/frontend/src/pages/post/PostDetail/BottomButtonPart/style.ts @@ -0,0 +1,15 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const BottomButtonContainer = styled.div` + display: none; + + @media (min-width: ${theme.breakpoint.sm}) { + display: flex; + width: 90%; + height: 40px; + margin-top: 40px; + gap: 10px; + } +`; diff --git a/frontend/src/pages/post/PostDetail/InnerHeaderPart/InnerHeaderPart.stories.tsx b/frontend/src/pages/post/PostDetail/InnerHeaderPart/InnerHeaderPart.stories.tsx new file mode 100644 index 000000000..9040e39bf --- /dev/null +++ b/frontend/src/pages/post/PostDetail/InnerHeaderPart/InnerHeaderPart.stories.tsx @@ -0,0 +1,43 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; + +import InnerHeaderPart from '.'; + +const meta: Meta = { + component: InnerHeaderPart, + decorators: [storyFn => {storyFn()}], +}; + +const handleEvent = { + movePage: { + moveWritePostPage: () => {}, + moveVoteStatisticsPage: () => {}, + movePostListPage: () => {}, + }, + controlPost: { + setEarlyClosePost: () => {}, + deletePost: () => {}, + reportPost: (reason: string) => {}, + reportNickname: (reason: string) => {}, + }, +}; + +export default meta; +type Story = StoryObj; + +export const isWriterAndIsClosedCase: Story = { + render: () => , +}; + +export const isNotWriterAndIsClosedCase: Story = { + render: () => , +}; + +export const isWriterAndIsNotClosedCase: Story = { + render: () => , +}; + +export const isNotWriterAndIsNotClosedCase: Story = { + render: () => , +}; diff --git a/frontend/src/pages/post/PostDetail/InnerHeaderPart/index.tsx b/frontend/src/pages/post/PostDetail/InnerHeaderPart/index.tsx new file mode 100644 index 000000000..911dad334 --- /dev/null +++ b/frontend/src/pages/post/PostDetail/InnerHeaderPart/index.tsx @@ -0,0 +1,132 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { PostAction, PostMenuItem } from '@type/menu'; + +import { useToggle } from '@hooks/useToggle'; + +import DeleteModal from '@components/common/DeleteModal'; +import HeaderTextButton from '@components/common/HeaderTextButton'; +import IconButton from '@components/common/IconButton'; +import PostMenu from '@components/common/PostMenu'; +import TagButton from '@components/common/TagButton'; +import ReportModal from '@components/ReportModal'; + +import * as S from './style'; + +type MovePageEvent = 'moveWritePostPage' | 'moveVoteStatisticsPage' | 'movePostListPage'; + +interface PostDetailPageChildProps { + isWriter: boolean; + isClosed: boolean; + handleEvent: { + movePage: Record void>; + controlPost: { + setEarlyClosePost: () => void; + deletePost: () => void; + reportPost: (reason: string) => void; + reportNickname: (reason: string) => void; + }; + }; +} + +const menuList: PostMenuItem[] = [ + { color: 'black', content: '닉네임 신고', action: 'NICKNAME_REPORT' }, + { color: 'black', content: '게시글 신고', action: 'POST_REPORT' }, +]; + +export default function InnerHeaderPart({ + isWriter, + isClosed, + handleEvent: { movePage, controlPost }, +}: PostDetailPageChildProps) { + const navigate = useNavigate(); + + const { moveWritePostPage, moveVoteStatisticsPage } = movePage; + const { setEarlyClosePost, deletePost, reportPost, reportNickname } = controlPost; + const { isOpen, toggleComponent, closeComponent } = useToggle(); + const [action, setAction] = useState(null); + + const handleMenuClick = (action: PostAction) => { + closeComponent(); + setAction(action); + }; + + const handleCancelClick = () => { + setAction(null); + }; + + return ( + <> + { + navigate(-1); + }} + /> + + {!isWriter ? ( + <> + + 신고 + + {isOpen && ( + + + + )} + + ) : !isClosed ? ( + <> + + 수정 + + handleMenuClick('DELETE')}> + 삭제 + + + + 조기마감 + + + + ) : ( + <> + handleMenuClick('DELETE')}> + 삭제 + + + + 통계보기 + + + + )} + {action === 'DELETE' && ( + + )} + {action === 'POST_REPORT' && ( + + )} + {action === 'NICKNAME_REPORT' && ( + + )} + + + ); +} diff --git a/frontend/src/pages/post/PostDetail/InnerHeaderPart/style.ts b/frontend/src/pages/post/PostDetail/InnerHeaderPart/style.ts new file mode 100644 index 000000000..83c6e901b --- /dev/null +++ b/frontend/src/pages/post/PostDetail/InnerHeaderPart/style.ts @@ -0,0 +1,22 @@ +import { styled } from 'styled-components'; + +export const HeaderWrapper = styled.div` + display: flex; + gap: 30px; +`; + +export const TagButtonWrapper = styled.div` + margin-right: 10px; + + position: absolute; + top: 55px; + right: 10px; +`; + +export const MenuWrapper = styled.div` + margin-right: 10px; + + position: absolute; + top: 45px; + right: 10px; +`; diff --git a/frontend/src/pages/post/PostDetail/PostDetail.stories.tsx b/frontend/src/pages/post/PostDetail/PostDetail.stories.tsx new file mode 100644 index 000000000..acdf4112b --- /dev/null +++ b/frontend/src/pages/post/PostDetail/PostDetail.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import PostDetailPage from '.'; + +const meta: Meta = { + component: PostDetailPage, +}; + +export default meta; +type Story = StoryObj; + +export const WriterCase: Story = { + render: () => , +}; diff --git a/frontend/src/pages/post/PostDetail/PostDetail/index.tsx b/frontend/src/pages/post/PostDetail/PostDetail/index.tsx new file mode 100644 index 000000000..2285ed922 --- /dev/null +++ b/frontend/src/pages/post/PostDetail/PostDetail/index.tsx @@ -0,0 +1,164 @@ +import { Suspense, useContext, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; + +import { PostInfo } from '@type/post'; +import { ReportRequest } from '@type/report'; + +import { AuthContext } from '@hooks/context/auth'; +import { useDeletePost } from '@hooks/query/post/useDeletePost'; +import { useEarlyClosePost } from '@hooks/query/post/useEarlyClosePost'; +import { usePostDetail } from '@hooks/query/post/usePostDetail'; +import { useToast } from '@hooks/useToast'; + +import { reportContent } from '@api/report'; + +import ErrorBoundary from '@pages/ErrorBoundary'; + +import CommentList from '@components/comment/CommentList'; +import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; +import Post from '@components/common/Post'; +import Skeleton from '@components/common/Skeleton'; +import Toast from '@components/common/Toast'; + +import { checkClosedPost } from '@utils/time'; + +import BottomButtonPart from '../BottomButtonPart'; +import InnerHeaderPart from '../InnerHeaderPart'; + +import * as S from './style'; + +export default function PostDetail() { + const navigate = useNavigate(); + + const params = useParams() as { postId: string }; + const postId = Number(params.postId); + const { isToastOpen, openToast, toastMessage } = useToast(); + + const { loggedInfo } = useContext(AuthContext); + const memberId = loggedInfo.id; + + const { data: postData } = usePostDetail(loggedInfo.isLoggedIn, postId); + const { + mutate: deletePost, + isSuccess: isDeleteSuccess, + isError: isDeleteError, + error: deleteError, + } = useDeletePost(postId, loggedInfo.isLoggedIn); + const { mutate: earlyClosePost } = useEarlyClosePost(postId); + + const postDataFallback = postData ?? ({} as PostInfo); + + const isWriter = postDataFallback.writer.id === memberId; + const isClosed = checkClosedPost(postDataFallback.deadline); + + const movePage = { + moveWritePostPage: () => { + if (postDataFallback.voteInfo.allPeopleCount) { + openToast('투표한 사용자가 있어 글 수정이 불가합니다.'); + return; + } + + navigate(`/posts/write/${postId}`); + }, + moveVoteStatisticsPage: () => { + navigate(`/posts/result/${postId}`); + }, + movePostListPage: () => { + navigate('/'); + }, + }; + + const controlPost = { + setEarlyClosePost: earlyClosePost, + deletePost: () => { + if (postDataFallback.voteInfo.allPeopleCount >= 20) { + openToast('20인 이상 투표한 게시물은 삭제할 수 없습니다.'); + return; + } + + deletePost(); + }, + reportPost: async (reason: string) => { + const reportData: ReportRequest = { type: 'POST', id: postId, reason }; + + await reportContent(reportData) + .then(res => { + openToast('게시물을 신고했습니다.'); + }) + .catch(e => { + if (e instanceof Error) { + const errorResposne = JSON.parse(e.message); + openToast(errorResposne.message); + return; + } + openToast('게시글 신고가 실패했습니다.'); + }); + }, + reportNickname: async (reason: string) => { + const reportData: ReportRequest = { + type: 'NICKNAME', + id: postDataFallback.writer.id, + reason, + }; + + await reportContent(reportData) + .then(res => { + openToast('작성자 닉네임을 신고했습니다.'); + }) + .catch(e => { + if (e instanceof Error) { + const errorResposne = JSON.parse(e.message); + openToast(errorResposne.message); + return; + } + openToast('작성자 닉네임 신고가 실패했습니다.'); + }); + }, + }; + + useEffect(() => { + if (isDeleteError && deleteError instanceof Error) { + openToast(deleteError.message); + } + }, [isDeleteError, deleteError]); + + useEffect(() => { + if (isDeleteSuccess) { + navigate('/'); + } + }, [isDeleteSuccess, navigate]); + + return ( + <> + + + + + + + + + + + + }> + + + + + {isToastOpen && ( + + {toastMessage} + + )} + + ); +} diff --git a/frontend/src/pages/post/PostDetail/PostDetail/style.ts b/frontend/src/pages/post/PostDetail/PostDetail/style.ts new file mode 100644 index 000000000..d22bc7688 --- /dev/null +++ b/frontend/src/pages/post/PostDetail/PostDetail/style.ts @@ -0,0 +1,32 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const HeaderContainer = styled.header` + position: fixed; + width: 100%; + top: 0; + + z-index: ${theme.zIndex.header}; + + @media (min-width: ${theme.breakpoint.sm}) { + display: none; + } +`; + +export const MainContainer = styled.main` + display: flex; + flex-direction: column; + align-items: center; + + margin: 70px 10px 20px 10px; + + @media (min-width: ${theme.breakpoint.sm}) { + margin-top: 30px; + } +`; + +export const BottomContainer = styled.div` + margin: 10px; + margin-bottom: 30px; +`; diff --git a/frontend/src/pages/post/PostDetail/PostDetailFallback/index.tsx b/frontend/src/pages/post/PostDetail/PostDetailFallback/index.tsx new file mode 100644 index 000000000..e09c35ae5 --- /dev/null +++ b/frontend/src/pages/post/PostDetail/PostDetailFallback/index.tsx @@ -0,0 +1,29 @@ +import { useNavigate } from 'react-router-dom'; + +import IconButton from '@components/common/IconButton'; +import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; +import Skeleton from '@components/common/Skeleton'; + +import * as S from './style'; + +export default function PostDetailFallback() { + const navigate = useNavigate(); + + return ( + + + + { + navigate(-1); + }} + /> + + + + + + + ); +} diff --git a/frontend/src/pages/post/PostDetail/PostDetailFallback/style.ts b/frontend/src/pages/post/PostDetail/PostDetailFallback/style.ts new file mode 100644 index 000000000..ecfd33923 --- /dev/null +++ b/frontend/src/pages/post/PostDetail/PostDetailFallback/style.ts @@ -0,0 +1,27 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Container = styled.div` + margin-top: 70px; + + @media (min-width: ${theme.breakpoint.sm}) { + margin-top: 30px; + } +`; + +export const HeaderContainer = styled.div` + position: fixed; + width: 100%; + top: 0; + + z-index: ${theme.zIndex.header}; + + @media (min-width: ${theme.breakpoint.sm}) { + display: none; + } +`; + +export const SkeletonWrapper = styled.div` + padding: 0 10px; +`; diff --git a/frontend/src/pages/post/PostDetail/index.tsx b/frontend/src/pages/post/PostDetail/index.tsx new file mode 100644 index 000000000..79eb1e7cc --- /dev/null +++ b/frontend/src/pages/post/PostDetail/index.tsx @@ -0,0 +1,20 @@ +import { Suspense } from 'react'; + +import ErrorBoundaryWithNarrowHeader from '@pages/ErrorBoundaryWithNarrowHeader'; + +import Layout from '@components/common/Layout'; + +import PostDetail from './PostDetail'; +import PostDetailFallback from './PostDetailFallback'; + +export default function PostDetailPage() { + return ( + + + }> + + + + + ); +} diff --git a/frontend/src/pages/user/RegisterPersonalInfo/RegisterPersonalInfo.stories.tsx b/frontend/src/pages/user/RegisterPersonalInfo/RegisterPersonalInfo.stories.tsx new file mode 100644 index 000000000..5b1dd83d4 --- /dev/null +++ b/frontend/src/pages/user/RegisterPersonalInfo/RegisterPersonalInfo.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import RegisterPersonalInfo from '.'; + +const meta: Meta = { + component: RegisterPersonalInfo, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; diff --git a/frontend/src/pages/user/RegisterPersonalInfo/index.tsx b/frontend/src/pages/user/RegisterPersonalInfo/index.tsx new file mode 100644 index 000000000..5119846f0 --- /dev/null +++ b/frontend/src/pages/user/RegisterPersonalInfo/index.tsx @@ -0,0 +1,189 @@ +import { ChangeEvent, FormEvent, useEffect, useState } from 'react'; +import { Navigate, useNavigate } from 'react-router-dom'; + +import { useUpdateUserInfo } from '@hooks/query/user/useUpdateUserInfo'; +import { useToast } from '@hooks/useToast'; + +import Accordion from '@components/common/Accordion'; +import Layout from '@components/common/Layout'; +import LogoButton from '@components/common/LogoButton'; +import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; +import SquareButton from '@components/common/SquareButton'; +import Toast from '@components/common/Toast'; + +import { ESSENTIAL_MAX_AGE } from '@constants/cookie'; +import { BIRTH_YEAR } from '@constants/user'; + +import { getCookie, setCookie } from '@utils/cookie'; + +import * as S from './style'; + +interface UserInfoForm { + gender: ''; + birthYear: string; + isTermsAgreed: boolean; +} + +export default function RegisterPersonalInfo() { + const navigate = useNavigate(); + const hasEssentialInfo = getCookie().hasEssentialInfo === 'true'; + + const { mutate: updateUserInfo, isSuccess, isError, error } = useUpdateUserInfo(); + const { isToastOpen, openToast, toastMessage } = useToast(); + + const [userInfoForm, setUserInfoForm] = useState({ + gender: '', + birthYear: '', + isTermsAgreed: false, + }); + + const handleFormInputChange = (e: ChangeEvent) => { + const { name, value, type, checked } = e.target; + + const newValue = type === 'checkbox' ? checked : value; + + setUserInfoForm(prev => ({ + ...prev, + [name]: newValue, + })); + }; + + const handleUserInfoFormSubmit = (e: FormEvent) => { + e.preventDefault(); + const { gender, birthYear, isTermsAgreed } = userInfoForm; + + if (!gender || !birthYear) { + alert('필수 개인 정보를 모두 입력해주세요.'); + return; + } + + if (isNaN(Number(birthYear))) { + alert('생년월일 값을 확인해주세요.'); + return; + } + + if (!isTermsAgreed) { + alert('개인 정보 약관에 동의해주세요.'); + return; + } + + const submittedUserInfo = { gender, birthYear: Number(birthYear) }; + updateUserInfo(submittedUserInfo); + setCookie({ key: 'hasEssentialInfo', value: 'true', maxAge: ESSENTIAL_MAX_AGE }); + + alert('개인 정보 등록 완료!'); + navigate('/'); + }; + + useEffect(() => { + if (isSuccess) { + setCookie({ key: 'hasEssentialInfo', value: 'true', maxAge: ESSENTIAL_MAX_AGE }); + alert('개인 정보가 등록 완료되었습니다.'); + navigate('/'); + } + }, [isSuccess]); + + useEffect(() => { + if (isError && error instanceof Error) { + openToast('개인 정보를 이미 등록 완료하셨습니다.'); + return; + } + }, [isError, error]); + + if (hasEssentialInfo) { + alert('개인 정보를 이미 등록 완료하셨습니다.'); + return ; + } + + return ( + + + + + navigate('/')} + /> + + + 개인 정보 등록 + + + + +
  • • 개인정보 항목: 성별, 나이
  • +
  • • 수집 방법: 회원가입 후 개인정보 등록 페이지에서 성별, 나이 저장
  • +
  • + • 수집 목적: 투표한 이용자의 성별 및 나이에 대한 투표 통계 제공 (단, 투표 통계는 + 글 작성자에 한하여 제공됨) +
  • +
  • • 보유 근거: 정보주체 동의
  • +
  • • 보유 기간: 회원 탈퇴 시 즉시 삭제
  • +

    + * 개인 정보 수집에 대한 동의를 거부할 수 있습니다. (단, 동의가 없을 경우 일부 + 서비스 이용에 제한이 있습니다.) +

    +
    +
    + +

    성별

    + + + + 남성 + + + + 여성 + + +
    + +

    출생 연도

    + +
    + + + 개인 정보 약관에 동의합니다. + + + + 저장 + + +
    +
    + {isToastOpen && ( + + {toastMessage} + + )} +
    +
    + ); +} diff --git a/frontend/src/pages/user/RegisterPersonalInfo/style.ts b/frontend/src/pages/user/RegisterPersonalInfo/style.ts new file mode 100644 index 000000000..1ab53d4ba --- /dev/null +++ b/frontend/src/pages/user/RegisterPersonalInfo/style.ts @@ -0,0 +1,123 @@ +import { styled } from 'styled-components'; + +import { theme } from '@styles/theme'; + +export const Wrapper = styled.main` + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 30px; + + padding-top: 55px; + + position: relative; + + @media (min-width: 768px) { + padding-top: 20px; + } + + @media (min-width: ${theme.breakpoint.md}) { + padding-top: 20px; + } +`; + +export const HeaderWrapper = styled.div` + width: 100%; + + position: fixed; + + z-index: ${theme.zIndex.header}; + + @media (min-width: ${theme.breakpoint.md}) { + display: none; + } +`; + +export const MainWrapper = styled.section` + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 20px; + + width: 90%; +`; + +export const Title = styled.h1` + width: 90%; + margin-top: 20px; + + font-size: 30px; + font-weight: bold; +`; + +export const InfoForm = styled.form` + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 25px; + + width: 90%; +`; + +export const TermsList = styled.ul` + display: flex; + flex-direction: column; + justify-content: center; + gap: 10px; +`; + +export const Label = styled.label` + display: flex; + justify-content: start; + align-items: center; + gap: 10px; + + font: var(--text-body); + + p { + font-weight: bold; + } + + input[type='number']::-webkit-outer-spin-button, + input[type='number']::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } +`; + +export const GenderLabel = styled.label` + display: flex; + justify-content: start; + align-items: center; + gap: 30px; + + margin-left: 20px; +`; + +export const Input = styled.input` + width: 70%; + height: 20px; + border: 1px solid #f2f2f2; + padding: 20px; +`; + +export const Radio = styled.input` + font-size: 14px; + font-weight: light; +`; + +export const Checkbox = styled.input` + width: 20px; + height: 20px; +`; + +export const ButtonWrapper = styled.div` + display: flex; + justify-content: end; + + width: 90px; + height: 50px; + margin-left: 70%; +`; diff --git a/frontend/src/routes/PrivateRoute.tsx b/frontend/src/routes/PrivateRoute.tsx new file mode 100644 index 000000000..bafff38c9 --- /dev/null +++ b/frontend/src/routes/PrivateRoute.tsx @@ -0,0 +1,52 @@ +import { PropsWithChildren, useContext } from 'react'; +import { Navigate } from 'react-router-dom'; + +import { AuthContext } from '@hooks/context/auth'; + +import { ACCESS_TOKEN_KEY } from '@constants/localStorage'; +import { PATH } from '@constants/path'; + +import { getCookie } from '@utils/cookie'; +import { getLocalStorage } from '@utils/localStorage'; + +interface Route extends PropsWithChildren { + isGuestAllowed?: boolean; + path?: (typeof PATH)[keyof typeof PATH]; +} + +const PrivateRoute = ({ children, isGuestAllowed = false, path = PATH.LOGIN }: Route) => { + const authInfo = useContext(AuthContext); + const isLoggedIn = getLocalStorage(ACCESS_TOKEN_KEY); + const hasEssentialInfo = getCookie().hasEssentialInfo; + + // const isAuthenticated = true; + if (!isGuestAllowed && !isLoggedIn) { + alert('해당 페이지에 접근하려면 로그인이 필요합니다.'); + + return ; + } + + /* + if (!isGuestAllowed && !isAuthenticated) { + alert('해당 페이지에 대한 접근 권한이 없습니다.'); + + return ; + } + */ + + if (isLoggedIn && hasEssentialInfo === 'false') { + alert('개인정보를 먼저 등록해주세요.'); + + return ; + } + + if (isLoggedIn && hasEssentialInfo === undefined) { + authInfo.clearLoggedInfo(); + + return ; + } + + return children; +}; + +export default PrivateRoute; diff --git a/frontend/src/routes/router.tsx b/frontend/src/routes/router.tsx new file mode 100644 index 000000000..f473cea1c --- /dev/null +++ b/frontend/src/routes/router.tsx @@ -0,0 +1,152 @@ +import { createBrowserRouter } from 'react-router-dom'; + +import Login from '@pages/auth/Login'; +import Redirection from '@pages/auth/Redirection'; +import Error from '@pages/Error'; +import Home from '@pages/Home'; +import MyInfo from '@pages/MyInfo'; +import NotFound from '@pages/NotFound'; +import CreatePostPage from '@pages/post/CreatePostPage'; +import EditPostPage from '@pages/post/EditPostPage'; +import PostDetailPage from '@pages/post/PostDetail'; +import Ranking from '@pages/Ranking'; +import RegisterPersonalInfo from '@pages/user/RegisterPersonalInfo'; +import VoteStatisticsPage from '@pages/VoteStatisticsPage'; + +import ScrollToTop from '@components/common/ScrollToTop'; + +import { PATH } from '@constants/path'; + +import PrivateRoute from './PrivateRoute'; + +const router = createBrowserRouter([ + { + path: PATH.HOME, + element: ( + + + + + ), + errorElement: , + children: [ + { + path: 'search', + element: ( + + + + + ), + }, + ], + }, + { + path: PATH.LOGIN, + element: , + errorElement: , + }, + { + path: 'auth/kakao/callback', + element: , + errorElement: , + }, + { + path: PATH.POST, + errorElement: , + children: [ + { + path: 'write', + element: ( + + + + + ), + }, + { + path: 'write/:postId', + element: ( + + + + + ), + }, + { + path: ':postId', + element: ( + + + + + ), + }, + { + path: 'result/:postId', + element: ( + + + + + ), + }, + { + path: 'category/:categoryId', + element: ( + + + + + ), + }, + ], + }, + { + path: PATH.USER, + errorElement: , + children: [ + { + path: 'myPage', + element: ( + + + + + ), + }, + { + path: 'posts', + element: ( + + + + + ), + }, + { + path: 'votes', + element: ( + + + + + ), + }, + { + path: 'register', + element: , + }, + ], + }, + { + path: PATH.RANKING, + element: , + errorElement: , + }, + { + path: '*', + element: , + }, +]); +export default router; diff --git a/frontend/src/styles/globalStyle.ts b/frontend/src/styles/globalStyle.ts new file mode 100644 index 000000000..107e4a39f --- /dev/null +++ b/frontend/src/styles/globalStyle.ts @@ -0,0 +1,47 @@ +import { createGlobalStyle } from 'styled-components'; + +import { reset } from './reset'; + +export const GlobalStyle = createGlobalStyle` + ${reset} + + * { + padding: 0; + margin: 0; + box-sizing: border-box; + border:none + } + + ul, + li { + list-style: none; + } + + html, + body { + font-family: sans-serif; + font-size: 62.5%; + } + + :root { + /* Colors *****************************************/ + --primary-color: #F85554; + --white: #ffffff; + --slate: #94A3B8; + --gray: #F4F4F4; + --red: #F51A18; + --dark-gray: #929292; + --header: #1f1f1f; + --graph-color-purple:#853DE1; + --graph-color-green:#5AEAA5; + --active-post: #00DFA2; + --text-dark-gray:#686868; + + /* Fonts *****************************************/ + --text-title: 600 2rem/2.4rem san-serif; + --text-subtitle: 600 1.8rem/2.8rem san-serif; + --text-body: 400 1.6rem/2.4rem san-serif; + --text-caption: 400 1.4rem/2rem san-serif; + --text-small: 400 1.2rem/1.8rem san-serif; + } +`; diff --git a/frontend/src/styles/reset.ts b/frontend/src/styles/reset.ts new file mode 100644 index 000000000..6880ce15f --- /dev/null +++ b/frontend/src/styles/reset.ts @@ -0,0 +1,79 @@ +export const reset = /*css*/ ` +/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */ +html, +body, +p, +ol, +ul, +li, +dl, +dt, +dd, +blockquote, +figure, +fieldset, +legend, +textarea, +pre, +iframe, +hr, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; + padding: 0; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: 100%; + font-weight: normal; +} +ul { + list-style: none; +} +button, +input, +select { + margin: 0; +} +html { + box-sizing: border-box; +} +*, +*::before, +*::after { + box-sizing: inherit; +} +img, +video { + height: auto; + max-width: 100%; +} +iframe { + border: 0; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} + +button{ + background: none; +} + +a{ + color: inherit; + text-decoration: none; +} +`; diff --git a/frontend/src/styles/theme.ts b/frontend/src/styles/theme.ts new file mode 100644 index 000000000..e751d42d2 --- /dev/null +++ b/frontend/src/styles/theme.ts @@ -0,0 +1,47 @@ +import { DefaultTheme, keyframes } from 'styled-components'; + +const breakpoint = { + /** @media (min-width: 576px) { ... } */ + sm: '576px', + /** @media (min-width: 768px) { ... } */ + md: '768px', + /** @media (min-width: 1440px) { ... }*/ + lg: '1440px', +}; + +const zIndex = { + select: 1, + header: 100, + modal: 200, +}; + +const animation = { + skeletonGradientPulse: keyframes` + 0% { + background-color: rgba(165, 165, 165, 0.1); + } + + 50% { + background-color: rgba(165, 165, 165, 0.3); + } + + 100% { + background-color: rgba(165, 165, 165, 0.1); + } + `, + skeletonGradientWave: keyframes` + to { + background-position-x: -200%; + } + `, +}; + +export type ZIndex = typeof zIndex; +export type Breakpoint = typeof breakpoint; +export type Animation = typeof animation; + +export const theme: DefaultTheme = { + breakpoint, + zIndex, + animation, +}; diff --git a/frontend/src/types/auth.ts b/frontend/src/types/auth.ts new file mode 100644 index 000000000..93df0af63 --- /dev/null +++ b/frontend/src/types/auth.ts @@ -0,0 +1,4 @@ +export interface AuthResponse { + accessToken: string; + hasEssentialInfo: boolean; +} diff --git a/frontend/src/types/category.ts b/frontend/src/types/category.ts new file mode 100644 index 000000000..24362818e --- /dev/null +++ b/frontend/src/types/category.ts @@ -0,0 +1,11 @@ +export interface Category { + id: number; + name: string; + isFavorite: boolean; +} + +export interface CategoryResponse { + id: number; + name: string; + isFavorite: boolean; +} diff --git a/frontend/src/types/comment.ts b/frontend/src/types/comment.ts new file mode 100644 index 000000000..c212df0f6 --- /dev/null +++ b/frontend/src/types/comment.ts @@ -0,0 +1,23 @@ +export interface Comment { + id: number; + member: { + id: number; + nickname: string; + }; + content: string; + createdAt: string; + isEdit: boolean; +} + +export interface CommentResponse { + id: number; + member: { + id: number; + nickname: string; + }; + content: string; + createdAt: string; + updatedAt: string; +} + +export type CommentRequest = Pick; diff --git a/frontend/src/types/menu.ts b/frontend/src/types/menu.ts new file mode 100644 index 000000000..c2891b4e9 --- /dev/null +++ b/frontend/src/types/menu.ts @@ -0,0 +1,15 @@ +export type MenuColor = 'black' | 'red'; + +//메뉴 컴포넌트에서 사용하는 기본적인 인터페이스 +interface MenuItem { + content: string; + color: MenuColor; + action: string; +} + +export type PostAction = 'NICKNAME_REPORT' | 'POST_REPORT' | 'DELETE'; + +//게시글 메뉴 컴포넌트에서 사용하는 확장된 인터페이스 (action을 제한) +export interface PostMenuItem extends MenuItem { + action: PostAction; +} diff --git a/frontend/src/types/post.ts b/frontend/src/types/post.ts new file mode 100644 index 000000000..44dd2ede5 --- /dev/null +++ b/frontend/src/types/post.ts @@ -0,0 +1,67 @@ +import { PostRequestKind, PostSorting, PostStatus } from '@components/post/PostListPage/types'; + +export interface WrittenVoteOptionType { + id: number; + text: string; + peopleCount: number; + percent: number; + imageUrl: string; +} + +export interface WrittenVoteOptionTypeResponse { + optionId: number; + content: string; + voteCount: number; + votePercent: number; + imageUrl: string; +} + +export interface PostInfo { + postId: number; + title: string; + writer: { id: number; nickname: string }; + content: string; + imageUrl: string; + category: { id: number; name: string }[]; + createTime: string; + deadline: string; + voteInfo: { + selectedOptionId: number; + allPeopleCount: number; + options: WrittenVoteOptionType[]; + }; +} + +export interface PostInfoResponse { + postId: number; + title: string; + writer: { id: number; nickname: string }; + content: string; + imageUrl: string; + categories: { id: number; name: string }[]; + createdAt: string; + deadline: string; + voteInfo: { + selectedOptionId: number; + totalVoteCount: number; + options: WrittenVoteOptionTypeResponse[]; + }; +} + +export interface PostList { + pageNumber: number; + postList: PostInfo[]; +} + +export interface PostListByRequiredOption { + postType: PostRequestKind; + postStatus: PostStatus; + postSorting: PostSorting; + pageNumber: number; + isLoggedIn: boolean; +} + +export interface PostListByOptionalOption { + categoryId: number; + keyword: string; +} diff --git a/frontend/src/types/ranking.ts b/frontend/src/types/ranking.ts new file mode 100644 index 000000000..804ee8655 --- /dev/null +++ b/frontend/src/types/ranking.ts @@ -0,0 +1,17 @@ +export interface PassionUser { + ranking: number; + nickname: string; + postCount: number; + voteCount: number; + score: number; +} + +export interface RankingPost { + ranking: number; + post: { + id: number; + writer: string; + title: string; + voteCount: number; + }; +} diff --git a/frontend/src/types/report.ts b/frontend/src/types/report.ts new file mode 100644 index 000000000..8211420ed --- /dev/null +++ b/frontend/src/types/report.ts @@ -0,0 +1,16 @@ +import { REPORT_MESSAGE } from '@components/ReportModal/constants'; + +export type ReportType = 'POST' | 'COMMENT' | 'NICKNAME'; + +export interface ReportRequest { + type: ReportType; + id: number; + reason: string; +} + +export type ReportMessage = keyof typeof REPORT_MESSAGE; + +export interface ReportInfo { + name: string; + reportMessageList: { [key: string]: string }; +} diff --git a/frontend/src/types/style.ts b/frontend/src/types/style.ts new file mode 100644 index 000000000..9ded76da3 --- /dev/null +++ b/frontend/src/types/style.ts @@ -0,0 +1 @@ +export type Size = 'sm' | 'md' | 'lg'; diff --git a/frontend/src/types/token.ts b/frontend/src/types/token.ts new file mode 100644 index 000000000..a2e003b47 --- /dev/null +++ b/frontend/src/types/token.ts @@ -0,0 +1,5 @@ +export interface AccessToken { + memberId: number; + iat: number; + exp: number; +} diff --git a/frontend/src/types/user.ts b/frontend/src/types/user.ts new file mode 100644 index 000000000..f87a53869 --- /dev/null +++ b/frontend/src/types/user.ts @@ -0,0 +1,30 @@ +export interface User { + nickname: string; + gender: 'FEMALE' | 'MALE'; + birthYear: number; + postCount: number; + voteCount: number; +} + +export interface UserInfoResponse { + nickname: string; + gender: 'FEMALE' | 'MALE'; + birthYear: number; + postCount: number; + voteCount: number; +} + +export interface ModifyNicknameRequest { + nickname: string; +} + +export interface LoggedInfo { + isLoggedIn: boolean; + id?: number; + userInfo?: User; +} + +export interface UpdateUserInfoRequest { + gender: 'MALE' | 'FEMALE'; + birthYear: number; +} diff --git a/frontend/src/utils/cookie/index.ts b/frontend/src/utils/cookie/index.ts new file mode 100644 index 000000000..ee9063e6b --- /dev/null +++ b/frontend/src/utils/cookie/index.ts @@ -0,0 +1,31 @@ +type CookieKey = 'hasEssentialInfo' | 'isAppInstallVisible'; + +export const setCookie = ({ + key, + value, + maxAge, +}: { + key: CookieKey; + value: string; + maxAge: number; +}) => { + document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent( + value + )}; max-age=${maxAge}; path=/`; +}; + +// token형식 = "key=value; key=value; key=value" +export const getCookie = (): Record => { + const cookie = document.cookie; + const cookieContent = cookie.split('; ').reduce((acc, pair) => { + const [key, value] = pair.split('='); + return { ...acc, [key]: value }; + }, {}) as Record; + + return cookieContent; +}; + +export const clearCookie = (key: string) => { + const expirationTime = new Date(Date.now() - 1); + document.cookie = `${encodeURIComponent(key)}=; expires=${expirationTime.toUTCString()}; path=/;`; +}; diff --git a/frontend/src/utils/fetch.ts b/frontend/src/utils/fetch.ts new file mode 100644 index 000000000..b47bf2a5c --- /dev/null +++ b/frontend/src/utils/fetch.ts @@ -0,0 +1,166 @@ +import { ACCESS_TOKEN_KEY } from '@constants/localStorage'; + +import { getLocalStorage } from './localStorage'; +import { silentLogin } from './token/silentLogin'; + +const headers = { + 'Content-Type': 'application/json', +}; + +const makeFetchHeaders = () => { + const accessToken = getLocalStorage(ACCESS_TOKEN_KEY); + + if (!accessToken) { + return headers; + } + + return { + ...headers, + Authorization: `Bearer ${accessToken}`, + }; +}; + +const makeFetchMultiHeaders = () => { + const accessToken = getLocalStorage(ACCESS_TOKEN_KEY); + + return { + Authorization: `Bearer ${accessToken}`, + }; +}; + +export const getFetch = async (url: string): Promise => { + try { + await silentLogin(); + const response = await fetch(url, { + method: 'GET', + headers: makeFetchHeaders(), + }); + + if (!response.ok) { + const errorMessage = await response.text(); + throw new Error(errorMessage); + } + + const data = await response.json(); + + return data; + } catch (e) { + const error = e as Error; + throw new Error(error.message); + } +}; + +export const postFetch = async (url: string, body: T) => { + try { + await silentLogin(); + const response = await fetch(url, { + method: 'POST', + body: JSON.stringify(body), + headers: makeFetchHeaders(), + }); + + if (!response.ok) { + const errorMessage = await response.text(); + throw new Error(errorMessage); + } + + return response; + } catch (e) { + const error = e as Error; + throw new Error(error.message); + } +}; + +export const putFetch = async (url: string, body: T) => { + try { + await silentLogin(); + const response = await fetch(url, { + method: 'PUT', + body: JSON.stringify(body), + headers: makeFetchHeaders(), + }); + + if (!response.ok) { + const errorMessage = await response.text(); + throw new Error(errorMessage); + } + } catch (e) { + const error = e as Error; + throw new Error(error.message); + } +}; + +export const patchFetch = async (url: string, body?: T) => { + try { + await silentLogin(); + const response = await fetch(url, { + method: 'PATCH', + headers: makeFetchHeaders(), + body: JSON.stringify(body), + }); + + if (!response.ok) { + const errorMessage = await response.text(); + throw new Error(errorMessage); + } + } catch (e) { + const error = e as Error; + throw new Error(error.message); + } +}; + +export const deleteFetch = async (url: string) => { + try { + await silentLogin(); + const response = await fetch(url, { + method: 'DELETE', + headers: makeFetchHeaders(), + }); + + if (!response.ok) { + const errorMessage = await response.text(); + throw new Error(errorMessage); + } + } catch (e) { + const error = e as Error; + throw new Error(error.message); + } +}; + +export const multiPostFetch = async (url: string, body: FormData) => { + try { + await silentLogin(); + const response = await fetch(url, { + method: 'POST', + body, + headers: makeFetchMultiHeaders(), + }); + + if (!response.ok) { + const errorMessage = await response.text(); + throw new Error(errorMessage); + } + } catch (e) { + const error = e as Error; + throw new Error(error.message); + } +}; + +export const multiPutFetch = async (url: string, body: FormData) => { + try { + await silentLogin(); + const response = await fetch(url, { + method: 'PUT', + body, + headers: makeFetchMultiHeaders(), + }); + + if (!response.ok) { + const errorMessage = await response.text(); + throw new Error(errorMessage); + } + } catch (e) { + const error = e as Error; + throw new Error(error.message); + } +}; diff --git a/frontend/src/utils/getPathFragment.ts b/frontend/src/utils/getPathFragment.ts new file mode 100644 index 000000000..725e6a98f --- /dev/null +++ b/frontend/src/utils/getPathFragment.ts @@ -0,0 +1,13 @@ +export const getPathFragment = (url: string) => { + const pathList = url.split('/'); + + const lastIndex = pathList.length - 1; + + if (Number(pathList[lastIndex]) > 0) { + pathList.pop(); + + return pathList.join('/'); + } + + return url; +}; diff --git a/frontend/src/utils/getTrimmedWord.ts b/frontend/src/utils/getTrimmedWord.ts new file mode 100644 index 000000000..94b159c73 --- /dev/null +++ b/frontend/src/utils/getTrimmedWord.ts @@ -0,0 +1,7 @@ +export const getTrimmedWord = (word: string) => { + return word + .split(' ') + .map(word => word.trim()) + .filter(word => word !== '') + .join(' '); +}; diff --git a/frontend/src/utils/localStorage.ts b/frontend/src/utils/localStorage.ts new file mode 100644 index 000000000..60e07f535 --- /dev/null +++ b/frontend/src/utils/localStorage.ts @@ -0,0 +1,11 @@ +export const getLocalStorage = (key: string): T | null => { + return JSON.parse(localStorage.getItem(key) || 'null'); +}; + +export const setLocalStorage = (key: string, value: T) => { + localStorage.setItem(key, JSON.stringify(value)); +}; + +export const removeLocalStorage = (key: string) => { + localStorage.removeItem(key); +}; diff --git a/frontend/src/utils/post/calculateDeadlineTime.ts b/frontend/src/utils/post/calculateDeadlineTime.ts new file mode 100644 index 000000000..ed82ae35f --- /dev/null +++ b/frontend/src/utils/post/calculateDeadlineTime.ts @@ -0,0 +1,32 @@ +export const calculateDeadlineTime = (createdAt?: string, deadLine?: string) => { + if (!createdAt || !deadLine) { + return { + day: 0, + hour: 0, + minute: 0, + }; + } + + const createTimeNumber = new Date(createdAt); + + const deadlineTimeNumber = new Date(deadLine); + + const timeDifference = Number(deadlineTimeNumber) - Number(createTimeNumber); + + const minuteUnit = 60_000; + const hourUnit = minuteUnit * 60; + const dayUnit = hourUnit * 24; + + const hourTimeInMilliseconds = timeDifference % dayUnit; + const minuteTimeInMilliseconds = hourTimeInMilliseconds % hourUnit; + + const day = Math.floor(timeDifference / dayUnit); + const hour = Math.floor(hourTimeInMilliseconds / hourUnit); + const minute = Math.floor(minuteTimeInMilliseconds / minuteUnit); + + return { + day, + hour, + minute, + }; +}; diff --git a/frontend/src/utils/post/changeCategoryToOption.ts b/frontend/src/utils/post/changeCategoryToOption.ts new file mode 100644 index 000000000..54fd1cb00 --- /dev/null +++ b/frontend/src/utils/post/changeCategoryToOption.ts @@ -0,0 +1,7 @@ +import { Category } from '@type/category'; + +import { Option } from '@components/common/MultiSelect/types'; + +export const changeCategoryToOption = (categoryList: Category[]): Option[] => { + return categoryList.map(category => ({ id: category.id, name: category.name })); +}; diff --git a/frontend/src/utils/post/checkWriter.ts b/frontend/src/utils/post/checkWriter.ts new file mode 100644 index 000000000..7d29b91ae --- /dev/null +++ b/frontend/src/utils/post/checkWriter.ts @@ -0,0 +1,16 @@ +import { ACCESS_TOKEN_KEY } from '@constants/localStorage'; + +import { getLocalStorage } from '@utils/localStorage'; +import { decodeToken } from '@utils/token/decodeToken'; + +export function checkWriter(writerId: number) { + const accessToken = getLocalStorage(ACCESS_TOKEN_KEY); + + if (!accessToken) { + return false; + } + + const memberId = decodeToken(accessToken).memberId; + + return writerId === memberId; +} diff --git a/frontend/src/utils/post/convertImageUrlToServerUrl.ts b/frontend/src/utils/post/convertImageUrlToServerUrl.ts new file mode 100644 index 000000000..ad991319e --- /dev/null +++ b/frontend/src/utils/post/convertImageUrlToServerUrl.ts @@ -0,0 +1,9 @@ +import { IMAGE_BASE_URL } from '@constants/post'; + +export const convertImageUrlToServerUrl = (imageUrl: string) => { + return `${IMAGE_BASE_URL}${imageUrl}`; +}; + +export const convertServerUrlToImageUrl = (imageUrl: string) => { + return imageUrl.replace(IMAGE_BASE_URL, ''); +}; diff --git a/frontend/src/utils/post/formatTime.ts b/frontend/src/utils/post/formatTime.ts new file mode 100644 index 000000000..8353817c7 --- /dev/null +++ b/frontend/src/utils/post/formatTime.ts @@ -0,0 +1,32 @@ +interface Time { + day: number; + hour: number; + minute: number; +} + +export function addTimeToDate(addTime: Time, baseTime: Date) { + const { day, hour, minute } = addTime; + if (day === 0 && hour === 0 && minute === 0) return; + + const newTime = new Date(baseTime); + + newTime.setDate(baseTime.getDate() + day); + newTime.setHours(baseTime.getHours() + hour); + newTime.setMinutes(baseTime.getMinutes() + minute); + + const newYear = newTime.getFullYear(); + const newDay = String(newTime.getDate()).padStart(2, '0'); + const newMonth = String(newTime.getMonth() + 1).padStart(2, '0'); + const newHour = String(newTime.getHours()).padStart(2, '0'); + const newMinute = String(newTime.getMinutes()).padStart(2, '0'); + + return `${newYear}-${newMonth}-${newDay} ${newHour}:${newMinute}`; +} + +export function formatTimeWithOption(option: string) { + if (option === '10분') return { day: 0, hour: 0, minute: 10 }; + else if (option === '30분') return { day: 0, hour: 0, minute: 30 }; + else if (option === '1시간') return { day: 0, hour: 1, minute: 0 }; + else if (option === '6시간') return { day: 0, hour: 6, minute: 0 }; + else return { day: 1, hour: 0, minute: 0 }; +} diff --git a/frontend/src/utils/post/getDeadlineTime.ts b/frontend/src/utils/post/getDeadlineTime.ts new file mode 100644 index 000000000..4952300ff --- /dev/null +++ b/frontend/src/utils/post/getDeadlineTime.ts @@ -0,0 +1,33 @@ +export const getDeadlineTime = ({ + day, + hour, + minute, +}: { + day: number; + hour: number; + minute: number; +}) => { + const timeMessage = []; + + if (day < 0 || hour < 0 || minute < 0) { + return '마감 시간을 다시 설정해주세요'; + } + + if (day === 0 && hour === 0 && minute === 0) { + return '마감 시간을 선택해주세요'; + } + + if (day > 0) { + timeMessage.push(`${day}일`); + } + + if (hour > 0) { + timeMessage.push(`${hour}시간`); + } + + if (minute > 0) { + timeMessage.push(`${minute}분`); + } + + return `${timeMessage.join(' ')} 후에 마감됩니다.`; +}; diff --git a/frontend/src/utils/post/getSelectedState.ts b/frontend/src/utils/post/getSelectedState.ts new file mode 100644 index 000000000..dae930607 --- /dev/null +++ b/frontend/src/utils/post/getSelectedState.ts @@ -0,0 +1,27 @@ +import { Category } from '@type/category'; + +import { PostRequestKind } from '@components/post/PostListPage/types'; + +export interface SelectedState { + postType: PostRequestKind; + categoryId: number; + categoryList: Category[]; +} + +export const getSelectedState = ({ postType, categoryId, categoryList }: SelectedState) => { + if (postType === 'category') { + const selectedCategory = categoryList.find(category => category.id === categoryId); + + return selectedCategory?.name ?? '전체'; + } + + if (postType === 'myPost') { + return '내가 작성한 글'; + } + + if (postType === 'myVote') { + return '내가 투표한 글'; + } + + return '전체'; +}; diff --git a/frontend/src/utils/post/getSelectedTimeOption.ts b/frontend/src/utils/post/getSelectedTimeOption.ts new file mode 100644 index 000000000..6729b144c --- /dev/null +++ b/frontend/src/utils/post/getSelectedTimeOption.ts @@ -0,0 +1,20 @@ +import { DeadlineOption } from '@components/PostForm/constants'; + +export const getSelectedTimeOption = ({ + day, + hour, + minute, +}: { + day: number; + hour: number; + minute: number; +}): DeadlineOption | '사용자지정' | null => { + if (day === 0 && hour === 0 && minute === 0) return null; + if (day === 0 && hour === 0 && minute === 10) return '10분'; + if (day === 0 && hour === 0 && minute === 30) return '30분'; + if (day === 0 && hour === 1 && minute === 0) return '1시간'; + if (day === 0 && hour === 6 && minute === 0) return '6시간'; + if (day === 1 && hour === 0 && minute === 0) return '1일'; + + return '사용자지정'; +}; diff --git a/frontend/src/utils/scrollToTop.ts b/frontend/src/utils/scrollToTop.ts new file mode 100644 index 000000000..c60a65322 --- /dev/null +++ b/frontend/src/utils/scrollToTop.ts @@ -0,0 +1,7 @@ +export const smoothScrollToTop = () => { + window.scroll({ top: 0, behavior: 'smooth' }); +}; + +export const defaultScrollToTop = () => { + window.scrollTo(0, 0); +}; diff --git a/frontend/src/utils/time.ts b/frontend/src/utils/time.ts new file mode 100644 index 000000000..6be18c6b3 --- /dev/null +++ b/frontend/src/utils/time.ts @@ -0,0 +1,79 @@ +import { addTimeToDate } from './post/formatTime'; + +const convertNowTimeToNumber = () => { + const now = new Date(); + + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + + return Number(`${year}${month}${day}${hours}${minutes}`); +}; + +const convertTimeFromStringToNumber = (date: string) => { + const dateComponents = date.split(' '); + const datePieces = dateComponents[0].split('-'); + const timePieces = dateComponents[1].split(':'); + return Number([...datePieces, ...timePieces].join('')); +}; + +export const checkClosedPost = (deadline: string) => { + const nowTimeNumber = convertNowTimeToNumber(); + const endTimeNumber = convertTimeFromStringToNumber(deadline); + return nowTimeNumber >= endTimeNumber; +}; + +type TimeType = 'day' | 'hour' | 'minute'; + +//시간 수정을 할 수 없다면 true +export const checkIrreplaceableTime = (addTime: Record, createTime: string) => { + const changedDeadline = addTimeToDate(addTime, new Date(createTime)); + // changedDeadline가 undefined인 경우는 작성일시에서 시간이 더해지지 않았을 경우라 거절 + if (!changedDeadline) return true; + + const limitDeadline = addTimeToDate({ day: 3, hour: 0, minute: 0 }, new Date(createTime))!; + const changedDeadlineNumber = convertTimeFromStringToNumber(changedDeadline); + const limitDeadlineNumber = convertTimeFromStringToNumber(limitDeadline); + + //작성일시로부터 3일된 일시보다 지정하고자 하는 일시가 크다면 거절 + if (changedDeadlineNumber >= limitDeadlineNumber) return true; + + //지금 일시보다 지정하고자 하는 일시가 작다면 거절 + return changedDeadlineNumber <= convertNowTimeToNumber(); +}; + +const time = { + day: 3, + hour: 24, + minute: 60, +}; + +export const convertTimeToWord = (date: string) => { + const targetDate = new Date(date); + const currentDate = new Date(); + + //분 단위로 산출됨 + const timeDifference = Math.floor((targetDate.getTime() - currentDate.getTime()) / 60000); + + if (timeDifference === 0) return '지금'; + + const afterBefore = timeDifference > 0 ? '후 마감' : '전 작성 |'; + + const positiveTimeDifference = Math.abs(timeDifference); + + if (Math.round(positiveTimeDifference / (time.hour * time.minute)) > 0) + return `${Math.round(positiveTimeDifference / (time.hour * time.minute))}일 ${afterBefore}`; + + if (Math.round(positiveTimeDifference / time.minute) > 0) + return `${Math.round(positiveTimeDifference / time.minute)}시간 ${afterBefore}`; + + return `${positiveTimeDifference}분 ${afterBefore}`; +}; + +export const convertDayToSecond = (days: number) => { + const secondsPerDay = 24 * 60 * 60; + + return secondsPerDay * days; +}; diff --git a/frontend/src/utils/token/decodeToken.ts b/frontend/src/utils/token/decodeToken.ts new file mode 100644 index 000000000..fccb2dfb6 --- /dev/null +++ b/frontend/src/utils/token/decodeToken.ts @@ -0,0 +1,9 @@ +import { AccessToken } from '@type/token'; + +export const decodeToken = (token: string): AccessToken => { + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const decodedData = JSON.parse(atob(base64)); + + return decodedData; +}; diff --git a/frontend/src/utils/token/isExpiredAccessToken.ts b/frontend/src/utils/token/isExpiredAccessToken.ts new file mode 100644 index 000000000..4b3c83cae --- /dev/null +++ b/frontend/src/utils/token/isExpiredAccessToken.ts @@ -0,0 +1,13 @@ +import { AccessToken } from '@type/token'; + +export const isExpiredAccessToken = ({ + decodedToken, + currentTime, +}: { + decodedToken: AccessToken; + currentTime: number; +}) => { + const accessTokenExpirationPeriod = decodedToken.exp; + + return currentTime >= accessTokenExpirationPeriod; +}; diff --git a/frontend/src/utils/token/isExpiredRefreshToken.ts b/frontend/src/utils/token/isExpiredRefreshToken.ts new file mode 100644 index 000000000..06af8ca29 --- /dev/null +++ b/frontend/src/utils/token/isExpiredRefreshToken.ts @@ -0,0 +1,17 @@ +import { AccessToken } from '@type/token'; + +import { REFRESH_EXPIRATION_TIME } from '@constants/token'; + +export const isExpiredRefreshToken = ({ + decodedToken, + currentTime, +}: { + decodedToken: AccessToken; + currentTime: number; +}) => { + const issuedTime = decodedToken.iat; + + const refreshTokenExpirationPeriod = issuedTime + REFRESH_EXPIRATION_TIME; + + return currentTime > refreshTokenExpirationPeriod; +}; diff --git a/frontend/src/utils/token/isRefreshTokenRequested.ts b/frontend/src/utils/token/isRefreshTokenRequested.ts new file mode 100644 index 000000000..97bc56bc3 --- /dev/null +++ b/frontend/src/utils/token/isRefreshTokenRequested.ts @@ -0,0 +1,30 @@ +import { ACCESS_TOKEN_KEY } from '@constants/localStorage'; + +import { getLocalStorage, removeLocalStorage } from '@utils/localStorage'; + +import { decodeToken } from './decodeToken'; +import { isExpiredAccessToken } from './isExpiredAccessToken'; +import { isExpiredRefreshToken } from './isExpiredRefreshToken'; + +export const isRefreshTokenRequested = () => { + const accessToken = getLocalStorage(ACCESS_TOKEN_KEY); + + const isGuest = !accessToken; + + if (isGuest) return false; + + const decodedToken = decodeToken(accessToken); + const currentTime = Math.floor(new Date().getTime() / 1000); + + if (!isExpiredAccessToken({ decodedToken, currentTime })) { + return false; + } + + if (isExpiredRefreshToken({ decodedToken, currentTime })) { + removeLocalStorage(ACCESS_TOKEN_KEY); + + return false; + } + + return true; +}; diff --git a/frontend/src/utils/token/silentLogin.ts b/frontend/src/utils/token/silentLogin.ts new file mode 100644 index 000000000..5d4b88bca --- /dev/null +++ b/frontend/src/utils/token/silentLogin.ts @@ -0,0 +1,30 @@ +import { postTokens } from '@api/token'; + +import { ACCESS_TOKEN_KEY } from '@constants/localStorage'; + +import { getLocalStorage, removeLocalStorage, setLocalStorage } from '../localStorage'; + +import { isRefreshTokenRequested } from './isRefreshTokenRequested'; + +export const silentLogin = async () => { + if (!isRefreshTokenRequested()) { + return; + } + + try { + const accessToken = getLocalStorage(ACCESS_TOKEN_KEY); + + if (!accessToken) return; + + const tokenData = await postTokens(accessToken); + + const updatedAccessToken = tokenData.accessToken; + + setLocalStorage(ACCESS_TOKEN_KEY, updatedAccessToken); + } catch (error) { + removeLocalStorage(ACCESS_TOKEN_KEY); + window.location.href = '/login'; + + throw new Error('로그인에 실패했습니다. 다시 로그인 해주세요.'); + } +}; diff --git a/frontend/styled-components.d.ts b/frontend/styled-components.d.ts new file mode 100644 index 000000000..a1fe63a63 --- /dev/null +++ b/frontend/styled-components.d.ts @@ -0,0 +1,10 @@ +import { Animation, Breakpoint, ZIndex } from '@styles/theme'; +import 'styled-components'; + +declare module 'styled-components' { + export interface DefaultTheme { + breakpoint: Breakpoint; + zIndex: ZIndex; + animation: Animation; + } +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 000000000..63e5fb99d --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,45 @@ +{ + "compilerOptions": { + "target": "es2021", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "noEmit": false, + "baseUrl": "src", + "paths": { + "@assets/*": ["assets/*"], + "@pages/*": ["pages/*"], + "@components/*": ["components/*"], + "@hooks/*": ["hooks/*"], + "@styles/*": ["styles/*"], + "@utils/*": ["utils/*"], + "@constants/*": ["constants/*"], + "@type/*": ["types/*"], + "@atoms/*": ["atoms/*"], + "@selectors/*": ["selectors/*"], + "@routes/*": ["routes/*"], + "@api/*": ["api/*"], + "@mocks/*": ["mocks/*"] + }, + "outDir": "./dist" + }, + "include": [ + "src", + "src/custom.d.ts", + "__test__", + "styled-components.d.ts", + "env.d.ts", + "window.d.ts" + ], + "exclude": ["node_modules"] +} diff --git a/frontend/webpack.common.js b/frontend/webpack.common.js new file mode 100644 index 000000000..7a366dacd --- /dev/null +++ b/frontend/webpack.common.js @@ -0,0 +1,94 @@ +const path = require('path'); + +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const CopyPlugin = require('copy-webpack-plugin'); +const DotenvWebpack = require('dotenv-webpack'); +const { EsbuildPlugin } = require('esbuild-loader'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + mode: 'development', + entry: './src/index.tsx', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist'), + clean: true, + publicPath: '/', + }, + resolve: { + extensions: ['.ts', '.tsx', '.js', '.jsx'], + alias: { + '@assets': path.resolve(__dirname, 'src/assets'), + '@pages': path.resolve(__dirname, 'src/pages'), + '@components': path.resolve(__dirname, 'src/components'), + '@hooks': path.resolve(__dirname, 'src/hooks'), + '@styles': path.resolve(__dirname, 'src/styles'), + '@utils': path.resolve(__dirname, 'src/utils'), + '@constants': path.resolve(__dirname, 'src/constants'), + '@type': path.resolve(__dirname, 'src/types'), + '@atoms': path.resolve(__dirname, 'src/atoms'), + '@selectors': path.resolve(__dirname, 'src/selectors'), + '@routes': path.resolve(__dirname, 'src/routes'), + '@api': path.resolve(__dirname, 'src/api'), + '@mocks': path.resolve(__dirname, 'src/mocks'), + }, + }, + module: { + rules: [ + { + test: /\.(js|jsx|ts|tsx)$/i, + exclude: /node_modules/, + loader: 'esbuild-loader', + options: { + target: 'es2021', + }, + }, + { + test: /\.css$/i, + use: [ + 'style-loader', + 'css-loader', + { + loader: 'esbuild-loader', + options: { + minify: true, + }, + }, + ], + }, + { + test: /\.svg/, + type: 'asset/inline', + }, + { + test: /\.(png|jpg|jpeg|gif)$/i, + type: 'asset/resource', + }, + ], + }, + plugins: [ + new HtmlWebpackPlugin({ + template: './public/index.html', + }), + new CleanWebpackPlugin(), + new DotenvWebpack(), + new CopyPlugin({ + patterns: [{ from: 'public/icons', to: 'icons' }], + }), + new ForkTsCheckerWebpackPlugin(), + ], + devtool: 'inline-source-map', + devServer: { + static: 'public', + hot: true, + open: true, + }, + optimization: { + minimizer: [ + new EsbuildPlugin({ + target: 'es2021', + }), + ], + }, +}; diff --git a/frontend/webpack.dev.js b/frontend/webpack.dev.js new file mode 100644 index 000000000..7cbf8c7a1 --- /dev/null +++ b/frontend/webpack.dev.js @@ -0,0 +1,13 @@ +const { merge } = require('webpack-merge'); + +const common = require('./webpack.common.js'); + +module.exports = merge(common, { + mode: 'development', // 현재 개발 모드 + devtool: 'eval', // 최대성능, 개발환경에 추천 + devServer: { + historyApiFallback: true, + port: 3000, + hot: true, + }, +}); diff --git a/frontend/webpack.prod.js b/frontend/webpack.prod.js new file mode 100644 index 000000000..41e6e21b6 --- /dev/null +++ b/frontend/webpack.prod.js @@ -0,0 +1,8 @@ +const { merge } = require('webpack-merge'); + +const common = require('./webpack.common.js'); + +module.exports = merge(common, { + mode: 'production', // 현재 배포 모드 + devtool: 'hidden-source-map', // 느리지만 안전 배포에 추천 +}); diff --git a/frontend/window.d.ts b/frontend/window.d.ts new file mode 100644 index 000000000..284bd9cce --- /dev/null +++ b/frontend/window.d.ts @@ -0,0 +1,14 @@ +export interface BeforeInstallPromptEvent extends Event { + readonly platforms: string[]; + readonly userChoice: Promise<{ + outcome: 'accepted' | 'dismissed'; + platform: string; + }>; + prompt(): Promise; +} + +declare global { + interface WindowEventMap { + beforeinstallprompt: BeforeInstallPromptEvent; + } +}