diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..f6527615 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,22 @@ +## feat: ์ œ๋ชฉ + +### ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป ์ž‘์—…ํ•œ ๋‚ด์šฉ + +- ์–ด์ฉŒ๊ตฌ ์ €์ฉŒ๊ตฌ + +### ๐Ÿ’ก ์ฐธ๊ณ  ์‚ฌํ•ญ + +- ๊ฐœ๋ฐœํ•˜๊ธด ํ–ˆ๋Š” ๋ฐ ์ข€ ์ฐ์ฐํ•˜๊ฑฐ๋‚˜ ๊ถ๊ธˆํ–ˆ๋˜๊ฒƒ๋“ค +- ์ค‘์ ์ ์œผ๋กœ ๋ด์คฌ์œผ๋ฉด ํ•˜๋Š” ๋ถ€๋ถ„ + +### ๐Ÿ”— ์ฐธ๊ณ  ๋งํฌ + +- figma +- stackoverflow +- ๋ฏธํŒ… ๋…ธํŠธ(notion) + +--- + +### ๐Ÿ’ฌ Commits + +((commitLogs)) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..d5a3dd35 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,61 @@ +name: Auto PR to dev + +on: + push: + branches: + - feature-* + - fix + +concurrency: + group: auto-pr-${{ github.ref_name }} + cancel-in-progress: true + +jobs: + create-pr: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + env: + BRANCH_NAME: ${{ github.ref_name }} + GH_TOKEN: ${{ secrets.MOYEORAIT_TOKEN }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.ref_name }} + token: ${{ secrets.MOYEORAIT_TOKEN }} + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Run Lint + run: yarn lint + + - name: Run Format + run: npx prettier --write . + + - name: Create Pull Request + if: success() + run: | + echo "๐Ÿ” Checking PR from $BRANCH_NAME to dev" + PR_EXISTS=$(gh pr list --base dev --head "$BRANCH_NAME" --state open --json number --jq 'length') + + COMMIT_LOGS=$(git log --pretty=format:'- %s' --no-merges origin/dev..$BRANCH_NAME | sed -E 's/^(.{100}).*/\1.../') + PR_TEMPLATE=$(cat .github/pull_request_template.md | sed 's/((commitLogs))/$COMMIT_LOGS/g') + export COMMIT_LOGS + PR_BODY=$(echo "$PR_TEMPLATE" | envsubst) + + if [ "$PR_EXISTS" -eq 0 ]; then + gh pr create \ + --base dev \ + --head "$BRANCH_NAME" \ + --title "์ž๋™ PR: $BRANCH_NAME โ†’ dev" \ + --body "$PR_BODY" \ + --assignee "$GITHUB_ACTOR" + else + PR_NUMBER=$(gh pr list --base dev --head "$BRANCH_NAME" --state open --json number --jq '.[0].number') + gh pr comment "$PR_NUMBER" --body "์ƒˆ๋กœ์šด ์ปค๋ฐ‹์ด ํ‘ธ์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค: $(git log -1 --pretty=%s)" + gh pr edit "$PR_NUMBER" --add-label "auto-updated" + fi diff --git a/.github/workflows/deploy.dev.yaml b/.github/workflows/deploy.dev.yaml new file mode 100644 index 00000000..c4412821 --- /dev/null +++ b/.github/workflows/deploy.dev.yaml @@ -0,0 +1,66 @@ +name: Dev deploy to EC2 + +on: + push: + branches: + - dev + - feature-ci-cd + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + # === [1] ํ˜„์žฌ ๋Ÿฌ๋„ˆ IP๋ฅผ ๋ณด์•ˆ ๊ทธ๋ฃน์— ์ถ”๊ฐ€ === + - name: Whitelist GitHub Actions IP + uses: bbharathkumarreddy/aws-whitelist-ip@v1.0 + with: + security-group-id: ${{ secrets.AWS_SECURITY_GROUP_ID }} + action: whitelist + port: 22 + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 # ์„œ์šธ ๋ฆฌ์ „ + + - name: SSH into EC2 and deploy + uses: appleboy/ssh-action@v1.0.0 + timeout-minutes: 30 + + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_KEY }} + script: | + export PATH=$PATH:/home/ubuntu/.nvm/versions/node/v22.16.0/bin + cd /home/ubuntu/projects/moyeora-it-FE + + echo "Pulling latest code from branch: ${{ github.ref_name }}" + git fetch origin + git checkout ${{ github.ref_name }} + git reset --hard origin/${{ github.ref_name }} + git pull origin ${{ github.ref_name }} + + pnpm install --frozen-lockfile + pnpm build + + echo "Restarting PM2" + pm2 restart ecosystem.config.js --env development + pm2 describe ecosystem.config.js > /dev/null 2>&1 + if [ $? -ne 0 ]; then + pm2 start ecosystem.config.js --env development + else + pm2 reload ecosystem.config.js --env development + fi + + # === [2] ๋ฐฐํฌ ํ›„ ๋Ÿฌ๋„ˆ IP๋ฅผ ๋ณด์•ˆ ๊ทธ๋ฃน์—์„œ ์ œ๊ฑฐ === + - name: Remove GitHub Actions IP + uses: bbharathkumarreddy/aws-whitelist-ip@v1.0 + with: + security-group-id: ${{ secrets.AWS_SECURITY_GROUP_ID }} + action: remove + port: 22 + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8cf067e4..010ebaa5 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +/certificates/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..5c3907cd --- /dev/null +++ b/.prettierignore @@ -0,0 +1,12 @@ +.github/ +node_modules/ +dist/ +build/ +README.md +components.json +postcss.config.mjs +tailwind.config.ts +tsconfig.json +tsconfig.node.json +public/ +next.config.ts \ No newline at end of file diff --git a/prettierrc b/.prettierrc similarity index 89% rename from prettierrc rename to .prettierrc index bbe99001..2a802e80 100644 --- a/prettierrc +++ b/.prettierrc @@ -1,5 +1,4 @@ { - "useTabs": true, "tabWidth": 2, "printWidth": 80, "trailingComma": "all", diff --git a/README.md b/README.md index 5ec1fb75..0d4d67a5 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,226 @@ -# ๋ชจ์—ฌ๋ผIT -## ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ +## ๐Ÿ“Œย ํ”„๋กœ์ ํŠธ ๊ฐœ์š” -### ์‹œ์ž‘ํ•˜๊ธฐ +๐Ÿ”—ย https://my.sjcpop.com/ -```bash +**๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ์Šคํ„ฐ๋””, ํ”„๋กœ์ ํŠธ ๋ชจ์ง‘ ์„œ๋น„์Šค** +๐Ÿ˜ตโ€๐Ÿ’ซ ์Šคํ„ฐ๋”” ๊ตฌํ•˜๋ ค๋‹ค๊ฐ€ ์˜คํ”ˆ์นดํ†ก ํƒ€๊ณ , ๋””์Šค์ฝ”๋“œ ๋“ค์–ด๊ฐ€๊ณ โ€ฆ ์‹œ์ž‘๋„ ์ „์— ์ง€์ณ๋ณด์‹  ์  ์žˆ์œผ์‹ ๊ฐ€์š”? -yarn dev +๐Ÿค” ์‹ ์ฒญ์€ ์™”๋Š”๋ฐ ๋‚ด๊ฐ€ ์ฐพ๋Š” ์‹ ์ฒญ์ž๊ฐ€ ์•„๋‹ˆ๋ผ์„œ ๋‚œ๊ฐํ–ˆ๋˜ ๊ฒฝํ—˜๋„ ํ•œ ๋ฒˆ์ฏค ์žˆ์œผ์…จ์ฃ ? +**๋ชจ์—ฌ๋ผIT**์€ โ€˜์ฐธ์—ฌํ•˜๊ธฐโ€™ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์ด๋ฉด ์‹ ์ฒญ ๋! ๐Ÿ™Œ + +์›ํ•˜๋Š” **๊ธฐ์ˆ  ์Šคํƒ์ด๋‚˜ ํฌ์ง€์…˜**์ด ๋ชจ์ž„์— ํ‘œ์‹œ๋˜์–ด ์žˆ์œผ๋‹ˆ๊นŒ ๋”ฑ ๋งž๋Š” ์‚ฌ๋žŒ์„ ์ฐพ๊ธฐ ์‰ฌ์›Œ์š” ๐Ÿ” + +์‹ ์ฒญ์ž์˜ ํ”„๋กœํ•„๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ , ๊ถ๊ธˆํ•œ ์ ์€ ๋Œ“๊ธ€๋กœ ๊ฐ€๋ณ๊ฒŒ ์†Œํ†ตํ•  ์ˆ˜๋„ ์žˆ์–ด์š” ๐Ÿ’ฌ๐Ÿ˜Š + +แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2025-06-23 แ„‹แ…ฉแ„’แ…ฎ 6 50 55 + +### ๐Ÿ”„ย ์„œ๋น„์Šค ํ๋ฆ„๋„ +[๐Ÿ”— ํ”Œ๋กœ์šฐ์ฐจํŠธ](https://www.figma.com/board/gLO4BefKt2EhU4QqC5VIIh/%EB%AA%A8%EC%97%AC%EB%9D%BC%EC%9E%87-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%B0%A8%ED%8A%B8?node-id=0-1&t=e68OLJ3BUTsbYdLM-1) + +
+ +## ๐Ÿš€ย ์„ค์น˜ ๋ฐ ์‹คํ–‰ + + + +```jsx +$ yarn install +$ yarn dev ``` -- ๋ฐฐํฌ url -- test id/password +### 1. ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์ฟ ํ‚ค ์‚ฌ์šฉ์„ ์œ„ํ•œ ์„ค์ • + +`SameSite=None` ๋ฐ `Secure=true` ์ฟ ํ‚ค๋ฅผ ์ •์ƒ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ™˜๊ฒฝ ๊ตฌ์„ฑ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +### โœ… `SameSite=None` ๋Œ€์‘ โ€“ ์ปค์Šคํ…€ ๋„๋ฉ”์ธ ์„ค์ • + +`SameSite=None` ์†์„ฑ์ด ์„ค์ •๋œ ์ฟ ํ‚ค๋Š” **๋„๋ฉ”์ธ์ด ๋‹ค๋ฅผ ๊ฒฝ์šฐ**์—๋„ ์ „์†ก๋˜๋ฏ€๋กœ, ๋กœ์ปฌ์—์„œ๋„ ์‹ค์ œ ๋ฐฐํฌ ๋„๋ฉ”์ธ(`sjcpop.com`)๊ณผ ์œ ์‚ฌํ•œ ๋„๋ฉ”์ธ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +1. `/etc/hosts` ํŒŒ์ผ ์ˆ˜์ •: + + ```bash + $ sudo vi /etc/hosts + ``` + +2. ๋‹ค์Œ ๋ผ์ธ์„ ์ถ”๊ฐ€: + + ```bash + 127.0.0.1 local.sjcpop.com + ``` + + +### โœ… `Secure=true` ๋Œ€์‘ โ€“ HTTPS ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ -## ์•„ํ‚คํ…์ฒ˜ +`Secure=true`๊ฐ€ ์„ค์ •๋œ ์ฟ ํ‚ค๋Š” **HTTPS ํ™˜๊ฒฝ์—์„œ๋งŒ** ์ „์†ก๋ฉ๋‹ˆ๋‹ค. ๋กœ์ปฌ ๊ฐœ๋ฐœ ์„œ๋ฒ„๋„ HTTPS๋กœ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. -**ํ™”๋ฉด ๊ตฌ์„ฑ/API** +1. Next.js๋ฅผ HTTPS๋กœ ์‹คํ–‰: + + ```bash + $ yarn install + $ yarn dev // => next dev --experimental-https + ``` + +2. ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ ‘์†: https://local.sjcpop.com:3000 + +์œ„ ์„ค์ •์„ ํ†ตํ•ด ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ๋„ ์‹ค์ œ ๋ฐฐํฌ ํ™˜๊ฒฝ์ฒ˜๋Ÿผ ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์ธ์ฆ๊ณผ API ์š”์ฒญ์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### 1-2. ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ • + +`.env.local` ํŒŒ์ผ ์ƒ์„ฑ ํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. + +```jsx +NEXT_PUBLIC_API_BASE_URL=https://my-api.sjcpop.com/api +ACCESS_TOKEN=accessToken +REFRESH_TOKEN=refreshToken +``` + +### 1-1. ์‚ฌ์šฉ ID, PW + +- ID +- Password + +## **์‚ฌ์šฉ ๊ธฐ์ˆ ** + + + + +
+ +## ๐Ÿ“ย ์•„ํ‚คํ…์ฒ˜ + +### 1. ํด๋” ๊ตฌ์กฐ + +```jsx +๐Ÿ“ฆsrc + โ”ฃ ๐Ÿ“‚__mocks__ + โ”ฃ ๐Ÿ“‚actions + โ”ฃ ๐Ÿ“‚api + โ”ฃ ๐Ÿ“‚app + โ”ƒ โ”ฃ ๐Ÿ“‚bookmark + โ”ฃ ๐Ÿ“‚components + โ”ƒ โ”ฃ ๐Ÿ“‚atoms + โ”ƒ โ”ฃ ๐Ÿ“‚error-boundary + โ”ƒ โ”ฃ ๐Ÿ“‚error-fallback + โ”ƒ โ”ฃ ๐Ÿ“‚molecules + โ”ƒ โ”ฃ ๐Ÿ“‚organisms + โ”ƒ โ”— ๐Ÿ“‚ui + โ”ฃ ๐Ÿ“‚features + โ”ฃ ๐Ÿ“‚hooks + โ”ฃ ๐Ÿ“‚lib + โ”ฃ ๐Ÿ“‚mocks + โ”ƒ โ”— ๐Ÿ“‚handler + โ”ฃ ๐Ÿ“‚providers + โ”ฃ ๐Ÿ“‚stores + โ”ฃ ๐Ÿ“‚types + โ”— ๐Ÿ“‚utils +``` -- ํŒŒ์ผ ๊ตฌ์กฐ๋„ +### 2. ์ปดํฌ๋„ŒํŠธ ์„ค๊ณ„ + +`components/` ๋””๋ ‰ํ† ๋ฆฌ๋Š” **Atomic Design** ํŒจํ„ด์— ๋”ฐ๋ผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค: + +- `ui/` : Shadcn UI ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์„ค์น˜๋˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ +- `atoms/` : ๋” ์ด์ƒ ๋ถ„ํ•ดํ•  ์ˆ˜ ์—†๋Š” ์ตœ์†Œ ๋‹จ์œ„ ์ปดํฌ๋„ŒํŠธ +- `molecules/` : atoms๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ๋‹จ์ผ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ +- `organisms/` : atoms + molecules ์กฐํ•ฉ, ๋…๋ฆฝ์ ์ธ UI ์˜์—ญ ๊ตฌ์„ฑ + +`features/` ๋””๋ ‰ํ† ๋ฆฌ๋Š” ๊ธฐ๋Šฅ ๋‹จ์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜๋ ค ๊ด€๋ จ ๋กœ์ง์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. + +`mocks/` ๋””๋ ‰ํ† ๋ฆฌ๋Š” API ์„œ๋ฒ„ ์—†์ด๋„ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ ๋ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก MSW๋ฅผ ํ™œ์šฉํ•ด ๊ฐ€์งœ API๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค + +### 3. ์ƒํƒœ ๊ด€๋ฆฌ + +- **Zustand**๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „์—ญ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +- ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ ๋ฐ ๋ฐ์ดํ„ฐ ํŒจ์นญ์€ **Tanstack Query**๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์บ์‹ฑ ๋ฐ ๋น„๋™๊ธฐ ์ƒํƒœ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. + +### 4. API ํ†ต์‹  ๊ตฌ์กฐ + +- `fetch`๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๋ฉฐ, ๊ณตํ†ต ์š”์ฒญ ๋กœ์ง์€ `request.ts`์— ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. + +### 5. UI ๋””์ž์ธ ์‹œ์Šคํ…œ + +- **Shadcn UI**์™€ **Tailwind CSS**๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๊ด€๋œ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +- Shadcn ์ปดํฌ๋„ŒํŠธ๋Š” `components/ui/` ํด๋”์— ์„ค์น˜๋˜๋ฉฐ, Tailwind ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•ฉ๋‹ˆ๋‹ค. + +### 6. ํ…Œ์ŠคํŠธ ๊ตฌ์กฐ + +- API ์„œ๋ฒ„๊ฐ€ ์ค€๋น„๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” **MSW(Mock Service Worker)**๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชฉ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐœ๋ฐœ ๋ฐ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค. +- ๊ฐ ํŽ˜์ด์ง€ ๋ผ์šฐํŠธ ํ•˜์œ„์— `__test__/` ํด๋”๋ฅผ ๋‘๊ณ  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + + ์˜ˆ: `app/group/[id]/__test__/` + + ```bash + $ yarn test + ``` + +- E2E ํ…Œ์ŠคํŠธ๋Š” `cypress` ๋ฅผ ํ†ตํ•ด ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค. + + ```bash + $ yarn cypress:open + ``` + + +### 7. CI/CD + +- **GitHub Actions**๋ฅผ ์ด์šฉํ•˜์—ฌ CI(ํ…Œ์ŠคํŠธ/๋นŒ๋“œ) ๋ฐ ๋ฐฐํฌ๋ฅผ ์ž๋™ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +- `dev` ๋ธŒ๋žœ์น˜์— ํ‘ธ์‹œ๋˜๋ฉด ์ž๋™์œผ๋กœ ๋ฐฐํฌ๊ฐ€ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. + + +### 7. CI/CD + +- **GitHub Actions**๋ฅผ ์ด์šฉํ•˜์—ฌ CI(ํ…Œ์ŠคํŠธ/๋นŒ๋“œ) ๋ฐ ๋ฐฐํฌ๋ฅผ ์ž๋™ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +- `dev` ๋ธŒ๋žœ์น˜์— ํ‘ธ์‹œ๋˜๋ฉด ์ž๋™์œผ๋กœ ๋ฐฐํฌ๊ฐ€ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. + +
## ์ฃผ์š” ๊ธฐ๋Šฅ +### ํ™ˆ +- ์ถ”์ฒœ ๋ชจ์ž„์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. +- ์Šคํ„ฐ๋””, ํ”„๋กœ์ ํŠธ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. +- ๋กœ๊ทธ์ธํ•œ ์œ ์ €๋Š” ๋ชจ์ž„์„ ๋งŒ๋“ค๊ฑฐ๋‚˜ ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ํšŒ์›๊ฐ€์ž… +- ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๊ณ„์ •์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +### ๋กœ๊ทธ์ธ +- ๊ฐ€์ž…ํ•œ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์ด๋ฉ”์ผ/๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ +- ํ•ด๋‹น ์ด๋ฉ”์ผ๋กœ ๊ฐ€์ž…ํ–ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- ์ด๋ฉ”์ผ๋กœ ์ž„์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐœ์†กํ•ด์ค๋‹ˆ๋‹ค. ๋ฐ›์€ ์ž„์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ๋ชจ์ž„ ๋งŒ๋“ค๊ธฐ +- ์›ํ•˜๋Š” ํƒœ๊ทธ๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋ชจ์ž„์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. + +### ๋ชจ์ž„ ์ƒ์„ธ ํŽ˜์ด์ง€ +- ๋ชจ์ž„ ์ƒ์„ธ ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๊ณ  ์ฐธ์—ฌ ์‹ ์ฒญ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- ์ฃผ์ตœ์ž๋Š” ๋ชจ์ž„ ์ทจ์†Œ/๊ณต์œ ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- ๋ชจ์ž„ ๊ด€๋ จ ๋Œ“๊ธ€์„ ๋‹ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +### ์ฐœํ•œ ๋ชจ์ž„ +- ๊ด€์‹ฌ์žˆ๋Š” ๋ชจ์ž„์„ ์ฐœํ•˜๊ธฐ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- ์ฐœํ•œ ๋ชจ์ž„๋“ค๋งŒ ๋ณด์—ฌ์ง‘๋‹ˆ๋‹ค. + +### ์œ ์ € ํŽ˜์ด์ง€ +- ์œ ์ €์˜ ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. +[ ๋งˆ์ด ํŽ˜์ด์ง€ ] +- ํ”„๋กœํ•„ ์ˆ˜์ •์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- ๋‚ด๊ฐ€ ๊ฐœ์„คํ•œ ๋ชจ์ž„์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. +- ํŒ”๋กœ์ž‰, ํŒ”๋กœ์›Œ ๋ชฉ๋ก์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. +- ๋น„๋ฐ€๋ฒˆํ˜ธ ์ˆ˜์ •์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- ํšŒ์› ํƒˆํ‡ด๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +[ ๋‹ค๋ฅธ ์œ ์ € ํŽ˜์ด์ง€ ] +- ํŒ”๋กœ์šฐ/์–ธํŒ”๋กœ์šฐ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + -- **ํ”„๋กœ์ ํŠธ ์ฃผ์š” ๊ธฐ๋Šฅ** -- ํ”„๋ก ํŠธ์˜ ๊ฒฝ์šฐ ์ด๋ฏธ์ง€๋ฅผ ํ•จ๊ป˜ ์ฒจ๋ถ€ํ•ด๋„ ์ข‹์Šต๋‹ˆ๋‹ค. -## ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ… -- ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ… ๊ฒฝํ—˜์— ๋Œ€ํ•ด ๊ธฐ์ˆ ํ•ด ์ฃผ์„ธ์š”. +## โœจ ๊ธฐ๋Šฅ ์ƒ์„ธ ์„ค๋ช… +[๐Ÿ”— Link](https://thrilling-cough-c32.notion.site/21b091373e7180ada9cae55ccb740bbb?source=copy_link) -## ์‚ฌ์šฉ ๊ธฐ์ˆ  -**๊ธฐ์ˆ  ์Šคํƒ, ์‚ฌ์šฉ ๊ธฐ์ˆ ** +## ๐Ÿž ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ… +[๐Ÿ”— Link](https://thrilling-cough-c32.notion.site/218091373e7180388816effc11ca98c6?source=copy_link) -- ์‚ฌ์šฉํ•œ ๊ธฐ์ˆ ๊ณผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ diff --git a/components.json b/components.json new file mode 100644 index 00000000..0f21f26c --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/cypress/e2e/auth/find-password.cy.ts b/cypress/e2e/auth/find-password.cy.ts new file mode 100644 index 00000000..ccffa5ca --- /dev/null +++ b/cypress/e2e/auth/find-password.cy.ts @@ -0,0 +1,116 @@ +describe('๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ', () => { + beforeEach(() => { + cy.fixture('auth/find-password.json').then((findPassword) => { + // ๋กœ๊ทธ์ธ ์ธํ„ฐ์…‰ํŠธ ์„ค์ • + cy.intercept( + 'POST', + 'https://my-api.sjcpop.com/api/v1/user/reset-password', + (req) => { + if (req.body.email !== 'asd@asd.asd') { + // ์ด๋ฉ”์ผ์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์€ ๊ฒฝ์šฐ + return req.reply(findPassword.findPasswordEmailFail); + } + + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ์ธ ๊ฒฝ์šฐ + return req.reply(findPassword.findPasswordSuccess); + }, + ); + + cy.intercept('GET', 'https://my-api.sjcpop.com/api/v1/notification*', { + statusCode: 200, + body: { + notifications: [], + totalCount: 0, + }, + }); + + cy.intercept( + 'GET', + 'https://my-api.sjcpop.com/api/v1/notification/unread-count*', + { + statusCode: 200, + body: { + unreadCount: 0, + }, + }, + ); + + cy.intercept( + 'GET', + 'https://my-api.sjcpop.com/api/v1/user/info*', + findPassword.getUserInfo, + ); + + // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ + cy.visit('http://localhost:3000/login'); + + // ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ๋งํฌ ํด๋ฆญ + cy.get('a') + .contains(/^๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ$/) + .click(); + + cy.wait(1000); + }); + }); + + it('๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ํŽ˜์ด์ง€ ๋ Œ๋”๋ง ํ…Œ์ŠคํŠธ', () => { + cy.get('p').contains(/^๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ$/); + + // ํผํ•„๋“œ ๋ Œ๋”๋ง ํ™•์ธ + cy.get('form').within(() => { + cy.contains('label', /^์ด๋ฉ”์ผ$/).should('exist'); + cy.get('input[name="email"]').should('exist'); + + cy.contains('button', /^๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™”$/).should('exist'); + }); + + // ํšŒ์›๊ฐ€์ž… ์ด๋ฉ”์ผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ๋ Œ๋”๋ง ํ™•์ธ + cy.contains('a', /^๋กœ๊ทธ์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ$/).should('exist'); + }); + + // ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ํผ ์œ ํšจ์„ฑ ํ…Œ์ŠคํŠธ + it('๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ํผ ์œ ํšจ์„ฑ ํ…Œ์ŠคํŠธ: ์•„๋ฌด๊ฒƒ๋„ ์ž…๋ ฅํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ => ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ์•„๋ฌด ๊ฒƒ๋„ ์—†์ด ํด๋ฆญ์ด ์—๋Ÿฌ ํ…์ŠคํŠธ ํ™•์ธ + cy.contains('button', /^๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™”$/).click(); + cy.get('p').contains('์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”').should('exist'); + }); + + // ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹ ํ…Œ์ŠคํŠธ + it('๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ํผ ์œ ํšจ์„ฑ ํ…Œ์ŠคํŠธ: ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹ => ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹ ์ž…๋ ฅ + cy.get('input[name="email"]').type('asd@a'); + + // ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™”$/).click(); + + // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ™•์ธ + cy.get('p').contains('์œ ํšจํ•œ ์ด๋ฉ”์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค').should('exist'); + }); + + // ์˜ฌ๋ฐ”๋ฅธ ํ˜•์‹์˜ ์ด๋ฉ”์ผ => ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ด๋ฉ”์ผ ํ…Œ์ŠคํŠธ + it('๋น„๋ฐ€๋ฒˆํ˜ธ API ์š”์ฒญ ์‹คํŒจ ํ…Œ์ŠคํŠธ: ์กด์žฌํ•˜๋Š” ์ด๋ฉ”์ผ ์—†์Œ => ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ + cy.get('input[name="email"]').type('nonexist@asd.asd'); + + // ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™”$/).click(); + + // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ™•์ธ + cy.get('p').contains('ํ•ด๋‹น ์ด๋ฉ”์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค').should('exist'); + }); + + // ์„ฑ๊ณต + it('๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™” API ์š”์ฒญ ์„ฑ๊ณต ํ…Œ์ŠคํŠธ => ์ด๋ฉ”์ผ ์ „์†ก ์„ฑ๊ณต ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ + cy.get('input[name="email"]').type('asd@asd.asd'); + + // ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™”$/).click(); + + cy.wait(1000); + + // ์ด๋ฉ”์ผ ์ „์†ก ์„ฑ๊ณต ๋ฉ”์‹œ์ง€ ํ™•์ธ + cy.get('p').contains('์ด๋ฉ”์ผ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”!').should('exist'); + cy.get('a').contains('๋กœ๊ทธ์ธํ•˜๋Ÿฌ ๊ฐ€๊ธฐ').should('exist'); + }); +}); diff --git a/cypress/e2e/auth/login.cy.ts b/cypress/e2e/auth/login.cy.ts new file mode 100644 index 00000000..f16bb1ef --- /dev/null +++ b/cypress/e2e/auth/login.cy.ts @@ -0,0 +1,148 @@ +describe('๋กœ๊ทธ์ธ ํŽ˜์ด์ง€', () => { + beforeEach(() => { + cy.fixture('auth/login.json').then((loginFixture) => { + // ๋กœ๊ทธ์ธ ์ธํ„ฐ์…‰ํŠธ ์„ค์ • + cy.intercept( + 'POST', + 'https://my-api.sjcpop.com/api/v1/user/login', + (req) => { + if (req.body.email !== 'asd@asd.asd') { + // ์ด๋ฉ”์ผ์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์€ ๊ฒฝ์šฐ + return req.reply(loginFixture.loginEmailFail); + } + + if (req.body.password !== 'correctPassword123') { + // ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์€ ๊ฒฝ์šฐ + return req.reply(loginFixture.loginPasswordFail); + } + + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ์ธ ๊ฒฝ์šฐ + return req.reply(loginFixture.loginSuccess); + }, + ); + + cy.intercept('GET', 'https://my-api.sjcpop.com/api/v1/notification*', { + statusCode: 200, + body: { + notifications: [], + totalCount: 0, + }, + }); + + cy.intercept( + 'GET', + 'https://my-api.sjcpop.com/api/v1/notification/unread-count*', + { + statusCode: 200, + body: { + unreadCount: 0, + }, + }, + ); + + cy.intercept( + 'GET', + 'https://my-api.sjcpop.com/api/v1/user/info*', + loginFixture.getUserInfo, + ); + + // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ + cy.visit('http://localhost:3000/login'); + }); + }); + + it('๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ๋ Œ๋”๋ง ํ…Œ์ŠคํŠธ', () => { + // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง ํ™•์ธ + cy.get('p').contains('๋กœ๊ทธ์ธ'); + + // ํผํ•„๋“œ ๋ Œ๋”๋ง ํ™•์ธ + cy.get('form').within(() => { + cy.contains('label', /^์ด๋ฉ”์ผ$/).should('exist'); + cy.get('input[name="email"]').should('exist'); + + cy.contains('label', /^๋น„๋ฐ€๋ฒˆํ˜ธ$/).should('exist'); + cy.get('input[name="password"]').should('exist'); + + cy.contains('button', /^๋กœ๊ทธ์ธ$/).should('exist'); + }); + + // ํšŒ์›๊ฐ€์ž… ์ด๋ฉ”์ผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ๋ Œ๋”๋ง ํ™•์ธ + cy.contains('a', /^ํšŒ์›๊ฐ€์ž…$/).should('exist'); + cy.contains('a', /^์ด๋ฉ”์ผ ์ฐพ๊ธฐ$/).should('exist'); + cy.contains('a', /^๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ$/).should('exist'); + }); + + // ๋กœ๊ทธ์ธ ํผ ์œ ํšจ์„ฑ ํ…Œ์ŠคํŠธ + it('๋กœ๊ทธ์ธ ํผ ์œ ํšจ์„ฑ ํ…Œ์ŠคํŠธ: ์•„๋ฌด๊ฒƒ๋„ ์ž…๋ ฅํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ => ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ์•„๋ฌด ๊ฒƒ๋„ ์—†์ด ํด๋ฆญ์ด ์—๋Ÿฌ ํ…์ŠคํŠธ ํ™•์ธ + cy.contains('button', /^๋กœ๊ทธ์ธ$/).click(); + cy.get('p').contains('์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”').should('exist'); + cy.get('p').contains('๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”').should('exist'); + }); + + // ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹ ํ…Œ์ŠคํŠธ + it('๋กœ๊ทธ์ธ ํผ ์œ ํšจ์„ฑ ํ…Œ์ŠคํŠธ: ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹ => ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์ ‘์† + + // ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹ ์ž…๋ ฅ + cy.get('input[name="email"]').type('asd@a'); + cy.get('input[name="password"]').type('validPassword123'); + + // ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^๋กœ๊ทธ์ธ$/).click(); + + // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ™•์ธ + cy.get('p').contains('์œ ํšจํ•œ ์ด๋ฉ”์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค').should('exist'); + }); + + // ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ˜•์‹๊ฒ€์‚ฌ๋ฅผ ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ํŒจ์Šค(์ž…๋ ฅ ์—ฌ๋ถ€๋งŒ ํŒ๋‹จ) + + // ์˜ฌ๋ฐ”๋ฅธ ํ˜•์‹์˜ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ + it('๋กœ๊ทธ์ธ API ์š”์ฒญ ์‹คํŒจ ํ…Œ์ŠคํŠธ: ์กด์žฌํ•˜๋Š” ์ด๋ฉ”์ผ ์—†์Œ => ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ + cy.get('input[name="email"]').type('nonexist@asd.asd'); + cy.get('input[name="password"]').type('correctPassword123'); + + // ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^๋กœ๊ทธ์ธ$/).click(); + + // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ™•์ธ + cy.get('p').contains('๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค').should('exist'); + }); + + // ํ‹€๋ฆฐ ๋น„๋ฐ€๋ฒˆํ˜ธ + it('๋กœ๊ทธ์ธ API ์š”์ฒญ ์‹คํŒจ ํ…Œ์ŠคํŠธ: ๋น„๋ฒˆํ‹€๋ฆผ => ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ + cy.get('input[name="email"]').type('asd@asd.asd'); + cy.get('input[name="password"]').type('wrongPassword123'); + + // ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^๋กœ๊ทธ์ธ$/).click(); + + // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ™•์ธ + cy.get('p').contains('๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค').should('exist'); + }); + + // ๋กœ๊ทธ์ธ ์„ฑ๊ณตํ›„ ํŠธ๋ฆฌ๊ฑฐ ํ…Œ์ŠคํŠธ + it('๋กœ๊ทธ์ธ ์„ฑ๊ณตํ›„ ํŠธ๋ฆฌ๊ฑฐ ํ…Œ์ŠคํŠธ: ๋ถ๋งˆํฌํŽ˜์ด์ง€ => ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ => ๋กœ๊ทธ์ธ ์„ฑ๊ณต => ๋ถ๋งˆํฌํŽ˜์ด์ง€๋กœ ๋˜๋Œ์•„๊ฐ€์•ผํ•จ', () => { + // ๋ถ๋งˆํฌ ํŽ˜์ด์ง€ ์ด๋™ + cy.visit('http://localhost:3000/bookmark'); + + // ๋กœ๊ทธ์ธ ํŠธ๋ฆฌ๊ฑฐ ์ž‘๋™ํ•  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ + cy.wait(500); + + // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ + // ์ ˆ๋Œ€ visit๋กœ ํ•˜๋ฉด ์•ˆ๋จ!! ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์œผ๋กœ ํ•ด์•ผํ•จ!! + cy.get('button').contains('๋กœ๊ทธ์ธ ๋ฐ ํšŒ์›๊ฐ€์ž…').click(); + + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ + cy.get('input[name="email"]').type('asd@asd.asd'); + cy.get('input[name="password"]').type('correctPassword123'); + + // ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^๋กœ๊ทธ์ธ$/).click(); + + // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ›„ ๋ถ๋งˆํฌ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ํ™•์ธ + cy.url().should('include', '/bookmark'); + }); +}); diff --git a/cypress/e2e/auth/register.cy.ts b/cypress/e2e/auth/register.cy.ts new file mode 100644 index 00000000..d37053e4 --- /dev/null +++ b/cypress/e2e/auth/register.cy.ts @@ -0,0 +1,240 @@ +describe('ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€', () => { + beforeEach(() => { + cy.fixture('auth/register.json').then((registerFixture) => { + // ๋กœ๊ทธ์ธ ์ธํ„ฐ์…‰ํŠธ ์„ค์ • + cy.intercept( + 'POST', + 'https://my-api.sjcpop.com/api/v1/user/signup', + (req) => { + if (req.body.email === 'same@same.com') { + // ์ด๋ฉ”์ผ์ด ์ค‘๋ณต์ธ ๊ฒฝ์šฐ + return req.reply(registerFixture.registerEmailFail); + } + + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ์ธ ๊ฒฝ์šฐ + return req.reply(registerFixture.registerSuccess); + }, + ); + + // ๋กœ๊ทธ์ธ ์ธํ„ฐ์…‰ํŠธ ์„ค์ • + cy.intercept( + 'POST', + 'https://my-api.sjcpop.com/api/v1/user/login', + (req) => { + return req.reply(registerFixture.loginSuccess); + }, + ); + + cy.intercept('GET', 'https://my-api.sjcpop.com/api/v1/notification*', { + statusCode: 200, + body: { + notifications: [], + totalCount: 0, + }, + }); + + cy.intercept( + 'GET', + 'https://my-api.sjcpop.com/api/v1/notification/unread-count*', + { + statusCode: 200, + body: { + unreadCount: 0, + }, + }, + ); + + cy.intercept( + 'PATCH', + 'https://my-api.sjcpop.com/api/v1/user/edit', + (req) => { + return req.reply(registerFixture.registerOptional); + }, + ); + + cy.intercept( + 'GET', + 'https://my-api.sjcpop.com/api/v1/user/info*', + registerFixture.getUserInfo, + ); + + // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ + cy.visit('http://localhost:3000/login'); + + // ํšŒ์›๊ฐ€์ž… ๋งํฌ ํด๋ฆญ + cy.contains('a', /^ํšŒ์›๊ฐ€์ž…$/).click(); + + // ํŽ˜์ด์ง€ ๋กœ๋”ฉ ๋Œ€๊ธฐ + cy.wait(2000); + }); + }); + + it('ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€ ๋ Œ๋”๋ง ํ…Œ์ŠคํŠธ', () => { + // ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€ ๋ Œ๋”๋ง ํ™•์ธ + cy.get('p').contains(/^ํšŒ์›๊ฐ€์ž…$/); + + // ํผํ•„๋“œ ๋ Œ๋”๋ง ํ™•์ธ + cy.get('form').within(() => { + cy.contains('label', /^์ด๋ฉ”์ผ$/).should('exist'); + cy.get('input[name="email"]').should('exist'); + + cy.contains('label', /^๋น„๋ฐ€๋ฒˆํ˜ธ$/).should('exist'); + cy.get('input[name="password"]').should('exist'); + + cy.contains('label', /^๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ$/).should('exist'); + cy.get('input[name="passwordConfirm"]').should('exist'); + + cy.contains('button', /^ํšŒ์›๊ฐ€์ž…$/).should('exist'); + }); + + // ๋กœ๊ทธ์ธํ•˜๋Ÿฌ๊ฐ€๊ธฐ ๋ Œ๋”๋ง ํ™•์ธ + cy.contains('a', '์ด๋ฏธ ํšŒ์›์ด์‹ ๊ฐ€์š”?').should('exist'); + }); + + // ํšŒ์›๊ฐ€์ž… ํผ ์œ ํšจ์„ฑ ํ…Œ์ŠคํŠธ + it('ํšŒ์›๊ฐ€์ž… ํผ ์œ ํšจ์„ฑ ํ…Œ์ŠคํŠธ: ์•„๋ฌด๊ฒƒ๋„ ์ž…๋ ฅํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ => ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ์•„๋ฌด ๊ฒƒ๋„ ์—†์ด ํด๋ฆญ์ด ์—๋Ÿฌ ํ…์ŠคํŠธ ํ™•์ธ + cy.contains('button', /^ํšŒ์›๊ฐ€์ž…$/).click(); + + cy.get('p').contains('์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”').should('exist'); + cy.get('p').contains('๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”').should('exist'); + cy.get('p').contains('๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ค์‹œ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”').should('exist'); + }); + + // ์ž˜๋ชป๋œ ํ˜•์‹ + it('ํšŒ์›๊ฐ€์ž… ํผ ์œ ํšจ์„ฑ ํ…Œ์ŠคํŠธ: ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ, ๋น„๋ฐ€๋ฒˆํ˜ธ ํ˜•์‹ => ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹ ์ž…๋ ฅ + cy.get('input[name="email"]').type('asd@a'); + cy.get('input[name="password"]').type('invalid123'); + cy.get('input[name="passwordConfirm"]').type('different@@'); + + // ํšŒ์›๊ฐ€์ž… ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^ํšŒ์›๊ฐ€์ž…$/).click(); + + // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ™•์ธ + cy.get('p').contains('์œ ํšจํ•œ ์ด๋ฉ”์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค').should('exist'); + cy.get('p') + .contains('์˜์–ด, ์ˆซ์ž, ํŠน์ˆ˜๋ฌธ์ž๋ฅผ ํ˜ผํ•ฉํ•˜์—ฌ 8์ž๋ฆฌ ์ด์ƒ') + .should('exist'); + cy.get('p').contains('๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค').should('exist'); + }); + + // ์˜ฌ๋ฐ”๋ฅธ ํ˜•์‹์˜ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ + it('ํšŒ์›๊ฐ€์ž… API ์š”์ฒญ ์‹คํŒจ ํ…Œ์ŠคํŠธ: ์ด๋ฉ”์ผ ์ค‘๋ณต => ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ + cy.get('input[name="email"]').type('same@same.com'); + cy.get('input[name="password"]').type('correctPassword123!@#'); + cy.get('input[name="passwordConfirm"]').type('correctPassword123!@#'); + + // ํšŒ์›๊ฐ€์ž… ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^ํšŒ์›๊ฐ€์ž…$/).click(); + + // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ™•์ธ + cy.get('p').contains('์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํšŒ์›์ž…๋‹ˆ๋‹ค').should('exist'); + }); + + // ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณตํ›„ ํ”„๋กœํ•„ ์„ค์ • ํผ ๋ Œ๋”๋ง ํ…Œ์ŠคํŠธ + it('ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณตํ›„ ํ”„๋กœํ•„ ์„ค์ • ํผ ๋ Œ๋”๋ง ํ…Œ์ŠคํŠธ', () => { + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ + cy.get('input[name="email"]').type('notsame@notsame.com'); + cy.get('input[name="password"]').type('correctPassword123!@#'); + cy.get('input[name="passwordConfirm"]').type('correctPassword123!@#'); + + // ํšŒ์›๊ฐ€์ž… ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^ํšŒ์›๊ฐ€์ž…$/).click(); + + // ํ”„๋กœํ•„ ์„ค์ • ํผ์ด ๋‚˜์™€์•ผํ•จ + cy.get('form').within(() => { + cy.contains('label', /^๋‹‰๋„ค์ž„$/).should('exist'); + cy.get('input[name="nickname"]').should('exist'); + + cy.contains('label', /^ํฌ์ง€์…˜$/).should('exist'); + cy.get("div[class='flex gap-4 flex-wrap']") + .eq(0) + .within(() => { + ['PM', 'PL', 'AA', 'TA', 'DA', 'QA', 'FE', 'BE', 'FS'].forEach( + (position) => { + cy.get('label').contains(position); + }, + ); + }); + + cy.contains('label', /^๊ธฐ์ˆ  ์Šคํƒ$/).should('exist'); + cy.get("div[class='flex gap-4 flex-wrap']") + .eq(1) + .within(() => { + [ + 'Java', + 'JavaScript', + 'HTML_CSS', + 'REACT', + 'Vue', + 'Kotlin', + 'Spring', + ].forEach((skill) => { + cy.get('label').contains(skill); + }); + }); + + cy.contains('button', /^ํ”„๋กœํ•„ ์„ค์ •ํ•˜๊ธฐ$/).should('exist'); + }); + }); + + // ํ”„๋กœํ•„ ์„ค์ • ํผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ + it('ํ”„๋กœํ•„ ์„ค์ • ๋นˆ๊ฐ’ ์ž…๋ ฅ => ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ™•์ธ', () => { + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ + cy.get('input[name="email"]').type('notsame@notsame.com'); + cy.get('input[name="password"]').type('correctPassword123!@#'); + cy.get('input[name="passwordConfirm"]').type('correctPassword123!@#'); + + // ํšŒ์›๊ฐ€์ž… ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^ํšŒ์›๊ฐ€์ž…$/).click(); + + cy.wait(2000); + + // ํ”„๋กœํ•„ ์„ค์ • ๋นˆ๊ฐ’ ์ž…๋ ฅ + cy.get('form').within(() => { + cy.contains('button', /^ํ”„๋กœํ•„ ์„ค์ •ํ•˜๊ธฐ$/).click(); + + cy.get('p').contains('ํฌ์ง€์…˜์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”').should('exist'); + }); + }); + + // ํ”„๋กœํ•„ ์„ค์ • ์„ฑ๊ณต ํ›„ ํŠธ๋ฆฌ๊ฑฐ ํ…Œ์ŠคํŠธ + it('ํ”„๋กœํ•„ ์„ค์ •ํ›„ ํŠธ๋ฆฌ๊ฑฐ ํ…Œ์ŠคํŠธ: ๋ถ๋งˆํฌํŽ˜์ด์ง€ => ๋กœ๊ทธ์ธํŽ˜์ด์ง€ => ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€ => ํ”„๋กœํ•„ ์„ค์ • ์™„๋ฃŒ => ๋ถ๋งˆํฌ ํŽ˜์ด์ง€', () => { + // ๋ถ๋งˆํฌ ํŽ˜์ด์ง€ ์ด๋™ + cy.visit('http://localhost:3000/bookmark'); + + // ๋กœ๊ทธ์ธ ํŠธ๋ฆฌ๊ฑฐ ์ž‘๋™ํ•  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ + cy.wait(2000); + + // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ + // ์ ˆ๋Œ€ visit๋กœ ํ•˜๋ฉด ์•ˆ๋จ!! ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์œผ๋กœ ํ•ด์•ผํ•จ!! + cy.get('button').contains('๋กœ๊ทธ์ธ ๋ฐ ํšŒ์›๊ฐ€์ž…').click(); + + // ํšŒ์›๊ฐ€์ž… ๋งํฌ ํด๋ฆญ + cy.contains('a', /^ํšŒ์›๊ฐ€์ž…$/).click(); + + cy.wait(2000); + + // ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ + cy.get('input[name="email"]').type('notsame@notsame.com'); + cy.get('input[name="password"]').type('correctPassword123!@#'); + cy.get('input[name="passwordConfirm"]').type('correctPassword123!@#'); + + // ํšŒ์›๊ฐ€์ž… ๋ฒ„ํŠผ ํด๋ฆญ + cy.contains('button', /^ํšŒ์›๊ฐ€์ž…$/).click(); + + // ํ”„๋กœํ•„ ์„ค์ • + cy.get('form').within(() => { + cy.get('input[name="nickname"]').type('testuser'); + cy.get('label').contains('FE').click(); + cy.get('label').contains('REACT').click(); + + cy.contains('button', /^ํ”„๋กœํ•„ ์„ค์ •ํ•˜๊ธฐ$/).click(); + }); + + // ํ”„๋กœํ•„ ์„ค์ • ์„ฑ๊ณต ํ›„ ๋ถ๋งˆํฌ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ํ™•์ธ + cy.url().should('include', '/bookmark'); + }); +}); diff --git a/cypress/fixtures/auth/find-password.json b/cypress/fixtures/auth/find-password.json new file mode 100644 index 00000000..2434be6f --- /dev/null +++ b/cypress/fixtures/auth/find-password.json @@ -0,0 +1,106 @@ +{ + "getUserInfo": { + "status": { + "success": true + }, + "items": { + "items": { + "id": 3, + "email": "user@example.com", + "nickname": null, + "provider": null, + "provider_id": null, + "created_at": "2025-06-05T04:44:54.842Z", + "updated_at": "2025-06-05T08:22:36.687Z", + "is_deleted": false, + "position": 1, + "skills": [ + 5, + 4, + 3, + 2 + ], + "email_authentication": null, + "group": [], + "waiting_list": [], + "bookmark": [], + "reply": [], + "rated_ratings": [], + "profileImage": null, + "followers": [ + { + "id": 7, + "email": "user2@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 38, + "email": "wwww1234@example.com", + "nickname": "wwww12", + "profile_image": "https://festivals3ss.s3.us-east-1.amazonaws.com/profiles/1749535332755_7309681.jpg", + "profileImage": "https://festivals3ss.s3.us-east-1.amazonaws.com/profiles/1749535332755_7309681.jpg" + }, + { + "id": 21, + "email": "te44st4@test.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 12, + "email": "local002@local.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 34, + "email": "pppp1234@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + } + ], + "following": [ + { + "id": 5, + "email": "texova9050@rowplant.com", + "nickname": "asdasd", + "profile_image": null, + "profileImage": null + }, + { + "id": 7, + "email": "user2@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 34, + "email": "pppp1234@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + } + ] + }, + "averageRating": 0 + } + }, + "findPasswordEmailFail": { + "status": { + "success": false, + "code": 404, + "message": "์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค." + } + }, + "findPasswordSuccess": { + "status": { + "success": true + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/auth/login.json b/cypress/fixtures/auth/login.json new file mode 100644 index 00000000..5e6a449d --- /dev/null +++ b/cypress/fixtures/auth/login.json @@ -0,0 +1,115 @@ +{ + "getUserInfo": { + "status": { + "success": true + }, + "items": { + "items": { + "id": 3, + "email": "user@example.com", + "nickname": null, + "provider": null, + "provider_id": null, + "created_at": "2025-06-05T04:44:54.842Z", + "updated_at": "2025-06-05T08:22:36.687Z", + "is_deleted": false, + "position": 1, + "skills": [ + 5, + 4, + 3, + 2 + ], + "email_authentication": null, + "group": [], + "waiting_list": [], + "bookmark": [], + "reply": [], + "rated_ratings": [], + "profileImage": null, + "followers": [ + { + "id": 7, + "email": "user2@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 38, + "email": "wwww1234@example.com", + "nickname": "wwww12", + "profile_image": "https://festivals3ss.s3.us-east-1.amazonaws.com/profiles/1749535332755_7309681.jpg", + "profileImage": "https://festivals3ss.s3.us-east-1.amazonaws.com/profiles/1749535332755_7309681.jpg" + }, + { + "id": 21, + "email": "te44st4@test.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 12, + "email": "local002@local.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 34, + "email": "pppp1234@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + } + ], + "following": [ + { + "id": 5, + "email": "texova9050@rowplant.com", + "nickname": "asdasd", + "profile_image": null, + "profileImage": null + }, + { + "id": 7, + "email": "user2@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 34, + "email": "pppp1234@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + } + ] + }, + "averageRating": 0 + } + }, + "loginEmailFail": { + "status": { + "success": false, + "code": 500, + "message": "ํ…Œ์ŠคํŠธ: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค." + } + }, + "loginPasswordFail": { + "status": { + "success": false, + "code": 500, + "message": "ํ…Œ์ŠคํŠธ: ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค." + } + }, + "loginSuccess": { + "status": { + "success": true, + "code": 200, + "message": "ํ…Œ์ŠคํŠธ: ๋กœ๊ทธ์ธ ์„ฑ๊ณต" + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/auth/register.json b/cypress/fixtures/auth/register.json new file mode 100644 index 00000000..9e644547 --- /dev/null +++ b/cypress/fixtures/auth/register.json @@ -0,0 +1,120 @@ +{ + "getUserInfo": { + "status": { + "success": true + }, + "items": { + "items": { + "id": 3, + "email": "user@example.com", + "nickname": null, + "provider": null, + "provider_id": null, + "created_at": "2025-06-05T04:44:54.842Z", + "updated_at": "2025-06-05T08:22:36.687Z", + "is_deleted": false, + "position": 1, + "skills": [ + 5, + 4, + 3, + 2 + ], + "email_authentication": null, + "group": [], + "waiting_list": [], + "bookmark": [], + "reply": [], + "rated_ratings": [], + "profileImage": null, + "followers": [ + { + "id": 7, + "email": "user2@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 38, + "email": "wwww1234@example.com", + "nickname": "wwww12", + "profile_image": "https://festivals3ss.s3.us-east-1.amazonaws.com/profiles/1749535332755_7309681.jpg", + "profileImage": "https://festivals3ss.s3.us-east-1.amazonaws.com/profiles/1749535332755_7309681.jpg" + }, + { + "id": 21, + "email": "te44st4@test.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 12, + "email": "local002@local.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 34, + "email": "pppp1234@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + } + ], + "following": [ + { + "id": 5, + "email": "texova9050@rowplant.com", + "nickname": "asdasd", + "profile_image": null, + "profileImage": null + }, + { + "id": 7, + "email": "user2@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + }, + { + "id": 34, + "email": "pppp1234@example.com", + "nickname": null, + "profile_image": null, + "profileImage": null + } + ] + }, + "averageRating": 0 + } + }, + "registerEmailFail": { + "status": { + "success": false, + "code": 409, + "message": "์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์ด๋ฉ”์ผ ์ž…๋‹ˆ๋‹ค." + }, + "message": "ํšŒ์›๊ฐ€์ž… ์‹คํŒจ" + }, + "registerOptional": { + "status": { + "success": true + } + }, + "registerSuccess": { + "status": { + "success": true + }, + "message": "ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต" + }, + "loginSuccess": { + "status": { + "success": true, + "code": 200, + "message": "ํ…Œ์ŠคํŠธ: ๋กœ๊ทธ์ธ ์„ฑ๊ณต" + } + } +} \ No newline at end of file diff --git a/ecosystem.config.js b/ecosystem.config.js new file mode 100644 index 00000000..7aca8430 --- /dev/null +++ b/ecosystem.config.js @@ -0,0 +1,18 @@ +module.exports = { + apps: [ + { + name: 'moyeora-it-FE', + script: 'pnpm', + args: 'start', + cwd: '/home/ubuntu/projects/moyeora-it-FE', + env: { + NODE_ENV: 'development', + PORT: 3000, + }, + env_production: { + NODE_ENV: 'production', + PORT: 3000, + }, + }, + ], +}; diff --git a/eslint.config.mjs b/eslint.config.mjs index c85fb67c..e15ec0dc 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,6 +1,6 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; -import { FlatCompat } from "@eslint/eslintrc"; +import { FlatCompat } from '@eslint/eslintrc'; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -10,7 +10,7 @@ const compat = new FlatCompat({ }); const eslintConfig = [ - ...compat.extends("next/core-web-vitals", "next/typescript"), + ...compat.extends('next/core-web-vitals', 'next/typescript'), ]; export default eslintConfig; diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 00000000..2d0aecb9 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,22 @@ +import type { Config } from 'jest'; +import nextJest from 'next/jest.js'; + +const createJestConfig = nextJest({ + // Provide the path to your Next.js app to load next.config.js and .env files in your test environment + dir: './', +}); + +// Add any custom config to be passed to Jest +const config: Config = { + coverageProvider: 'v8', + testEnvironment: 'jest-fixed-jsdom', + // ํ…Œ์ŠคํŠธ ์ „์— ์‹คํ–‰ํ•  ์„ค์ • ํŒŒ์ผ์„ ์ง€์ • + setupFilesAfterEnv: ['/jest.setup.ts'], + testMatch: [ + '/src/**/*.{spec,test}.{js,ts,tsx}', + '/__tests__/**/*.{spec,test}.{js,ts,tsx}', + ], +}; + +// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async +export default createJestConfig(config); diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 00000000..ca39f4e1 --- /dev/null +++ b/jest.setup.ts @@ -0,0 +1,7 @@ +// DOM ์š”์†Œ๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•œ ์ปค์Šคํ…€ ๋งค์ฒ˜(matcher)๋“ค์„ ์ œ๊ณต +// toBeInTheDocument(), toHaveTextContent(), toBeVisible() ๋“ฑ +// 1. ์ „์—ญ ์„ค์ •: ๋ชจ๋“  ํ…Œ์ŠคํŠธ ํŒŒ์ผ์—์„œ ์ด ๋งค์ฒ˜๋“ค์„ ๋ณ„๋„๋กœ import ํ•˜์ง€ ์•Š๊ณ ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +// 2. ์ฝ”๋“œ ์ค‘๋ณต ๋ฐฉ์ง€: ๊ฐ ํ…Œ์ŠคํŠธ ํŒŒ์ผ๋งˆ๋‹ค ๋™์ผํ•œ import๋ฌธ์„ ๋ฐ˜๋ณตํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. +// 3. ์ผ๊ด€์„ฑ ์œ ์ง€: ๋ชจ๋“  ํ…Œ์ŠคํŠธ์— ๋™์ผํ•œ ํ™•์žฅ ๊ธฐ๋Šฅ์ด ์ ์šฉ๋˜๋ฏ€๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. +// 4. ์„ค์ • ์ง‘์ค‘ํ™”: ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์„ค์ •์„ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด ๋‚˜์ค‘์— ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•  ๋•Œ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. +import '@testing-library/jest-dom'; diff --git a/next.config.ts b/next.config.ts index e9ffa308..294254ca 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,13 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { + images: { + remotePatterns: [ + { + hostname: "picsum.photos", + }, + ], + }, /* config options here */ }; diff --git a/package.json b/package.json index 6d63f7c2..f724a6ed 100644 --- a/package.json +++ b/package.json @@ -3,41 +3,99 @@ "version": "0.1.0", "private": false, "scripts": { - "dev": "next dev", + "dev": "next dev --experimental-https", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "test": "jest", + "test:watch": "jest --watch", + "cypress:open": "cypress open" }, "dependencies": { + "@hookform/resolvers": "^5.0.1", + "@radix-ui/react-alert-dialog": "^1.1.14", + "@radix-ui/react-avatar": "^1.1.9", + "@radix-ui/react-checkbox": "^1.3.1", + "@radix-ui/react-dialog": "^1.1.13", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-label": "^2.1.6", + "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-progress": "^1.1.6", + "@radix-ui/react-radio-group": "^1.3.6", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tabs": "^1.1.12", + "@radix-ui/react-tooltip": "^1.2.7", + "@tailwindcss/typography": "^0.5.16", "@tanstack/react-query": "^5.76.1", + "@tiptap-extend/emoji-replacer": "^2.1.6", + "@tiptap/extension-blockquote": "^2.22.3", + "@tiptap/extension-bullet-list": "^2.22.3", + "@tiptap/extension-code": "^2.22.3", + "@tiptap/extension-code-block": "^2.22.3", + "@tiptap/extension-heading": "^2.22.3", + "@tiptap/extension-highlight": "^2.12.0", + "@tiptap/extension-horizontal-rule": "^2.22.3", + "@tiptap/extension-list-item": "^2.22.3", + "@tiptap/extension-ordered-list": "^2.22.3", + "@tiptap/extension-task-item": "^2.12.0", + "@tiptap/extension-task-list": "^2.12.0", + "@tiptap/extension-typography": "^2.12.0", + "@tiptap/react": "^2.12.0", + "@tiptap/starter-kit": "^2.12.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "lucide-react": "^0.510.0", + "date-fns": "^4.1.0", + "isomorphic-dompurify": "^2.25.0", + "lucide-react": "^0.511.0", + "mock-socket": "^9.3.1", + "msw": "^2.8.4", "next": "15.3.2", + "next-themes": "^0.4.6", "postcss": "^8.5.3", "react": "^19.0.0", + "react-day-picker": "8.10.1", + "react-dom": "^19.1.0", "react-hook-form": "^7.56.3", + "react-intersection-observer": "^9.16.0", + "socket.io-client": "^4.8.1", + "sonner": "^2.0.3", "tailwind-merge": "^3.3.0", - "zod": "^3.24.4", + "zod": "^3.25.13", "zustand": "^5.0.4" }, "devDependencies": { "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4.1.6", + "@tanstack/react-query-devtools": "5.76.1", "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", + "@types/jest": "^29.5.14", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "cypress": "^14.4.1", "eslint": "^9", "eslint-config-next": "15.3.2", "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jest-fixed-jsdom": "^0.0.9", "tailwindcss": "^4.1.6", - "tw-animate-css": "^1.2.9", + "ts-jest": "^29.3.4", + "ts-node": "^10.9.2", + "tw-animate-css": "^1.3.0", "typescript": "^5" }, "main": "app/page.tsx", "repository": "https://github.com/e-sum-e/gatedalraem", "author": "e-sum-e", - "license": "MIT" + "license": "MIT", + "msw": { + "workerDirectory": [ + "public" + ] + }, + "description": "๋ชจ์—ฌ๋ผ์ž‡ ํ”„๋กœ์ ํŠธ" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..d9b77d5b --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,10117 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@hookform/resolvers': + specifier: ^5.0.1 + version: 5.1.1(react-hook-form@7.58.1(react@19.1.0)) + '@radix-ui/react-alert-dialog': + specifier: ^1.1.14 + version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-avatar': + specifier: ^1.1.9 + version: 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-checkbox': + specifier: ^1.3.1 + version: 1.3.2(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-dialog': + specifier: ^1.1.13 + version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.15 + version: 2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-label': + specifier: ^2.1.6 + version: 2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-popover': + specifier: ^1.1.14 + version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-progress': + specifier: ^1.1.6 + version: 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-radio-group': + specifier: ^1.3.6 + version: 1.3.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-select': + specifier: ^2.2.5 + version: 2.2.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-separator': + specifier: ^1.1.7 + version: 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': + specifier: ^1.2.3 + version: 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-tabs': + specifier: ^1.1.12 + version: 1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-tooltip': + specifier: ^1.2.7 + version: 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tailwindcss/typography': + specifier: ^0.5.16 + version: 0.5.16(tailwindcss@4.1.10) + '@tanstack/react-query': + specifier: ^5.76.1 + version: 5.81.2(react@19.1.0) + '@tiptap-extend/emoji-replacer': + specifier: ^2.1.6 + version: 2.1.6(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-blockquote': + specifier: ^2.22.3 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-bullet-list': + specifier: ^2.22.3 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-code': + specifier: ^2.22.3 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-code-block': + specifier: ^2.22.3 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3) + '@tiptap/extension-heading': + specifier: ^2.22.3 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-highlight': + specifier: ^2.12.0 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-horizontal-rule': + specifier: ^2.22.3 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3) + '@tiptap/extension-list-item': + specifier: ^2.22.3 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-ordered-list': + specifier: ^2.22.3 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-task-item': + specifier: ^2.12.0 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3) + '@tiptap/extension-task-list': + specifier: ^2.12.0 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-typography': + specifier: ^2.12.0 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/react': + specifier: ^2.12.0 + version: 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tiptap/starter-kit': + specifier: ^2.12.0 + version: 2.22.3 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 + isomorphic-dompurify: + specifier: ^2.25.0 + version: 2.25.0 + lucide-react: + specifier: ^0.511.0 + version: 0.511.0(react@19.1.0) + mock-socket: + specifier: ^9.3.1 + version: 9.3.1 + msw: + specifier: ^2.8.4 + version: 2.10.2(@types/node@20.19.1)(typescript@5.8.3) + next: + specifier: 15.3.2 + version: 15.3.2(@babel/core@7.27.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + postcss: + specifier: ^8.5.3 + version: 8.5.6 + react: + specifier: ^19.0.0 + version: 19.1.0 + react-day-picker: + specifier: 8.10.1 + version: 8.10.1(date-fns@4.1.0)(react@19.1.0) + react-dom: + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) + react-hook-form: + specifier: ^7.56.3 + version: 7.58.1(react@19.1.0) + react-intersection-observer: + specifier: ^9.16.0 + version: 9.16.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + socket.io-client: + specifier: ^4.8.1 + version: 4.8.1 + sonner: + specifier: ^2.0.3 + version: 2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + tailwind-merge: + specifier: ^3.3.0 + version: 3.3.1 + zod: + specifier: ^3.25.13 + version: 3.25.67 + zustand: + specifier: ^5.0.4 + version: 5.0.5(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) + devDependencies: + '@eslint/eslintrc': + specifier: ^3 + version: 3.3.1 + '@tailwindcss/postcss': + specifier: ^4.1.6 + version: 4.1.10 + '@tanstack/react-query-devtools': + specifier: 5.76.1 + version: 5.76.1(@tanstack/react-query@5.81.2(react@19.1.0))(react@19.1.0) + '@testing-library/dom': + specifier: ^10.4.0 + version: 10.4.0 + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.6.3 + '@testing-library/react': + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 + '@types/node': + specifier: ^20 + version: 20.19.1 + '@types/react': + specifier: ^19 + version: 19.1.8 + '@types/react-dom': + specifier: ^19 + version: 19.1.6(@types/react@19.1.8) + cypress: + specifier: ^14.4.1 + version: 14.5.0 + eslint: + specifier: ^9 + version: 9.29.0(jiti@2.4.2) + eslint-config-next: + specifier: 15.3.2 + version: 15.3.2(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)) + jest-environment-jsdom: + specifier: ^29.7.0 + version: 29.7.0 + jest-fixed-jsdom: + specifier: ^0.0.9 + version: 0.0.9(jest-environment-jsdom@29.7.0) + tailwindcss: + specifier: ^4.1.6 + version: 4.1.10 + ts-jest: + specifier: ^29.3.4 + version: 29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)))(typescript@5.8.3) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.19.1)(typescript@5.8.3) + tw-animate-css: + specifier: ^1.3.0 + version: 1.3.4 + typescript: + specifier: ^5 + version: 5.8.3 + +packages: + + '@adobe/css-tools@4.4.3': + resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==} + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.27.5': + resolution: {integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.27.4': + resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.27.5': + resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.27.6': + resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.5': + resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.27.1': + resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.27.6': + resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.27.4': + resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.6': + resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@bundled-es-modules/cookie@2.0.1': + resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@csstools/color-helpers@5.0.2': + resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.0.10': + resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@cypress/request@3.0.8': + resolution: {integrity: sha512-h0NFgh1mJmm1nr4jCwkGHwKneVYKghUyWe6TMNrk0B9zsjAJxpg8C4/+BAcmLgCPa1vj1V8rNUaILl+zYRUWBQ==} + engines: {node: '>= 6'} + + '@cypress/xvfb@1.2.4': + resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} + + '@emnapi/core@1.4.3': + resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@emnapi/wasi-threads@1.0.2': + resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.20.1': + resolution: {integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.2.3': + resolution: {integrity: sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.14.0': + resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.0': + resolution: {integrity: sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.29.0': + resolution: {integrity: sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.2': + resolution: {integrity: sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@floating-ui/core@1.7.1': + resolution: {integrity: sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==} + + '@floating-ui/dom@1.7.1': + resolution: {integrity: sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==} + + '@floating-ui/react-dom@2.1.3': + resolution: {integrity: sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.9': + resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + + '@hookform/resolvers@5.1.1': + resolution: {integrity: sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==} + peerDependencies: + react-hook-form: ^7.55.0 + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@img/sharp-darwin-arm64@0.34.2': + resolution: {integrity: sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.2': + resolution: {integrity: sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.1.0': + resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.1.0': + resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.1.0': + resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.1.0': + resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.1.0': + resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.1.0': + resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.1.0': + resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.2': + resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.2': + resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.34.2': + resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.2': + resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.2': + resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.2': + resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.2': + resolution: {integrity: sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.2': + resolution: {integrity: sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.2': + resolution: {integrity: sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.2': + resolution: {integrity: sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@inquirer/confirm@5.1.12': + resolution: {integrity: sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.1.13': + resolution: {integrity: sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.12': + resolution: {integrity: sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.7': + resolution: {integrity: sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + 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 + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + 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 + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@mswjs/interceptors@0.39.2': + resolution: {integrity: sha512-RuzCup9Ct91Y7V79xwCb146RaBRHZ7NBbrIUySumd1rpKqHL5OonaqrGIbug5hNwP/fRyxFMA6ISgw4FTtYFYg==} + engines: {node: '>=18'} + + '@napi-rs/wasm-runtime@0.2.11': + resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} + + '@next/env@15.3.2': + resolution: {integrity: sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==} + + '@next/eslint-plugin-next@15.3.2': + resolution: {integrity: sha512-ijVRTXBgnHT33aWnDtmlG+LJD+5vhc9AKTJPquGG5NKXjpKNjc62woIhFtrAcWdBobt8kqjCoaJ0q6sDQoX7aQ==} + + '@next/swc-darwin-arm64@15.3.2': + resolution: {integrity: sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@15.3.2': + resolution: {integrity: sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@15.3.2': + resolution: {integrity: sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@15.3.2': + resolution: {integrity: sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@15.3.2': + resolution: {integrity: sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@15.3.2': + resolution: {integrity: sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@15.3.2': + resolution: {integrity: sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@15.3.2': + resolution: {integrity: sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.2': + resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} + + '@radix-ui/react-alert-dialog@1.1.14': + resolution: {integrity: sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.10': + resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.2': + resolution: {integrity: sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.14': + resolution: {integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.10': + resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.15': + resolution: {integrity: sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.2': + resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.15': + resolution: {integrity: sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.14': + resolution: {integrity: sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.7': + resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.4': + resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.7': + resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.3.7': + resolution: {integrity: sha512-9w5XhD0KPOrm92OTTE0SysH3sYzHsSTHNvZgUBo/VZ80VdYyB5RneDbc0dKpURS24IxkoFRu/hI0i4XyfFwY6g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.10': + resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.5': + resolution: {integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tabs@1.1.12': + resolution: {integrity: sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.7': + resolution: {integrity: sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@rushstack/eslint-patch@1.11.0': + resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tailwindcss/node@4.1.10': + resolution: {integrity: sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==} + + '@tailwindcss/oxide-android-arm64@4.1.10': + resolution: {integrity: sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.10': + resolution: {integrity: sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.10': + resolution: {integrity: sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.10': + resolution: {integrity: sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10': + resolution: {integrity: sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.10': + resolution: {integrity: sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.10': + resolution: {integrity: sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.10': + resolution: {integrity: sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.10': + resolution: {integrity: sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.10': + resolution: {integrity: sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.10': + resolution: {integrity: sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.10': + resolution: {integrity: sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.10': + resolution: {integrity: sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.10': + resolution: {integrity: sha512-B+7r7ABZbkXJwpvt2VMnS6ujcDoR2OOcFaqrLIo1xbcdxje4Vf+VgJdBzNNbrAjBj/rLZ66/tlQ1knIGNLKOBQ==} + + '@tailwindcss/typography@0.5.16': + resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + + '@tanstack/query-core@5.81.2': + resolution: {integrity: sha512-QLYkPdrudoMATDFa3MiLEwRhNnAlzHWDf0LKaXUqJd0/+QxN8uTPi7bahRlxoAyH0UbLMBdeDbYzWALj7THOtw==} + + '@tanstack/query-devtools@5.76.0': + resolution: {integrity: sha512-1p92nqOBPYVqVDU0Ua5nzHenC6EGZNrLnB2OZphYw8CNA1exuvI97FVgIKON7Uug3uQqvH/QY8suUKpQo8qHNQ==} + + '@tanstack/react-query-devtools@5.76.1': + resolution: {integrity: sha512-LFVWgk/VtXPkerNLfYIeuGHh0Aim/k9PFGA+JxLdRaUiroQ4j4eoEqBrUpQ1Pd/KXoG4AB9vVE/M6PUQ9vwxBQ==} + peerDependencies: + '@tanstack/react-query': ^5.76.1 + react: ^18 || ^19 + + '@tanstack/react-query@5.81.2': + resolution: {integrity: sha512-pe8kFlTrL2zFLlcAj2kZk9UaYYHDk9/1hg9EBaoO3cxDhOZf1FRGJeziSXKrVZyxIfs7b3aoOj/bw7Lie0mDUg==} + peerDependencies: + react: ^18 || ^19 + + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.6.3': + resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@tiptap-extend/emoji-replacer@2.1.6': + resolution: {integrity: sha512-OF7RoEX29u6u10u2/DfT8slvHuvFpq0oSJJTZG7pjO/pIuEILDgW9ofI13RpZ4KTggzEjRYbeGERMZa2UDJuXA==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/core@2.22.3': + resolution: {integrity: sha512-czyBPXZG/ZFyObZEF1kyusGf58Ai3X8TnaxlUUn3gqLLWPy0idXZg85NETCidzi/gAxWxL9j6Pcy+zwS4pbZYQ==} + peerDependencies: + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-blockquote@2.22.3': + resolution: {integrity: sha512-HvTXvqeGaANg0owk0Xxkgyc4lJMO5CZES2Lc3JJp8u5kV+HZIwd78eJ7fbKBMtkpKb4zOk4xQsHQ/TuhghJaeA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bold@2.22.3': + resolution: {integrity: sha512-J3GxKwijD42eqCwU1SS7PK5aSgnp0wgQDetLz9izAD0RQBrKj5WZA13GnPoTTlzLU4qwjcPRV+6mvF+llH6b6A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bubble-menu@2.22.3': + resolution: {integrity: sha512-8iQLNrRf3iBPKqI3dQnfvMxMfgp6y9TAbO803LihvzbIGqBaX264ES7fHtoyFIIeVjy2xFruVsTZCZofWTupGg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-bullet-list@2.22.3': + resolution: {integrity: sha512-SYvLIxqmuV0kTj4/3ZFlnZ1fr9Y233qX00BKuIpGnczeFsWQmzBJo8vGm3d1IlKPCQN+jTRtDdDE1aSum8Kv2w==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-code-block@2.22.3': + resolution: {integrity: sha512-twPCBpb/ygNixlSBAXgvfo+t56Ucpb8lvPDiZn+cH8OjmmO0ayBoSfSrjKWgaEWGPcXBrFAfsBRbYHyoHj7pXg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-code@2.22.3': + resolution: {integrity: sha512-s+W6jHezq+n9cC40xZ3hZF6cGGSl+fBELik1b2x8+cb0WoIlqmcdWin1dgeMNrWlRZUw1aD2DNwy/PdXI5vn2g==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-document@2.22.3': + resolution: {integrity: sha512-7MnILbhRZRyROlMUgyntzRZ/EZlqNB8fO761RNjJxR2WMb49R4yc04fz7/+f/QH/hwxoS13bKfsNUDAsDxA5Aw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-dropcursor@2.22.3': + resolution: {integrity: sha512-yQxSfTWjdUQS+bh6KiNLR9KIMsn1SElzycQe4XE+0eoaetapGtKqxfwkTbbQdNgQOU5wQG1KOda221mnPvkpAA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-floating-menu@2.22.3': + resolution: {integrity: sha512-GeJRRdulxpwsshxzBkpOf/xJkLD2fa+49o+3FqRCmrm7AioC8oUcZZmzuzjLj5a3ZNGKPuJ9xxDkYWUjH4tE1g==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-gapcursor@2.22.3': + resolution: {integrity: sha512-6Q8TLL4PVGcZLn27eQazCC+be8LP8uzuz5Z5e4TpIeswPAju49cerQOdEGNFKkuYv/FelWIhXNtkWFMf4eSmyw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-hard-break@2.22.3': + resolution: {integrity: sha512-tbEji/V4Za3UhxYwB36amYhyonwe5j66iYTNRWzgjNixjrcGDbWk6cfaF9jMAgPgIDBmmtQLJY+moKskwgpnZg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-heading@2.22.3': + resolution: {integrity: sha512-+MexJD+kXtNwMDbNTFa7jCFipx1DqAdT+n9GgInqebAN9bK+CWjC+SskzZNRqeMrQ0Er7QTsi6YC09M+74sevA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-highlight@2.22.3': + resolution: {integrity: sha512-cdPSeQ3QcThhJdzkjK9a1871uPQjwmOf0WzTGW33lJyJDQHypWIRNUus56c3pGA7BgV9P59QW7Fm8rDnM8XkbA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-history@2.22.3': + resolution: {integrity: sha512-F9sC45zPw7vbjKrwSKuSLZ0ODyc/X3bGPeCa6HYLEHKfgqsdt2v2fQLvxjpmlwO2ZMrnkBkg76KDxHfVyrZ2zQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-horizontal-rule@2.22.3': + resolution: {integrity: sha512-3GvY798p9pCXUBbCebIdSmi1q80l7VZz/B6NN4uUMQ9iwxWopd8yaZ0O7xx2hM2UBzPEtY3M4FAhhpYUTXNFgQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-italic@2.22.3': + resolution: {integrity: sha512-W/rQDo7qFL7MfwfaYEcdtbk862fOmBv30qIEwVdqElBye7BFJYKtRuWBzNbG2BwKanjwMbVc/tBXF5W1sqfT7Q==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-list-item@2.22.3': + resolution: {integrity: sha512-B7Fze+eM1sYbGOZtDDAwAivnj1ow2wN5RqaQPC1la3wdTK4Wgp7bdzGjvUbrN6gp3zMFCEWlqP2toc/mRAHCtA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-ordered-list@2.22.3': + resolution: {integrity: sha512-pHGkuZhV/uAAHI9vzk/lpAkbdpMT4wUR1FI17/GE3zNrogfzx0VopCQrXq4+sQVsLUW4I6Cj6VeBjm9wB6qlIw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-paragraph@2.22.3': + resolution: {integrity: sha512-TYvgS7CweNFo/xVxsKWSt0wnm46Y8OtsfDSjnLbSC4Pj4ZNa6PU3zpvDTW+UxYakr+8zIPvI2WgLBkyTHq6oQA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-strike@2.22.3': + resolution: {integrity: sha512-I+s2Csw2cTHae2vFJiojnHK+NnQjDr6441mSlAd+e7kEly1kjZ4g7J+JMj02ajNQhr/ob8/hb5r6EdIyv2xtoA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-task-item@2.22.3': + resolution: {integrity: sha512-aVfSa2dLF77bfXpAlrsfPUNdhiHJhw3VJ/pnCTxrEnBXYilDuH59AhtU6DygSNhMZWUgzI4OPqf3crF+yzrHag==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-task-list@2.22.3': + resolution: {integrity: sha512-prOnD/S6mHOhzj2CLvd4Q/GJymyJMcdgTTJaI+Yk/Plup1OuH6fRlBdo67Tve0xzeQz4sfxrzp9kQ6EsEwhv0w==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text-style@2.22.3': + resolution: {integrity: sha512-M3FLOUPcO8fR+rM97mR2gQ54KFkdlAUQtEPKQpO1f312gtcVdBNxgq0WgqTnBY7thWLyqQSKiAsL6y88+JddSA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text@2.22.3': + resolution: {integrity: sha512-07cymWkPTfq6nuum88Yf90YYArbowed8nNiu0Tw3jCvwpzf9J9TDaovT+LAKuSKtrOsnNpFB/9IqUwFxZepOGw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-typography@2.22.3': + resolution: {integrity: sha512-pkZUMSDnt1vAR8XwoO9um7WDzBkclSYvWEBdqUN9pnH7Fpz2kbuer/Hqyk94vvWz2D/svndrg1Fb2dLEvfQopA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/pm@2.22.3': + resolution: {integrity: sha512-uWPeIScnpQVCYdTnL140XgcvbT1qH288CstMJ6S0Y11lC5PclPK9CxfAipsqgWWrIK7yatxKUVCg6TzfG9zpmA==} + + '@tiptap/react@2.22.3': + resolution: {integrity: sha512-Te6e6/carhUAavcJgxC03rffLAZz1or4cjnRDFNF8G4vPOqkNgQd368N47wTMjwh5mQTdMUUI3ToZIpc45Q7Tw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tiptap/starter-kit@2.22.3': + resolution: {integrity: sha512-GkvheaR2ORnHJ9g9R6xIT38w2uppGja/iAIrXLZ9vY1QuR+0cya/ZZ5vKU6r9C2PeyBs3aKYxRD1/j3HDhuGXw==} + + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.7': + resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/jsdom@20.0.1': + resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/node@20.19.1': + resolution: {integrity: sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA==} + + '@types/react-dom@19.1.6': + resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react@19.1.8': + resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} + + '@types/sinonjs__fake-timers@8.1.1': + resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} + + '@types/sizzle@2.3.9': + resolution: {integrity: sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@typescript-eslint/eslint-plugin@8.34.1': + resolution: {integrity: sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.34.1 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/parser@8.34.1': + resolution: {integrity: sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/project-service@8.34.1': + resolution: {integrity: sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.34.1': + resolution: {integrity: sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.34.1': + resolution: {integrity: sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/type-utils@8.34.1': + resolution: {integrity: sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/types@8.34.1': + resolution: {integrity: sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.34.1': + resolution: {integrity: sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.34.1': + resolution: {integrity: sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.34.1': + resolution: {integrity: sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@unrs/resolver-binding-android-arm-eabi@1.9.1': + resolution: {integrity: sha512-dd7yIp1hfJFX9ZlVLQRrh/Re9WMUHHmF9hrKD1yIvxcyNr2BhQ3xc1upAVhy8NijadnCswAxWQu8MkkSMC1qXQ==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.9.1': + resolution: {integrity: sha512-EzUPcMFtDVlo5yrbzMqUsGq3HnLXw+3ZOhSd7CUaDmbTtnrzM+RO2ntw2dm2wjbbc5djWj3yX0wzbbg8pLhx8g==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.9.1': + resolution: {integrity: sha512-nB+dna3q4kOleKFcSZJ/wDXIsAd1kpMO9XrVAt8tG3RDWJ6vi+Ic6bpz4cmg5tWNeCfHEY4KuqJCB+pKejPEmQ==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.9.1': + resolution: {integrity: sha512-aKWHCrOGaCGwZcekf3TnczQoBxk5w//W3RZ4EQyhux6rKDwBPgDU9Y2yGigCV1Z+8DWqZgVGQi+hdpnlSy3a1w==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.9.1': + resolution: {integrity: sha512-4dIEMXrXt0UqDVgrsUd1I+NoIzVQWXy/CNhgpfS75rOOMK/4Abn0Mx2M2gWH4Mk9+ds/ASAiCmqoUFynmMY5hA==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.9.1': + resolution: {integrity: sha512-vtvS13IXPs1eE8DuS/soiosqMBeyh50YLRZ+p7EaIKAPPeevRnA9G/wu/KbVt01ZD5qiGjxS+CGIdVC7I6gTOw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.9.1': + resolution: {integrity: sha512-BfdnN6aZ7NcX8djW8SR6GOJc+K+sFhWRF4vJueVE0vbUu5N1bLnBpxJg1TGlhSyo+ImC4SR0jcNiKN0jdoxt+A==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.9.1': + resolution: {integrity: sha512-Jhge7lFtH0QqfRz2PyJjJXWENqywPteITd+nOS0L6AhbZli+UmEyGBd2Sstt1c+l9C+j/YvKTl9wJo9PPmsFNg==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.9.1': + resolution: {integrity: sha512-ofdK/ow+ZSbSU0pRoB7uBaiRHeaAOYQFU5Spp87LdcPL/P1RhbCTMSIYVb61XWzsVEmYKjHFtoIE0wxP6AFvrA==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.9.1': + resolution: {integrity: sha512-eC8SXVn8de67HacqU7PoGdHA+9tGbqfEdD05AEFRAB81ejeQtNi5Fx7lPcxpLH79DW0BnMAHau3hi4RVkHfSCw==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.9.1': + resolution: {integrity: sha512-fIkwvAAQ41kfoGWfzeJ33iLGShl0JEDZHrMnwTHMErUcPkaaZRJYjQjsFhMl315NEQ4mmTlC+2nfK/J2IszDOw==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.9.1': + resolution: {integrity: sha512-RAAszxImSOFLk44aLwnSqpcOdce8sBcxASledSzuFAd8Q5ZhhVck472SisspnzHdc7THCvGXiUeZ2hOC7NUoBQ==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.9.1': + resolution: {integrity: sha512-QoP9vkY+THuQdZi05bA6s6XwFd6HIz3qlx82v9bTOgxeqin/3C12Ye7f7EOD00RQ36OtOPWnhEMMm84sv7d1XQ==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.9.1': + resolution: {integrity: sha512-/p77cGN/h9zbsfCseAP5gY7tK+7+DdM8fkPfr9d1ye1fsF6bmtGbtZN6e/8j4jCZ9NEIBBkT0GhdgixSelTK9g==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.9.1': + resolution: {integrity: sha512-wInTqT3Bu9u50mDStEig1v8uxEL2Ht+K8pir/YhyyrM5ordJtxoqzsL1vR/CQzOJuDunUTrDkMM0apjW/d7/PA==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-wasm32-wasi@1.9.1': + resolution: {integrity: sha512-eNwqO5kUa+1k7yFIircwwiniKWA0UFHo2Cfm8LYgkh9km7uMad+0x7X7oXbQonJXlqfitBTSjhA0un+DsHIrhw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.9.1': + resolution: {integrity: sha512-Eaz1xMUnoa2mFqh20mPqSdbYl6crnk8HnIXDu6nsla9zpgZJZO8w3c1gvNN/4Eb0RXRq3K9OG6mu8vw14gIqiA==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.9.1': + resolution: {integrity: sha512-H/+d+5BGlnEQif0gnwWmYbYv7HJj563PUKJfn8PlmzF8UmF+8KxdvXdwCsoOqh4HHnENnoLrav9NYBrv76x1wQ==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.9.1': + resolution: {integrity: sha512-rS86wI4R6cknYM3is3grCb/laE8XBEbpWAMSIPjYfmYp75KL5dT87jXF2orDa4tQYg5aajP5G8Fgh34dRyR+Rw==} + cpu: [x64] + os: [win32] + + abab@2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead + + acorn-globals@7.0.1: + resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arch@2.2.0: + resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + + assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + aws-sign2@0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + + aws4@1.13.2: + resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} + + axe-core@4.10.3: + resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-preset-current-node-syntax@1.1.0: + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + + blob-util@2.0.2: + resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.25.0: + resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + cachedir@2.4.0: + resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} + engines: {node: '>=6'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001724: + resolution: {integrity: sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==} + + caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + check-more-types@2.24.0: + resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} + engines: {node: '>= 0.8.0'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + ci-info@4.2.0: + resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==} + engines: {node: '>=8'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-table3@0.6.1: + resolution: {integrity: sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==} + engines: {node: 10.* || >= 12.*} + + cli-truncate@2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + + common-tags@1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssom@0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + + cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + + cssstyle@2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} + + cssstyle@4.5.0: + resolution: {integrity: sha512-/7gw8TGrvH/0g564EnhgFZogTMVe+lifpB7LWU+PEsiq5o83TUXR3fDbzTRXOJhoJwck5IS9ez3Em5LNMMO2aw==} + engines: {node: '>=18'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + cypress@14.5.0: + resolution: {integrity: sha512-1HOnKvWep0LkWuFwPeWkZ0TDl7ivi2/Mz+WNU4dfkeLJaFndS3Ow6TXT7YjuTqLFI2peJKzPKljVUFdymI2K5g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + dashdash@1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} + + data-urls@3.0.2: + resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} + engines: {node: '>=12'} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.5.0: + resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + + dedent@1.6.0: + resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + domexception@4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + deprecated: Use your platform's native DOMException instead + + dompurify@3.2.6: + resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ecc-jsbn@0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + electron-to-chromium@1.5.171: + resolution: {integrity: sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + engine.io-client@6.6.3: + resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==} + + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + engines: {node: '>=10.13.0'} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + eslint-config-next@15.3.2: + resolution: {integrity: sha512-FerU4DYccO4FgeYFFglz0SnaKRe1ejXQrDb8kWUkTAg036YWi+jUsgg4sIGNCDhAsDITsZaL4MzBWKB6f4G1Dg==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.29.0: + resolution: {integrity: sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter2@6.4.7: + resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==} + + execa@4.1.0: + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + executable@4.1.1: + resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} + engines: {node: '>=4'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + extsprintf@1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + forever-agent@0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + + form-data@4.0.3: + resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} + engines: {node: '>= 6'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + getos@3.2.1: + resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} + + getpass@0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + global-dirs@3.0.1: + resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} + engines: {node: '>=10'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + graphql@16.11.0: + resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasha@5.2.2: + resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + + html-encoding-sniffer@3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http-signature@1.4.0: + resolution: {integrity: sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==} + engines: {node: '>=0.10'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@2.0.0: + resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} + engines: {node: '>=10'} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-installed-globally@0.4.0: + resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} + engines: {node: '>=10'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isomorphic-dompurify@2.25.0: + resolution: {integrity: sha512-bcpJzu9DOjN21qaCVpcoCwUX1ytpvA6EFqCK5RNtPg5+F0Jz9PX50jl6jbEicBNeO87eDDfC7XtPs4zjDClZJg==} + engines: {node: '>=18'} + + isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jake@10.9.2: + resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} + engines: {node: '>=10'} + hasBin: true + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + 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 + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-jsdom@29.7.0: + resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-fixed-jsdom@0.0.9: + resolution: {integrity: sha512-KPfqh2+sn5q2B+7LZktwDcwhCpOpUSue8a1I+BcixWLOQoEVyAjAGfH+IYZGoxZsziNojoHGRTC8xRbB1wDD4g==} + engines: {node: '>=18.0.0'} + peerDependencies: + jest-environment-jsdom: '>=28.0.0' + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsbn@0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + + jsdom@20.0.3: + resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} + engines: {node: '>=14'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + + jsdom@26.1.0: + resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + jsprim@2.0.2: + resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} + engines: {'0': node >=0.6.0} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + lazy-ass@1.6.0: + resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} + engines: {node: '> 0.8'} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + listr2@3.14.0: + resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==} + engines: {node: '>=10.0.0'} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.castarray@4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + log-update@4.0.0: + resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} + engines: {node: '>=10'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.511.0: + resolution: {integrity: sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + + mock-socket@9.3.1: + resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==} + engines: {node: '>= 8'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msw@2.10.2: + resolution: {integrity: sha512-RCKM6IZseZQCWcSWlutdf590M8nVfRHG1ImwzOtwz8IYxgT4zhUO0rfTcTvDGiaFE0Rhcc+h43lcF3Jc9gFtwQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.2.4: + resolution: {integrity: sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + + next@15.3.2: + resolution: {integrity: sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nwsapi@2.2.20: + resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + + ospath@1.2.2: + resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==} + + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-bytes@5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + prosemirror-changeset@2.3.1: + resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==} + + prosemirror-collab@1.3.1: + resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} + + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + + prosemirror-dropcursor@1.8.2: + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} + + prosemirror-gapcursor@1.3.2: + resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==} + + prosemirror-history@1.4.1: + resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==} + + prosemirror-inputrules@1.5.0: + resolution: {integrity: sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-markdown@1.13.2: + resolution: {integrity: sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==} + + prosemirror-menu@1.2.5: + resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==} + + prosemirror-model@1.25.1: + resolution: {integrity: sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==} + + prosemirror-schema-basic@1.2.4: + resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.3: + resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} + + prosemirror-tables@1.7.1: + resolution: {integrity: sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==} + + prosemirror-trailing-node@3.0.0: + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} + peerDependencies: + prosemirror-model: ^1.22.1 + prosemirror-state: ^1.4.2 + prosemirror-view: ^1.33.8 + + prosemirror-transform@1.10.4: + resolution: {integrity: sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==} + + prosemirror-view@1.40.0: + resolution: {integrity: sha512-2G3svX0Cr1sJjkD/DYWSe3cfV5VPVTBOxI9XQEGWJDFEpsZb/gh4MV29ctv+OJx2RFX4BLt09i+6zaGM/ldkCw==} + + proxy-from-env@1.0.0: + resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} + + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-day-picker@8.10.1: + resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==} + peerDependencies: + date-fns: ^2.28.0 || ^3.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + peerDependencies: + react: ^19.1.0 + + react-hook-form@7.58.1: + resolution: {integrity: sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + react-intersection-observer@9.16.0: + resolution: {integrity: sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + react-dom: + optional: true + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + request-progress@3.0.0: + resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + sharp@0.34.2: + resolution: {integrity: sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + socket.io-client@4.8.1: + resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==} + engines: {node: '>=10.0.0'} + + socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + + sonner@2.0.5: + resolution: {integrity: sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + sshpk@1.18.0: + resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} + engines: {node: '>=0.10.0'} + hasBin: true + + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + + tailwindcss@4.1.10: + resolution: {integrity: sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==} + + tapable@2.2.2: + resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + engines: {node: '>=6'} + + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + throttleit@1.0.1: + resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tippy.js@6.3.7: + resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} + + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + + tmp@0.2.3: + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + tr46@3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-jest@29.4.0: + resolution: {integrity: sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: '*' + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + tw-animate-css@1.3.4: + resolution: {integrity: sha512-dd1Ht6/YQHcNbq0znIT6dG8uhO7Ce+VIIhZUhjsryXsMPJQz3bZg7Q2eNzLwipb25bRZslGb2myio5mScd1TFg==} + + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unrs-resolver@1.9.1: + resolution: {integrity: sha512-4AZVxP05JGN6DwqIkSP4VKLOcwQa5l37SWHF/ahcuqBMbfxbpN1L1QKafEhWCziHhzKex9H/AR09H0OuVyU+9g==} + + untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + verror@1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + w3c-xmlserializer@4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + 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 + + ws@8.18.2: + resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} + 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 + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + xmlhttprequest-ssl@2.1.2: + resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} + engines: {node: '>=0.4.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + + zod@3.25.67: + resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} + + zustand@5.0.5: + resolution: {integrity: sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@adobe/css-tools@4.4.3': {} + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.27.5': {} + + '@babel/core@7.27.4': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4) + '@babel/helpers': 7.27.6 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + convert-source-map: 2.0.0 + debug: 4.4.1(supports-color@8.1.1) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.27.5': + dependencies: + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.27.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.27.6': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.27.6 + + '@babel/parser@7.27.5': + dependencies: + '@babel/types': 7.27.6 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/runtime@7.27.6': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + + '@babel/traverse@7.27.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/types': 7.27.6 + debug: 4.4.1(supports-color@8.1.1) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.27.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@bcoe/v8-coverage@0.2.3': {} + + '@bundled-es-modules/cookie@2.0.1': + dependencies: + cookie: 0.7.2 + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.2 + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@csstools/color-helpers@5.0.2': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.0.2 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@cypress/request@3.0.8': + dependencies: + aws-sign2: 0.7.0 + aws4: 1.13.2 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 4.0.3 + http-signature: 1.4.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + performance-now: 2.1.0 + qs: 6.14.0 + safe-buffer: 5.2.1 + tough-cookie: 5.1.2 + tunnel-agent: 0.6.0 + uuid: 8.3.2 + + '@cypress/xvfb@1.2.4(supports-color@8.1.1)': + dependencies: + debug: 3.2.7(supports-color@8.1.1) + lodash.once: 4.1.1 + transitivePeerDependencies: + - supports-color + + '@emnapi/core@1.4.3': + dependencies: + '@emnapi/wasi-threads': 1.0.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.4.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.0.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.7.0(eslint@9.29.0(jiti@2.4.2))': + dependencies: + eslint: 9.29.0(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.20.1': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.1(supports-color@8.1.1) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.2.3': {} + + '@eslint/core@0.14.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/core@0.15.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.1(supports-color@8.1.1) + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.29.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.2': + dependencies: + '@eslint/core': 0.15.0 + levn: 0.4.1 + + '@floating-ui/core@1.7.1': + dependencies: + '@floating-ui/utils': 0.2.9 + + '@floating-ui/dom@1.7.1': + dependencies: + '@floating-ui/core': 1.7.1 + '@floating-ui/utils': 0.2.9 + + '@floating-ui/react-dom@2.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/dom': 1.7.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@floating-ui/utils@0.2.9': {} + + '@hookform/resolvers@5.1.1(react-hook-form@7.58.1(react@19.1.0))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.58.1(react@19.1.0) + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@img/sharp-darwin-arm64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.1.0 + optional: true + + '@img/sharp-darwin-x64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.1.0 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.1.0': + optional: true + + '@img/sharp-libvips-darwin-x64@1.1.0': + optional: true + + '@img/sharp-libvips-linux-arm64@1.1.0': + optional: true + + '@img/sharp-libvips-linux-arm@1.1.0': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.1.0': + optional: true + + '@img/sharp-libvips-linux-s390x@1.1.0': + optional: true + + '@img/sharp-libvips-linux-x64@1.1.0': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + optional: true + + '@img/sharp-linux-arm64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.1.0 + optional: true + + '@img/sharp-linux-arm@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.1.0 + optional: true + + '@img/sharp-linux-s390x@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.1.0 + optional: true + + '@img/sharp-linux-x64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.1.0 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + optional: true + + '@img/sharp-wasm32@0.34.2': + dependencies: + '@emnapi/runtime': 1.4.3 + optional: true + + '@img/sharp-win32-arm64@0.34.2': + optional: true + + '@img/sharp-win32-ia32@0.34.2': + optional: true + + '@img/sharp-win32-x64@0.34.2': + optional: true + + '@inquirer/confirm@5.1.12(@types/node@20.19.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@20.19.1) + '@inquirer/type': 3.0.7(@types/node@20.19.1) + optionalDependencies: + '@types/node': 20.19.1 + + '@inquirer/core@10.1.13(@types/node@20.19.1)': + dependencies: + '@inquirer/figures': 1.0.12 + '@inquirer/type': 3.0.7(@types/node@20.19.1) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 20.19.1 + + '@inquirer/figures@1.0.12': {} + + '@inquirer/type@3.0.7(@types/node@20.19.1)': + optionalDependencies: + '@types/node': 20.19.1 + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.1 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.1 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.1 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.19.1 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 20.19.1 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.27.4 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.19.1 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@mswjs/interceptors@0.39.2': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@napi-rs/wasm-runtime@0.2.11': + dependencies: + '@emnapi/core': 1.4.3 + '@emnapi/runtime': 1.4.3 + '@tybys/wasm-util': 0.9.0 + optional: true + + '@next/env@15.3.2': {} + + '@next/eslint-plugin-next@15.3.2': + dependencies: + fast-glob: 3.3.1 + + '@next/swc-darwin-arm64@15.3.2': + optional: true + + '@next/swc-darwin-x64@15.3.2': + optional: true + + '@next/swc-linux-arm64-gnu@15.3.2': + optional: true + + '@next/swc-linux-arm64-musl@15.3.2': + optional: true + + '@next/swc-linux-x64-gnu@15.3.2': + optional: true + + '@next/swc-linux-x64-musl@15.3.2': + optional: true + + '@next/swc-win32-arm64-msvc@15.3.2': + optional: true + + '@next/swc-win32-x64-msvc@15.3.2': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@nolyfill/is-core-module@1.0.39': {} + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + + '@popperjs/core@2.11.8': {} + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.2': {} + + '@radix-ui/react-alert-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-checkbox@1.3.2(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-direction@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-menu': 2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-menu@2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-popover@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react-dom': 2.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/rect': 1.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-radio-group@1.3.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-select@2.2.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-tabs@1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + use-sync-external-store: 1.5.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/rect@1.1.1': {} + + '@remirror/core-constants@3.0.0': {} + + '@rtsao/scc@1.1.0': {} + + '@rushstack/eslint-patch@1.11.0': {} + + '@sinclair/typebox@0.27.8': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@socket.io/component-emitter@3.1.2': {} + + '@standard-schema/utils@0.3.0': {} + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.1.10': + dependencies: + '@ampproject/remapping': 2.3.0 + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + lightningcss: 1.30.1 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.10 + + '@tailwindcss/oxide-android-arm64@4.1.10': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.10': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.10': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.10': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.10': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.10': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.10': + optional: true + + '@tailwindcss/oxide@4.1.10': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.10 + '@tailwindcss/oxide-darwin-arm64': 4.1.10 + '@tailwindcss/oxide-darwin-x64': 4.1.10 + '@tailwindcss/oxide-freebsd-x64': 4.1.10 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.10 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.10 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.10 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.10 + '@tailwindcss/oxide-linux-x64-musl': 4.1.10 + '@tailwindcss/oxide-wasm32-wasi': 4.1.10 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.10 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.10 + + '@tailwindcss/postcss@4.1.10': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.10 + '@tailwindcss/oxide': 4.1.10 + postcss: 8.5.6 + tailwindcss: 4.1.10 + + '@tailwindcss/typography@0.5.16(tailwindcss@4.1.10)': + dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + postcss-selector-parser: 6.0.10 + tailwindcss: 4.1.10 + + '@tanstack/query-core@5.81.2': {} + + '@tanstack/query-devtools@5.76.0': {} + + '@tanstack/react-query-devtools@5.76.1(@tanstack/react-query@5.81.2(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/query-devtools': 5.76.0 + '@tanstack/react-query': 5.81.2(react@19.1.0) + react: 19.1.0 + + '@tanstack/react-query@5.81.2(react@19.1.0)': + dependencies: + '@tanstack/query-core': 5.81.2 + react: 19.1.0 + + '@testing-library/dom@10.4.0': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.27.6 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.6.3': + dependencies: + '@adobe/css-tools': 4.4.3 + aria-query: 5.3.2 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash: 4.17.21 + redent: 3.0.0 + + '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@testing-library/dom': 10.4.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@tiptap-extend/emoji-replacer@2.1.6(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + tslib: 2.8.1 + + '@tiptap/core@2.22.3(@tiptap/pm@2.22.3)': + dependencies: + '@tiptap/pm': 2.22.3 + + '@tiptap/extension-blockquote@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-bold@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-bubble-menu@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3)': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + '@tiptap/pm': 2.22.3 + tippy.js: 6.3.7 + + '@tiptap/extension-bullet-list@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-code-block@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3)': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + '@tiptap/pm': 2.22.3 + + '@tiptap/extension-code@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-document@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-dropcursor@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3)': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + '@tiptap/pm': 2.22.3 + + '@tiptap/extension-floating-menu@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3)': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + '@tiptap/pm': 2.22.3 + tippy.js: 6.3.7 + + '@tiptap/extension-gapcursor@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3)': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + '@tiptap/pm': 2.22.3 + + '@tiptap/extension-hard-break@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-heading@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-highlight@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-history@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3)': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + '@tiptap/pm': 2.22.3 + + '@tiptap/extension-horizontal-rule@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3)': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + '@tiptap/pm': 2.22.3 + + '@tiptap/extension-italic@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-list-item@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-ordered-list@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-paragraph@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-strike@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-task-item@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3)': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + '@tiptap/pm': 2.22.3 + + '@tiptap/extension-task-list@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-text-style@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-text@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/extension-typography@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + + '@tiptap/pm@2.22.3': + dependencies: + prosemirror-changeset: 2.3.1 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.3.2 + prosemirror-history: 1.4.1 + prosemirror-inputrules: 1.5.0 + prosemirror-keymap: 1.2.3 + prosemirror-markdown: 1.13.2 + prosemirror-menu: 1.2.5 + prosemirror-model: 1.25.1 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.3 + prosemirror-tables: 1.7.1 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0) + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.0 + + '@tiptap/react@2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + '@tiptap/extension-bubble-menu': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3) + '@tiptap/extension-floating-menu': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3) + '@tiptap/pm': 2.22.3 + '@types/use-sync-external-store': 0.0.6 + fast-deep-equal: 3.1.3 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.5.0(react@19.1.0) + + '@tiptap/starter-kit@2.22.3': + dependencies: + '@tiptap/core': 2.22.3(@tiptap/pm@2.22.3) + '@tiptap/extension-blockquote': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-bold': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-bullet-list': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-code': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-code-block': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3) + '@tiptap/extension-document': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-dropcursor': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3) + '@tiptap/extension-gapcursor': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3) + '@tiptap/extension-hard-break': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-heading': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-history': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3) + '@tiptap/extension-horizontal-rule': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3))(@tiptap/pm@2.22.3) + '@tiptap/extension-italic': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-list-item': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-ordered-list': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-paragraph': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-strike': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-text': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/extension-text-style': 2.22.3(@tiptap/core@2.22.3(@tiptap/pm@2.22.3)) + '@tiptap/pm': 2.22.3 + + '@tootallnate/once@2.0.0': {} + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@tybys/wasm-util@0.9.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.7 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.27.6 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + + '@types/babel__traverse@7.20.7': + dependencies: + '@babel/types': 7.27.6 + + '@types/cookie@0.6.0': {} + + '@types/estree@1.0.8': {} + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 20.19.1 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/jsdom@20.0.1': + dependencies: + '@types/node': 20.19.1 + '@types/tough-cookie': 4.0.5 + parse5: 7.3.0 + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdurl@2.0.0': {} + + '@types/node@20.19.1': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.1.6(@types/react@19.1.8)': + dependencies: + '@types/react': 19.1.8 + + '@types/react@19.1.8': + dependencies: + csstype: 3.1.3 + + '@types/sinonjs__fake-timers@8.1.1': {} + + '@types/sizzle@2.3.9': {} + + '@types/stack-utils@2.0.3': {} + + '@types/statuses@2.0.6': {} + + '@types/tough-cookie@4.0.5': {} + + '@types/trusted-types@2.0.7': + optional: true + + '@types/use-sync-external-store@0.0.6': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 20.19.1 + optional: true + + '@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.34.1 + '@typescript-eslint/type-utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.34.1 + eslint: 9.29.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.34.1 + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.34.1 + debug: 4.4.1(supports-color@8.1.1) + eslint: 9.29.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.34.1(typescript@5.8.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.8.3) + '@typescript-eslint/types': 8.34.1 + debug: 4.4.1(supports-color@8.1.1) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.34.1': + dependencies: + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/visitor-keys': 8.34.1 + + '@typescript-eslint/tsconfig-utils@8.34.1(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + + '@typescript-eslint/type-utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.1(supports-color@8.1.1) + eslint: 9.29.0(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.34.1': {} + + '@typescript-eslint/typescript-estree@8.34.1(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.34.1(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.8.3) + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/visitor-keys': 8.34.1 + debug: 4.4.1(supports-color@8.1.1) + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.34.1 + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3) + eslint: 9.29.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.34.1': + dependencies: + '@typescript-eslint/types': 8.34.1 + eslint-visitor-keys: 4.2.1 + + '@unrs/resolver-binding-android-arm-eabi@1.9.1': + optional: true + + '@unrs/resolver-binding-android-arm64@1.9.1': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.9.1': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.9.1': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.9.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.9.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.9.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.9.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.9.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.9.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.9.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.9.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.9.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.9.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.9.1': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.9.1': + dependencies: + '@napi-rs/wasm-runtime': 0.2.11 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.9.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.9.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.9.1': + optional: true + + abab@2.0.6: {} + + acorn-globals@7.0.1: + dependencies: + acorn: 8.15.0 + acorn-walk: 8.3.4 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + agent-base@7.1.3: {} + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arch@2.2.0: {} + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assert-plus@1.0.0: {} + + ast-types-flow@0.0.8: {} + + astral-regex@2.0.0: {} + + async-function@1.0.0: {} + + async@3.2.6: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + aws-sign2@0.7.0: {} + + aws4@1.13.2: {} + + axe-core@4.10.3: {} + + axobject-query@4.1.0: {} + + babel-jest@29.7.0(@babel/core@7.27.4): + dependencies: + '@babel/core': 7.27.4 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.27.4) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.27.1 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.27.6 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.7 + + babel-preset-current-node-syntax@1.1.0(@babel/core@7.27.4): + dependencies: + '@babel/core': 7.27.4 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.27.4) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.27.4) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.27.4) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.27.4) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.27.4) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.27.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.27.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.27.4) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.27.4) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.27.4) + + babel-preset-jest@29.6.3(@babel/core@7.27.4): + dependencies: + '@babel/core': 7.27.4 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.27.4) + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + + blob-util@2.0.2: {} + + bluebird@3.7.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.25.0: + dependencies: + caniuse-lite: 1.0.30001724 + electron-to-chromium: 1.5.171 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.0) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-crc32@0.2.13: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + cachedir@2.4.0: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001724: {} + + caseless@0.12.0: {} + + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + + check-more-types@2.24.0: {} + + chownr@3.0.0: {} + + ci-info@3.9.0: {} + + ci-info@4.2.0: {} + + cjs-module-lexer@1.4.3: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clean-stack@2.2.0: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-table3@0.6.1: + dependencies: + string-width: 4.2.3 + optionalDependencies: + colors: 1.4.0 + + cli-truncate@2.1.0: + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + + cli-width@4.1.0: {} + + client-only@0.0.1: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.2: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + + colorette@2.0.20: {} + + colors@1.4.0: + optional: true + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@6.2.1: {} + + common-tags@1.8.2: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cookie@0.7.2: {} + + core-util-is@1.0.2: {} + + create-jest@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + create-require@1.1.1: {} + + crelt@1.0.6: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css.escape@1.5.1: {} + + cssesc@3.0.0: {} + + cssom@0.3.8: {} + + cssom@0.5.0: {} + + cssstyle@2.3.0: + dependencies: + cssom: 0.3.8 + + cssstyle@4.5.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + + csstype@3.1.3: {} + + cypress@14.5.0: + dependencies: + '@cypress/request': 3.0.8 + '@cypress/xvfb': 1.2.4(supports-color@8.1.1) + '@types/sinonjs__fake-timers': 8.1.1 + '@types/sizzle': 2.3.9 + arch: 2.2.0 + blob-util: 2.0.2 + bluebird: 3.7.2 + buffer: 5.7.1 + cachedir: 2.4.0 + chalk: 4.1.2 + check-more-types: 2.24.0 + ci-info: 4.2.0 + cli-cursor: 3.1.0 + cli-table3: 0.6.1 + commander: 6.2.1 + common-tags: 1.8.2 + dayjs: 1.11.13 + debug: 4.4.1(supports-color@8.1.1) + enquirer: 2.4.1 + eventemitter2: 6.4.7 + execa: 4.1.0 + executable: 4.1.1 + extract-zip: 2.0.1(supports-color@8.1.1) + figures: 3.2.0 + fs-extra: 9.1.0 + getos: 3.2.1 + hasha: 5.2.2 + is-installed-globally: 0.4.0 + lazy-ass: 1.6.0 + listr2: 3.14.0(enquirer@2.4.1) + lodash: 4.17.21 + log-symbols: 4.1.0 + minimist: 1.2.8 + ospath: 1.2.2 + pretty-bytes: 5.6.0 + process: 0.11.10 + proxy-from-env: 1.0.0 + request-progress: 3.0.0 + semver: 7.7.2 + supports-color: 8.1.1 + tmp: 0.2.3 + tree-kill: 1.2.2 + untildify: 4.0.0 + yauzl: 2.10.0 + + damerau-levenshtein@1.0.8: {} + + dashdash@1.14.1: + dependencies: + assert-plus: 1.0.0 + + data-urls@3.0.2: + dependencies: + abab: 2.0.6 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + date-fns@4.1.0: {} + + dayjs@1.11.13: {} + + debug@3.2.7(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + debug@4.4.1(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decimal.js@10.5.0: {} + + dedent@1.6.0: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.0.4: {} + + detect-newline@3.1.0: {} + + detect-node-es@1.1.0: {} + + diff-sequences@29.6.3: {} + + diff@4.0.2: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + domexception@4.0.0: + dependencies: + webidl-conversions: 7.0.0 + + dompurify@3.2.6: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ecc-jsbn@0.1.2: + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + + ejs@3.1.10: + dependencies: + jake: 10.9.2 + + electron-to-chromium@1.5.171: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + engine.io-client@6.6.3: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + engine.io-parser: 5.2.3 + ws: 8.17.1 + xmlhttprequest-ssl: 2.1.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + engine.io-parser@5.2.3: {} + + enhanced-resolve@5.18.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + entities@4.5.0: {} + + entities@6.0.1: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.24.0: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + eslint-config-next@15.3.2(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3): + dependencies: + '@next/eslint-plugin-next': 15.3.2 + '@rushstack/eslint-patch': 1.11.0 + '@typescript-eslint/eslint-plugin': 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.29.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.29.0(jiti@2.4.2)) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7(supports-color@8.1.1) + is-core-module: 2.16.1 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.4.2)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.1(supports-color@8.1.1) + eslint: 9.29.0(jiti@2.4.2) + get-tsconfig: 4.10.1 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.14 + unrs-resolver: 1.9.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.4.2)): + dependencies: + debug: 3.2.7(supports-color@8.1.1) + optionalDependencies: + '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.29.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7(supports-color@8.1.1) + doctrine: 2.1.0 + eslint: 9.29.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@9.29.0(jiti@2.4.2)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.10.3 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.29.0(jiti@2.4.2) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@5.2.0(eslint@9.29.0(jiti@2.4.2)): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + + eslint-plugin-react@7.37.5(eslint@9.29.0(jiti@2.4.2)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.29.0(jiti@2.4.2) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.29.0(jiti@2.4.2): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.1 + '@eslint/config-helpers': 0.2.3 + '@eslint/core': 0.14.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.29.0 + '@eslint/plugin-kit': 0.3.2 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1(supports-color@8.1.1) + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.4.2 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + eventemitter2@6.4.7: {} + + execa@4.1.0: + dependencies: + cross-spawn: 7.0.6 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + executable@4.1.1: + dependencies: + pify: 2.3.0 + + exit@0.1.2: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + extend@3.0.2: {} + + extract-zip@2.0.1(supports-color@8.1.1): + dependencies: + debug: 4.4.1(supports-color@8.1.1) + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + extsprintf@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.1: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + filelist@1.0.4: + dependencies: + minimatch: 5.1.6 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + forever-agent@0.6.1: {} + + form-data@4.0.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + + get-stream@6.0.1: {} + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + getos@3.2.1: + dependencies: + async: 3.2.6 + + getpass@0.1.7: + dependencies: + assert-plus: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + global-dirs@3.0.1: + dependencies: + ini: 2.0.0 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + graphql@16.11.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasha@5.2.2: + dependencies: + is-stream: 2.0.1 + type-fest: 0.8.1 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + headers-polyfill@4.0.3: {} + + html-encoding-sniffer@3.0.0: + dependencies: + whatwg-encoding: 2.0.0 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-escaper@2.0.2: {} + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + http-signature@1.4.0: + dependencies: + assert-plus: 1.0.0 + jsprim: 2.0.2 + sshpk: 1.18.0 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + human-signals@1.1.1: {} + + human-signals@2.1.0: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@2.0.0: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.2: + optional: true + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.7.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-installed-globally@0.4.0: + dependencies: + global-dirs: 3.0.1 + is-path-inside: 3.0.3 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-node-process@1.2.0: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-potential-custom-element-name@1.0.1: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-typedarray@1.0.0: {} + + is-unicode-supported@0.1.0: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isomorphic-dompurify@2.25.0: + dependencies: + dompurify: 3.2.6 + jsdom: 26.1.0 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + + isstream@0.1.2: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.27.4 + '@babel/parser': 7.27.5 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.27.4 + '@babel/parser': 7.27.5 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.1(supports-color@8.1.1) + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + jake@10.9.2: + dependencies: + async: 3.2.6 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.2 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.1 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.6.0 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)): + dependencies: + '@babel/core': 7.27.4 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.27.4) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.19.1 + ts-node: 10.9.2(@types/node@20.19.1)(typescript@5.8.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-jsdom@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/jsdom': 20.0.1 + '@types/node': 20.19.1 + jest-mock: 29.7.0 + jest-util: 29.7.0 + jsdom: 20.0.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.1 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-fixed-jsdom@0.0.9(jest-environment-jsdom@29.7.0): + dependencies: + jest-environment-jsdom: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 20.19.1 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.27.1 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.1 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.10 + resolve.exports: 2.0.3 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.1 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.1 + chalk: 4.1.2 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.27.4 + '@babel/generator': 7.27.5 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.4) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.4) + '@babel/types': 7.27.6 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.27.4) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.1 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.1 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@29.7.0: + dependencies: + '@types/node': 20.19.1 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jiti@2.4.2: {} + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsbn@0.1.1: {} + + jsdom@20.0.3: + dependencies: + abab: 2.0.6 + acorn: 8.15.0 + acorn-globals: 7.0.1 + cssom: 0.5.0 + cssstyle: 2.3.0 + data-urls: 3.0.2 + decimal.js: 10.5.0 + domexception: 4.0.0 + escodegen: 2.1.0 + form-data: 4.0.3 + 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.20 + parse5: 7.3.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + 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.18.2 + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsdom@26.1.0: + dependencies: + cssstyle: 4.5.0 + data-urls: 5.0.0 + decimal.js: 10.5.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.20 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.18.2 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema@0.4.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json-stringify-safe@5.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsprim@2.0.2: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + lazy-ass@1.6.0: {} + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + + lines-and-columns@1.2.4: {} + + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + listr2@3.14.0(enquirer@2.4.1): + dependencies: + cli-truncate: 2.1.0 + colorette: 2.0.20 + log-update: 4.0.0 + p-map: 4.0.0 + rfdc: 1.4.1 + rxjs: 7.8.2 + through: 2.3.8 + wrap-ansi: 7.0.0 + optionalDependencies: + enquirer: 2.4.1 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.castarray@4.4.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.memoize@4.1.2: {} + + lodash.merge@4.6.2: {} + + lodash.once@4.1.1: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + log-update@4.0.0: + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.511.0(react@19.1.0): + dependencies: + react: 19.1.0 + + lz-string@1.5.0: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + + math-intrinsics@1.1.0: {} + + mdurl@2.0.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + + mock-socket@9.3.1: {} + + ms@2.1.3: {} + + msw@2.10.2(@types/node@20.19.1)(typescript@5.8.3): + dependencies: + '@bundled-es-modules/cookie': 2.0.1 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.1.12(@types/node@20.19.1) + '@mswjs/interceptors': 0.39.2 + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.6 + graphql: 16.11.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + strict-event-emitter: 0.5.1 + type-fest: 4.41.0 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - '@types/node' + + mute-stream@2.0.0: {} + + nanoid@3.3.11: {} + + napi-postinstall@0.2.4: {} + + natural-compare@1.4.0: {} + + next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + next@15.3.2(@babel/core@7.27.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@next/env': 15.3.2 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 + busboy: 1.6.0 + caniuse-lite: 1.0.30001724 + postcss: 8.4.31 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + styled-jsx: 5.1.6(@babel/core@7.27.4)(react@19.1.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.3.2 + '@next/swc-darwin-x64': 15.3.2 + '@next/swc-linux-arm64-gnu': 15.3.2 + '@next/swc-linux-arm64-musl': 15.3.2 + '@next/swc-linux-x64-gnu': 15.3.2 + '@next/swc-linux-x64-musl': 15.3.2 + '@next/swc-win32-arm64-msvc': 15.3.2 + '@next/swc-win32-x64-msvc': 15.3.2 + sharp: 0.34.2 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-int64@0.4.0: {} + + node-releases@2.0.19: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nwsapi@2.2.20: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + orderedmap@2.1.1: {} + + ospath@1.2.2: {} + + outvariant@1.4.3: {} + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + p-try@2.2.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-to-regexp@6.3.0: {} + + pend@1.2.0: {} + + performance-now@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + possible-typed-array-names@1.1.0: {} + + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + pretty-bytes@5.6.0: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + process@0.11.10: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + prosemirror-changeset@2.3.1: + dependencies: + prosemirror-transform: 1.10.4 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.3 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.0 + + prosemirror-gapcursor@1.3.2: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-view: 1.40.0 + + prosemirror-history@1.4.1: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.0 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.0: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.3 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.2: + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.0 + prosemirror-model: 1.25.1 + + prosemirror-menu@1.2.5: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.4.1 + prosemirror-state: 1.4.3 + + prosemirror-model@1.25.1: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.1 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-state@1.4.3: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.0 + + prosemirror-tables@1.7.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.0 + + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0): + dependencies: + '@remirror/core-constants': 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-view: 1.40.0 + + prosemirror-transform@1.10.4: + dependencies: + prosemirror-model: 1.25.1 + + prosemirror-view@1.40.0: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + proxy-from-env@1.0.0: {} + + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode.js@2.3.1: {} + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + querystringify@2.2.0: {} + + queue-microtask@1.2.3: {} + + react-day-picker@8.10.1(date-fns@4.1.0)(react@19.1.0): + dependencies: + date-fns: 4.1.0 + react: 19.1.0 + + react-dom@19.1.0(react@19.1.0): + dependencies: + react: 19.1.0 + scheduler: 0.26.0 + + react-hook-form@7.58.1(react@19.1.0): + dependencies: + react: 19.1.0 + + react-intersection-observer@9.16.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0): + dependencies: + react: 19.1.0 + react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + + react-remove-scroll@2.7.1(@types/react@19.1.8)(react@19.1.0): + dependencies: + react: 19.1.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.1.8)(react@19.1.0) + react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.1.8)(react@19.1.0) + use-sidecar: 1.1.3(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + + react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.0): + dependencies: + get-nonce: 1.0.1 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + + react@19.1.0: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + request-progress@3.0.0: + dependencies: + throttleit: 1.0.1 + + require-directory@2.1.1: {} + + requires-port@1.0.0: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rope-sequence@1.3.4: {} + + rrweb-cssom@0.8.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.26.0: {} + + semver@6.3.1: {} + + semver@7.7.2: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + sharp@0.34.2: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.2 + '@img/sharp-darwin-x64': 0.34.2 + '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-ppc64': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-linux-arm': 0.34.2 + '@img/sharp-linux-arm64': 0.34.2 + '@img/sharp-linux-s390x': 0.34.2 + '@img/sharp-linux-x64': 0.34.2 + '@img/sharp-linuxmusl-arm64': 0.34.2 + '@img/sharp-linuxmusl-x64': 0.34.2 + '@img/sharp-wasm32': 0.34.2 + '@img/sharp-win32-arm64': 0.34.2 + '@img/sharp-win32-ia32': 0.34.2 + '@img/sharp-win32-x64': 0.34.2 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + slice-ansi@3.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + socket.io-client@4.8.1: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + engine.io-client: 6.6.3 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + sonner@2.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + source-map-js@1.2.1: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sprintf-js@1.0.3: {} + + sshpk@1.18.0: + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + + stable-hash@0.0.5: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + statuses@2.0.2: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + streamsearch@1.1.0: {} + + strict-event-emitter@0.5.1: {} + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.0 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@3.0.0: {} + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + styled-jsx@5.1.6(@babel/core@7.27.4)(react@19.1.0): + dependencies: + client-only: 0.0.1 + react: 19.1.0 + optionalDependencies: + '@babel/core': 7.27.4 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + tailwind-merge@3.3.1: {} + + tailwindcss@4.1.10: {} + + tapable@2.2.2: {} + + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + throttleit@1.0.1: {} + + through@2.3.8: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + + tippy.js@6.3.7: + dependencies: + '@popperjs/core': 2.11.8 + + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + + tmp@0.2.3: {} + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@3.0.0: + dependencies: + punycode: 2.3.1 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + ts-api-utils@2.1.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + ts-jest@29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)))(typescript@5.8.3): + dependencies: + bs-logger: 0.2.6 + ejs: 3.1.10 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.2 + type-fest: 4.41.0 + typescript: 5.8.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.27.4 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.27.4) + jest-util: 29.7.0 + + ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.1 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.8.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + tw-animate-css@1.3.4: {} + + tweetnacl@0.14.5: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + type-fest@0.8.1: {} + + type-fest@4.41.0: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@5.8.3: {} + + uc.micro@2.1.0: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + + universalify@0.2.0: {} + + universalify@2.0.1: {} + + unrs-resolver@1.9.1: + dependencies: + napi-postinstall: 0.2.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.9.1 + '@unrs/resolver-binding-android-arm64': 1.9.1 + '@unrs/resolver-binding-darwin-arm64': 1.9.1 + '@unrs/resolver-binding-darwin-x64': 1.9.1 + '@unrs/resolver-binding-freebsd-x64': 1.9.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.9.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.9.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.9.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.9.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.9.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.9.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.9.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.9.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.9.1 + '@unrs/resolver-binding-linux-x64-musl': 1.9.1 + '@unrs/resolver-binding-wasm32-wasi': 1.9.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.9.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.9.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.9.1 + + untildify@4.0.0: {} + + update-browserslist-db@1.1.3(browserslist@4.25.0): + dependencies: + browserslist: 4.25.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + use-callback-ref@1.3.3(@types/react@19.1.8)(react@19.1.0): + dependencies: + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + + use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + + use-sync-external-store@1.5.0(react@19.1.0): + dependencies: + react: 19.1.0 + + util-deprecate@1.0.2: {} + + uuid@8.3.2: {} + + v8-compile-cache-lib@3.0.1: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + verror@1.10.0: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + + w3c-keyname@2.2.8: {} + + w3c-xmlserializer@4.0.0: + dependencies: + xml-name-validator: 4.0.0 + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@2.0.0: + dependencies: + iconv-lite: 0.6.3 + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@3.0.0: {} + + whatwg-mimetype@4.0.0: {} + + whatwg-url@11.0.0: + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + ws@8.17.1: {} + + ws@8.18.2: {} + + xml-name-validator@4.0.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + xmlhttprequest-ssl@2.1.2: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@5.0.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} + + yoctocolors-cjs@2.1.2: {} + + zod@3.25.67: {} + + zustand@5.0.5(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)): + optionalDependencies: + '@types/react': 19.1.8 + react: 19.1.0 + use-sync-external-store: 1.5.0(react@19.1.0) diff --git a/public/icons/alarm-active.svg b/public/icons/alarm-active.svg new file mode 100644 index 00000000..5d1a3920 --- /dev/null +++ b/public/icons/alarm-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/alarm-default.svg b/public/icons/alarm-default.svg new file mode 100644 index 00000000..f6d9e4a3 --- /dev/null +++ b/public/icons/alarm-default.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/bookmark-active.svg b/public/icons/bookmark-active.svg new file mode 100644 index 00000000..ac123c5f --- /dev/null +++ b/public/icons/bookmark-active.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/bookmark-default.svg b/public/icons/bookmark-default.svg new file mode 100644 index 00000000..3738f11f --- /dev/null +++ b/public/icons/bookmark-default.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/btn_edit.svg b/public/icons/btn_edit.svg new file mode 100644 index 00000000..f37a9c79 --- /dev/null +++ b/public/icons/btn_edit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icons/menu.svg b/public/icons/menu.svg new file mode 100644 index 00000000..7158816b --- /dev/null +++ b/public/icons/menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/more.svg b/public/icons/more.svg new file mode 100644 index 00000000..ad147f27 --- /dev/null +++ b/public/icons/more.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/public/icons/person.svg b/public/icons/person.svg new file mode 100644 index 00000000..ebac668a --- /dev/null +++ b/public/icons/person.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/plus.svg b/public/icons/plus.svg new file mode 100644 index 00000000..9ba50e78 --- /dev/null +++ b/public/icons/plus.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/icons/search.svg b/public/icons/search.svg new file mode 100644 index 00000000..356e64d2 --- /dev/null +++ b/public/icons/search.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/public/icons/x.svg b/public/icons/x.svg new file mode 100644 index 00000000..52629cd5 --- /dev/null +++ b/public/icons/x.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/default-profile.png b/public/images/default-profile.png new file mode 100644 index 00000000..325697c7 Binary files /dev/null and b/public/images/default-profile.png differ diff --git a/public/logos/HTMLCSS.png b/public/logos/HTMLCSS.png new file mode 100644 index 00000000..90dba7e7 Binary files /dev/null and b/public/logos/HTMLCSS.png differ diff --git a/public/logos/Java.png b/public/logos/Java.png new file mode 100644 index 00000000..c9f125c2 Binary files /dev/null and b/public/logos/Java.png differ diff --git a/public/logos/JavaScript.png b/public/logos/JavaScript.png new file mode 100644 index 00000000..2950f64d Binary files /dev/null and b/public/logos/JavaScript.png differ diff --git a/public/logos/Kotlin.png b/public/logos/Kotlin.png new file mode 100644 index 00000000..59e56d8c Binary files /dev/null and b/public/logos/Kotlin.png differ diff --git a/public/logos/REACT.png b/public/logos/REACT.png new file mode 100644 index 00000000..ea4fa624 Binary files /dev/null and b/public/logos/REACT.png differ diff --git a/public/logos/Spring.png b/public/logos/Spring.png new file mode 100644 index 00000000..2370ccf8 Binary files /dev/null and b/public/logos/Spring.png differ diff --git a/public/logos/Vue.png b/public/logos/Vue.png new file mode 100644 index 00000000..b929f6a3 Binary files /dev/null and b/public/logos/Vue.png differ diff --git a/public/logos/logo-img.svg b/public/logos/logo-img.svg new file mode 100644 index 00000000..fac7e957 --- /dev/null +++ b/public/logos/logo-img.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/logos/logo-text.png b/public/logos/logo-text.png new file mode 100644 index 00000000..3c7794f8 Binary files /dev/null and b/public/logos/logo-text.png differ diff --git a/public/logos/my-img.png b/public/logos/my-img.png new file mode 100644 index 00000000..e5a8e643 Binary files /dev/null and b/public/logos/my-img.png differ diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js new file mode 100644 index 00000000..de7bc0f2 --- /dev/null +++ b/public/mockServiceWorker.js @@ -0,0 +1,344 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + */ + +const PACKAGE_VERSION = '2.10.2' +const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +addEventListener('install', function () { + self.skipWaiting() +}) + +addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +addEventListener('message', async function (event) { + const clientId = Reflect.get(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: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + 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 + } + } +}) + +addEventListener('fetch', function (event) { + // Bypass navigation requests. + if (event.request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if ( + event.request.cache === 'only-if-cached' && + event.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 + } + + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +/** + * @param {FetchEvent} event + * @param {string} requestId + */ +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const requestCloneForEvents = event.request.clone() + 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)) { + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Clone the response so both the client and the library could consume it. + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + headers: Object.fromEntries(responseClone.headers.entries()), + body: responseClone.body, + }, + }, + }, + responseClone.body ? [serializedRequest.body, responseClone.body] : [], + ) + } + + 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. + * @param {FetchEvent} event + * @returns {Promise} + */ +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + 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) + }) +} + +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @returns {Promise} + */ +async function getResponse(event, client, requestId) { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = event.request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const serializedRequest = await serializeRequest(event.request) + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + ...serializedRequest, + }, + }, + [serializedRequest.body], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [ + channel.port2, + ...transferrables.filter(Boolean), + ]) + }) +} + +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.arrayBuffer(), + keepalive: request.keepalive, + } +} diff --git a/src/actions/invalidate.ts b/src/actions/invalidate.ts new file mode 100644 index 00000000..f9c26c22 --- /dev/null +++ b/src/actions/invalidate.ts @@ -0,0 +1,11 @@ +'use server'; + +import { revalidateTag } from 'next/cache'; + +/** + * ์ฃผ์–ด์ง„ ํƒœ๊ทธ์— ์—ฐ๊ฒฐ๋œ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•ฉ๋‹ˆ๋‹ค. + * @param tag - fetch์˜ next: { tags } ์˜ต์…˜์—์„œ ์‚ฌ์šฉํ•œ ํƒœ๊ทธ ํ‚ค + */ +export const invalidateTag = async (tag: string) => { + await revalidateTag(tag); +}; diff --git a/src/api/request.ts b/src/api/request.ts new file mode 100644 index 00000000..ebc45431 --- /dev/null +++ b/src/api/request.ts @@ -0,0 +1,186 @@ +import fetchRefreshToken from '@/features/auth/utils/fetchRefreshToken'; +import useAuthStore from '@/stores/useAuthStore'; + +const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL; //ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ๋ถ„๋ฆฌ + +type RequestOptions = { + method?: 'GET' | 'POST' | 'PATCH' | 'DELETE'; + headers?: HeadersInit; + body?: BodyInit | object; + credentials?: RequestCredentials; +}; + +/** + * ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉ + * @param endpoint api ์—”๋“œํฌ์ธํŠธ + * @param queryParams ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ + * @returns ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์žˆ๋Š” ์—”๋“œํฌ์ธํŠธ + */ +const buildUrl = ( + endpoint: string, + queryParams?: Record>, +) => { + if (!queryParams) return `${baseUrl}${endpoint}`; + const queryString = Object.entries(queryParams) + .filter(([, value]) => { + return value !== 'null'; + }) // order๊ฐ€ desc์ธ ๊ฒฝ์šฐ cursor=null์ด๊ณ  ์ด๋Š” ์ œ์™ธํ•˜๊ณ  ์š”์ฒญํ•ด์•ผ ํ•จ + .map(([key, value]) => { + const encodedKey = encodeURIComponent(key); // ํŠน์ˆ˜๋ฌธ์ž, ํ•œ๊ธ€์ธ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ธ์ฝ”๋”ฉ + if (Array.isArray(value)) { + return `${encodedKey}=${value + .map((v) => encodeURIComponent(v)) + .join(',')}`; // ๋ฐฐ์—ด ๊ฒฝ์šฐ ๋ฐฐ์—ด ์š”์†Œ ์ธ์ฝ”๋”ฉ + } + return `${encodedKey}=${encodeURIComponent(value)}`; + }) + .join('&'); + return `${baseUrl}${endpoint}?${queryString}`; +}; + +/** + * ์š”์ฒญ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ + * @param url ์š”์ฒญ ์ฃผ์†Œ + * @param options ์š”์ฒญ ์˜ต์…˜ + * @returns ์š”์ฒญ ๊ฒฐ๊ณผ + */ +const fetchHandler = async ( + url: string, + { + method = 'GET', + headers, + body, + credentials = 'same-origin', + }: RequestOptions = {}, +) => { + let triedRefresh = false; // ํ† ํฐ ๊ฐฑ์‹  ์‹œ๋„ ์—ฌ๋ถ€ + const headersObj = new Headers(headers || {}); + + const makeRequest = async () => { + // ํ—ค๋”์— ์ปจํ…์ธ  ํƒ€์ž…์ด ์žˆ๋Š”์ง€ ํ™•์ธ + const contentType = + headersObj.get('content-type') || headersObj.get('Content-Type'); + const isJson = contentType?.toLowerCase().includes('application/json'); + + return fetch(url, { + method, + headers: headersObj, + body: + isJson && typeof body === 'object' + ? JSON.stringify(body) + : (body as BodyInit), + credentials, + }); + }; + + const response = await makeRequest(); + + if (!response.ok) { + if (response.status === 401 && !triedRefresh) { + try { + const success = await fetchRefreshToken(); + triedRefresh = true; + + if (!success) { + throw new Error('refresh ๋งŒ๋ฃŒ'); + } + + const response2 = await makeRequest(); + return response2.json(); + } catch (e) { + console.log(e); + + // ์‹คํŒจ์‹œ ๋กœ๊ทธ์•„์›ƒ ์š”์ฒญํ›„ + await request.post( + '/v1/user/logout', + { + 'Content-Type': 'application/json', + }, + '{}', + { + credentials: 'include', + }, + ); + + useAuthStore.setState(() => ({ + user: null, + })); + + throw new Error('refresh ๋งŒ๋ฃŒ'); + } + } + + const errorText = await response.text(); + switch (response.status) { + case 401: + throw new Error('Unauthorized'); + case 403: + throw new Error('Forbidden'); + case 404: + throw new Error('Not Found'); + default: + throw new Error( + `[${response.status}] ${response.statusText} - ${errorText}`, + ); + } + } + + return response.json(); +}; + +/** + * get, post, patch, delete ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฐ์ฒด + * @param endpoint: api ์—”๋“œํฌ์ธํŠธ + * @param queryParams: ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ + * @returns + */ +export const request = { + get: async ( + endpoint: string, + queryParams?: Record>, + options?: Pick, + headers?: HeadersInit, + ) => { + const url = buildUrl(endpoint, queryParams); + return await fetchHandler(url, { + method: 'GET', + credentials: options?.credentials ?? 'same-origin', // ๊ธฐ๋ณธ๊ฐ’ ์„ค์ • + headers, + }); + }, + post: async ( + endpoint: string, + headers: HeadersInit, + body?: BodyInit, + options?: Pick, + ) => { + return await fetchHandler(`${baseUrl}${endpoint}`, { + method: 'POST', + headers, + body, + credentials: options?.credentials ?? 'same-origin', + }); + }, + patch: async ( + endpoint: string, + headers: HeadersInit, + body: object, + options?: Pick, + ) => { + return await fetchHandler(`${baseUrl}${endpoint}`, { + method: 'PATCH', + headers, + body, + credentials: options?.credentials ?? 'same-origin', + }); + }, + delete: async ( + endpoint: string, + options?: Pick, + ) => { + return await fetchHandler(`${baseUrl}${endpoint}`, { + method: 'DELETE', + credentials: options?.credentials ?? 'same-origin', + }); + }, +}; diff --git a/src/app/(auth)/find-email/page.tsx b/src/app/(auth)/find-email/page.tsx new file mode 100644 index 00000000..1dab89a7 --- /dev/null +++ b/src/app/(auth)/find-email/page.tsx @@ -0,0 +1,17 @@ +'use client'; + +import Link from 'next/link'; +import FindEmailForm from '@/components/organisms/find-email-form'; +import { routes } from '@/utils/routes'; + +export default function Page() { + return ( + <> +

์ด๋ฉ”์ผ ์ฐพ๊ธฐ

+ + + ๋กœ๊ทธ์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ + + + ); +} diff --git a/src/app/(auth)/find-password/page.tsx b/src/app/(auth)/find-password/page.tsx new file mode 100644 index 00000000..f4bb1239 --- /dev/null +++ b/src/app/(auth)/find-password/page.tsx @@ -0,0 +1,17 @@ +'use client'; + +import Link from 'next/link'; +import FindPassword from '@/components/organisms/find-password-form'; +import { routes } from '@/utils/routes'; + +export default function Page() { + return ( + <> +

๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ

+ + + ๋กœ๊ทธ์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ + + + ); +} diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx new file mode 100644 index 00000000..b5322f90 --- /dev/null +++ b/src/app/(auth)/layout.tsx @@ -0,0 +1,31 @@ +import Image from 'next/image'; + +export default function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <> +
+
+

๋ชจ์—ฌ๋ผ! ์ด๊ณณ์œผ๋กœ!

+

+ ๋ชจ์—ฌ๋ผit์—์„œ ์‹ค๋ ฅ๊ณผ ํŒ€์›, +
๋‘ ๋งˆ๋ฆฌ ํ† ๋ผ๋ฅผ ๋ชจ๋‘ ์žก์œผ์„ธ์š” +

+ logo +
+
+ {children} +
+
+ + ); +} diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx new file mode 100644 index 00000000..7efc501c --- /dev/null +++ b/src/app/(auth)/login/page.tsx @@ -0,0 +1,35 @@ +import Link from 'next/link'; +import LoginForm from '@/components/organisms/login-form'; +import { headers } from 'next/headers'; +import LoginTriggerManager from '@/features/auth/components/LoginTriggerManager'; +import { routes } from '@/utils/routes'; + +export default async function Page() { + const headerList = await headers(); + // next-url: next์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š” ์ด์ „ path, Link๋‚˜ router.push, redirect๋กœ ์ด๋™ํ•  ๋•Œ ์ž๋“ฑ์œผ๋กœ ์ €์žฅ๋จ + const nextUrl = headerList.get('next-url') || '/'; + + return ( + <> +

๋กœ๊ทธ์ธ

+ + +
+
+

๋ชจ์—ฌ๋ผit์ด ์ฒ˜์Œ์ด์‹ ๊ฐ€์š”?

+ + ํšŒ์›๊ฐ€์ž… + +
+ + ์ด๋ฉ”์ผ ์ฐพ๊ธฐ + + + ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ + +
+ + + + ); +} diff --git a/src/app/(auth)/register/page.tsx b/src/app/(auth)/register/page.tsx new file mode 100644 index 00000000..9c498960 --- /dev/null +++ b/src/app/(auth)/register/page.tsx @@ -0,0 +1,28 @@ +'use client'; +import useAuthStore from '@/stores/useAuthStore'; +import Link from 'next/link'; +import RegisterForm from '@/components/organisms/register-form'; +import RegisterOptionalForm from '@/components/organisms/register-optional-form'; +import { routes } from '@/utils/routes'; + +export default function Page() { + const user = useAuthStore((store) => store.user); + + return ( + <> +

ํšŒ์›๊ฐ€์ž…

+ {/* ํšŒ์›๊ฐ€์ž… ์ „ ๋กœ๊ทธ์ธ์ด ๋˜์ง€ ์•Š์€ ์ƒํƒœ์˜ ๊ฒฝ์šฐ ํšŒ์›๊ฐ€์ž… ํผ์„ ์ถœ๋ ฅ */} + {!user && } + + {/* ํšŒ์›๊ฐ€์ž… ํ›„ ๋กœ๊ทธ์ธ์ƒํƒœ์—์„œ ์ถœ๋ ฅ, ์ด๋ฉ”์ผ์€ ์žˆ์ง€๋งŒ ์•„์ง ๋‹‰๋„ค์ž„์€ ์„ค์ •์ด ์•ˆ๋จ */} + {user && } + {/* ํšŒ์›๊ฐ€์ž… ํ›„ ๋กœ๊ทธ์ธ ๋œ ์ƒํƒœ์—์„  ํ˜ผ๋ž€ ๋ฐฉ์ง€์šฉ ๊ฒธ ๊ฐ€๊ธ‰์  ํ”„๋กœํ•„ ์„ค์ • */} + {!user && ( + + ์ด๋ฏธ ํšŒ์›์ด์‹ ๊ฐ€์š”? + ๋กœ๊ทธ์ธ ํ•˜๋Ÿฌ ๊ฐ€๊ธฐ + + )} + + ); +} diff --git a/src/app/(user)/layout.tsx b/src/app/(user)/layout.tsx new file mode 100644 index 00000000..39c31349 --- /dev/null +++ b/src/app/(user)/layout.tsx @@ -0,0 +1,38 @@ +// import checkAuthCookie from '@/features/auth/utils/checkAuthCookie'; +// import { redirect } from 'next/navigation'; +import { UserPageTabs } from '@/features/user/components/user-page-tabs'; +import { UserProfile } from '@/features/user/components/user-profile'; + +export default async function UserPageLayout({ + children, +}: { + children: React.ReactNode; +}) { + // ISSUE1: ์ฟ ํ‚ค๊ฐ€ ์žˆ๋‹ค๊ณ  ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋ฉด ์•ˆ๋จ -> ์ฟ ํ‚ค๋Š” ์œ ์ €๊ฐ€ ์ž„์˜๋กœ ์กฐ์ž‘๊ฐ€๋Šฅ, api ํ†ต์‹ ํ•„์š” + // ISSUE2: ๊ทผ๋ฐ ๋กœ๊ทธ์ธ ์•ˆํ–‡๋‹ค๊ณ  ํ•ด๋‹น ํŽ˜์ด์ง€ ์•ˆ๋ณด์—ฌ์ฃผ๋‚˜์š”? ์ผ๋‹จ ๊ฐ€๋“œ์ฒ˜๋ฆฌํ•ด๋’€์–ด์š” + + // const isValid = await checkAuthCookie(); + + // if (!isValid) { + // redirect('/login'); + // } + + // if (!hasToken) { + // redirect('/login'); + // } + + return ( +
+
+

์œ ์ € ํŽ˜์ด์ง€

+
+ +
+ +
{children}
+
+
+
+
+ ); +} diff --git a/src/app/(user)/users/[id]/groups/created/error.tsx b/src/app/(user)/users/[id]/groups/created/error.tsx new file mode 100644 index 00000000..c69af481 --- /dev/null +++ b/src/app/(user)/users/[id]/groups/created/error.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { startTransition } from 'react'; +import { handleError } from '@/components/error-boundary/error-handler'; + +export default function Error({ + error, + reset, +}: { + error: Error; + reset: () => void; +}) { + const router = useRouter(); + + return handleError({ + error, + resetErrorBoundary: () => { + router.refresh(); + startTransition(() => { + reset(); + }); + }, + }); +} diff --git a/src/app/(user)/users/[id]/groups/created/page.tsx b/src/app/(user)/users/[id]/groups/created/page.tsx new file mode 100644 index 00000000..0ad0b757 --- /dev/null +++ b/src/app/(user)/users/[id]/groups/created/page.tsx @@ -0,0 +1,101 @@ +import { Suspense } from 'react'; +import { + QueryClient, + HydrationBoundary, + dehydrate, +} from '@tanstack/react-query'; +import { QueryErrorBoundary } from '@/components/query-error-boundary'; +import { GroupList } from '@/features/user/group/components/group-list'; +import { request } from '@/api/request'; +import { getAuthCookieHeader } from '@/utils/cookie'; +import { GroupListLoading } from '@/features/user/group/components/group-list-loading'; + +type CreatedGroupsPageWrapperProps = { + params: Promise<{ id: string }>; + searchParams: Promise<{ + search: string; + type: string; + order: string; + }>; +} + +type CreatedGroupsPageProps = { + params: { id: string }; + searchParams: { + search: string; + type: string; + order: string; + }; +}; + +/** + * loading.tsx ํŒŒ์ผ์˜ ๋กœ๋”ฉ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ํ˜ธ์ถœ๋˜๋Š” ๋“ฏ ํ•จ. + * ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด๋„ loading.tsx ํŒŒ์ผ์˜ ๋กœ๋”ฉ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์Œ. + * ๊ทธ๋ž˜์„œ ๋”ฐ๋กœ Wrapper ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ๋กœ๋”ฉ ์ค‘์ด๋ฉด, ๋กœ๋”ฉ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ํ•จ. + */ +export default async function CreatedGroupsPageWrapper({ + params, + searchParams, +}: CreatedGroupsPageWrapperProps) { + const awaitedSearchParams = await searchParams; + const awaitedParams = await params; + return ( + } key={JSON.stringify(awaitedSearchParams)}> + + + ) +} + +const CreatedGroupsPage = async ({ + params, + searchParams, +}: CreatedGroupsPageProps) => { + const { search, type, order } = searchParams; + + const { id } = params; + + const cookieHeaderValue = await getAuthCookieHeader(); + + const queryClient = new QueryClient(); + + const queryParams = { + type: type ?? 'study', + status: 'PARTICIPATING', + ...(search && { search }), + size: 10, + order: order === 'latest' || !order ? 'desc' : 'asc', + }; + + await queryClient.fetchInfiniteQuery({ + queryKey: ['items', `/v2/groups/usergroup/${id}`, queryParams], + queryFn() { + return request.get( + `/v2/groups/usergroup/${id}`, + { + ...queryParams, + }, + { + credentials: 'include', + }, + { + Cookie: cookieHeaderValue, + }, + ); + }, + initialPageParam: 0, + retry: 0, + }); + + return ( + <> + + + + + + + ); +} diff --git a/src/app/(user)/users/[id]/groups/ended/error.tsx b/src/app/(user)/users/[id]/groups/ended/error.tsx new file mode 100644 index 00000000..c69af481 --- /dev/null +++ b/src/app/(user)/users/[id]/groups/ended/error.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { startTransition } from 'react'; +import { handleError } from '@/components/error-boundary/error-handler'; + +export default function Error({ + error, + reset, +}: { + error: Error; + reset: () => void; +}) { + const router = useRouter(); + + return handleError({ + error, + resetErrorBoundary: () => { + router.refresh(); + startTransition(() => { + reset(); + }); + }, + }); +} diff --git a/src/app/(user)/users/[id]/groups/ended/page.tsx b/src/app/(user)/users/[id]/groups/ended/page.tsx new file mode 100644 index 00000000..286c8b22 --- /dev/null +++ b/src/app/(user)/users/[id]/groups/ended/page.tsx @@ -0,0 +1,92 @@ +import { Suspense } from 'react'; +import { + QueryClient, + HydrationBoundary, + dehydrate, +} from '@tanstack/react-query'; +import { QueryErrorBoundary } from '@/components/query-error-boundary'; +import { GroupList } from '@/features/user/group/components/group-list'; +import { request } from '@/api/request'; +import { getAuthCookieHeader } from '@/utils/cookie'; +import { GroupListLoading } from '@/features/user/group/components/group-list-loading'; + +type EndedGroupsPageWrapperProps = { + params: Promise<{ id: string }>; + searchParams: Promise<{ + search: string; + type: string; + order: string; + }>; +}; + +type EndedGroupsPageProps = { + params: { id: string }; + searchParams: { + search: string; + type: string; + }; +}; + +export default async function EndedGroupsPageWrapper({ + params, + searchParams, +}: EndedGroupsPageWrapperProps) { + const awaitedSearchParams = await searchParams; + const awaitedParams = await params; + return ( + } + key={JSON.stringify(awaitedSearchParams)} + > + + + ); +} + +const EndedGroupsPage = async ({ + params, + searchParams, +}: EndedGroupsPageProps) => { + const { id } = params; + + const { search, type } = searchParams; + + const queryClient = new QueryClient(); + + const cookieHeaderValue = await getAuthCookieHeader(); + + const queryParams = { + type: type ?? 'study', + ...(search && { search }), + size: 50, + }; + + await queryClient.fetchInfiniteQuery({ + queryKey: ['items', `/v2/groups/usergroup/${id}`, queryParams], + queryFn({ pageParam }) { + return request.get( + `/v2/groups/usergroup/${id}`, + { + ...queryParams, + cursor: pageParam, + }, + { + credentials: 'include', + }, + { + Cookie: cookieHeaderValue, + }, + ); + }, + initialPageParam: 0, + retry: 0, + }); + + return ( + + + + + + ); +}; diff --git a/src/app/(user)/users/[id]/groups/layout.tsx b/src/app/(user)/users/[id]/groups/layout.tsx new file mode 100644 index 00000000..c05bc9f7 --- /dev/null +++ b/src/app/(user)/users/[id]/groups/layout.tsx @@ -0,0 +1,10 @@ +import { GroupFilter } from '@/components/molecules/group-filter/group-filter'; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + <> + + {children} + + ); +} diff --git a/src/app/(user)/users/[id]/page.tsx b/src/app/(user)/users/[id]/page.tsx new file mode 100644 index 00000000..8136cb92 --- /dev/null +++ b/src/app/(user)/users/[id]/page.tsx @@ -0,0 +1,62 @@ +import { Suspense } from 'react'; +import { + QueryClient, + HydrationBoundary, + dehydrate, +} from '@tanstack/react-query'; +import { QueryErrorBoundary } from '@/components/query-error-boundary'; +import { GroupList } from '@/features/user/group/components/group-list'; +import { request } from '@/api/request'; +import { getAuthCookieHeader } from '@/utils/cookie'; +import { GroupListLoading } from '@/features/user/group/components/group-list-loading'; + +type UserPageProps = { + params: Promise<{ id: string }>; +}; + +export default async function UserPage({ params }: UserPageProps) { + const { id } = await params; + + const cookieHeaderValue = await getAuthCookieHeader(); + + const queryClient = new QueryClient(); + + const queryParams = { + status: 'PARTICIPATING', + size: 10, + order: 'desc', + }; + + await queryClient.prefetchInfiniteQuery({ + queryKey: ['items', `/v2/groups/usergroup/${id}`, queryParams], + queryFn() { + return request.get( + `/v2/groups/usergroup/${id}`, + { + ...queryParams, + }, + { + credentials: 'include', + }, + { + Cookie: cookieHeaderValue, + }, + ); + }, + initialPageParam: 0, + retry: 0, + }); + + return ( + <> + + +

์ฐธ์—ฌ ์ค‘์ธ ๋ชจ์ž„

+ }> + + +
+
+ + ); +} diff --git a/src/app/(user)/users/[id]/social/followers/error.tsx b/src/app/(user)/users/[id]/social/followers/error.tsx new file mode 100644 index 00000000..d670823c --- /dev/null +++ b/src/app/(user)/users/[id]/social/followers/error.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { ErrorFallback } from '@/components/error-fallback'; + +export default function Error({ + error, + reset, +}: { + error: Error; + reset: () => void; +}) { + return ( + + ํŒ”๋กœ์›Œ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์–ด์š”. +
+ ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”. +
+ ); +} diff --git a/src/app/(user)/users/[id]/social/followers/loading.tsx b/src/app/(user)/users/[id]/social/followers/loading.tsx new file mode 100644 index 00000000..26ba160e --- /dev/null +++ b/src/app/(user)/users/[id]/social/followers/loading.tsx @@ -0,0 +1,13 @@ +import { Skeleton } from '@/components/ui/skeleton'; +import FollowListItemsLoading from '@/features/user/follow/components/follow-list-items-loading'; + +export default function SocialPageLoading() { + return ( +
+ +
    + +
+
+ ); +} diff --git a/src/app/(user)/users/[id]/social/followers/page.tsx b/src/app/(user)/users/[id]/social/followers/page.tsx new file mode 100644 index 00000000..8e021e88 --- /dev/null +++ b/src/app/(user)/users/[id]/social/followers/page.tsx @@ -0,0 +1,67 @@ +import { + QueryClient, + HydrationBoundary, + dehydrate, +} from '@tanstack/react-query'; +import { request } from '@/api/request'; +import { Suspense } from 'react'; +import { FollowersList } from '@/features/user/follow/components/followers-list'; +import { QueryErrorBoundary } from '@/components/query-error-boundary'; +import { getAuthCookieHeader } from '@/utils/cookie'; +import FollowersPageLoading from '@/app/(user)/users/[id]/social/followers/loading'; + +type FollowersPageProps = { + params: Promise<{ + id: string; + }>; + searchParams: Promise<{ + search: string; + }>; +}; + +export default async function FollowersPage({ + params, + searchParams, +}: FollowersPageProps) { + const queryParams = await searchParams; + + const cookieHeaderValue = await getAuthCookieHeader(); + + const { id } = await params; + + const queryClient = new QueryClient(); + + await queryClient.prefetchInfiniteQuery({ + queryKey: ['items', `/v1/follow/${id}/followers`, queryParams], + queryFn({ pageParam }) { + return request.get( + `/v1/follow/${id}/followers`, + { + ...(queryParams.search && { name: queryParams.search }), + cursor: pageParam, + size: 10, + }, + { + credentials: 'include', + }, + { + Cookie: cookieHeaderValue, + }, + ); + }, + initialPageParam: 0, + retry: 0, + }); + + return ( + <> + + + }> + + + + + + ); +} diff --git a/src/app/(user)/users/[id]/social/followings/error.tsx b/src/app/(user)/users/[id]/social/followings/error.tsx new file mode 100644 index 00000000..5a4d2218 --- /dev/null +++ b/src/app/(user)/users/[id]/social/followings/error.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { ErrorFallback } from '@/components/error-fallback'; + +export default function Error({ + error, + reset, +}: { + error: Error; + reset: () => void; +}) { + return ( + + ํŒ”๋กœ์ž‰ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์–ด์š”. +
+ ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”. +
+ ); +} diff --git a/src/app/(user)/users/[id]/social/followings/loading.tsx b/src/app/(user)/users/[id]/social/followings/loading.tsx new file mode 100644 index 00000000..26ba160e --- /dev/null +++ b/src/app/(user)/users/[id]/social/followings/loading.tsx @@ -0,0 +1,13 @@ +import { Skeleton } from '@/components/ui/skeleton'; +import FollowListItemsLoading from '@/features/user/follow/components/follow-list-items-loading'; + +export default function SocialPageLoading() { + return ( +
+ +
    + +
+
+ ); +} diff --git a/src/app/(user)/users/[id]/social/followings/page.tsx b/src/app/(user)/users/[id]/social/followings/page.tsx new file mode 100644 index 00000000..0fe5bead --- /dev/null +++ b/src/app/(user)/users/[id]/social/followings/page.tsx @@ -0,0 +1,62 @@ +import { + QueryClient, + HydrationBoundary, + dehydrate, +} from '@tanstack/react-query'; +import { request } from '@/api/request'; +import { Suspense } from 'react'; +import { FollowingList } from '@/features/user/follow/components/following-list'; +import { QueryErrorBoundary } from '@/components/query-error-boundary'; +import { getAuthCookieHeader } from '@/utils/cookie'; +import FollowingsPageLoading from '@/app/(user)/users/[id]/social/followings/loading'; + +type FollowingsPageProps = { + params: Promise<{ + id: string; + }>; + searchParams: Promise<{ + search: string; + }>; +}; + +export default async function FollowingsPage({ + params, + searchParams, +}: FollowingsPageProps) { + const queryParams = await searchParams; + + const cookieHeaderValue = await getAuthCookieHeader(); + + const { id } = await params; + + const queryClient = new QueryClient(); + + await queryClient.prefetchInfiniteQuery({ + queryKey: ['items', `/v1/follow/${id}/following`, queryParams], + queryFn({ pageParam }) { + return request.get(`/v1/follow/${id}/following`, { + ...(queryParams.search && { name: queryParams.search }), + cursor: pageParam, + size: 10, + }, { + credentials: 'include', + }, { + Cookie: cookieHeaderValue, + }); + }, + initialPageParam: 0, + retry: 0, + }); + + return ( + <> + + + }> + + + + + + ); +} diff --git a/src/app/(user)/users/[id]/social/layout.tsx b/src/app/(user)/users/[id]/social/layout.tsx new file mode 100644 index 00000000..87a20abe --- /dev/null +++ b/src/app/(user)/users/[id]/social/layout.tsx @@ -0,0 +1,18 @@ +import { FollowTabs } from '@/features/user/follow/components/follow-tabs'; +import { SearchInput } from '@/components/molecules/search-input/search-input'; + +export default function SocialPageLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <> +
+ + +
+ {children} + + ); +} diff --git a/src/app/bookmark/BookmarkPageClient.tsx b/src/app/bookmark/BookmarkPageClient.tsx new file mode 100644 index 00000000..4caadc0e --- /dev/null +++ b/src/app/bookmark/BookmarkPageClient.tsx @@ -0,0 +1,139 @@ +'use client'; + +import { GroupCard } from '@/components/molecules/group/group-card'; +import { Tab, TabType } from '@/components/molecules/tab'; +import { Empty } from '@/components/organisms/empty'; +import { useFetchInView } from '@/hooks/useFetchInView'; +import { useFetchItems } from '@/hooks/useFetchItems'; +import { Group, GroupType } from '@/types'; +import flattenPages from '@/utils/flattenPages'; +import Image from 'next/image'; +import { useState } from 'react'; + +const CURSOR_SIZE = 100; + +export const CardSkeleton = () => { + return ( +
+ {[1, 2, 3].map((i) => ( +
+
+
+
+
+
+
+ ))} +
+ ); +}; + +const tabList: TabType[] = [ + { value: '', label: '๋ชจ๋“  ๊ทธ๋ฃน' }, + { value: GroupType.STUDY, label: '์Šคํ„ฐ๋””' }, + { value: GroupType.PROJECT, label: 'ํ”„๋กœ์ ํŠธ' }, +]; +export function BookmarkPageClient() { + // ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” state๋กœ ๊ด€๋ฆฌ + const [queryParams, setQueryParams] = useState({ + size: CURSOR_SIZE, + cursor: 0, + sort: 'createdAt', + order: 'asc', + }); + + //ISSUE: ์ผ๋‹จ ์ „์ฒด ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ + const { + data, + isError, + fetchNextPage, + isLoading, + hasNextPage, + isFetchingNextPage, + } = useFetchItems({ + url: '/v2/groups', + queryParams, + options: { + staleTime: 1000 * 30, + gcTime: 1000 * 60 * 30, + refetchOnWindowFocus: true, + }, + }); + + const { ref } = useFetchInView({ + fetchNextPage, + isLoading, + isFetchingNextPage, + options: { + rootMargin: '50px', + }, + }); + + const items = flattenPages(data.pages); + const bookmarkItems = items.filter((item) => item.isBookmark); + + // ํƒญ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ + const handleValueChange = (value: GroupType) => { + setQueryParams((prev) => ({ + ...prev, + type: `${value}`, + })); + }; + + return ( +
+
+ ๋ชจ์—ฌ๋ผ-IT +
+

์ฐœํ•œ ๋ชจ์ž„

+

๋งˆ๊ฐ๋˜๊ธฐ ์ „์— ์ง€๊ธˆ ๋ฐ”๋กœ ์ฐธ์—ฌํ•ด๋ณด์„ธ์š”!

+
+
+
+ +
+ {isError && ( + + )} + {isLoading ? ( + + ) : bookmarkItems.length === 0 && !isError ? ( + + ) : ( + <> +
    + {bookmarkItems.map((group) => ( +
  • + +
  • + ))} +
+ {hasNextPage && !isFetchingNextPage && ( +
+ )} + + )} +
+ +
+
+ ); +} diff --git a/src/app/bookmark/layout.tsx b/src/app/bookmark/layout.tsx new file mode 100644 index 00000000..f50d06f3 --- /dev/null +++ b/src/app/bookmark/layout.tsx @@ -0,0 +1,11 @@ +export default function BookmarkLayouyt({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+ {children} +
+ ); +} diff --git a/src/app/bookmark/page.tsx b/src/app/bookmark/page.tsx new file mode 100644 index 00000000..77dca138 --- /dev/null +++ b/src/app/bookmark/page.tsx @@ -0,0 +1,16 @@ +import { Metadata } from 'next'; +import { BookmarkPageClient } from './BookmarkPageClient'; + +export const metadata: Metadata = { + title: '์ฐœํ•œ ๋ชจ์ž„ | ๋ชจ์—ฌ๋ผ-IT', + description: '์ฐœํ•œ ๋ชจ์ž„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.', + openGraph: { + title: '์ฐœํ•œ ๋ชจ์ž„ | ๋ชจ์—ฌ๋ผ-IT', + description: '์ฐœํ•œ ๋ชจ์ž„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.', + images: [{ url: '/logos/logo-img.svg' }], + }, +}; + +export default function BookmarkPage() { + return ; +} diff --git a/src/app/favicon.ico b/src/app/favicon.ico new file mode 100644 index 00000000..2059cd56 Binary files /dev/null and b/src/app/favicon.ico differ diff --git a/src/app/globals.css b/src/app/globals.css index 9711dafc..fe68d2df 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,12 +1,11 @@ -@import "tailwindcss"; -@import "tw-animate-css"; +@import 'tailwindcss'; +@import 'tw-animate-css'; @custom-variant dark (&:is(.dark *)); @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); @@ -45,71 +44,69 @@ :root { --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.129 0.042 264.695); --card: oklch(1 0 0); - --card-foreground: oklch(0.129 0.042 264.695); + --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); - --popover-foreground: oklch(0.129 0.042 264.695); - --primary: oklch(0.208 0.042 265.755); - --primary-foreground: oklch(0.984 0.003 247.858); - --secondary: oklch(0.968 0.007 247.896); - --secondary-foreground: oklch(0.208 0.042 265.755); - --muted: oklch(0.968 0.007 247.896); - --muted-foreground: oklch(0.554 0.046 257.417); - --accent: oklch(0.968 0.007 247.896); - --accent-foreground: oklch(0.208 0.042 265.755); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.7153 0.1256 185.44); /* green-500 */ + --primary-foreground: oklch(1 0 0); /* green-500 */ + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.5547 0.097467 185.6207); --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.929 0.013 255.508); - --input: oklch(0.929 0.013 255.508); - --ring: oklch(0.704 0.04 256.788); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.984 0.003 247.858); - --sidebar-foreground: oklch(0.129 0.042 264.695); - --sidebar-primary: oklch(0.208 0.042 265.755); - --sidebar-primary-foreground: oklch(0.984 0.003 247.858); - --sidebar-accent: oklch(0.968 0.007 247.896); - --sidebar-accent-foreground: oklch(0.208 0.042 265.755); - --sidebar-border: oklch(0.929 0.013 255.508); - --sidebar-ring: oklch(0.704 0.04 256.788); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); } .dark { - --background: oklch(0.129 0.042 264.695); - --foreground: oklch(0.984 0.003 247.858); - --card: oklch(0.208 0.042 265.755); - --card-foreground: oklch(0.984 0.003 247.858); - --popover: oklch(0.208 0.042 265.755); - --popover-foreground: oklch(0.984 0.003 247.858); - --primary: oklch(0.929 0.013 255.508); - --primary-foreground: oklch(0.208 0.042 265.755); - --secondary: oklch(0.279 0.041 260.031); - --secondary-foreground: oklch(0.984 0.003 247.858); - --muted: oklch(0.279 0.041 260.031); - --muted-foreground: oklch(0.704 0.04 256.788); - --accent: oklch(0.279 0.041 260.031); - --accent-foreground: oklch(0.984 0.003 247.858); + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); - --ring: oklch(0.551 0.027 264.364); + --ring: oklch(0.556 0 0); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.208 0.042 265.755); - --sidebar-foreground: oklch(0.984 0.003 247.858); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.984 0.003 247.858); - --sidebar-accent: oklch(0.279 0.041 260.031); - --sidebar-accent-foreground: oklch(0.984 0.003 247.858); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.551 0.027 264.364); + --sidebar-ring: oklch(0.556 0 0); } @layer base { @@ -120,3 +117,100 @@ @apply bg-background text-foreground; } } + +@layer components { + .group-title { + @apply text-lg font-semibold; + } + + /* ์Šคํฌ๋กค๋ฐ” ์ˆจ๊ธฐ๋Š” ํด๋ž˜์Šค(Edge, Firefox) */ + .scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; + } + + /* ์Šคํฌ๋กค๋ฐ” ์ˆจ๊ธฐ๋Š” ํด๋ž˜์Šค(Chrome, Safari) */ + .scrollbar-hide::-webkit-scrollbar { + display: none; /* Chrome, Safari */ + } +} + +/* ๋ชจ์—ฌ๋ผ-IT ๋””์ž์ธ ์‹œ์Šคํ…œ */ +@theme { + --breakpoint-md: 745px; + --breakpoint-sm: 375px; + + --color-green-50: #dcfffc; + --color-green-100: #95fff7; + --color-green-200: #69fff3; + --color-green-300: #10fae8; + --color-green-400: #00dccb; + --color-green-500: #02bcae; + --color-green-600: #00a79a; + --color-green-700: #00857b; + --color-green-800: #006c63; + --color-green-900: #005750; + --color-green-950: #003d39; + + --color-gray-50: #f9fafb; + --color-gray-100: #f3f4f6; + --color-gray-200: #e5e7eb; + --color-gray-300: #d1d5db; + --color-gray-400: #9ca3af; + --color-gray-500: #6b7280; + --color-gray-600: #4b5563; + --color-gray-700: #374151; + --color-gray-800: #1f2937; + --color-gray-900: #111827; + --color-gray-950: #030712; + + --color-red-600: #e42412; + + --font-sans: 'Pretendard', 'sans-serif'; + + --font-size-xs: 12px; + --leading-xs: 16px; + --font-size-sm: 14px; + --leading-sm: 20px; + --font-size-base: 16px; + --leading-base: 24px; + --font-size-lg: 18px; + --leading-lg: 28px; + --font-size-xl: 20px; + --leading-xl: 28px; + --font-size-2xl: 24px; + --leading-2xl: 32px; + --font-size-3x: 30px; + --leading-3x: 36px; + + --font-weight-light: 300; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; +} + +/* ์† ํ”๋“œ๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ */ +@theme { + --animate-hello: hello 1s ease-in infinite; + --animate-bounce-up: bounce-up 1s linear infinite; + @keyframes hello { + 0%, + 100% { + transform: rotate(20deg); + } + 50% { + transform: rotate(0deg); + } + } + + @keyframes bounce-up { + 0%, + 100% { + transform: translateY(-10px); + } + 50% { + transform: translateY(0px); + } + } +} diff --git a/src/app/groups/[groupId]/not-found.tsx b/src/app/groups/[groupId]/not-found.tsx new file mode 100644 index 00000000..0f7e6908 --- /dev/null +++ b/src/app/groups/[groupId]/not-found.tsx @@ -0,0 +1,10 @@ +import { Empty } from '@/components/organisms/empty'; + +export default function GroupDetailNotFoundPAge() { + return ( + + ); +} diff --git a/src/app/groups/[groupId]/page.tsx b/src/app/groups/[groupId]/page.tsx new file mode 100644 index 00000000..3e67b270 --- /dev/null +++ b/src/app/groups/[groupId]/page.tsx @@ -0,0 +1,177 @@ +import { Empty } from '@/components/organisms/empty'; +import { GroupActionButtons } from '@/features/group/components/group-action-buttons'; +import { GroupDescription } from '@/features/group/components/group-description'; +import { GroupDetailCard } from '@/features/group/components/group-detail-card'; +import { ReplySection } from '@/features/reply/components/reply-section'; +import { GroupDetail } from '@/types'; +import { getAuthCookieHeader } from '@/utils/cookie'; +import { isBeforeToday } from '@/utils/dateUtils'; +import { Metadata } from 'next'; +import { notFound } from 'next/navigation'; + +type GroupDetailResponse = { + status: { + code: number; + message: string; + success: boolean; + }; + items: GroupDetail; +}; + +type GroupDetailPageProps = { + params: Promise<{ groupId: string }>; +}; + +const convertHtmlToPlainText = (html: string) => { + return html + .replace(/]*>(.*?)<\/h[1-6]>/gi, '$1. ') // ํ—ค๋”ฉ์€ ๊ฐ•์กฐ + .replace(/]*>(.*?)<\/p>/gi, '$1 ') // ๋‹จ๋ฝ ์œ ์ง€ + .replace(/<[^>]*>/g, '') // ๋‚˜๋จธ์ง€ ํƒœ๊ทธ ์ œ๊ฑฐ + .replace(/\s+/g, ' ') // ๊ณต๋ฐฑ ์ •๋ฆฌ + .trim(); +}; + +const fallbackMetadata: Metadata = { + title: '404 | ๋ชจ์—ฌ๋ผ-IT', + description: '์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ชจ์ž„์ž…๋‹ˆ๋‹ค.', + openGraph: { + title: '404 | ๋ชจ์—ฌ๋ผ-IT', + description: '์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ชจ์ž„์ž…๋‹ˆ๋‹ค.', + images: [{ url: '/logos/logo-img.svg' }], + }, +}; + +export async function generateMetadata({ + params, +}: GroupDetailPageProps): Promise { + const groupId = Number((await params).groupId); + const cookieHeaderValue = await getAuthCookieHeader(); + + let group; + + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/${groupId}`, + { + headers: { + Cookie: cookieHeaderValue, + }, + }, + ); + + if (!response.ok) return fallbackMetadata; + + const data: GroupDetailResponse = await response.json(); + + group = data?.items?.group; + } catch { + return fallbackMetadata; + } + + if (!group) return fallbackMetadata; + + const plainDescription = convertHtmlToPlainText(group.description); + + return { + title: `${group.title} | ๋ชจ์—ฌ๋ผ-IT`, + description: plainDescription, + openGraph: { + title: `${group.title} | ๋ชจ์—ฌ๋ผ-IT`, + description: plainDescription, + images: [{ url: '/logos/logo-img.svg' }], + }, + }; +} + +export default async function GroupDetailPage({ + params, +}: GroupDetailPageProps) { + const groupId = Number((await params).groupId); + const cookieHeaderValue = await getAuthCookieHeader(); + + let response: Response; + + try { + response = await fetch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/${groupId}`, + { + headers: { + Cookie: cookieHeaderValue, + }, + next: { tags: [`group-detail-${groupId}`] }, + }, + ); + } catch (error) { + console.error('Fetch ์š”์ฒญ ์‹คํŒจ:', error); + return ( + + ); + } + + if (response.status === 404) { + return notFound(); + } + + if (!response.ok) { + console.error('์‘๋‹ต ์ƒํƒœ ์˜ค๋ฅ˜:', response.status); + return ; + } + + let responseBody: GroupDetailResponse; + + try { + responseBody = await response.json(); + } catch (err) { + console.error('JSON ํŒŒ์‹ฑ ์˜ค๋ฅ˜:', err); + return ; + } + + if (!responseBody.items) { + return notFound(); + } + + if (!responseBody.status.success) { + console.error('API ์„ฑ๊ณต ์ƒํƒœ false:', responseBody.status); + return ; + } + + const data = responseBody.items; + + const { group, host, isApplicant, isJoined } = data; + + const isRecruiting = + !isBeforeToday(group.deadline) && + group.participants.length < group.maxParticipants; + + return ( + <> +
+
+ +
+
+ + +
+
+ {isRecruiting && ( +
+ +
+ )} + + ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87eb..ab1ccaa6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,20 +1,17 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); +import { Header } from '@/components/organisms/header'; +import { AutoLoginManager } from '@/features/auth/components/AutoLoginManager'; +import { ReactQueryProvider } from '@/providers/ReactQueryProvider'; +import { SocketProvider } from '@/providers/WSProvider'; +import type { Metadata } from 'next'; +import { Toaster } from 'sonner'; +import './globals.css'; export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: '๋ชจ์—ฌ๋ผ-IT', + description: '๊ฐœ๋ฐœ์ž๋“ค์˜ ์Šคํ„ฐ๋””, ์‚ฌ์ดํŠธ ํ”„๋กœ์ ํŠธ ๋ชจ์ง‘ ํ”Œ๋žซํผ', + icons: { + icon: '/logos/logo-img.svg', + }, }; export default function RootLayout({ @@ -23,11 +20,17 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - - {children} + + + + {/* ์†Œ์ผ“ ์„ค์ • ์ „์— ๋กœ๊ทธ์ธ ํŒ๋‹จํ•ด์•ผํ•˜๋ฏ€๋กœ ๋กœ๊ทธ์ธ ์œ„๋กœ ์˜ฌ๋ฆผ */} + + +
+ {children} + + + ); diff --git a/src/app/page.tsx b/src/app/page.tsx index 7bcd29e7..21a64709 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,100 @@ -export default function Home() { - return
; +import { request } from '@/api/request'; +import { Groups } from '@/components/organisms/group'; +import RecommendGroup from '@/components/organisms/recommend-group'; +import { QueryErrorBoundary } from '@/components/query-error-boundary'; +import { Position, Skill } from '@/types/enums'; +import { getAuthCookieHeader } from '@/utils/cookie'; +import { + dehydrate, + HydrationBoundary, + QueryClient, +} from '@tanstack/react-query'; + +export default async function Home({ + searchParams, +}: { + searchParams: Promise>; +}) { + const awaitedSearchParams = await searchParams; // searchParams๊ฐ€ Promise ๊ฐ์ฒด์—ฌ์„œ await์œผ๋กœ ๋ฒ—๊ฒจ๋‚ด์•ผ ํ•จ + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, // ๊ธฐ๋ณธ ์บ์‹ฑ ์‹œ๊ฐ„(1๋ถ„) + }, + }, + }); + + const queryParams = { + type: awaitedSearchParams.type ?? '', + skill: awaitedSearchParams.skill + ? awaitedSearchParams.skill.split(',') + ? awaitedSearchParams.skill + .split(',') + .map((v) => Skill[v as keyof typeof Skill]) + .join(',') + : Skill[awaitedSearchParams.skill as keyof typeof Skill] + : '', + position: awaitedSearchParams.position + ? awaitedSearchParams.position.split(',') + ? awaitedSearchParams.position + .split(',') + .map((v) => Position[v as keyof typeof Position]) + .join(',') + : Position[awaitedSearchParams.position as keyof typeof Position] + : '', + sort: awaitedSearchParams.sort ?? 'createdAt', + order: awaitedSearchParams.order ?? 'desc', + search: awaitedSearchParams.search ?? '', + }; + + // console.log('โœ… Fetching data from server ', queryParams); // DEV: ๐Ÿ’ก ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ prefetch ํ•˜๋Š”์ง€ ํ™•์ธ์šฉ + const cookieHeaderValue = await getAuthCookieHeader(); + + try { + await queryClient.fetchInfiniteQuery({ + queryKey: ['items', '/v2/groups', { size: 10, ...queryParams }], + queryFn({ pageParam }) { + return request.get( + '/v2/groups', + { + ...queryParams, + size: 10, + cursor: + queryParams.order === 'desc' || !queryParams.order + ? 'null' + : pageParam, + }, + { credentials: 'include' }, + { Cookie: cookieHeaderValue }, + ); + }, + initialPageParam: 0, + }); + } catch (e) { + //ISSUE: ์—๋Ÿฌ ์„ค์ • + console.log(e); + return
Error
; + } + + return ( +
+
+ +
+ ๐Ÿ”ฅ ์ธ๊ธฐ๊ธ€ +
+ + + โš ๏ธ ๊ทธ๋ฃน์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”. +
+ } + > + + + +
+
+ ); } diff --git a/src/app/write/__tests__/writeform.test.ts b/src/app/write/__tests__/writeform.test.ts new file mode 100644 index 00000000..6bc094f2 --- /dev/null +++ b/src/app/write/__tests__/writeform.test.ts @@ -0,0 +1,79 @@ +import { request } from '@/api/request'; +import { groupsHandlers } from '@/mocks/handler/groups'; +import { GroupType, WriteFormWithCreatedAt } from '@/types'; +import { http, HttpResponse } from 'msw'; +import { setupServer } from 'msw/node'; + +const server = setupServer(...groupsHandlers); + +// ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ์‹œ์ž‘ํ•˜๊ธฐ ์ „ MSW ์„œ๋ฒ„๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. +beforeAll(() => server.listen()); +// ์ด์ „ ํ…Œ์ŠคํŠธ์˜ ๋ชจ์˜ ์‘๋‹ต์ด ๋‹ค์Œ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋„๋ก ์ด์ „ ํ…Œ์ŠคํŠธ์—์„œ ์„ค์ •๋œ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. +afterEach(() => server.resetHandlers()); +// ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ์™„๋ฃŒ๋œ ํ›„์— MSW ์„œ๋ฒ„๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค. +afterAll(() => server.close()); + +describe('write form ํ…Œ์ŠคํŠธ', () => { + const tempBody: WriteFormWithCreatedAt = { + title: '์Šคํ„ฐ๋”” ๋งŒ๋“ค๊ธฐ', + maxParticipants: 10, + deadline: new Date(2024, 5, 26), + startDate: new Date(2024, 5, 27), + endDate: new Date(2024, 6, 26), + description: + '

์žฌ๋ฐŒ๋Š” Next.js ์Šคํ„ฐ๋””

๊ฐ™์ดํ•ด๋ณด์•„์š”
', + autoAllow: false, + type: GroupType.STUDY, + skills: ['Typescript', 'Next.js'], + position: ['FE'], + createdAt: new Date(2024.05, 26), + }; + + test('group์ด ์ •์ƒ์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์„ ์‹œ { success: true }๋ฅผ ๋ฐ›๋Š”๋‹ค', async () => { + const result = await request.post( + '/v2/groups', + { 'Content-Type': 'application/json' }, + JSON.stringify(tempBody), + ); + + expect(result).toEqual({ success: true }); + }); + + test('group ์ƒ์„ฑ ์‹œ ์‘๋‹ต์€ ์™”์ง€๋งŒ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ { success: false, code: 400 }๋ฅผ ๋ฐ›๋Š”๋‹ค', async () => { + server.use( + http.post(`${process.env.NEXT_PUBLIC_API_BASE_URL}/group`, () => { + return HttpResponse.json( + { success: false, code: 400 }, + { status: 200 }, + ); + }), + ); + + const result = await request.post( + '/v2/groups', + { 'Content-Type': 'application/json' }, + JSON.stringify(tempBody), + ); + + expect(result).toEqual({ success: false, code: 400 }); + }); + + test('group ์ƒ์„ฑ ์‹œ ์„œ๋ฒ„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ Unexpected Error๋ฅผ ๋ฐ›๋Š”๋‹ค', async () => { + server.use( + http.post(`${process.env.NEXT_PUBLIC_API_BASE_URL}/group`, () => { + return HttpResponse.json( + { message: 'Bad Request' }, + { status: 400 }, // 400 ์ƒํƒœ์ฝ”๋“œ -> response.ok === false + ); + }), + ); + + const result = request.post( + '/v2/groups', + { 'Content-Type': 'application/json' }, + JSON.stringify(tempBody), + ); + + await expect(result).rejects.toThrow('Unexpected Error'); + }); +}); diff --git a/src/app/write/page.tsx b/src/app/write/page.tsx new file mode 100644 index 00000000..74cef58d --- /dev/null +++ b/src/app/write/page.tsx @@ -0,0 +1,40 @@ +'use client'; + +import { WriteForm } from '@/components/organisms/write-form'; +import useIsClient from '@/hooks/useIsClient'; +import useAuthStore from '@/stores/useAuthStore'; +import { routes } from '@/utils/routes'; +import { useRouter } from 'next/navigation'; +import { useEffect } from 'react'; + +export default function Page() { + const user = useAuthStore((state) => state.user); + const router = useRouter(); + const isClient = useIsClient(); + + useEffect(() => { + const handleBeforeUnload = (e: BeforeUnloadEvent) => { + e.preventDefault(); + e.returnValue = ''; // ๋ธŒ๋ผ์šฐ์ € ๊ฒฝ๊ณ ์ฐฝ์„ ๋„์šฐ๊ธฐ ์œ„ํ•œ ํŠธ๋ฆฌ๊ฑฐ + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + return () => { + window.removeEventListener('beforeunload', handleBeforeUnload); + }; + }, []); + + if (!isClient) return null; + + if (!user) { + router.push(routes.login); + + return; + } + + return ( +
+ +
+ ); +} diff --git a/src/components/atoms/avatar/index.tsx b/src/components/atoms/avatar/index.tsx new file mode 100644 index 00000000..588119c1 --- /dev/null +++ b/src/components/atoms/avatar/index.tsx @@ -0,0 +1,34 @@ +import { + Avatar as ShadcnAvatar, + AvatarFallback, + AvatarImage, +} from '@/components/ui/avatar'; + +type AvatarProps = { + imageSrc: string; + fallback: string; + className?: string; + onClick?: () => void; +}; + +/** + * ์•„๋ฐ”ํƒ€ ์ปดํฌ๋„ŒํŠธ + * @param imageSrc ์ด๋ฏธ์ง€ ์ฃผ์†Œ(๋ฌธ์ž์—ด) + * @param fallback ์•„๋ฐ”ํƒ€ ํด๋ฐฑ ๋ฌธ์ž์—ด(๋ฌธ์ž์—ด) + * @param className ํด๋ž˜์Šค๋ช…(๋ฌธ์ž์—ด) + * @param onClick ํด๋ฆญ ์ด๋ฒคํŠธ(ํ•จ์ˆ˜) + * @returns ์•„๋ฐ”ํƒ€ ์ปดํฌ๋„ŒํŠธ + */ +export const Avatar = ({ + imageSrc, + fallback, + className, + onClick, +}: AvatarProps) => { + return ( + + + {fallback} + + ); +}; diff --git a/src/components/atoms/badge/half-round-badge.tsx b/src/components/atoms/badge/half-round-badge.tsx new file mode 100644 index 00000000..eb1fc08e --- /dev/null +++ b/src/components/atoms/badge/half-round-badge.tsx @@ -0,0 +1,13 @@ +type BadgeProps = { + text: string; + className?: string; +}; +export const HalfRoundBadge = ({ text, className }: BadgeProps) => { + return ( + + {text} + + ); +}; diff --git a/src/components/atoms/badge/index.tsx b/src/components/atoms/badge/index.tsx new file mode 100644 index 00000000..a5fb8fa3 --- /dev/null +++ b/src/components/atoms/badge/index.tsx @@ -0,0 +1,11 @@ +type BadgeProps = { + text: string; + className?: string; +}; +export const Badge = ({ text, className }: BadgeProps) => { + return ( + + {text} + + ); +}; diff --git a/src/components/atoms/circle-icon/circle-info.tsx b/src/components/atoms/circle-icon/circle-info.tsx new file mode 100644 index 00000000..039055bd --- /dev/null +++ b/src/components/atoms/circle-icon/circle-info.tsx @@ -0,0 +1,11 @@ +type CircleInfoProps = { className?: string }; + +export const CircleInfo = ({ className }: CircleInfoProps) => { + return ( +
+ i +
+ ); +}; diff --git a/src/components/atoms/circle-icon/circle-number.tsx b/src/components/atoms/circle-icon/circle-number.tsx new file mode 100644 index 00000000..aad9d8d6 --- /dev/null +++ b/src/components/atoms/circle-icon/circle-number.tsx @@ -0,0 +1,11 @@ +type CircleNumberProps = { number: number; className?: string }; + +export const CircleNumber = ({ number, className }: CircleNumberProps) => { + return ( +
+ {number} +
+ ); +}; diff --git a/src/components/atoms/group/close-cover.tsx b/src/components/atoms/group/close-cover.tsx new file mode 100644 index 00000000..459c36eb --- /dev/null +++ b/src/components/atoms/group/close-cover.tsx @@ -0,0 +1,17 @@ +import Link from 'next/link'; + +type CloseCoverProps = { + itemId: number; +}; + +export const CloseCover = ({ itemId }: CloseCoverProps) => { + return ( + +
+

+ โŒ›๏ธ ๋ชจ์ง‘์ด ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +

+
+ + ); +}; diff --git a/src/components/atoms/group/deadline.tsx b/src/components/atoms/group/deadline.tsx new file mode 100644 index 00000000..22f235a7 --- /dev/null +++ b/src/components/atoms/group/deadline.tsx @@ -0,0 +1,17 @@ +type DeadlineProps = { + text: string; + className?: string; +}; + +export const Deadline = ({ text, className }: DeadlineProps) => { + return ( +
+ + ๋งˆ๊ฐ์ผ + + {text} +
+ ); +}; diff --git a/src/components/atoms/group/group-positions.tsx b/src/components/atoms/group/group-positions.tsx new file mode 100644 index 00000000..a18c6cd7 --- /dev/null +++ b/src/components/atoms/group/group-positions.tsx @@ -0,0 +1,22 @@ +import { Position } from '@/types/enums'; +import { PositionBadge } from '../../molecules/position-badge'; + +type GroupPositionsProps = { + positions: Position[]; + className?: string; +}; + +export const GroupPositions = ({ + positions, + className, +}: GroupPositionsProps) => { + return ( +
    + {positions.map((position, i) => ( +
  • + +
  • + ))} +
+ ); +}; diff --git a/src/components/atoms/group/group-skills.tsx b/src/components/atoms/group/group-skills.tsx new file mode 100644 index 00000000..e4941e37 --- /dev/null +++ b/src/components/atoms/group/group-skills.tsx @@ -0,0 +1,19 @@ +import { Skill } from '@/types/enums'; +import { SkillBadge } from '../../molecules/skill-badge'; + +type GroupSkillsProps = { + skills: Skill[]; + className?: string; +}; + +export const GroupSkills = ({ skills, className }: GroupSkillsProps) => { + return ( +
    + {skills?.map((skill, i) => ( +
  • + +
  • + ))} +
+ ); +}; diff --git a/src/components/atoms/group/group-title.tsx b/src/components/atoms/group/group-title.tsx new file mode 100644 index 00000000..125973a9 --- /dev/null +++ b/src/components/atoms/group/group-title.tsx @@ -0,0 +1,14 @@ +type GroupTitleProps = { + text: string; + className?: string; +}; + +export const GroupTitle = ({ text, className }: GroupTitleProps) => { + return ( +
+ {text} +
+ ); +}; diff --git a/src/components/atoms/group/particiapant-progress.tsx b/src/components/atoms/group/particiapant-progress.tsx new file mode 100644 index 00000000..b0eb36b1 --- /dev/null +++ b/src/components/atoms/group/particiapant-progress.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { Progress } from '@/components/ui/progress'; +import Image from 'next/image'; +import { useEffect, useState } from 'react'; + +type GroupProgressProps = { + participantsCount: number; + maxParticipants: number; + className?: string; +}; + +export const GroupProgress = ({ + participantsCount, + maxParticipants, + className, +}: GroupProgressProps) => { + const [progress, setProgress] = useState(0); + + useEffect(() => { + requestAnimationFrame(() => { + setProgress((participantsCount / maxParticipants) * 100); + }); + }, [maxParticipants, participantsCount]); + + return ( +
+
+ person icon + {`${participantsCount}/${maxParticipants}`} +
+ +
+ ); +}; diff --git a/src/components/atoms/input-text/index.tsx b/src/components/atoms/input-text/index.tsx new file mode 100644 index 00000000..fab263ef --- /dev/null +++ b/src/components/atoms/input-text/index.tsx @@ -0,0 +1,23 @@ +import { Input } from '@/components/ui/input'; +import * as React from 'react'; + +// ํ‚ค๋ณด๋“œ๋กœ ์ง์ ‘ ์ž…๋ ฅํ•˜๋Š” type์œผ๋กœ ์ œํ•œ +export type InputTextProps = React.ComponentProps<'input'> & { + type?: 'text' | 'password' | 'search' | 'email' | 'url' | 'tel' | 'number'; +}; + +// ์‚ฌ์šฉ์ž๊ฐ€ ํ‚ค๋ณด๋“œ๋กœ ์ง์ ‘ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” Input์ž…๋‹ˆ๋‹ค +// type์„ ์ž…๋ ฅํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ๊ธฐ๋ณธ ํƒ€์ž…์€ text์ž…๋‹ˆ๋‹ค +export const InputText = ({ + className, + type = 'text', + ...props +}: InputTextProps) => { + return ( + + ); +}; diff --git a/src/components/atoms/login-require-button/index.tsx b/src/components/atoms/login-require-button/index.tsx new file mode 100644 index 00000000..27e52829 --- /dev/null +++ b/src/components/atoms/login-require-button/index.tsx @@ -0,0 +1,69 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogTitle, +} from '@/components/ui/dialog'; +import useAuthStore from '@/stores/useAuthStore'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; + +type LoginRequireButtonProps = { + children: React.ReactNode; + onClick: () => void; + disabled?: boolean; + className?: string; +}; + +export const LoginRequireButton = ({ + children, + onClick, + disabled = false, + className = '', +}: LoginRequireButtonProps) => { + const [isOpen, setIsOpen] = useState(false); + const user = useAuthStore((state) => state.user); + const router = useRouter(); + + const LoginRequireButtonClickHandler = () => { + if (!user) { + setIsOpen(true); // ๋กœ๊ทธ์ธ ๋ชจ๋‹ฌ ์—ด๊ธฐ + } else { + onClick(); // ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฉด ์›๋ž˜ ๋™์ž‘ ์ˆ˜ํ–‰ + } + }; + + return ( + <> + + + + + ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค + + + ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋จผ์ € ๋กœ๊ทธ์ธํ•ด ์ฃผ์„ธ์š”. + + + + + + + + ); +}; diff --git a/src/components/atoms/logout-button/index.tsx b/src/components/atoms/logout-button/index.tsx new file mode 100644 index 00000000..2b1fc9c7 --- /dev/null +++ b/src/components/atoms/logout-button/index.tsx @@ -0,0 +1,52 @@ +'use client'; + +import useAuthStore from '@/stores/useAuthStore'; +import { request } from '@/api/request'; +import { Button } from '@/components/ui/button'; +import { useRouter } from 'next/navigation'; +import { useMutation } from '@tanstack/react-query'; + +const LogoutButton = () => { + const { user, clearUser } = useAuthStore(); + const router = useRouter(); + + const { mutate, isPending } = useMutation({ + mutationFn: async () => { + await request.post( + '/v1/user/logout', + { + 'Content-Type': 'application/json', + }, + '{}', + { + credentials: 'include', + }, + ); + }, + onSuccess: () => { + clearUser(); + router.push('/login'); + }, + onError: (error) => { + // ๋กœ๊ทธ์•„์›ƒ ์‹คํŒจ์ฒ˜๋ฆฌ + console.error('๋กœ๊ทธ์•„์›ƒ ์‹คํŒจ:', error); + }, + }); + + if (!user) { + return null; // ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋ฒ„ํŠผ์„ ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Œ + } + + const onClick = async () => { + if (isPending) return; + mutate(); + }; + + return ( + + ); +}; + +export default LogoutButton; diff --git a/src/components/atoms/notification-item/index.tsx b/src/components/atoms/notification-item/index.tsx new file mode 100644 index 00000000..829123b0 --- /dev/null +++ b/src/components/atoms/notification-item/index.tsx @@ -0,0 +1,65 @@ +import useNotificationStore from '@/stores/useNotificationStore'; +import { Notification } from '@/types/index'; +import { formatRelativeTime } from '@/utils/dateUtils'; +import { useRouter } from 'next/navigation'; +import React from 'react'; + +const NotificationItemComponent = ({ + notification, + onClose, +}: { + notification: Notification; + onClose?: () => void; +}) => { + const { setReadNotification } = useNotificationStore(); + const router = useRouter(); + + const alarmClickHandler = () => { + // ์ฝ์Œ ์ฒ˜๋ฆฌ + if (!notification.isRead) { + setReadNotification(notification.id); + } + + // URL์ด ์žˆ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ํŽ˜์ด์ง€๋กœ ์ด๋™ + if (notification.url) { + router.push(notification.url); + // ํŒ์˜ค๋ฒ„ ๋‹ซ๊ธฐ + onClose?.(); + } + }; + + return ( +
+
+

+ {notification.message} +

+ + {formatRelativeTime(notification.createdAt.toString())} + +
+ {!notification.isRead && ( + +
+ + )} +
+ ); +}; + +export const NotificationItem = React.memo(NotificationItemComponent); diff --git a/src/components/atoms/recommend/recommend-deadline.tsx b/src/components/atoms/recommend/recommend-deadline.tsx new file mode 100644 index 00000000..b4ba0321 --- /dev/null +++ b/src/components/atoms/recommend/recommend-deadline.tsx @@ -0,0 +1,17 @@ +type DeadlineProps = { + text: string; + className?: string; +}; + +export const RecommendDeadline = ({ text, className }: DeadlineProps) => { + return ( +
+ + ๋งˆ๊ฐ์ผ + + {text} +
+ ); +}; diff --git a/src/components/atoms/recommend/recommend-group-title.tsx b/src/components/atoms/recommend/recommend-group-title.tsx new file mode 100644 index 00000000..ebd33c83 --- /dev/null +++ b/src/components/atoms/recommend/recommend-group-title.tsx @@ -0,0 +1,17 @@ +type RecommendGroupTitleProps = { + text: string; + className?: string; +}; + +export const RecommendGroupTitle = ({ + text, + className, +}: RecommendGroupTitleProps) => { + return ( +
+ {text} +
+ ); +}; diff --git a/src/components/atoms/share-button/index.tsx b/src/components/atoms/share-button/index.tsx new file mode 100644 index 00000000..1728b5fc --- /dev/null +++ b/src/components/atoms/share-button/index.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { toast } from 'sonner'; + +export const ShareButton = () => { + const shareButtonClickHandler = async () => { + const currentUrl = window.location.href; + + try { + await navigator.clipboard.writeText(`${currentUrl}`); + toast.success('๋งํฌ๊ฐ€ ๋ณต์‚ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'); + } catch { + toast.error('๋งํฌ ๋ณต์‚ฌ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.'); + } + }; + + return ( + + ); +}; diff --git a/src/components/atoms/star/index.tsx b/src/components/atoms/star/index.tsx new file mode 100644 index 00000000..a6a27e67 --- /dev/null +++ b/src/components/atoms/star/index.tsx @@ -0,0 +1,36 @@ +type StarProps = { + fillPercent: number; + width?: number; + height?: number; + className?: string; +}; + +/** + * ๋ณ„์  ์ปดํฌ๋„ŒํŠธ + * @param fillPercent ์ฑ„์›Œ์ง„ ๋ณ„์  ํผ์„ผํŠธ + * @param width ๋ณ„์  ๋„ˆ๋น„ + * @param height ๋ณ„์  ๋†’์ด + * @param className ๋ณ„์  ํด๋ž˜์Šค๋ช… + * @returns ๋ณ„์  ์ปดํฌ๋„ŒํŠธ + */ +export const Star: React.FC = ({ fillPercent, width = 50, height = 50, className }) => ( + + + + + + + + + + + +); diff --git a/src/components/atoms/thumbnail/index.tsx b/src/components/atoms/thumbnail/index.tsx new file mode 100644 index 00000000..cbce1bbd --- /dev/null +++ b/src/components/atoms/thumbnail/index.tsx @@ -0,0 +1,27 @@ +import Image from 'next/image'; + +type ThumbnailProps = { + imageSrc: string; + alt: string; + width: number; + height: number; + className?: string; +}; +export const Thumbnail = ({ + imageSrc, + alt, + width, + height, + className, +}: ThumbnailProps) => { + return ( + //TODO:contain์œผ๋กœ ํ• ์ง€ cover๋กœ ํ• ์ง€ ๊ฒฐ์ •ํ•ด์•ผํ•จ -> ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค ๋•Œ ์ตœ์ ์‚ฌ์ด์ฆˆ ์ œ๊ณตํ•˜๋ฉด ์ข‹์Œ + {alt} + ); +}; diff --git a/src/components/atoms/title/index.tsx b/src/components/atoms/title/index.tsx new file mode 100644 index 00000000..7bd94376 --- /dev/null +++ b/src/components/atoms/title/index.tsx @@ -0,0 +1,12 @@ +type TitleProps = { + title: string; +}; + +export const Title = ({ title }: TitleProps) => { + return ( +
+

{title}

+
+ ); + +}; diff --git a/src/components/atoms/write-form/categoryName.tsx b/src/components/atoms/write-form/categoryName.tsx new file mode 100644 index 00000000..6a7cacd9 --- /dev/null +++ b/src/components/atoms/write-form/categoryName.tsx @@ -0,0 +1,20 @@ +import { CircleNumber } from '../circle-icon/circle-number'; + +type CategoryNameProps = { + number?: number; + text: string; + className?: string; +}; + +export const CategoryName = ({ + number, + text, + className, +}: CategoryNameProps) => { + return ( +
+ {number && } +
{text}
+
+ ); +}; diff --git a/src/components/atoms/write-form/form-label.tsx b/src/components/atoms/write-form/form-label.tsx new file mode 100644 index 00000000..487b52f3 --- /dev/null +++ b/src/components/atoms/write-form/form-label.tsx @@ -0,0 +1,44 @@ +import { FormLabel } from '@/components/ui/form'; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { CircleInfo } from '../circle-icon/circle-info'; + +type WriteFormLabelProps = { + htmlFor?: string; + text: string; + className?: string; + info?: string; + isTooltipOpen?: boolean; +}; + +export const WriteFormLabel = ({ + htmlFor, + text, + className, + info, + isTooltipOpen, +}: WriteFormLabelProps) => { + return ( +
+ + {text} + + {info && ( + + + + + +

{info}

+
+
+ )} +
+ ); +}; diff --git a/src/components/button/index.tsx b/src/components/button/index.tsx deleted file mode 100644 index 11e6e17a..00000000 --- a/src/components/button/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const Button = () => { - return ; -}; - -export default Button; diff --git a/src/components/error-boundary/error-handler.tsx b/src/components/error-boundary/error-handler.tsx new file mode 100644 index 00000000..cfb91a98 --- /dev/null +++ b/src/components/error-boundary/error-handler.tsx @@ -0,0 +1,43 @@ +import { ErrorFallback } from '@/components/error-fallback'; + +interface ErrorHandlerProps { + error: Error | null; + resetErrorBoundary: () => void; + defaultMessage?: string; // ๊ธฐ๋ณธ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•  ์ˆ˜ ์žˆ์Œ +} +/** + * ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜ - fallback ์ปดํฌ๋„ŒํŠธ ํ‘œ์‹œ + * @param param0 + * @returns + */ +export const handleError = ({ + error, + resetErrorBoundary, + defaultMessage = '๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค' +}: ErrorHandlerProps) => { + if (error instanceof Error) { + // ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ + if (error.message.includes('Network')) { + return ( + + ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š” + + ); + } + + // ์ธ์ฆ ์—๋Ÿฌ + if (error.message.includes('401') || error.message.toLowerCase().includes('unauthorized') || error.message.includes('refresh ๋งŒ๋ฃŒ')) { + return ( + + ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค + + ); + } + } + + return ( + + {defaultMessage} + + ); +}; \ No newline at end of file diff --git a/src/components/error-boundary/index.tsx b/src/components/error-boundary/index.tsx new file mode 100644 index 00000000..17953b50 --- /dev/null +++ b/src/components/error-boundary/index.tsx @@ -0,0 +1,57 @@ +'use client'; + +/** + * ์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ๋Š” ํด๋ž˜์Šค๋กœ ๊ตฌํ˜„ํ•ด์•ผ๋จ + * ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋  ๋•Œ ์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์Œ + */ +import { Component, ReactNode } from 'react'; + +interface Props { + children: ReactNode; + fallback: (props: { error: Error | null; resetErrorBoundary: () => void }) => ReactNode; + onReset?: (error: Error | null) => void; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false, error: null }; // ์ดˆ๊ธฐ ์ƒํƒœ + } + + // ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + static getDerivedStateFromError(error: Error) { + return { hasError: true, error }; + } + + resetErrorBoundary = () => { + console.log('resetErrorBoundary'); + const {error} = this.state; + // ์—๋Ÿฌ ์ƒํƒœ ์ดˆ๊ธฐํ™” + this.setState({ hasError: false, error: null }); + this.props.onReset?.(error); + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + // ์—ฌ๊ธฐ์— Sentry ๋“ฑ ์—๋Ÿฌ ๋กœ๊น… ์„œ๋น„์Šค ์—ฐ๋™ ๊ฐ€๋Šฅ + // ์‚ฌ์šฉํ•˜์ง„ ์•Š์ง€๋งŒ ์ฐธ๊ณ ์šฉ์œผ๋กœ ์ƒ์„ฑ + console.error('Error caught by ErrorBoundary:', error, errorInfo); + } + + // ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ fallback ์ปดํฌ๋„ŒํŠธ ํ‘œ์‹œ + render() { + if (this.state.hasError) { + return this.props.fallback({ + error: this.state.error, + resetErrorBoundary: this.resetErrorBoundary + }); + } + + // ์—๋Ÿฌ ์—†๋Š” ๊ฒฝ์šฐ ์ž์‹ ์ปดํฌ๋„ŒํŠธ ํ‘œ์‹œ + return this.props.children; + } +} \ No newline at end of file diff --git a/src/components/error-fallback/index.tsx b/src/components/error-fallback/index.tsx new file mode 100644 index 00000000..a78df166 --- /dev/null +++ b/src/components/error-fallback/index.tsx @@ -0,0 +1,65 @@ +'use client'; + +import useAuthStore from '@/stores/useAuthStore'; +import { useRouter } from 'next/navigation'; +import { ReactNode } from 'react'; +import { Button } from '../ui/button'; + +interface ErrorFallbackProps { + error: Error | null; + resetErrorBoundary: () => void; + children: ReactNode; +} + +/** + * ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ fallback๋˜๋Š” ์ปดํฌ๋„ŒํŠธ + * @param error ์—๋Ÿฌ ๊ฐ์ฒด + * @param resetErrorBoundary ์—๋Ÿฌ ์žฌ์‹œ๋„ ํ•จ์ˆ˜ + * @param children ํ‘œ์‹œํ•  ๋ฉ”์‹œ์ง€ + * @returns + */ +export const ErrorFallback = ({ + error, + resetErrorBoundary, + children, +}: ErrorFallbackProps) => { + const { clearUser } = useAuthStore(); + + const router = useRouter(); + + const handleClick = () => { + if ( + error?.message.includes('401') || + error?.message.toLowerCase().includes('unauthorized') || + error?.message.toLowerCase().includes('refresh ๋งŒ๋ฃŒ') + ) { + clearUser(); + console.log('401 ์—๋Ÿฌ ๋ฐœ์ƒ'); + router.push('/login'); // 401 ์—๋Ÿฌ๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ + } else if (error?.message.includes('Network')) { + resetErrorBoundary(); // ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ๋ฉด ์žฌ์‹œ๋„ + } else { + resetErrorBoundary(); // ๊ธฐํƒ€ ์—๋Ÿฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์žฌ์‹œ๋„ + } + }; + + return ( +
+ {/* ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ */} +

{children}

+ {/* ์—๋Ÿฌ ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ ์—๋Ÿฌ ์ƒ์„ธ ๋ฉ”์‹œ์ง€ */} + {/* {error &&
{error.message}
} */} + {/* resetErrorBoundary์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ ์žฌ์‹œ๋„ ๋ฒ„ํŠผ */} + +
+ ); +}; diff --git a/src/components/molecules/bookmark-card-contents/index.tsx b/src/components/molecules/bookmark-card-contents/index.tsx new file mode 100644 index 00000000..d981fcbe --- /dev/null +++ b/src/components/molecules/bookmark-card-contents/index.tsx @@ -0,0 +1,62 @@ +import { Badge } from '@/components/atoms/badge'; +import { Title } from '@/components/atoms/title'; +import { ContentInfo } from '@/components/organisms/bookmark-card'; +import { Progress } from '@/components/ui/progress'; +import { BookmarkButtonContainer } from '@/features/bookmark/components/bookmark-button-container'; +import { getPosition } from '@/types/enums'; +import { formatYearMonthDayWithDot } from '@/utils/dateUtils'; + +type BookmarkCardContentsProps = { + className?: string; + info: ContentInfo; +}; + +export const BookmarkCardContents = ({ + className, + info, +}: BookmarkCardContentsProps) => { + const isCompleted = info.participants.length === info.maxParticipants; + return ( + info && ( +
+
+
+
+ + </header> + <div className="flex flex-row gap-2"> + <Badge + text={formatYearMonthDayWithDot(info.deadline)} + className="bg-gray-200 text-gray-500" + /> + {info.position.map((position) => ( + <Badge + text={getPosition(position)} + className="bg-gray-900 text-gray-100" + key={position} + /> + ))} + </div> + </div> + <BookmarkButtonContainer + isBookmark={info.isBookmark} + groupId={info.id} + /> + </div> + <footer> + <div className="flex flex-col gap-2"> + <p> + <span> + {info.participants.length}/{info.maxParticipants} + </span> + </p> + <div className="flex flex-row gap-2"> + <Progress value={50} /> + <span>{isCompleted ? '์™„๋ฃŒ' : '๋ชจ์ง‘์ค‘'}</span> + </div> + </div> + </footer> + </section> + ) + ); +}; diff --git a/src/components/molecules/card-image/index.tsx b/src/components/molecules/card-image/index.tsx new file mode 100644 index 00000000..d42a6342 --- /dev/null +++ b/src/components/molecules/card-image/index.tsx @@ -0,0 +1,33 @@ +import { Badge } from '../../atoms/badge'; +import { Thumbnail } from '../../atoms/thumbnail'; + +type CardImageProps = { + imageSrc: string; + alt: string; + width: number; + height: number; + className?: string; +}; +export const CardImage = ({ + imageSrc, + alt, + width, + height, + className, +}: CardImageProps) => { + return ( + <div className="relative inline-block" style={{ width, height }}> + <Badge + text="์˜ค๋Š˜ ๋งˆ๊ฐ" + className="absolute right-0 top-0 p-1 text-sm bg-white text-black rounded-bl-md" + /> + <Thumbnail + imageSrc={imageSrc} + alt={alt} + width={width} + height={height} + className={className} + /> + </div> + ); +}; diff --git a/src/components/molecules/group-create-button/index.tsx b/src/components/molecules/group-create-button/index.tsx new file mode 100644 index 00000000..337e52a6 --- /dev/null +++ b/src/components/molecules/group-create-button/index.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { LoginRequireButton } from '@/components/atoms/login-require-button'; +import { Button } from '@/components/ui/button'; +import useAuthStore from '@/stores/useAuthStore'; +import { routes } from '@/utils/routes'; +import { PlusIcon } from 'lucide-react'; +import { useRouter } from 'next/navigation'; + +export const WriteGroupButton = () => { + const user = useAuthStore((state) => state.user); + const router = useRouter(); + + const writeButtonClickHandler = () => { + if (!user) { + router.push(routes.login); + return; + } + + router.push(routes.write); + }; + + return ( + <div className="fixed md:absolute bottom-0 md:top-0 md:bottom-auto right-0 my-6 z-10"> + <Button + onClick={writeButtonClickHandler} + className="cursor-pointer rounded-[50%] md:hidden" + > + <PlusIcon /> + </Button> + <LoginRequireButton + onClick={writeButtonClickHandler} + className="hidden md:block cursor-pointer" + > + ๋งŒ๋“ค๊ธฐ + </LoginRequireButton> + </div> + ); +}; diff --git a/src/components/molecules/group-filter/group-filter.tsx b/src/components/molecules/group-filter/group-filter.tsx new file mode 100644 index 00000000..86a8c79b --- /dev/null +++ b/src/components/molecules/group-filter/group-filter.tsx @@ -0,0 +1,91 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; +import { SearchInput } from '@/components/molecules/search-input/search-input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { SelectGroup } from '@radix-ui/react-select'; + +/** + * ๋ชจ์ž„ ๋ชฉ๋ก ํ•„ํ„ฐ ์ปดํฌ๋„ŒํŠธ + * + * ์‚ฌ์šฉ์ž์˜ ์ฐธ๊ฐ€/๋ชจ์ง‘ ์ค‘์ธ ๋ชจ์ž„ ๋ชฉ๋ก์„ ํ•„ํ„ฐ๋งํ•œ๋‹ค. + * + * @returns ๋ชจ์ž„ ๋ชฉ๋ก ํ•„ํ„ฐ ์ปดํฌ๋„ŒํŠธ + */ +export const GroupFilter = () => { + const router = useRouter(); + + const pathname = usePathname(); + + const searchParams = useSearchParams(); + + const type = searchParams.get('type') ?? 'study'; + + const order = searchParams.get('order') ?? 'latest'; + + const handleOrderSelectChange = (value: string) => { + const queryParams = new URLSearchParams(searchParams); + if (queryParams.has('order') && queryParams.get('order') === value) { + return; + } + queryParams.set('order', value); + router.push(`${pathname}?${queryParams.toString()}`); + }; + + return ( + <div className="flex flex-col gap-y-2"> + <div className="flex justify-between"> + <ul className="flex gap-x-2 items-center"> + {[ + { + label: '์Šคํ„ฐ๋””', + value: 'study', + }, + { + label: 'ํ”„๋กœ์ ํŠธ', + value: 'project', + }, + ].map((item) => ( + <li key={item.value}> + <Link + className={`shrink-0 ${type === item.value ? 'text-gray-900' : 'text-gray-400'} font-semibold`} + href={`${pathname}?type=${item.value}`} + > + {item.label} + </Link> + </li> + ))} + </ul> + <SearchInput + containerClassName="w-[150px] md:w-[200px]" + placeholder="๊ฒ€์ƒ‰" + /> + </div> + <div className="flex"> + <Select + defaultValue={order ?? 'latest'} + onValueChange={handleOrderSelectChange} + > + <SelectTrigger> + <SelectValue placeholder="์ตœ์‹ ์ˆœ" /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectItem defaultChecked value="latest"> + ์ตœ์‹ ์ˆœ + </SelectItem> + <SelectItem value="oldest">์˜ค๋ž˜๋œ ์ˆœ</SelectItem> + </SelectGroup> + </SelectContent> + </Select> + </div> + </div> + ); +}; diff --git a/src/components/molecules/group/filter.tsx b/src/components/molecules/group/filter.tsx new file mode 100644 index 00000000..7c1e34ec --- /dev/null +++ b/src/components/molecules/group/filter.tsx @@ -0,0 +1,158 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { Popover, PopoverTrigger } from '@/components/ui/popover'; +import { + DEFAULT_POSITION_NAMES, + DEFAULT_SKILL_NAMES, + PositionName, + SkillName, +} from '@/types'; +import { PopoverContent } from '@radix-ui/react-popover'; +import { useSearchParams } from 'next/navigation'; + +type FilterProps = { + updateQueryParams: (queries: Record<string, string>) => void; +}; + +export const Filter = ({ updateQueryParams }: FilterProps) => { + const searchParams = useSearchParams(); + const selectedSkills = searchParams.get('skill')?.split(',') ?? []; + const selectedPositions = searchParams.get('position')?.split(',') ?? []; + const isSelectedSkills = selectedSkills?.length > 0; + const isSelectedPosition = selectedPositions?.length > 0; + + const skillSelectHandler = (skill: SkillName) => { + if (skill === '') { + // ์ „์ฒด ์„ ํƒํ•œ ๊ฒฝ์šฐ ์‚ญ์ œ + updateQueryParams({ skill: '' }); + return; + } + + if (selectedSkills.length === 0) { + // ์•„์ง ์„ ํƒํ•œ ๊ธฐ์ˆ ์ด ํ•˜๋‚˜๋„ ์—†๋‹ค๋ฉด ํ˜„์žฌ ์„ ํƒํ•œ ๊ธฐ์ˆ ๋งŒ ์ถ”๊ฐ€ + updateQueryParams({ skill: skill }); + return; + } + if (!selectedSkills.includes(skill)) { + // ๊ธฐ์กด์— ์„ ํƒ๋œ ๊ธฐ์ˆ ์— ํ˜„์žฌ ์„ ํƒํ•œ ๊ธฐ์ˆ ์ด ์—†๋‹ค๋ฉด ์ถ”๊ฐ€ + updateQueryParams({ skill: [...selectedSkills, skill].join(',') }); + return; + } + + // ์œ„์— ํ•ด๋‹นํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์ด๋ฏธ ์„ ํƒ๋˜์–ด์žˆ๋Š” ๊ธฐ์ˆ ์„ ๋˜ ์„ ํƒํ•œ ๊ฒƒ์ด๋ฏ€๋กœ ์‚ญ์ œ๋ผ์•ผ ํ•จ + const nextSkill = selectedSkills.filter( + (selectedSkill) => selectedSkill !== skill, + ); + + updateQueryParams({ + skill: nextSkill.join(','), + }); + }; + + // skillSlectHandler์™€ ๋™์ผํ•œ ๋กœ์ง + const positionSelectHandler = (position: PositionName) => { + if (position === '') { + updateQueryParams({ position: '' }); + return; + } + + if (selectedPositions.length === 0) { + updateQueryParams({ position: position }); + return; + } + if (!selectedPositions.includes(position)) { + updateQueryParams({ + position: [...selectedPositions, position].join(','), + }); + return; + } + + const nextSelectedPositions = selectedPositions.filter( + (selectedPosition) => selectedPosition !== position, + ); + + updateQueryParams({ + position: nextSelectedPositions.join(','), + }); + }; + + return ( + <div className="flex gap-1"> + <Popover> + <PopoverTrigger asChild> + <Button + variant={isSelectedSkills ? 'default' : 'outline'} + className="cursor-pointer" + > + ๊ธฐ์ˆ  ์Šคํƒ + </Button> + </PopoverTrigger> + <PopoverContent className="flex gap-1 mt-1 p-3 bg-white ring-1 ring-gray-200 rounded-md z-10"> + <Button + variant="outline" + onClick={() => skillSelectHandler('')} + className={`cursor-pointer + ${selectedSkills.length === 0 ? 'ring-2 ring-green-400' : ''} + `} + > + ์ „์ฒด + </Button> + {DEFAULT_SKILL_NAMES.map((skill) => ( + <Button + variant="outline" + key={skill} + onClick={() => skillSelectHandler(skill)} + className={`cursor-pointer + ${ + selectedSkills?.includes(skill) ? 'ring-2 ring-green-400' : '' + } + `} + > + {skill} + </Button> + ))} + </PopoverContent> + </Popover> + <Popover> + <PopoverTrigger asChild> + <Button + variant={isSelectedPosition ? 'default' : 'outline'} + className="cursor-pointer" + > + ํฌ์ง€์…˜ + </Button> + </PopoverTrigger> + <PopoverContent className="flex gap-1 mt-2 p-2 bg-white ring-1 ring-gray-200 rounded-md z-10"> + <Button + variant="outline" + onClick={() => positionSelectHandler('')} + className={`cursor-pointer + ${ + selectedPositions.length === 0 ? 'ring-2 ring-green-400' : '' + } + `} + > + ์ „์ฒด + </Button> + {DEFAULT_POSITION_NAMES.map((position) => ( + <Button + variant="outline" + key={position} + onClick={() => positionSelectHandler(position)} + className={`cursor-pointer + ${ + selectedPositions?.includes(position) + ? 'ring-2 ring-green-400' + : '' + } + `} + > + {position} + </Button> + ))} + </PopoverContent> + </Popover> + </div> + ); +}; diff --git a/src/components/molecules/group/group-card.tsx b/src/components/molecules/group/group-card.tsx new file mode 100644 index 00000000..722192d4 --- /dev/null +++ b/src/components/molecules/group/group-card.tsx @@ -0,0 +1,60 @@ +'use client'; + +import { Badge } from '@/components/atoms/badge'; +import { CloseCover } from '@/components/atoms/group/close-cover'; +import { Deadline } from '@/components/atoms/group/deadline'; +import { GroupPositions } from '@/components/atoms/group/group-positions'; +import { GroupTitle } from '@/components/atoms/group/group-title'; +import { GroupProgress } from '@/components/atoms/group/particiapant-progress'; +import { BookmarkButtonContainer } from '@/features/bookmark/components/bookmark-button-container'; +import { Group, GroupTypeName } from '@/types'; +import { formatYearMonthDayWithDot, isBeforeToday } from '@/utils/dateUtils'; +import { routes } from '@/utils/routes'; +import Link from 'next/link'; +import { GroupSkills } from '../../atoms/group/group-skills'; + +type GroupCardProps = { + item: Group; +}; + +// TODO : ์„น์…˜๋ณ„๋กœ component ๋‚˜๋ˆ„๊ธฐ +export const GroupCard = ({ item }: GroupCardProps) => { + const isClosed = + isBeforeToday(item.deadline) || + item.participants.length >= item.maxParticipants; + + return ( + <div className="relative p-5 h-[280px] lg:h-[316px] bg-white shadow-sm ring-2 ring-gray-300/30 rounded-xl"> + <div className="absolute top-0 right-0 m-6"> + <BookmarkButtonContainer + isBookmark={item.isBookmark} + groupId={item.id} + /> + </div> + <Link + href={routes.groupDetail(item.id)} + className="flex flex-col justify-between h-full" + > + <div> + <Badge + text={GroupTypeName[item.type]} + className="w-[fit-content] text-sm font-semibold bg-gray-200" + /> + <Deadline text={formatYearMonthDayWithDot(item.deadline)} /> + <GroupTitle text={item.title} /> + </div> + <GroupProgress + participantsCount={item.participants.length} + maxParticipants={item.maxParticipants} + /> + <div className="mt-4"> + <div className="flex flex-col gap-2"> + <GroupSkills skills={item.skills} /> + <GroupPositions positions={item.position} /> + </div> + </div> + </Link> + {isClosed && <CloseCover itemId={item.id} />} + </div> + ); +}; diff --git a/src/components/molecules/group/group-list.tsx b/src/components/molecules/group/group-list.tsx new file mode 100644 index 00000000..c753898d --- /dev/null +++ b/src/components/molecules/group/group-list.tsx @@ -0,0 +1,113 @@ +'use client'; + +import { GroupCard } from '@/components/molecules/group/group-card'; +import { Empty } from '@/components/organisms/empty'; +import { useFetchInView } from '@/hooks/useFetchInView'; +import { useFetchItems } from '@/hooks/useFetchItems'; +import { Group } from '@/types'; +import { Position, Skill } from '@/types/enums'; +import flattenPages from '@/utils/flattenPages'; +import { useEffect, useMemo, useState } from 'react'; + +enum EMPTY_INFO_MESSAGE { + EMPTY_INITIAL = '์ƒ์„ฑ๋œ ๊ทธ๋ฃน์ด ์—†์Šต๋‹ˆ๋‹ค', + SEARCH = '๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค', + FILTER = '์กฐ๊ฑด์— ํ•ด๋‹นํ•˜๋Š” ๊ทธ๋ฃน์ด ์—†์Šต๋‹ˆ๋‹ค.', +} + +type GroupListProps = { + serverQueryParams: Record<string, string | undefined>; +}; + +export const GroupList = ({ serverQueryParams }: GroupListProps) => { + const [isEmptyItems, setIsEmptyItems] = useState(true); + const [emptyInfoMessage, setEmptyInfoMessage] = + useState<EMPTY_INFO_MESSAGE | null>(null); + + const queryParams = useMemo(() => { + return { + type: serverQueryParams.type ?? '', + skill: serverQueryParams.skill + ? serverQueryParams.skill + .split(',') + .map((v) => Skill[v as keyof typeof Skill]) + .join(',') + : '', + position: serverQueryParams.position + ? serverQueryParams.position + .split(',') + .map((v) => Position[v as keyof typeof Position]) + .join(',') + : '', + sort: serverQueryParams.sort ?? 'createdAt', + order: serverQueryParams.order ?? 'desc', + search: serverQueryParams.search ?? '', + }; + }, [ + serverQueryParams.type, + serverQueryParams.skill, + serverQueryParams.position, + serverQueryParams.sort, + serverQueryParams.order, + serverQueryParams.search, + ]); + + const { data, fetchNextPage, hasNextPage, isLoading, isFetchingNextPage } = + useFetchItems<Group>({ + url: '/v2/groups', + queryParams: { + ...queryParams, + size: 10, + }, + }); + + const { ref } = useFetchInView({ + fetchNextPage, + isLoading, + isFetchingNextPage, + options: { + rootMargin: '300px', + }, + }); + + const items = flattenPages(data.pages); + + // ๋นˆ data ์ฒ˜๋ฆฌ ๋กœ์ง + useEffect(() => { + if (items.length === 0) { + setIsEmptyItems(true); + if (queryParams.search) { + setEmptyInfoMessage(EMPTY_INFO_MESSAGE.SEARCH); + return; + } else if ( + queryParams.type || + queryParams.skill || + queryParams.position + ) { + setEmptyInfoMessage(EMPTY_INFO_MESSAGE.FILTER); + return; + } else { + setEmptyInfoMessage(EMPTY_INFO_MESSAGE.EMPTY_INITIAL); + return; + } + } else { + setIsEmptyItems(false); + setEmptyInfoMessage(null); + } + }, [queryParams, items.length]); + + return ( + <> + {isEmptyItems && emptyInfoMessage !== null ? ( + <Empty mainText={emptyInfoMessage} subText="" /> + ) : ( + <ul className="grid mt-8 gap-4 md:grid-cols-2 xl:grid-cols-4"> + {items.map((group) => ( + <GroupCard key={group.id} item={group} /> + ))} + {hasNextPage && <div className="h-[300px]" ref={ref}></div>} + </ul> + )} + </> + ); +}; diff --git a/src/components/molecules/group/sort-order.tsx b/src/components/molecules/group/sort-order.tsx new file mode 100644 index 00000000..1161b86f --- /dev/null +++ b/src/components/molecules/group/sort-order.tsx @@ -0,0 +1,103 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { Popover } from '@/components/ui/popover'; +import { GroupSort, Order } from '@/types'; +import { + PopoverClose, + PopoverContent, + PopoverTrigger, +} from '@radix-ui/react-popover'; +import { useSearchParams } from 'next/navigation'; + +type OrderProps = { + updateQueryParams: (queries: Record<string, string>) => void; +}; + +export const SortOrder = ({ updateQueryParams }: OrderProps) => { + const searchParams = useSearchParams(); + const selectedSort = searchParams.get('sort'); + const selectedOrder = searchParams.get('order'); + + const orderOptions: { + name: string; + value: { sort: GroupSort; order: Order }; + }[] = [ + { name: '์ž‘์„ฑ์ผ์ž โ–ผ', value: { sort: 'createdAt', order: 'desc' } }, + { name: '์ž‘์„ฑ์ผ์ž โ–ฒ', value: { sort: 'createdAt', order: 'asc' } }, + { name: '๋งˆ๊ฐ์ผ์ž โ–ผ', value: { sort: 'deadline', order: 'desc' } }, + { name: '๋งˆ๊ฐ์ผ์ž โ–ฒ', value: { sort: 'deadline', order: 'asc' } }, + ]; + + const getSelectedOrderOptionName = () => { + const found = orderOptions.find( + (option) => + option.value.sort === selectedSort && + option.value.order === selectedOrder, + ); + + if (!selectedSort && !selectedOrder) { + // ์ดˆ๊ธฐ๊ฐ’์€ sort, order๊ฐ€ ๋ชจ๋‘ ์—†์œผ๋ฏ€๋กœ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜ + return '์ž‘์„ฑ์ผ์ž โ–ผ'; + } + + return found?.name; + }; + + const orderSelectHandler = (option: { sort: GroupSort; order: Order }) => { + if (selectedSort === option.sort && selectedOrder === option.order) { + // ์ด๋ฏธ ์„ ํƒ๋œ ์ •๋ ฌ ์˜ต์…˜๊ณผ ํ˜„์žฌ ์„ ํƒํ•œ ์ •๋ ฌ ์˜ต์…˜์ด ๊ฐ™์„ ๊ฒฝ์šฐ ์•„๋ฌด ๋™์ž‘ํ•˜์ง€ ์•Š์Œ + return; + } + + // ์ด๋ฏธ ์„ ํƒ๋œ sort๋‚˜ order๊ฐ€ ํ˜„์žฌ ์„ ํƒํ•œ sort๋‚˜ order์™€ ๊ฐ™์„ ๊ฒฝ์šฐ updateQueryParams์—์„œ ์ œ์™ธํ•˜์—ฌ ํ† ๊ธ€๋˜์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค + if (selectedSort === option.sort) { + if (option.order === 'desc') { + updateQueryParams({ order: option.order, cursor: 'null' }); + return; + } + updateQueryParams({ order: option.order }); + return; + } + if (selectedOrder === option.order) { + if (option.order === 'desc') { + updateQueryParams({ sort: option.sort, cursor: 'null' }); + return; + } + updateQueryParams({ sort: option.sort }); + return; + } + + if (option.order === 'desc') { + updateQueryParams({ + sort: option.sort, + order: option.order, + cursor: 'null', + }); + return; + } + // ์ด๋ฏธ ์„ ํƒ๋œ ์ •๋ ฌ ์˜ต์…˜๊ณผ ์•„์˜ˆ ๋‹ค๋ฅธ ๊ฒฝ์šฐ updateQueryParams์—์„œ ๋ชจ๋‘ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค + updateQueryParams({ sort: option.sort, order: option.order }); + }; + + return ( + <Popover> + <PopoverTrigger asChild> + <Button variant="outline" className=" ml-auto cursor-pointer"> + {getSelectedOrderOptionName()} + </Button> + </PopoverTrigger> + <PopoverContent className="flex flex-col gap-1 px-4 py-2 text-sm text-primary font-medium bg-white rounded-[12px] border border-gray-200 z-10"> + {orderOptions.map((option) => ( + <PopoverClose + key={option.name} + onClick={() => orderSelectHandler(option.value)} + className="p-1 cursor-pointer" + > + {option.name} + </PopoverClose> + ))} + </PopoverContent> + </Popover> + ); +}; diff --git a/src/components/molecules/input-checkbox-field/index.tsx b/src/components/molecules/input-checkbox-field/index.tsx new file mode 100644 index 00000000..097227d2 --- /dev/null +++ b/src/components/molecules/input-checkbox-field/index.tsx @@ -0,0 +1,82 @@ +// components/molecules/FormCheckboxGroupField.tsx + +import { + ControllerRenderProps, + FieldPath, + FieldValues, + UseFormReturn, +} from 'react-hook-form'; +import { + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Checkbox } from '@/components/ui/checkbox'; + +interface FormCheckboxGroupFieldProps<T extends FieldValues> { + form: UseFormReturn<T>; + name: FieldPath<T>; + label: string; + description?: string; + options: string[]; +} + +export const FormCheckboxGroupField = <T extends FieldValues>({ + form, + name, + label, + description, + options, +}: FormCheckboxGroupFieldProps<T>) => { + return ( + <FormItem> + <div className="mb-4"> + <FormLabel className="text-base">{label}</FormLabel> + {description && <FormDescription>{description}</FormDescription>} + </div> + <div className="flex gap-4 flex-wrap"> + {options.map((option) => ( + <FormField + key={option} + control={form.control} + name={name} + render={({ + field, + }: { + field: ControllerRenderProps<T, FieldPath<T>>; + }) => { + return ( + <FormItem className="flex items-start space-x-3 space-y-0"> + <FormControl> + <Checkbox + className="hidden" + checked={field.value?.includes(option)} + onCheckedChange={(checked) => { + return checked + ? field.onChange([...field.value, option]) + : field.onChange( + field.value?.filter((v: string) => v !== option), + ); + }} + /> + </FormControl> + <FormLabel + className={`font-normal p-1 rounded text-gray-600 cursor-pointer bg-gray-300 ${ + form.watch(name).includes(option) && 'bg-red-300' + }`} + > + {option} + </FormLabel> + </FormItem> + ); + }} + /> + ))} + </div> + <FormMessage /> + </FormItem> + ); +}; diff --git a/src/components/molecules/input-radiogroup-field/index.tsx b/src/components/molecules/input-radiogroup-field/index.tsx new file mode 100644 index 00000000..26677306 --- /dev/null +++ b/src/components/molecules/input-radiogroup-field/index.tsx @@ -0,0 +1,70 @@ +import { + ControllerRenderProps, + FieldPath, + FieldValues, + UseFormReturn, +} from 'react-hook-form'; +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; + +interface FormRadioGroupFieldProps<T extends FieldValues> { + form: UseFormReturn<T>; + name: FieldPath<T>; + label: string; + options: string[]; +} + +export const FormRadioGroupField = <T extends FieldValues>({ + form, + name, + label, + options, +}: FormRadioGroupFieldProps<T>) => { + return ( + <FormField + control={form.control} + name={name} + render={({ + field, + }: { + field: ControllerRenderProps<T, FieldPath<T>>; + }) => ( + <FormItem className="space-y-3"> + <FormLabel>{label}</FormLabel> + <FormControl> + <RadioGroup + onValueChange={field.onChange} + defaultValue={field.value} + className="flex gap-4 flex-wrap" + > + {options.map((option) => ( + <FormItem + key={option} + className="flex items-center space-x-3 space-y-0" + > + <FormControl> + <RadioGroupItem value={option} className="hidden" /> + </FormControl> + <FormLabel + className={`font-normal p-1 rounded text-gray-600 cursor-pointer bg-gray-300 ${ + form.watch(name) === option && 'bg-red-300' + }`} + > + {option} + </FormLabel> + </FormItem> + ))} + </RadioGroup> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + ); +}; diff --git a/src/components/molecules/input-select-field/index.tsx b/src/components/molecules/input-select-field/index.tsx new file mode 100644 index 00000000..7b78fc53 --- /dev/null +++ b/src/components/molecules/input-select-field/index.tsx @@ -0,0 +1,63 @@ +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { FieldPath, FieldValues, UseFormReturn } from 'react-hook-form'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; + +type InputSelectFieldProps<T extends FieldValues> = { + form: UseFormReturn<T>; + name: FieldPath<T>; + label: string; + placeholder: string; + options: { + value: string; + label: string; + }[]; + selectTriggerClassName?: string; +}; + +export const InputSelectField = <T extends FieldValues>({ + form, + name, + label, + placeholder, + options, + selectTriggerClassName, +}: InputSelectFieldProps<T>) => { + return ( + <FormField + control={form.control} + name={name} + render={({ field }) => ( + <FormItem> + <FormLabel>{label}</FormLabel> + <Select onValueChange={field.onChange} defaultValue={field.value ?? undefined}> + <FormControl> + <SelectTrigger className={selectTriggerClassName ?? ''}> + <SelectValue placeholder={placeholder} /> + </SelectTrigger> + </FormControl> + <SelectContent> + {options.map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + <FormMessage /> + </FormItem> + )} + /> + ); +}; diff --git a/src/components/molecules/input-text-field/index.tsx b/src/components/molecules/input-text-field/index.tsx new file mode 100644 index 00000000..24b84f67 --- /dev/null +++ b/src/components/molecules/input-text-field/index.tsx @@ -0,0 +1,41 @@ +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { InputText } from '@/components/atoms/input-text'; +import { FieldPath, FieldValues, UseFormReturn } from 'react-hook-form'; + +type InputTextFieldProps<T extends FieldValues> = { + form: UseFormReturn<T>; // ์ œ๋„ค๋ฆญ์œผ๋กœ ํ™•์žฅ ๊ฐ€๋Šฅ + name: FieldPath<T>; + label: string; + placeholder?: string; + type?: 'text' | 'password' | 'search' | 'email' | 'url' | 'tel' | 'number'; +}; + +export const InputTextField = <T extends FieldValues>({ + label, + type = 'text', + name, + form, + placeholder, +}: InputTextFieldProps<T>) => { + return ( + <FormField + control={form.control} + name={name} + render={({ field }) => ( + <FormItem> + <FormLabel className="font-bold text-gray-900">{label}</FormLabel> + <FormControl> + <InputText placeholder={placeholder} type={type} {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + ); +}; diff --git a/src/components/molecules/notification-badge/index.tsx b/src/components/molecules/notification-badge/index.tsx new file mode 100644 index 00000000..d5034d54 --- /dev/null +++ b/src/components/molecules/notification-badge/index.tsx @@ -0,0 +1,11 @@ +import { Badge } from '@/components/ui/badge'; + +/** + * ์•ˆ ์ฝ์€ ์•Œ๋žŒ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ํ™œ์„ฑํ™” -> ์•ˆ ์ฝ์€ ์•Œ๋žŒ๊ฐœ์ˆ˜ ์—…๋ฐ์ดํŠธ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ถ”๊ฐ€ ํ•„์š” + * @returns + */ +export const NotificationBadge = () => { + return ( + <Badge className='w-2 h-2 p-0 absolute top-0 right-0 bg-red-500'/> + ) +}; diff --git a/src/components/molecules/notification-list/index.tsx b/src/components/molecules/notification-list/index.tsx new file mode 100644 index 00000000..2c0bb10d --- /dev/null +++ b/src/components/molecules/notification-list/index.tsx @@ -0,0 +1,165 @@ +import { request } from '@/api/request'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { useFetchInView } from '@/hooks/useFetchInView'; +import { useFetchItems } from '@/hooks/useFetchItems'; +import useAuthStore from '@/stores/useAuthStore'; +import useNotificationStore from '@/stores/useNotificationStore'; +import { Notification as NotificationType } from '@/types/index'; +import { useQuery } from '@tanstack/react-query'; +import Image from 'next/image'; +import { useEffect, useState } from 'react'; +import { z } from 'zod'; +import { NotificationItem } from '../../atoms/notification-item'; + +const NotificationPageSchema = z.object({ + status: z.object({ + success: z.boolean(), + }), + notifications: z.object({ + hasNext: z.boolean(), + cursor: z.number().nullable(), + items: z.array(z.any()), + }), +}); + +export const NotificationList = () => { + const [isOpen, setIsOpen] = useState(false); + const { setNotifications, setUnreadCount } = useNotificationStore(); + const unreadCount = useNotificationStore((state) => state.unreadCount); + const notifications = useNotificationStore((state) => state.notifications); + const user = useAuthStore((state) => state.user); + + // ์ „์ฒด ์•Œ๋ฆผ ๋ชฉ๋ก ์กฐํšŒ + // TODO: ๋ฐฑ์—”๋“œ ๋ฐ์ดํ„ฐ ํƒ€์ž… ๊ณ ์ณ์•ผ๋จ - ํ”„์  ๋๋‚˜๊ณ  ๊ณ ์น ์˜ˆ์ • + const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = + useFetchItems({ + url: '/v1/notification', + getNextPageParam: (lastPage) => { + const page = NotificationPageSchema.parse(lastPage); + return page.notifications.hasNext ? page.notifications.cursor : null; + }, + }); + + // ์•ˆ ์ฝ์€ ์•Œ๋ฆผ ๋ชฉ๋ก ์กฐํšŒ + const { data: unreadData, isLoading: isUnreadLoading } = useQuery<{ + unreadCount: number; + }>({ + queryKey: ['unread-count'], + queryFn: async () => { + return await request.get( + '/v1/notification/unread-count', + {}, + { + credentials: 'include', + }, + ); + }, + }); + + const { ref } = useFetchInView({ + fetchNextPage, + isLoading: isUnreadLoading, + isFetchingNextPage, + }); + + const normalizeNotification = ( + item: Record<string, unknown>, + ): NotificationType => { + const { content, created_at, createdAt, isRead, read, message, ...rest } = + item as Partial<NotificationType>; + return { + ...rest, + message: content || message, + isRead: !!(isRead || read), + createdAt: createdAt || created_at, + } as NotificationType; + }; + + useEffect(() => { + if (!data) return; + + const allPages = data.pages.map((page) => { + const result = NotificationPageSchema.safeParse(page); + if (result.success) { + return result.data.notifications.items ?? []; + } + return []; + }); + + const newItems = allPages.flat().map(normalizeNotification); + setNotifications(newItems); + // eslint-disable-next-line + }, [data]); + + // ์•ˆ ์ฝ์€ ์•Œ๋žŒ ์—…๋ฐ์ดํŠธ + useEffect(() => { + if (!unreadData) return; + setUnreadCount(Number(unreadData.unreadCount) || 0); + }, [unreadData, setUnreadCount]); + + const openhandler = (open: boolean) => { + setIsOpen(open); + }; + + const renderNotificationItems = () => + notifications.map( + (notification: NotificationType, idx: number) => + notification && ( + <div + key={notification.id} + ref={idx === notifications.length - 1 ? ref : undefined} + > + <NotificationItem + notification={notification} + onClose={() => setIsOpen(false)} + /> + </div> + ), + ); + + if (!user) return null; + + return ( + <Popover open={isOpen} onOpenChange={openhandler}> + <PopoverTrigger className="relative"> + <div className="p-2 rounded-full hover:bg-gray-100 transition-colors duration-200"> + <Image + src={`/icons/alarm-${unreadCount > 0 ? 'active' : 'default'}.svg`} + alt="์•Œ๋ฆผ" + width={24} + height={24} + className="opacity-70" + /> + </div> + </PopoverTrigger> + <PopoverContent className="w-80 p-0 shadow-lg" align="end"> + <div className="flex flex-col"> + <div className="px-4 py-3 border-b border-gray-100"> + <h4 className="text-sm font-semibold text-gray-700">์•Œ๋ฆผ</h4> + </div> + <div className="max-h-[400px] overflow-y-auto"> + {notifications.length ? ( + renderNotificationItems() + ) : ( + <div className="flex flex-col items-center justify-center py-12 text-gray-500"> + <Image + src="/icons/alarm-default.svg" + alt="๋นˆ ์•Œ๋ฆผ" + width={32} + height={32} + className="opacity-50 mb-2" + /> + <p className="text-sm">์•Œ๋ฆผ์ด ์—†์Šต๋‹ˆ๋‹ค</p> + </div> + )} + {hasNextPage && <div ref={ref} className="h-4" />} + </div> + </div> + </PopoverContent> + </Popover> + ); +}; diff --git a/src/components/molecules/notification-list/notification-list.test.tsx b/src/components/molecules/notification-list/notification-list.test.tsx new file mode 100644 index 00000000..39fafa45 --- /dev/null +++ b/src/components/molecules/notification-list/notification-list.test.tsx @@ -0,0 +1,80 @@ +import { server } from '@/mocks/server'; +import useAuthStore from '@/stores/useAuthStore'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen, waitFor } from '@testing-library/react'; +import { http, HttpResponse } from 'msw'; +import { Suspense } from 'react'; +import { NotificationList } from '.'; + +const createTestQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); + +const renderWithClient = (ui: React.ReactElement) => { + const queryClient = createTestQueryClient(); + return render( + <QueryClientProvider client={queryClient}> + <Suspense fallback={<div>Loading...</div>}>{ui}</Suspense> + </QueryClientProvider>, + ); +}; + +const mockUser = { + userId: 1, + email: 'test@test.com', + nickname: 'Test User', + profileImage: null, + position: null, + skills: null, + isFollowing: false, + isFollower: false, + rate: 0, +}; + +describe('์•Œ๋žŒ ๋ชฉ๋ก ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ', () => { + beforeAll(() => { + server.listen(); + useAuthStore.setState({ user: mockUser }); + }); + + afterEach(() => { + server.resetHandlers(); + }); + + afterAll(() => server.close()); + + it('์•ˆ ์ฝ์€ ์•Œ๋žŒ์ด 1๊ฐœ ์ด์ƒ์ธ ๊ฒฝ์šฐ badge๊ฐ€ ํ‘œ์‹œ๋œ๋‹ค', async () => { + // Set cookie before making requests + document.cookie = 'accessToken=test-token'; + + server.use( + http.get('/api/notification', () => { + return HttpResponse.json({ items: [] }); + }), + + http.get('/api/notification/unread-count', () => { + return HttpResponse.json({ unreadCount: 1 }); + }), + ); + + renderWithClient(<NotificationList />); + + const badge = await screen.findByRole('status'); + expect(badge).toBeInTheDocument(); + }); + + it('๋กœ๊ทธ์ธ์ด ์•ˆ๋œ ๊ฒฝ์šฐ list๊ฐ€ ๋ Œ๋”๋ง ๋˜์ง€ ์•Š๋Š”๋‹ค', async () => { + useAuthStore.setState({ user: null }); + renderWithClient(<NotificationList />); + + //๋น„๋™๊ธฐ ์ปดํฌ๋„ŒํŠธ๋ผ wait + await waitFor(() => { + expect(screen.queryByText('Notification')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/molecules/participant-card/index.tsx b/src/components/molecules/participant-card/index.tsx new file mode 100644 index 00000000..12b00a0f --- /dev/null +++ b/src/components/molecules/participant-card/index.tsx @@ -0,0 +1,21 @@ +import { Avatar } from '@/components/atoms/avatar'; +import { UserSummary } from '@/types'; +import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback'; +import Link from 'next/link'; + +export const ParticipantCard = ({ + userId, + nickname, + profileImage, + email, +}: UserSummary) => { + return ( + <Link href={`/users/${userId}`} className="flex gap-3 boder-2 "> + <Avatar + imageSrc={getDisplayProfileImage(profileImage)} + fallback={getDisplayNickname(nickname, email)} + /> + <div>{getDisplayNickname(nickname, email)}</div> + </Link> + ); +}; diff --git a/src/components/molecules/position-badge/index.tsx b/src/components/molecules/position-badge/index.tsx new file mode 100644 index 00000000..53bb2d63 --- /dev/null +++ b/src/components/molecules/position-badge/index.tsx @@ -0,0 +1,67 @@ +'use client'; + +import { Badge } from '@/components/atoms/badge'; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { Position } from '@/types/enums'; + +/** ์ˆซ์ž enum Skill์˜ ํ‚ค ์ด๋ฆ„์œผ๋กœ ์ด๋ฏธ์ง€ ์ฃผ์†Œ์™€ ๋งค์นญ ์‹œํ‚ค๊ธฐ */ +export const allPositionlKeys = Object.keys(Position).filter((key) => + isNaN(Number(key)), +); + +const positionWithDescription: Record< + (typeof allPositionlKeys)[number], + string +> = { + PM: 'ํ”„๋กœ์ ํŠธ ๋งค๋‹ˆ์ €', + PL: 'ํŒŒํŠธ ๋ฆฌ๋”', + AA: '๊ฐœ๋ฐœ ์„ค๊ณ„ ๋‹ด๋‹น์ž', + TA: '๊ธฐ์ˆ  ๋‹ด๋‹น์ž', + DA: '๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€', + QA: 'ํ’ˆ์งˆ ๋ณด์ฆ ๋‹ด๋‹น์ž', + FE: 'ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž', + BE: '๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž', + FS: 'ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž', +}; + +const positionLogoMap: { [key: string]: string } = {}; + +allPositionlKeys.forEach((position) => { + positionLogoMap[position] = `/logos/${position.replace( + /[^a-zA-Z0-9]/g, + '', + )}.png`; +}); + +type PositionBadgeProps = { + name: string; + positionClickHandler?: (name: string) => void; +}; + +export const PositionBadge = ({ + name, + positionClickHandler, +}: PositionBadgeProps) => { + return ( + <Tooltip> + <div className="position-badge flex flex-row cursor-pointer"> + <TooltipTrigger + type="button" + onClick={() => positionClickHandler?.(name)} + > + <Badge + text={name} + className="text-sm font-medium bg-gray-100 text-gray-600 px-1" + /> + </TooltipTrigger> + </div> + <TooltipContent> + <p>{positionWithDescription[name]}</p> + </TooltipContent> + </Tooltip> + ); +}; diff --git a/src/components/molecules/recommend-group/recommend-group-card.tsx b/src/components/molecules/recommend-group/recommend-group-card.tsx new file mode 100644 index 00000000..b87e4837 --- /dev/null +++ b/src/components/molecules/recommend-group/recommend-group-card.tsx @@ -0,0 +1,41 @@ +import { Badge } from '@/components/atoms/badge'; +import { HalfRoundBadge } from '@/components/atoms/badge/half-round-badge'; +import { RecommendDeadline } from '@/components/atoms/recommend/recommend-deadline'; +import { RecommendGroupTitle } from '@/components/atoms/recommend/recommend-group-title'; +import { Group, GroupTypeName } from '@/types'; +import { + formatRelativeTime, + formatYearMonthDayWithDot, +} from '@/utils/dateUtils'; +import { routes } from '@/utils/routes'; +import Link from 'next/link'; + +type RecommendGroupCardProps = { + item: Group; +}; + +// TODO : ์„น์…˜๋ณ„๋กœ component ๋‚˜๋ˆ„๊ธฐ +export const RecommendGroupCard = ({ item }: RecommendGroupCardProps) => { + return ( + <div className="w-[210px] h-[120px] lg:w-[276px] lg:h-[160px] p-4 lg:p-6 bg-white ring-2 ring-gray-400/30 rounded-2xl"> + <Link href={routes.groupDetail(item.id)}> + <div className="flex justify-between"> + <Badge + text={GroupTypeName[item.type]} + className="hidden lg:block h-[22px] text-xs md:text-sm font-semibold bg-gray-200" + /> + <HalfRoundBadge + text={`๐Ÿšจ๋งˆ๊ฐ ${ + formatRelativeTime(item.deadline).includes('๋…„') + ? formatRelativeTime(item.deadline).slice(6) + : formatRelativeTime(item.deadline) + }`} // formatRelativeTime์ด ๋…„์›”์ผ์„ ํ‘œ๊ธฐํ•ด์•ผ ํ•˜๋Š” ํ•จ์ˆ˜๋ผ์„œ ์›”์ผ๋งŒ ์ž๋ฆ„ + className="h-[22px] text-xs text-red-600 border-1 border-red-600" + /> + </div> + <RecommendDeadline text={formatYearMonthDayWithDot(item.endDate)} /> + <RecommendGroupTitle text={item.title} /> + </Link> + </div> + ); +}; diff --git a/src/components/molecules/search-input/search-input.tsx b/src/components/molecules/search-input/search-input.tsx new file mode 100644 index 00000000..35756547 --- /dev/null +++ b/src/components/molecules/search-input/search-input.tsx @@ -0,0 +1,81 @@ +'use client'; + +import { Input } from '@/components/ui/input'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; +import { ComponentProps, useEffect, useRef } from 'react'; +import { cn } from '@/lib/utils'; +import Image from 'next/image'; + +type SearchInputProps = { + inputClassName?: string; + containerClassName?: string; +}; + +/** + * ์‚ฌ์šฉ์ž ์ด๋ฆ„, ๋ชจ์ž„ ๋ช…์„ ๊ฒ€์ƒ‰ํ•˜๊ธฐ ์œ„ํ•œ ์ปดํฌ๋„ŒํŠธ + * + * @param props ๊ธฐ๋ณธ input ์š”์†Œ์˜ props + * @returns ๊ฒ€์ƒ‰ ์ธํ’‹ ์ปดํฌ๋„ŒํŠธ + */ +export const SearchInput = ({ + inputClassName, + containerClassName, + ...props +}: ComponentProps<'input'> & SearchInputProps) => { + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + const inputRef = useRef<HTMLInputElement>(null); + + // search ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด, ์ธํ’‹ ์š”์†Œ์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•œ๋‹ค. + useEffect(() => { + if (inputRef.current) { + inputRef.current.value = searchParams.get('search') ?? ''; + } + }, [searchParams]); + + /** + * ์—”ํ„ฐ ํ‚ค๊ฐ€ ๋ˆŒ๋ฆฌ๋ฉด, ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋œ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ๋‹ค. + * + * ์ธํ’‹ ์š”์†Œ์˜ ๊ฐ’์ด ๋นˆ ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ, search ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ญ์ œํ•œ๋‹ค. + * ์ธํ’‹ ์š”์†Œ์˜ ๊ฐ’์ด ๋นˆ ๋ฌธ์ž์—ด์ด ์•„๋‹Œ ๊ฒฝ์šฐ, search ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ๋ณ€๊ฒฝํ•œ๋‹ค. + * + * @param e keyDown ์ด๋ฒคํŠธ + */ + const keyDownHandler: React.KeyboardEventHandler<HTMLInputElement> = (e) => { + if (e.nativeEvent.isComposing) return; + + if (e.key === 'Enter') { + if (inputRef.current) { + const queryParams = new URLSearchParams(searchParams); + const search = inputRef.current.value; + if (search) { + queryParams.set('search', search); + } else { + queryParams.delete('search'); + } + router.push(`${pathname}?${queryParams.toString()}`); + } + } + }; + + return ( + <div + className={cn( + 'flex items-center gap-x-[10px] rounded-[30px] bg-gray-100 px-5 py-2 w-[200px] text-gray-500 h-9 self-end', + containerClassName, + )} + > + <Image src="/icons/search.svg" alt="search-icon" width={17} height={17} /> + <Input + {...props} + className={cn( + 'bg-gray-100 border-none shadow-none focus-visible:ring-0 p-0', + inputClassName, + )} + onKeyDown={keyDownHandler} + ref={inputRef} + /> + </div> + ); +}; diff --git a/src/components/molecules/skill-badge/index.tsx b/src/components/molecules/skill-badge/index.tsx new file mode 100644 index 00000000..b839fd3f --- /dev/null +++ b/src/components/molecules/skill-badge/index.tsx @@ -0,0 +1,48 @@ +'use client'; + +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { SkillName } from '@/types'; +import { Skill } from '@/types/enums'; +import Image from 'next/image'; + +/** ์ˆซ์ž enum Skill์˜ ํ‚ค ์ด๋ฆ„์œผ๋กœ ์ด๋ฏธ์ง€ ์ฃผ์†Œ์™€ ๋งค์นญ ์‹œํ‚ค๊ธฐ */ +export const allSkillKeys = Object.keys(Skill).filter((key) => + isNaN(Number(key)), +); + +const skillLogoMap: { [key: string]: string } = {}; + +allSkillKeys.forEach((skill) => { + skillLogoMap[skill] = `/logos/${skill.replace(/[^a-zA-Z0-9]/g, '')}.png`; +}); + +type SkillBadgeProps = { + name: SkillName; + isDefault?: boolean; + skillClickHandler?: (name: SkillName) => void; +}; + +export const SkillBadge = ({ + name, + isDefault = true, + skillClickHandler, +}: SkillBadgeProps) => { + return ( + <Tooltip> + <div className="skill-badge flex w-full h-full flex-row border p-1 items-center justify-center rounded-full cursor-pointer"> + <TooltipTrigger type="button" onClick={() => skillClickHandler?.(name)}> + {isDefault && ( + <Image src={skillLogoMap[name]} alt="logo" width={20} height={20} /> + )} + </TooltipTrigger> + </div> + <TooltipContent> + <p>{name}</p> + </TooltipContent> + </Tooltip> + ); +}; diff --git a/src/components/molecules/star-rating/index.tsx b/src/components/molecules/star-rating/index.tsx new file mode 100644 index 00000000..f976717b --- /dev/null +++ b/src/components/molecules/star-rating/index.tsx @@ -0,0 +1,96 @@ +import { Star } from '@/components/atoms/star'; +import React, { useRef, useState } from 'react'; + +interface StarRatingProps { + initialRating?: number; + onRatingChange?: (rating: number) => void; + readOnly?: boolean; + maxRating?: number; +} + +/** + * ๋ณ„์  ์ปดํฌ๋„ŒํŠธ 5์  ๊ธฐ์ค€ + * @param initialRating ์ดˆ๊ธฐ ๋ณ„์  + * @param onRatingChange ๋ณ„์  ๋ณ€๊ฒฝ ์‹œ ์ฝœ๋ฐฑ + * @param readOnly ์ฝ๊ธฐ ์ „์šฉ ์—ฌ๋ถ€ + * @param maxRating ์ตœ๋Œ€ ๋ณ„์  + * @returns ๋ณ„์  ์ปดํฌ๋„ŒํŠธ + */ +export const StarRating: React.FC<StarRatingProps> = ({ + initialRating = 0, + onRatingChange, + readOnly = false, + maxRating = 5, +}) => { + const [rating, setRating] = useState(initialRating); + const [isDragging, setIsDragging] = useState(false); + const containerRef = useRef<HTMLDivElement>(null); //๋ณ„์  ์ปจํ…Œ์ด๋„ˆ + + const calculateRating = (clientX: number) => { + if (!containerRef.current) return undefined; + + const { left, width } = containerRef.current.getBoundingClientRect(); //๋ณ„์  ์ปจํ…Œ์ด๋„ˆ์˜ ์œ„์น˜์™€ ๋„ˆ๋น„๋ฅผ ๊ฐ€์ ธ์˜ด, width๋Š” ๋ณ„์  ์ปจํ…Œ์ด๋„ˆ์˜ ๋„ˆ๋น„ + const x = Math.max(0, Math.min(width, clientX - left)); //ํ˜„์žฌ ๋งˆ์šฐ์Šค ์œ„์น˜๊ฐ’ 0~100 + return Math.round((x / width) * maxRating * 10) / 10; //๋ณ„์  ์ปจํ…Œ์ด๋„ˆ์˜ ๋„ˆ๋น„์— ๋”ฐ๋ฅธ ๋ณ„์  ๊ณ„์‚ฐ + }; + + const mouseDownHandler = (e: React.MouseEvent) => { + setIsDragging(true); + const newRating = calculateRating(e.clientX); + if (newRating !== undefined) { + setRating(Math.min(maxRating, Math.max(0, newRating))); + onRatingChange?.(Math.min(maxRating, Math.max(0, newRating))); + } + }; + + const mouseMoveHandler = (e: React.MouseEvent) => { + if (isDragging) { + const newRating = calculateRating(e.clientX); + if (newRating !== undefined) { + setRating(Math.min(maxRating, Math.max(0, newRating))); + onRatingChange?.(Math.min(maxRating, Math.max(0, newRating))); + } + } + }; + + const mouseUpHandler = () => { + setIsDragging(false); + }; + + const mouseLeaveHandler = () => { + setIsDragging(false); + }; + + const eventHandlers = readOnly + ? {} + : { + onMouseDown: mouseDownHandler, + onMouseMove: mouseMoveHandler, + onMouseUp: mouseUpHandler, + onMouseLeave: mouseLeaveHandler, + }; + + return ( + <div className="flex items-center flex-col"> + <div + ref={containerRef} + className={`flex gap-2 ${ + readOnly ? 'cursor-default' : 'cursor-pointer' + }`} + {...eventHandlers} + > + {[...Array(maxRating)].map((_, i) => { + const fillPercent = (() => { + if (rating <= i) return 0; + if (rating >= i + 1) return 100; + return (rating - i) * 100; + })(); + return <Star key={i} className="w-8 h-8" fillPercent={fillPercent} />; + })} + </div> + <span className="ml-2"> + {rating.toFixed(1)} / {maxRating} + </span> + </div> + ); +}; diff --git a/src/components/molecules/tab/index.tsx b/src/components/molecules/tab/index.tsx new file mode 100644 index 00000000..a9e68e2f --- /dev/null +++ b/src/components/molecules/tab/index.tsx @@ -0,0 +1,42 @@ +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { GroupType } from '@/types'; + +export type TabType = { + value: GroupType | ''; // ์ „์ฒด ์„ ํƒ์€ ''๋กœ ํ‘œ๊ธฐํ•˜๋ฉฐ, ํƒ€์ž… ์ด๋ฆ„ ์—†์ด ๋ชจ๋“  ๊ทธ๋ฃน์„ ํฌํ•จ + label: string; +}; + +export const Tab = ({ + tabList, + children, + onValueChange, +}: { + tabList: TabType[]; + children: React.ReactNode; + onValueChange: (value: GroupType) => void; +}) => { + const handleValueChange = (value: string) => { + onValueChange(value as GroupType); + }; + + return ( + <Tabs defaultValue={tabList[0].value} onValueChange={handleValueChange}> + <TabsList className="py-3 md:py-5 -ml-2"> + {tabList.map((tab) => ( + <TabsTrigger + key={tab.value} + value={tab.value} + className="text-[20px] md:text-2xl font-extrabold cursor-pointer hover:text-gray-600" + > + {tab.label} + </TabsTrigger> + ))} + </TabsList> + {tabList.map((tab) => ( + <TabsContent key={tab.value} value={tab.value}> + {children} + </TabsContent> + ))} + </Tabs> + ); +}; diff --git a/src/components/molecules/write-form/autoAllow.tsx b/src/components/molecules/write-form/autoAllow.tsx new file mode 100644 index 00000000..71da8e60 --- /dev/null +++ b/src/components/molecules/write-form/autoAllow.tsx @@ -0,0 +1,34 @@ +import { WriteFormLabel } from '@/components/atoms/write-form/form-label'; +import { Checkbox } from '@/components/ui/checkbox'; +import { FormControl, FormField, FormItem } from '@/components/ui/form'; +import { WriteForm } from '@/types'; +import { UseFormReturn } from 'react-hook-form'; + +type TitleProps = { + form: UseFormReturn<WriteForm>; +}; + +export const AutoAllow = ({ form }: TitleProps) => { + return ( + <> + <FormField + control={form.control} + name="autoAllow" + render={(field) => ( + <FormItem className="flex items-center"> + <FormControl> + <Checkbox + id="autoAllow" + onClick={() => { + /** ํด๋ฆญ ์‹œ field์˜ ๊ฐ’์„ ๋ฐ›์•„์„œ ํ† ๊ธ€ */ + form.setValue('autoAllow', !field.field.value); + }} + /> + </FormControl> + <WriteFormLabel htmlFor="autoAllow" text="์ฐธ๊ฐ€ ์ž๋™ ์ˆ˜๋ฝ" /> + </FormItem> + )} + /> + </> + ); +}; diff --git a/src/components/molecules/write-form/deadlineCalendar.tsx b/src/components/molecules/write-form/deadlineCalendar.tsx new file mode 100644 index 00000000..4b14c1c9 --- /dev/null +++ b/src/components/molecules/write-form/deadlineCalendar.tsx @@ -0,0 +1,119 @@ +import { WriteFormLabel } from '@/components/atoms/write-form/form-label'; +import { Button } from '@/components/ui/button'; +import { Calendar } from '@/components/ui/calendar'; +import { + FormControl, + FormField, + FormItem, + FormMessage, +} from '@/components/ui/form'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { WriteForm } from '@/types'; +import { formatYearMonthDayWithDot } from '@/utils/dateUtils'; +import clsx from 'clsx'; +import { CalendarIcon } from 'lucide-react'; +import { UseFormReturn } from 'react-hook-form'; + +type TitleProps = { + form: UseFormReturn<WriteForm>; + isDeadlineCalendarOpen: boolean; + deadlineSelect: (date: Date) => void; + openDeadlineCalendar: () => void; + closeDeadlineCalendar: () => void; + validDeadline: Date; +}; + +export const DeadlineCalendar = ({ + form, + isDeadlineCalendarOpen, + deadlineSelect, + openDeadlineCalendar, + closeDeadlineCalendar, + validDeadline, +}: TitleProps) => { + const hasError = !!form.formState.errors.deadline; + /** calendar์—์„œ ๋‚ ์งœ ์„ ํƒ ํ›„ calendar๊ฐ€ ๋‹ซํžˆ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜ */ + const deadlineSelectHandler = ( + date: Date | undefined, + onChange: (date: Date | undefined) => void, + ) => { + if (!date) return; + + deadlineSelect(date); + onChange(date); + closeDeadlineCalendar(); + }; + + return ( + <> + <FormField + control={form.control} + name="deadline" + render={({ field, fieldState }) => ( + <FormItem> + <WriteFormLabel + text="๋ชจ์ง‘ ๋งˆ๊ฐ์ผ" + info="์˜ค๋Š˜ ์ดํ›„์˜ ๋‚ ์งœ๋ถ€ํ„ฐ ์„ ํƒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค" + /> + <Popover + open={isDeadlineCalendarOpen} + onOpenChange={ + isDeadlineCalendarOpen + ? closeDeadlineCalendar + : openDeadlineCalendar + } + > + <div className="flex items-center gap-5 relative w-[fit-content]"> + <div> + {field.value ? ( + formatYearMonthDayWithDot(field.value) + ) : ( + <> + <div className="text-gray-400"> + {formatYearMonthDayWithDot(validDeadline)} + </div> + </> + )} + </div> + <FormControl className="inline-block"> + <input + {...field} + tabIndex={-1} + className={clsx( + 'absolute top-[-4px] right-[-4px] bottom-[-4px] left-[-4px] text-transparent caret-transparent z-[-1] rounded-md', + hasError + ? 'outline-1 outline-destructive focus-visible:ring-red-500/20 focus-visible:ring-[3px]' + : 'border-none outline-none', + )} + aria-hidden="true" + value={field.value ? field.value.toISOString() : ''} + /> + </FormControl> + <PopoverTrigger className="cursor-pointer" asChild> + <Button type="button" className="w-[fit-content]"> + <CalendarIcon /> + </Button> + </PopoverTrigger> + <PopoverContent> + <Calendar + mode="single" + selected={field.value} + onSelect={(e) => { + deadlineSelectHandler(e, field.onChange); + }} + disabled={{ before: validDeadline }} + /> + </PopoverContent> + </div> + </Popover> + <FormMessage>{fieldState.error?.message}</FormMessage> + </FormItem> + )} + /> + </> + ); +}; diff --git a/src/components/molecules/write-form/endDateCalendar.tsx b/src/components/molecules/write-form/endDateCalendar.tsx new file mode 100644 index 00000000..435d8e0a --- /dev/null +++ b/src/components/molecules/write-form/endDateCalendar.tsx @@ -0,0 +1,117 @@ +import { WriteFormLabel } from '@/components/atoms/write-form/form-label'; +import { Button } from '@/components/ui/button'; +import { Calendar } from '@/components/ui/calendar'; +import { + FormControl, + FormField, + FormItem, + FormMessage, +} from '@/components/ui/form'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { WriteForm } from '@/types'; +import { formatYearMonthDayWithDot } from '@/utils/dateUtils'; +import clsx from 'clsx'; +import { CalendarIcon } from 'lucide-react'; +import { UseFormReturn } from 'react-hook-form'; + +type TitleProps = { + form: UseFormReturn<WriteForm>; + isEndDateCalendarOpen: boolean; + openEndDateCalendar: () => void; + closeEndDateCalendar: () => void; + validEndDate: Date; +}; + +export const EndDateCalendar = ({ + form, + isEndDateCalendarOpen, + openEndDateCalendar, + closeEndDateCalendar, + validEndDate, +}: TitleProps) => { + const hasError = !!form.formState.errors.endDate; + + /** calendar์—์„œ ๋‚ ์งœ ์„ ํƒ ํ›„ calendar๊ฐ€ ๋‹ซํžˆ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜ */ + const endDateSelect = ( + date: Date | undefined, + onChange: (date: Date | undefined) => void, + ) => { + if (!date) return; + + onChange(date); + closeEndDateCalendar(); + }; + + return ( + <> + <FormField + control={form.control} + name="endDate" + render={({ field, fieldState }) => ( + <FormItem> + <WriteFormLabel + text="๋ชจ์ž„ ์ข…๋ฃŒ์ผ" + info="๋ชจ์ž„ ์‹œ์ž‘์ผ์˜ ์ผ์ฃผ์ผ ์ดํ›„๋ถ€ํ„ฐ ์„ ํƒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค" + /> + <Popover + open={isEndDateCalendarOpen} + onOpenChange={ + isEndDateCalendarOpen + ? closeEndDateCalendar + : openEndDateCalendar + } + > + <div className="flex items-center gap-5"> + <div> + {field.value ? ( + formatYearMonthDayWithDot(field.value) + ) : ( + <> + <div className="text-gray-500"> + {formatYearMonthDayWithDot(validEndDate)} + </div> + </> + )} + </div> + <FormControl> + <input + {...field} + tabIndex={-1} + className={clsx( + 'absolute top-[-4px] right-[-4px] bottom-[-4px] left-[-4px] text-transparent caret-transparent z-[-1] rounded-md', + hasError + ? 'outline-1 outline-destructive focus-visible:ring-red-500/20 focus-visible:ring-[3px]' + : 'border-none outline-none', + )} + aria-hidden="true" + value={field.value ? field.value.toISOString() : ''} + /> + </FormControl> + <PopoverTrigger className="cursor-pointer" asChild> + <Button type="button" className="w-[fit-content]"> + <CalendarIcon /> + </Button> + </PopoverTrigger> + <PopoverContent> + <Calendar + mode="single" + selected={field.value} + onSelect={(e) => { + endDateSelect(e, field.onChange); + }} + disabled={{ before: validEndDate }} + /> + </PopoverContent> + </div> + </Popover> + <FormMessage>{fieldState.error?.message}</FormMessage> + </FormItem> + )} + /> + </> + ); +}; diff --git a/src/components/molecules/write-form/maxParticipants.tsx b/src/components/molecules/write-form/maxParticipants.tsx new file mode 100644 index 00000000..ed237f28 --- /dev/null +++ b/src/components/molecules/write-form/maxParticipants.tsx @@ -0,0 +1,84 @@ +import { WriteFormLabel } from '@/components/atoms/write-form/form-label'; +import { + FormControl, + FormField, + FormItem, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { WriteForm } from '@/types'; +import { useEffect, useState } from 'react'; +import { UseFormReturn } from 'react-hook-form'; + +type TitleProps = { + form: UseFormReturn<WriteForm>; +}; + +export const MaxParticipants = ({ form }: TitleProps) => { + const [isTooltipOpen, setIsTooltipOpen] = useState(false); + + const inputBlurHandler = (e: React.ChangeEvent<HTMLInputElement>) => { + const value = Number(e.target.value); + + if (value === 0) { + // ์ž…๋ ฅ๊ฐ’์ด ์—†๊ฑฐ๋‚˜ 0์ด๋ฉด 2๋กœ ์„ธํŒ… + form.setValue('maxParticipants', 2); + return; + } + + if (value < 2 || value > 30) { + const settedValue = Math.max(2, Math.min(30, value)); + form.setValue('maxParticipants', settedValue); + + setIsTooltipOpen(true); + } + }; + + /** + * ์ •์› ๋ณด์ • ํ›„ ํˆดํŒ์ด 3์ดˆ๋’ค์— ์‚ฌ๋ผ์ง€๊ฒŒ ํ•˜๊ธฐ + */ + + useEffect(() => { + if (isTooltipOpen) { + const timer = setTimeout(() => { + setIsTooltipOpen(false); + }, 3000); // 3์ดˆ + + return () => clearTimeout(timer); + } + }, [isTooltipOpen]); + + return ( + <> + <FormField + control={form.control} + name="maxParticipants" + render={({ field }) => ( + <FormItem> + <WriteFormLabel + htmlFor="maxParticipants" + text="์ •์›" + info="์ตœ์†Œ 2๋ช… ~ ์ตœ๋Œ€ 30๋ช…๊นŒ์ง€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค" + isTooltipOpen={isTooltipOpen} + /> + <FormControl> + <Input + id="maxParticipants" + min={2} + max={30} + placeholder="์ •์›์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" + {...field} + type="number" + onBlur={(e) => { + inputBlurHandler(e); + field.onBlur(); + }} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </> + ); +}; diff --git a/src/components/molecules/write-form/selcetType.tsx b/src/components/molecules/write-form/selcetType.tsx new file mode 100644 index 00000000..7da44fcf --- /dev/null +++ b/src/components/molecules/write-form/selcetType.tsx @@ -0,0 +1,56 @@ +import { WriteFormLabel } from '@/components/atoms/write-form/form-label'; +import { + FormControl, + FormField, + FormItem, + FormMessage, +} from '@/components/ui/form'; +import { Label } from '@/components/ui/label'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { GroupType, WriteForm } from '@/types'; +import { UseFormReturn } from 'react-hook-form'; + +type TitleProps = { + form: UseFormReturn<WriteForm>; +}; + +const ItemWrap = ({ children }: { children: React.ReactNode }) => { + return <div className="flex items-center gap-1">{children}</div>; +}; + +export const SelectType = ({ form }: TitleProps) => { + return ( + <> + <FormField + control={form.control} + name="type" + render={({ field }) => ( + <FormItem> + <WriteFormLabel htmlFor="autoAllow" text="๋ชจ์ง‘ ๊ตฌ๋ถ„" /> + <FormControl> + <RadioGroup + value={field.value} + onValueChange={field.onChange} + className="flex gap-4" + > + <ItemWrap> + <RadioGroupItem value={GroupType.STUDY} id="study" /> + <Label htmlFor="study" className="text-base"> + ์Šคํ„ฐ๋”” + </Label> + </ItemWrap> + <ItemWrap> + <RadioGroupItem value={GroupType.PROJECT} id="project" /> + <Label htmlFor="project" className="text-base"> + ํ”„๋กœ์ ํŠธ + </Label> + </ItemWrap> + </RadioGroup> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </> + ); +}; diff --git a/src/components/molecules/write-form/selectPosition.tsx b/src/components/molecules/write-form/selectPosition.tsx new file mode 100644 index 00000000..d9c7bd6b --- /dev/null +++ b/src/components/molecules/write-form/selectPosition.tsx @@ -0,0 +1,91 @@ +import { WriteFormLabel } from '@/components/atoms/write-form/form-label'; +import { PositionBadge } from '@/components/molecules/position-badge'; +import { + FormControl, + FormField, + FormItem, + FormMessage, +} from '@/components/ui/form'; +import { DEFAULT_POSITION_NAMES, PositionName, WriteForm } from '@/types'; +import clsx from 'clsx'; +import { useState } from 'react'; +import { UseFormReturn } from 'react-hook-form'; + +type SelectPositionProps = { + form: UseFormReturn<WriteForm>; +}; + +export const SelectPosition = ({ form }: SelectPositionProps) => { + const [selectedPositions, setSelectedPositions] = useState<PositionName[]>( + [], + ); + const hasError = !!form.formState.errors.position; + + const positionClickHandler = (position: PositionName) => { + const isSelected = selectedPositions.find( + (selectedSkill) => selectedSkill === position, + ); + + // toggle. ์ง€๊ธˆ ์„ ํƒํ•œ ์Šคํ‚ฌ์ด ์ด๋ฏธ ์„ ํƒ๋˜์–ด ์žˆ์—ˆ๋‹ค๋ฉด ์ œ์™ธ๋ผ์•ผ ํ•จ + if (isSelected) { + const nextSelectedPositions = selectedPositions.filter( + (selectedSkill) => selectedSkill !== position, + ); + + setSelectedPositions(nextSelectedPositions); + form.setValue('position', nextSelectedPositions); + } else { + const nextSelectedPositions = [...selectedPositions, position]; + + setSelectedPositions(nextSelectedPositions); + form.setValue('position', nextSelectedPositions); + } + }; + return ( + <> + <FormField + control={form.control} + name="position" + render={({ field, fieldState }) => ( + <FormItem> + <WriteFormLabel text="๋ชจ์ง‘ ํฌ์ง€์…˜" /> + <div className="relative w-[fit-content]"> + <FormControl> + <input + {...field} + tabIndex={-1} + className={clsx( + 'absolute top-[-4px] right-[-4px] bottom-[-4px] left-[-4px] text-transparent caret-transparent z-[-1] rounded-md', + hasError + ? 'outline-1 outline-destructive focus-visible:ring-red-500/20 focus-visible:ring-[3px]' + : 'border-none outline-none', + )} + aria-hidden="true" + value={field.value ?? ''} + /> + </FormControl> + <ul className="flex gap-2"> + {DEFAULT_POSITION_NAMES.map((position, i) => ( + <li + key={i} + className={ + selectedPositions.includes(position) + ? '[&_.position-badge]:border-3 [&_.position-badge]:border-green-500' + : '' + } + > + <PositionBadge + name={position} + positionClickHandler={positionClickHandler} + /> + </li> + ))} + </ul> + </div> + <FormMessage>{fieldState.error?.message}</FormMessage> + </FormItem> + )} + /> + </> + ); +}; diff --git a/src/components/molecules/write-form/selectSkill.tsx b/src/components/molecules/write-form/selectSkill.tsx new file mode 100644 index 00000000..ef192458 --- /dev/null +++ b/src/components/molecules/write-form/selectSkill.tsx @@ -0,0 +1,90 @@ +import { WriteFormLabel } from '@/components/atoms/write-form/form-label'; +import { + FormControl, + FormField, + FormItem, + FormMessage, +} from '@/components/ui/form'; +import { DEFAULT_SKILL_NAMES, WriteForm } from '@/types'; +import { SkillName } from '@/types/index'; +import clsx from 'clsx'; +import { useState } from 'react'; +import { UseFormReturn } from 'react-hook-form'; +import { SkillBadge } from '../skill-badge'; + +type SelectSkillProps = { + form: UseFormReturn<WriteForm>; +}; + +export const SelectSkill = ({ form }: SelectSkillProps) => { + const [selectedSkills, setSelectedSkills] = useState<SkillName[]>([]); + const hasError = !!form.formState.errors.skills; + + const skillClickHandler = (skill: SkillName) => { + const isSelected = selectedSkills.find( + (selectedSkill) => selectedSkill === skill, + ); + + // toggle. ์ง€๊ธˆ ์„ ํƒํ•œ ์Šคํ‚ฌ์ด ์ด๋ฏธ ์„ ํƒ๋˜์–ด ์žˆ์—ˆ๋‹ค๋ฉด ์ œ์™ธ๋ผ์•ผ ํ•จ + if (isSelected) { + const nextSelectedSkills = selectedSkills.filter( + (selectedSkill) => selectedSkill !== skill, + ); + + setSelectedSkills(nextSelectedSkills); + form.setValue('skills', nextSelectedSkills); + } else { + const nextSelectedSkills = [...selectedSkills, skill]; + + setSelectedSkills(nextSelectedSkills); + form.setValue('skills', nextSelectedSkills); + } + }; + return ( + <> + <FormField + control={form.control} + name="skills" + render={({ field, fieldState }) => ( + <FormItem> + <WriteFormLabel text="๊ธฐ์ˆ  ์Šคํƒ" /> + <div className="relative w-[fit-content]"> + <FormControl> + <input + {...field} + tabIndex={-1} + className={clsx( + 'absolute top-[-4px] right-[-4px] bottom-[-4px] left-[-4px] text-transparent caret-transparent z-[-1] rounded-md', + hasError + ? 'outline-1 outline-destructive focus-visible:ring-red-500/20 focus-visible:ring-[3px]' + : 'border-none outline-none', + )} + aria-hidden="true" + value={field.value ?? ''} + /> + </FormControl> + <ul className="flex gap-2"> + {DEFAULT_SKILL_NAMES.map((skill, i) => ( + <li + key={i} + className={ + selectedSkills.includes(skill) + ? '[&_.skill-badge]:border-3 [&_.skill-badge]:border-green-500' + : '' + } + > + <SkillBadge + name={skill} + skillClickHandler={skillClickHandler} + /> + </li> + ))} + </ul> + </div> + <FormMessage>{fieldState.error?.message}</FormMessage> + </FormItem> + )} + /> + </> + ); +}; diff --git a/src/components/molecules/write-form/startDateCalendar.tsx b/src/components/molecules/write-form/startDateCalendar.tsx new file mode 100644 index 00000000..a5bfa6f3 --- /dev/null +++ b/src/components/molecules/write-form/startDateCalendar.tsx @@ -0,0 +1,117 @@ +import { WriteFormLabel } from '@/components/atoms/write-form/form-label'; +import { Button } from '@/components/ui//button'; +import { Calendar } from '@/components/ui//calendar'; +import { + FormControl, + FormField, + FormItem, + FormMessage, +} from '@/components/ui//form'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui//popover'; +import { WriteForm } from '@/types'; +import { formatYearMonthDayWithDot } from '@/utils/dateUtils'; +import clsx from 'clsx'; +import { CalendarIcon } from 'lucide-react'; +import { UseFormReturn } from 'react-hook-form'; + +type TitleProps = { + form: UseFormReturn<WriteForm>; + isStartDateCalendarOpen: boolean; + openStartDateCalendar: () => void; + closeStartDateCalendar: () => void; + validStartDate: Date; +}; + +export const StartDateCalendar = ({ + form, + isStartDateCalendarOpen, + openStartDateCalendar, + closeStartDateCalendar, + validStartDate, +}: TitleProps) => { + const hasError = !!form.formState.errors.startDate; + + /** calendar์—์„œ ๋‚ ์งœ ์„ ํƒ ํ›„ calendar๊ฐ€ ๋‹ซํžˆ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜ */ + const startDateSelect = ( + date: Date | undefined, + onChange: (date: Date | undefined) => void, + ) => { + if (!date) return; + + onChange(date); + closeStartDateCalendar(); + }; + + return ( + <> + <FormField + control={form.control} + name="startDate" + render={({ field, fieldState }) => ( + <FormItem> + <WriteFormLabel + text="๋ชจ์ž„ ์‹œ์ž‘์ผ" + info="๋ชจ์ง‘ ๋งˆ๊ฐ์ผ์˜ ๋‹ค์Œ ๋‚ ๋ถ€ํ„ฐ ์„ ํƒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค" + /> + <Popover + open={isStartDateCalendarOpen} + onOpenChange={ + isStartDateCalendarOpen + ? closeStartDateCalendar + : openStartDateCalendar + } + > + <div className="flex items-center gap-5 relative w-[fit-content]"> + <div> + {field.value ? ( + formatYearMonthDayWithDot(field.value) + ) : ( + <> + <div className="text-gray-500"> + {formatYearMonthDayWithDot(validStartDate)} + </div> + </> + )} + </div> + <FormControl> + <input + {...field} + tabIndex={-1} + className={clsx( + 'absolute top-[-4px] right-[-4px] bottom-[-4px] left-[-4px] text-transparent caret-transparent z-[-1] rounded-md', + hasError + ? 'outline-1 outline-destructive focus-visible:ring-red-500/20 focus-visible:ring-[3px]' + : 'border-none outline-none', + )} + aria-hidden="true" + value={field.value ? field.value.toISOString() : ''} + /> + </FormControl> + <PopoverTrigger className="cursor-pointer" asChild> + <Button type="button" className="w-[fit-content]"> + <CalendarIcon /> + </Button> + </PopoverTrigger> + <PopoverContent> + <Calendar + mode="single" + selected={field.value} + onSelect={(e) => { + startDateSelect(e, field.onChange); + }} + disabled={{ before: validStartDate }} + /> + </PopoverContent> + </div> + </Popover> + <FormMessage>{fieldState.error?.message}</FormMessage> + </FormItem> + )} + /> + </> + ); +}; diff --git a/src/components/molecules/write-form/tiptap/desctiption.tsx b/src/components/molecules/write-form/tiptap/desctiption.tsx new file mode 100644 index 00000000..c445888f --- /dev/null +++ b/src/components/molecules/write-form/tiptap/desctiption.tsx @@ -0,0 +1,141 @@ +'use client'; + +import { + FormControl, + FormField, + FormItem, + FormMessage, +} from '@/components/ui/form'; +import { WriteForm } from '@/types'; +import { EmojiReplacer } from '@tiptap-extend/emoji-replacer'; +import Blockquote from '@tiptap/extension-blockquote'; +import BulletList from '@tiptap/extension-bullet-list'; +import Code from '@tiptap/extension-code'; +import CodeBlock from '@tiptap/extension-code-block'; +import Heading from '@tiptap/extension-heading'; +import Highlight from '@tiptap/extension-highlight'; +import HorizontalRule from '@tiptap/extension-horizontal-rule'; +import ListItem from '@tiptap/extension-list-item'; +import OrderedList from '@tiptap/extension-ordered-list'; +import TaskItem from '@tiptap/extension-task-item'; +import TaskList from '@tiptap/extension-task-list'; +import Typography from '@tiptap/extension-typography'; +import { EditorContent, useEditor } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +import clsx from 'clsx'; +import { useEffect } from 'react'; +import { UseFormReturn } from 'react-hook-form'; +import { emojiRules } from './emojiRules'; + +const tiptapStyleExtensions = [ + Heading.configure({ + HTMLAttributes: { + class: 'text-xl font-bold capitalize', + levels: [2], + }, + }), + Blockquote.configure({ + HTMLAttributes: { + class: 'border-l-6 pl-2 border-gray-400', + }, + }), + // Bold, + // Italic, + ListItem, + BulletList.configure({ + HTMLAttributes: { + class: 'list-disc ml-2', + }, + }), + OrderedList.configure({ + HTMLAttributes: { + class: 'list-decimal ml-2', + }, + }), + Code.configure({ + HTMLAttributes: { + class: 'bg-gray-100 py-[1px] px-1 rounded-sm text-[inherit]', + }, + }), + HorizontalRule.configure({ + HTMLAttributes: { + class: 'my-[10px]', + }, + }), + CodeBlock.configure({ + HTMLAttributes: { + class: 'bg-gray-100 p-3 rounded-sm', + }, + }), + TaskItem.configure({ + nested: true, + }), + TaskList.configure({ + itemTypeName: 'taskItem', + HTMLAttributes: { + class: 'bg-red', + }, + }), + EmojiReplacer.configure({ + ruleConfigs: emojiRules, + shouldUseExtraReplacementSpace: false, + }), +]; + +type DescriptionProps = { + form: UseFormReturn<WriteForm>; +}; + +export const Description = ({ form }: DescriptionProps) => { + const hasError = !!form.formState.errors.description; + + const editor = useEditor({ + editable: true, + extensions: [ + StarterKit, + Highlight, + Typography, + EmojiReplacer, + ...tiptapStyleExtensions, + ], + editorProps: { + attributes: { + class: clsx( + 'h-[500px] overflow-y-auto w-full mt-0 py-2 px-3 text-sm rounded-md border border-input bg-background ring-offset-background shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px]', + hasError + ? 'text-red-500 border-red-500 focus-visible:ring-red-500/20' + : 'focus-visible:border-ring focus-visible:ring-ring/50', + ), + }, + }, + }); + + useEffect(() => { + if (!editor) return; + + /** useForm์œผ๋กœ ๋งŒ๋“  form์˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ. ๋ Œ๋”๋ง๊ณผ๋Š” ๋ฌด๊ด€ํ•œ ๋กœ์ง์œผ๋กœ useEffect์—์„œ ๊ด€๋ฆฌ */ + const descriptionStateUpdate = () => + editor.on('update', () => form.setValue('description', editor.getHTML())); + + descriptionStateUpdate(); + + return () => { + editor.off('update', descriptionStateUpdate); + }; + }, [editor, form]); + + return ( + <FormField + control={form.control} + name="description" + render={() => ( + <FormItem className="block h-[500px] bg-gray-100 rounded"> + <FormControl> + <EditorContent editor={editor} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + ); +}; diff --git a/src/components/molecules/write-form/tiptap/emojiRules.ts b/src/components/molecules/write-form/tiptap/emojiRules.ts new file mode 100644 index 00000000..db888ef4 --- /dev/null +++ b/src/components/molecules/write-form/tiptap/emojiRules.ts @@ -0,0 +1,123 @@ +/** ์ด๋ชจ์ง€ ๋‹จ์ถ•์–ด๋กœ ๋Œ€์ฒด์‹œํ‚ฌ ์ด๋ชจ์ง€ ๋ชฉ๋ก */ +export const emojiRules = [ + { find: ':)', replace: ':)' }, + { find: ':/', replace: ':/' }, + { find: ':smile:', replace: '๐Ÿ˜„' }, + { find: ':laughing:', replace: '๐Ÿ˜†' }, + { find: ':blush:', replace: '๐Ÿ˜Š' }, + { find: ':heart:', replace: 'โค๏ธ' }, + { find: ':+1:', replace: '๐Ÿ‘' }, + { find: ':-1:', replace: '๐Ÿ‘Ž' }, + { find: ':tada:', replace: '๐ŸŽ‰' }, + { find: ':rocket:', replace: '๐Ÿš€' }, + { find: ':fire:', replace: '๐Ÿ”ฅ' }, + { find: ':bug:', replace: '๐Ÿ›' }, + { find: ':memo:', replace: '๐Ÿ“' }, + { find: ':sparkles:', replace: 'โœจ' }, + { find: ':star:', replace: 'โญ' }, + { find: ':warning:', replace: 'โš ๏ธ' }, + { find: ':lock:', replace: '๐Ÿ”’' }, + { find: ':key:', replace: '๐Ÿ”‘' }, + { find: ':construction:', replace: '๐Ÿšง' }, + { find: ':recycle:', replace: 'โ™ป๏ธ' }, + { find: ':package:', replace: '๐Ÿ“ฆ' }, + { find: ':zap:', replace: 'โšก' }, + { find: ':mag:', replace: '๐Ÿ”' }, + { find: ':heavy_check_mark:', replace: 'โœ”๏ธ' }, + { find: ':hourglass_flowing_sand:', replace: 'โณ' }, + { find: ':globe_with_meridians:', replace: '๐ŸŒ' }, + { find: ':iphone:', replace: '๐Ÿ“ฑ' }, + { find: ':speech_balloon:', replace: '๐Ÿ’ฌ' }, + { find: ':thought_balloon:', replace: '๐Ÿ’ญ' }, + { find: ':eyes:', replace: '๐Ÿ‘€' }, + { find: ':zzz:', replace: '๐Ÿ’ค' }, + { find: ':clap:', replace: '๐Ÿ‘' }, + { find: ':confetti_ball:', replace: '๐ŸŽŠ' }, + { find: ':moneybag:', replace: '๐Ÿ’ฐ' }, + { find: ':star2:', replace: '๐ŸŒŸ' }, + { find: ':balloon:', replace: '๐ŸŽˆ' }, + { find: ':gift:', replace: '๐ŸŽ' }, + { find: ':camera:', replace: '๐Ÿ“ท' }, + { find: ':mag_right:', replace: '๐Ÿ”Ž' }, + { find: ':pencil:', replace: 'โœ๏ธ' }, + { find: ':chart_with_upwards_trend:', replace: '๐Ÿ“ˆ' }, + { find: ':chart_with_downwards_trend:', replace: '๐Ÿ“‰' }, + { find: ':bookmark:', replace: '๐Ÿ”–' }, + { find: ':hourglass:', replace: 'โŒ›' }, + { find: ':new:', replace: '๐Ÿ†•' }, + { find: ':soon:', replace: '๐Ÿˆ' }, + { find: ':bell:', replace: '๐Ÿ””' }, + { find: ':link:', replace: '๐Ÿ”—' }, + { find: ':paperclip:', replace: '๐Ÿ“Ž' }, + { find: ':pushpin:', replace: '๐Ÿ“Œ' }, + { find: ':mega:', replace: '๐Ÿ“ฃ' }, + { find: ':bookmark_tabs:', replace: '๐Ÿ“‘' }, + { find: ':wrench:', replace: '๐Ÿ”ง' }, + { find: ':hammer:', replace: '๐Ÿ”จ' }, + { find: ':tools:', replace: '๐Ÿ› ๏ธ' }, + { find: ':nut_and_bolt:', replace: '๐Ÿ”ฉ' }, + { find: ':gear:', replace: 'โš™๏ธ' }, + { find: ':link:', replace: '๐Ÿ”—' }, + { find: ':file_folder:', replace: '๐Ÿ“' }, + { find: ':open_file_folder:', replace: '๐Ÿ“‚' }, + { find: ':page_facing_up:', replace: '๐Ÿ“„' }, + { find: ':clipboard:', replace: '๐Ÿ“‹' }, + { find: ':calendar:', replace: '๐Ÿ“…' }, + { find: ':date:', replace: '๐Ÿ“†' }, + { find: ':card_index:', replace: '๐Ÿ“‡' }, + { find: ':file_cabinet:', replace: '๐Ÿ—„๏ธ' }, + { find: ':bar_chart:', replace: '๐Ÿ“Š' }, + { find: ':clipboard:', replace: '๐Ÿ“‹' }, + { find: ':file_text:', replace: '๐Ÿ“' }, + { find: ':email:', replace: 'โœ‰๏ธ' }, + { find: ':incoming_envelope:', replace: '๐Ÿ“จ' }, + { find: ':envelope_with_arrow:', replace: '๐Ÿ“ฉ' }, + { find: ':outbox_tray:', replace: '๐Ÿ“ค' }, + { find: ':inbox_tray:', replace: '๐Ÿ“ฅ' }, + { find: ':package:', replace: '๐Ÿ“ฆ' }, + { find: ':mailbox_closed:', replace: '๐Ÿ“ช' }, + { find: ':mailbox:', replace: '๐Ÿ“ซ' }, + { find: ':mailbox_with_mail:', replace: '๐Ÿ“ฌ' }, + { find: ':mailbox_with_no_mail:', replace: '๐Ÿ“ญ' }, + { find: ':postbox:', replace: '๐Ÿ“ฎ' }, + { find: ':newspaper:', replace: '๐Ÿ“ฐ' }, + { find: ':iphone:', replace: '๐Ÿ“ฑ' }, + { find: ':calling:', replace: '๐Ÿ“ฒ' }, + { find: ':telephone_receiver:', replace: '๐Ÿ“ž' }, + { find: ':fax:', replace: '๐Ÿ“ ' }, + { find: ':satellite:', replace: '๐Ÿ“ก' }, + { find: ':loudspeaker:', replace: '๐Ÿ“ข' }, + { find: ':mega:', replace: '๐Ÿ“ฃ' }, + { find: ':outbox_tray:', replace: '๐Ÿ“ค' }, + { find: ':inbox_tray:', replace: '๐Ÿ“ฅ' }, + { find: ':scroll:', replace: '๐Ÿ“œ' }, + { find: ':pager:', replace: '๐Ÿ“Ÿ' }, + { find: ':bar_chart:', replace: '๐Ÿ“Š' }, + { find: ':chart_with_upwards_trend:', replace: '๐Ÿ“ˆ' }, + { find: ':chart_with_downwards_trend:', replace: '๐Ÿ“‰' }, + { find: ':clipboard:', replace: '๐Ÿ“‹' }, + { find: ':pushpin:', replace: '๐Ÿ“Œ' }, + { find: ':paperclip:', replace: '๐Ÿ“Ž' }, + { find: ':round_pushpin:', replace: '๐Ÿ“' }, + { find: ':postal_horn:', replace: '๐Ÿ“ฏ' }, + { find: ':fax:', replace: '๐Ÿ“ ' }, + { find: ':battery:', replace: '๐Ÿ”‹' }, + { find: ':electric_plug:', replace: '๐Ÿ”Œ' }, + { find: ':bulb:', replace: '๐Ÿ’ก' }, + { find: ':flashlight:', replace: '๐Ÿ”ฆ' }, + { find: ':candle:', replace: '๐Ÿ•ฏ๏ธ' }, + { find: ':wrench:', replace: '๐Ÿ”ง' }, + { find: ':hammer:', replace: '๐Ÿ”จ' }, + { find: ':nut_and_bolt:', replace: '๐Ÿ”ฉ' }, + { find: ':gear:', replace: 'โš™๏ธ' }, + { find: ':magnet:', replace: '๐Ÿงฒ' }, + { find: ':ladder:', replace: '๐Ÿชœ' }, + { find: ':alembic:', replace: 'โš—๏ธ' }, + { find: ':microscope:', replace: '๐Ÿ”ฌ' }, + { find: ':telescope:', replace: '๐Ÿ”ญ' }, + { find: ':satellite:', replace: '๐Ÿ“ก' }, + { find: ':syringe:', replace: '๐Ÿ’‰' }, + { find: ':pill:', replace: '๐Ÿ’Š' }, + { find: ':dna:', replace: '๐Ÿงฌ' }, + { find: ':test_tube:', replace: '๐Ÿงช' }, +]; diff --git a/src/components/molecules/write-form/title.tsx b/src/components/molecules/write-form/title.tsx new file mode 100644 index 00000000..f4c47802 --- /dev/null +++ b/src/components/molecules/write-form/title.tsx @@ -0,0 +1,34 @@ +import { WriteFormLabel } from '@/components/atoms/write-form/form-label'; +import { + FormControl, + FormField, + FormItem, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { WriteForm } from '@/types'; +import { UseFormReturn } from 'react-hook-form'; + +type TitleProps = { + form: UseFormReturn<WriteForm>; +}; + +export const Title = ({ form }: TitleProps) => { + return ( + <> + <FormField + control={form.control} + name="title" + render={({ field }) => ( + <FormItem> + <WriteFormLabel text="์ œ๋ชฉ" /> + <FormControl> + <Input placeholder="์ œ๋ชฉ๊ณผ ๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </> + ); +}; diff --git a/src/components/organisms/bookmark-card/index.tsx b/src/components/organisms/bookmark-card/index.tsx new file mode 100644 index 00000000..477bda81 --- /dev/null +++ b/src/components/organisms/bookmark-card/index.tsx @@ -0,0 +1,40 @@ +'use client'; + +import { BookmarkCardContents } from '@/components/molecules/bookmark-card-contents'; +import { Group } from '@/types'; +import { isBeforeToday } from '@/utils/dateUtils'; +import { useRouter } from 'next/navigation'; + +export type ContentInfo = Pick< + Group, + | 'id' + | 'title' + | 'deadline' + | 'maxParticipants' + | 'position' + | 'skills' + | 'participants' + | 'isBookmark' +>; + +type CardProps = { + info: ContentInfo; +}; + +export const BookmarkCard = ({ info }: CardProps) => { + const router = useRouter(); + const isBeforeDeadline = isBeforeToday(info.deadline); + + return ( + <div className="relative" onClick={() => router.push(`/groups/${info.id}`)}> + <article className="flex w-full"> + <BookmarkCardContents className="flex-1" info={info} /> + </article> + {isBeforeDeadline && ( + <div className="absolute inset-0 flex items-center justify-center bg-black/50 rounded-lg"> + <p className="text-white font-medium">๋ชจ์ง‘์ด ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.</p> + </div> + )} + </div> + ); +}; diff --git a/src/components/organisms/empty/index.tsx b/src/components/organisms/empty/index.tsx new file mode 100644 index 00000000..793fe068 --- /dev/null +++ b/src/components/organisms/empty/index.tsx @@ -0,0 +1,37 @@ +import Image from 'next/image'; +import Link from 'next/link'; + +type EmptyProps = { + mainText: string; + subText?: string; + targetUrl?: string; + className?: string; +}; + +export const Empty = ({ + mainText, + subText, + targetUrl, + className, +}: EmptyProps) => { + return ( + <div + className={`flex flex-col items-center justify-center gap-2 my-2 ${className}`} + > + <Image + src="/logos/my-img.png" + alt="empty groups" + width={120} + height={120} + className="grayscale" + /> + <p className="mt-2 text-gray-500 text-lg">{mainText}</p> + {subText && <p className="text-gray-400">{subText}</p>} + {targetUrl && ( + <p className="text-gray-400"> + ์ž์„ธํ•œ ๋‚ด์šฉ์€ <Link href={targetUrl}>์ด๊ณณ</Link>์—์„œ ํ™•์ธํ•ด๋ณด์„ธ์š” + </p> + )} + </div> + ); +}; diff --git a/src/components/organisms/find-email-form/index.tsx b/src/components/organisms/find-email-form/index.tsx new file mode 100644 index 00000000..715316ce --- /dev/null +++ b/src/components/organisms/find-email-form/index.tsx @@ -0,0 +1,103 @@ +'use client'; + +import { z } from 'zod'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useState } from 'react'; +import { Form } from '@/components/ui/form'; +import { InputTextField } from '@/components/molecules/input-text-field'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; +import { request } from '@/api/request'; + +const formSchema = z.object({ + email: z.string().nonempty({ message: '์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”' }).email({ + message: '์œ ํšจํ•œ ์ด๋ฉ”์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค.', + }), +}); + +const FindEmailForm = () => { + const form = useForm<z.infer<typeof formSchema>>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: '', + }, + }); + + const [disabled, setDisabled] = useState(false); + + // find-email์ธ ๊ฒฝ์šฐ + const [isExisted, setIsExisted] = useState(false); + const [isNotExisted, setIsNotExisted] = useState(false); + + // ์ด๋ฉ”์ผ ์ฐพ๊ธฐ + const onSubmit = async (values: z.infer<typeof formSchema>) => { + try { + setDisabled(true); + + // find-email์ธ ๊ฒฝ์šฐ + // ์ค‘๋ณต์ด๋ฉด false, ์ค‘๋ณต์ด ์•„๋‹ˆ๋ฉด true + const { + status: { success }, + } = await request.post( + '/v1/user/check-email', + { + 'Content-Type': 'application/json', + }, + JSON.stringify(values), + ); + + if (success) { + setIsExisted(false); + setIsNotExisted(true); + } else { + setIsExisted(true); + setIsNotExisted(false); + } + } catch (e) { + // ์ค‘๋ณต์œผ๋กœ ์ฒ˜๋ฆฌํ•˜ใ…”์…”์„œ ์—๋Ÿฌ๋‚˜๋ฉด ์กด์žฌํ•˜๋Š”๊ฑฐ์ž„ + // TODO: ์ด๋ฉ”์ผ ์ฐพ๊ธฐ ์‹คํŒจ์‹œ ์—๋Ÿฌ์ฝ”๋“œ ๋งž์ถฐ์„œ ์„ค์ •ํ•ด์ฃผ๊ธฐ + setIsExisted(true); + setIsNotExisted(false); + console.log(e); + } finally { + setDisabled(false); + } + }; + + return ( + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> + <InputTextField + form={form} + name="email" + label="์ด๋ฉ”์ผ" + type="email" + placeholder="์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" + /> + + {/* ์„ฑ๊ณต์‹œ ๋กœ๊ทธ์ธํŽ˜์ด์ง€๋กœ ๊ฐˆ์ง€ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ๋กœ ๊ฐˆ์ง€ ๋ฌผ์–ด๋ณด๊ธฐ */} + {isExisted && ( + <div className="flex flex-col justify-center items-center gap-2"> + <p className="text-blue-900 text-center"> + ํ•ด๋‹น ์ด๋ฉ”์ผ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค! + </p> + <Link href="/login">๋กœ๊ทธ์ธํ•˜๋Ÿฌ ๊ฐ€๊ธฐ</Link> + <Link href="/find-password">๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ</Link> + </div> + )} + + {isNotExisted && ( + <p className="text-red-600 text-center"> + ํ•ด๋‹น ์ด๋ฉ”์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + </p> + )} + <Button className="w-full bg-gray-400" disabled={disabled}> + ์ด๋ฉ”์ผ ์ฐพ๊ธฐ + </Button> + </form> + </Form> + ); +}; + +export default FindEmailForm; diff --git a/src/components/organisms/find-password-form/index.tsx b/src/components/organisms/find-password-form/index.tsx new file mode 100644 index 00000000..6c952b95 --- /dev/null +++ b/src/components/organisms/find-password-form/index.tsx @@ -0,0 +1,97 @@ +'use client'; + +import { z } from 'zod'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useState } from 'react'; +import { Form } from '@/components/ui/form'; +import { InputTextField } from '@/components/molecules/input-text-field'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; +import { request } from '@/api/request'; + +const formSchema = z.object({ + email: z.string().nonempty({ message: '์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”' }).email({ + message: '์œ ํšจํ•œ ์ด๋ฉ”์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค.', + }), +}); + +const FindPassword = () => { + const form = useForm<z.infer<typeof formSchema>>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: '', + }, + }); + + const [disabled, setDisabled] = useState(false); + + const [isNotExisted, setIsNotExisted] = useState(false); + // reset-password์ผ ๊ฒฝ์šฐ + const [isSuccessEmailSend, setIsSuccessEmailSend] = useState(false); + + // ์ด๋ฉ”์ผ ์ฐพ๊ธฐ + const onSubmit = async (values: z.infer<typeof formSchema>) => { + setDisabled(true); + try { + // reset-password์ผ ๊ฒฝ์šฐ + // TODO: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ ๋กœ์ง ์ž‘์„ฑ /find-email + const { + status: { success }, + } = await request.post( + '/v1/user/reset-password', + { + 'Content-Type': 'application/json', + }, + JSON.stringify(values), + ); + + if (success) { + setIsSuccessEmailSend(true); + setIsNotExisted(false); + } else { + throw new Error('์ด๋ฉ”์ผ ์ „์†ก ์‹คํŒจ'); + } + } catch (e) { + // TODO: ์ด๋ฉ”์ผ ์ฐพ๊ธฐ ์‹คํŒจ์‹œ ์—๋Ÿฌ์ฝ”๋“œ ๋งž์ถฐ์„œ ์„ค์ •ํ•ด์ฃผ๊ธฐ + setIsNotExisted(true); + setIsSuccessEmailSend(false); + console.log(e); + } finally { + setDisabled(false); + } + }; + + return ( + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> + <InputTextField + form={form} + name="email" + label="์ด๋ฉ”์ผ" + type="email" + placeholder="์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" + /> + + {/* ์ด๋ฉ”์ผ ์ „์†ก์‹œ ๋กœ๊ทธ์ธํŽ˜์ด์ง€๋กœ ๊ฐ€๊ธฐ*/} + {isSuccessEmailSend && ( + <div className="flex flex-col justify-center items-center gap-2"> + <p className="text-blue-900">์ด๋ฉ”์ผ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”!</p> + <Link href="/login">๋กœ๊ทธ์ธํ•˜๋Ÿฌ ๊ฐ€๊ธฐ</Link> + </div> + )} + + {isNotExisted && ( + <p className="text-red-600 text-center"> + ํ•ด๋‹น ์ด๋ฉ”์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค + </p> + )} + <Button className="w-full bg-gray-400" disabled={disabled}> + ๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™” + </Button> + </form> + </Form> + ); +}; + +export default FindPassword; diff --git a/src/components/organisms/group/index.tsx b/src/components/organisms/group/index.tsx new file mode 100644 index 00000000..f11dd741 --- /dev/null +++ b/src/components/organisms/group/index.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { WriteGroupButton } from '@/components/molecules/group-create-button'; +import { Filter } from '@/components/molecules/group/filter'; +import { GroupList } from '@/components/molecules/group/group-list'; +import { SortOrder } from '@/components/molecules/group/sort-order'; +import { SearchInput } from '@/components/molecules/search-input/search-input'; +import { Tab, TabType } from '@/components/molecules/tab'; +import { GroupType } from '@/types'; +import { useRouter } from 'next/navigation'; +import { Suspense } from 'react'; +import { Loading } from '../loading'; + +type GroupListProps = { + serverQueryParams: Record<string, string | undefined>; +}; + +const tabList: TabType[] = [ + { value: '', label: '๋ชจ๋“  ๊ทธ๋ฃน' }, + { value: GroupType.STUDY, label: '์Šคํ„ฐ๋””' }, + { value: GroupType.PROJECT, label: 'ํ”„๋กœ์ ํŠธ' }, +]; + +export const Groups = ({ serverQueryParams }: GroupListProps) => { + const router = useRouter(); + /* + * router.push๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜ + * @param queries ์—ฌ๋Ÿฌ query key๋ฅผ ํ•œ๋ฒˆ์— ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ธ์ž๋ฅผ Record ํƒ€์ž…์œผ๋กœ ๋ฐ›๋Š”๋‹ค + */ + const updateQueryParams = (queries: Record<string, string>) => { + const params = new URLSearchParams(); + + // ๊ธฐ์กด searchParams๋ฅผ params์— ๋„ฃ๊ธฐ + Object.entries(serverQueryParams).forEach(([key, value]) => { + if (value !== undefined && value !== '') { + params.set(key, value.toString()); + } + }); + + // ์—…๋ฐ์ดํŠธํ•  ์ฟผ๋ฆฌ ์ ์šฉ + Object.entries(queries).forEach(([key, value]) => { + const prevValue = params.get(key); + if (value === '' || value === 'all' || value === 'null') { + // ์ „์ฒด ์„ ํƒ ์‹œ ํ•ด๋‹น key ์‚ญ์ œ + params.delete(key); + } else if (prevValue === value) { + // ์ด๋ฏธ ์„ ํƒํ•œ ํ•„ํ„ฐ๋ฅผ ๋‹ค์‹œ ์„ ํƒํ•œ ๊ฒฝ์šฐ params์—์„œ ์‚ญ์ œ + params.delete(key); + } else { + params.set(key, value); + } + }); + + router.push(`?${params.toString()}`); + }; + + return ( + <div className="relative"> + <Tab + tabList={tabList} + onValueChange={(value) => updateQueryParams({ type: value })} + > + <WriteGroupButton /> + + <div className="flex justify-start "> + <Filter updateQueryParams={updateQueryParams} /> + <div className="flex-none flex gap-1 ml-auto"> + <SearchInput /> + <SortOrder updateQueryParams={updateQueryParams} /> + </div> + </div> + <Suspense + fallback={ + <div> + <Loading /> + </div> + } + > + <GroupList serverQueryParams={serverQueryParams} /> + </Suspense> + </Tab> + </div> + ); +}; diff --git a/src/components/organisms/header/index.tsx b/src/components/organisms/header/index.tsx new file mode 100644 index 00000000..f7925046 --- /dev/null +++ b/src/components/organisms/header/index.tsx @@ -0,0 +1,195 @@ +'use client'; + +import { Avatar } from '@/components/atoms/avatar'; +import LogoutButton from '@/components/atoms/logout-button'; +import { ErrorBoundary } from '@/components/error-boundary'; +import { handleError } from '@/components/error-boundary/error-handler'; +import { NotificationList } from '@/components/molecules/notification-list'; +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import useAuthStore from '@/stores/useAuthStore'; +import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback'; +import Image from 'next/image'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { useState } from 'react'; + +type MenuItem = { + label: string; + href: string; +}; + +const menuItems: MenuItem[] = [ + // { label: 'ํ”„๋กœ์ ํŠธ ์ฐพ๊ธฐ', href: '/projects' }, + { label: '์ฐœํ•œ ๋ชจ์ž„', href: '/bookmark' }, +]; + +const Logo = ({ isMobile = false }: { isMobile?: boolean }) => ( + <Link href="/" className="flex items-center"> + <Image + src="/logos/logo-text.png" + alt="๋ชจ์—ฌ๋ผ-IT" + width={isMobile ? 100 : 120} + height={isMobile ? 25 : 30} + className={`w-auto ${isMobile ? 'h-7' : 'h-8'}`} + priority + /> + </Link> +); + +const MenuLinks = ({ onClick }: { onClick?: () => void }) => { + const pathname = usePathname(); + return ( + <> + {menuItems.map(({ label, href }) => ( + <Link + key={href} + href={href} + className={`text-sm font-medium text-gray-800 hover:text-primary ${ + pathname === href ? 'text-primary' : '' + }`} + onClick={onClick} + > + {label} + </Link> + ))} + </> + ); +}; + +const MobileMenuLinks = ({ onClick }: { onClick?: () => void }) => { + const pathname = usePathname(); + return ( + <> + {menuItems.map(({ label, href }) => ( + <DropdownMenuItem key={href} asChild onClick={onClick}> + <Link + href={href} + className={`${pathname === href ? 'text-primary' : ''}`} + > + {label} + </Link> + </DropdownMenuItem> + ))} + </> + ); +}; + +const NotificationWithBoundary = () => ( + <ErrorBoundary + fallback={({ error, resetErrorBoundary }) => + handleError({ + error, + resetErrorBoundary, + defaultMessage: '์•Œ๋ฆผ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค', + }) + } + > + <NotificationList /> + </ErrorBoundary> +); + +export const Header = () => { + const user = useAuthStore((state) => state.user); + const isLoggedIn = Boolean(user); + const userId = user?.userId ?? 0; + const profileImage = getDisplayProfileImage(user?.profileImage ?? null); + + const [isMenuOpen, setIsMenuOpen] = useState(false); + + return ( + <header className="w-full bg-white flex justify-between items-center h-16 md:px-8"> + <nav className="hidden md:flex items-center"> + <Logo /> + <ul className="flex gap-8 ml-8"> + <MenuLinks /> + </ul> + </nav> + + <div className="hidden md:flex items-center gap-4"> + {isLoggedIn ? ( + <> + <NotificationWithBoundary /> + <DropdownMenu> + <DropdownMenuTrigger asChild> + <button type="button" className="cursor-pointer"> + <Avatar + imageSrc={profileImage} + fallback={getDisplayNickname( + user?.nickname ?? '', + user?.email ?? '', + )} + className="rounded-full w-8 h-8" + /> + </button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + <DropdownMenuItem asChild> + <Link href={`/users/${userId}`}>๋งˆ์ด ํŽ˜์ด์ง€</Link> + </DropdownMenuItem> + <DropdownMenuItem asChild> + <LogoutButton /> + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </> + ) : ( + <Link href="/login"> + <Button className="text-sm cursor-pointer font-semibold bg-green-500 text-white"> + ๋กœ๊ทธ์ธ ๋ฐ ํšŒ์›๊ฐ€์ž… + </Button> + </Link> + )} + </div> + + <div className="flex md:hidden justify-between items-center h-14 px-4 w-full"> + <Logo isMobile /> + + <div className="flex items-center gap-2"> + {isLoggedIn && <NotificationWithBoundary />} + + <DropdownMenu open={isMenuOpen} onOpenChange={setIsMenuOpen}> + <DropdownMenuTrigger asChild> + <button type="button" aria-label="๋ฉ”๋‰ด ์—ด๊ธฐ"> + <Image + src="/icons/menu.svg" + alt="menu" + width={28} + height={28} + /> + </button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + <MobileMenuLinks onClick={() => setIsMenuOpen(false)} /> + {!isLoggedIn ? ( + <DropdownMenuItem asChild onClick={() => setIsMenuOpen(false)}> + <Link href="/login">๋กœ๊ทธ์ธ ๋ฐ ํšŒ์›๊ฐ€์ž…</Link> + </DropdownMenuItem> + ) : ( + <> + <DropdownMenuItem + asChild + onClick={() => setIsMenuOpen(false)} + > + <Link href={`/users/${userId}`}>๋งˆ์ด ํŽ˜์ด์ง€</Link> + </DropdownMenuItem> + <DropdownMenuItem + asChild + onClick={() => setIsMenuOpen(false)} + > + <LogoutButton /> + </DropdownMenuItem> + </> + )} + </DropdownMenuContent> + </DropdownMenu> + </div> + </div> + </header> + ); +}; diff --git a/src/components/organisms/loading/index.tsx b/src/components/organisms/loading/index.tsx new file mode 100644 index 00000000..0687257b --- /dev/null +++ b/src/components/organisms/loading/index.tsx @@ -0,0 +1,65 @@ +const HandShakingIcon = () => { + return ( + <svg + width="36" + height="36" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M8.59029 5.5549C9.07644 5.06902 9.86498 5.06885 10.351 5.5549L17.6938 12.8987C17.7323 12.8337 17.7817 12.6998 17.7817 12.4836C17.7817 12.1441 15.7452 8.40968 17.3295 7.73068C18.9138 7.05192 20.0451 12.0307 20.7241 13.2756C21.4031 14.5204 22.8741 16.8964 19.7055 20.4045C16.5369 23.9126 11.8973 22.2154 10.3129 20.6311C9.58221 19.9003 8.8898 19.184 8.269 18.5354C8.2637 18.5303 8.25762 18.5259 8.2524 18.5207L3.61079 13.8791C3.12494 13.393 3.12476 12.6044 3.61079 12.1184C4.09683 11.6323 4.88538 11.6325 5.37154 12.1184L8.84615 15.593L9.01802 15.4387L3.83736 10.258C3.35123 9.7719 3.35123 8.98342 3.83736 8.49729C4.32339 8.01151 5.11104 8.0116 5.59712 8.49729L10.8667 13.7658L11.0385 13.6106L5.42134 7.99436C4.93569 7.50832 4.93571 6.72064 5.42134 6.23459C5.90747 5.74846 6.69595 5.74846 7.18208 6.23459L12.8862 11.9387L13.0571 11.7834L8.59029 7.31564C8.10416 6.82951 8.10416 6.04103 8.59029 5.5549Z" + fill="#00A79A" + /> + <g> + <path + d="M12.3975 2.97168C12.8189 2.95565 13.8212 3.1201 14.4588 3.90618C15.0965 4.69225 15.2717 5.65799 15.2795 6.04259" + stroke="#00DCCB" + stroke-width="1.19251" + stroke-linecap="round" + /> + <path + d="M15.1716 1.29492C15.4323 1.2855 16.0526 1.38965 16.4482 1.8816C16.8438 2.37355 16.9535 2.97708 16.9589 3.21735" + stroke="#00DCCB" + stroke-width="1.19251" + stroke-linecap="round" + /> + <path + d="M7.33813 20.6328C6.92036 20.6905 5.90671 20.6262 5.19433 19.9071C4.48194 19.1881 4.21197 18.2444 4.16603 17.8625" + stroke="#00DCCB" + stroke-width="1.19251" + stroke-linecap="round" + /> + <path + d="M4.74438 22.5762C4.48591 22.6114 3.85836 22.5692 3.416 22.1188C2.97363 21.6685 2.80465 21.0788 2.77546 20.8403" + stroke="#00DCCB" + stroke-width="1.19251" + stroke-linecap="round" + /> + </g> + </svg> + ); +}; + +export const Loading = () => { + const loadingText = 'Loading....'; + + return ( + <div className="flex flex-col items-center justify-center gap-2 my-10"> + <div className="relative flex items-center"> + <div className="animate-hello mr-2"> + <HandShakingIcon /> + </div> + {loadingText.split('').map((char, i) => ( + <span + key={i} + className="px-[2px] text-2xl font-semibold animate-pulse " + style={{ animationDelay: `${i * 0.2}s` }} + > + {char} + </span> + ))} + </div> + </div> + ); +}; diff --git a/src/components/organisms/login-form/index.tsx b/src/components/organisms/login-form/index.tsx new file mode 100644 index 00000000..502c8af9 --- /dev/null +++ b/src/components/organisms/login-form/index.tsx @@ -0,0 +1,111 @@ +'use client'; + +import React, { useState } from 'react'; +import { Form } from '@/components/ui/form'; +import { InputTextField } from '@/components/molecules/input-text-field'; +import { Button } from '@/components/ui/button'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useRouter } from 'next/navigation'; +import useAuthStore from '@/stores/useAuthStore'; +import { request } from '@/api/request'; +import addBookmarkWhenAuth from '@/features/auth/utils/addBookmarkWhenAuth'; + +const formSchema = z.object({ + email: z.string().nonempty({ message: '์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”' }).email({ + message: '์œ ํšจํ•œ ์ด๋ฉ”์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค', + }), + password: z.string().nonempty({ message: '๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”' }), +}); + +const LoginForm = () => { + const form = useForm<z.infer<typeof formSchema>>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: '', + password: '', + }, + }); + + const [disabled, setDisabled] = useState(false); + const [isLoginFailed, setIsLoginFailed] = useState(false); + const fetchAndSetUser = useAuthStore((s) => s.fetchAndSetUser); + const router = useRouter(); + + // ๋กœ๊ทธ์ธ + const onSubmit = async (values: z.infer<typeof formSchema>) => { + try { + setDisabled(true); + // ๋กœ๊ทธ์ธ ๋กœ์ง ์ž‘์„ฑ /login + // ์„ฑ๊ณต์ฒ˜๋ฆฌ ์ถ”๊ฐ€ + const { + status: { success }, + } = await request.post( + '/v1/user/login', + { + 'Content-Type': 'application/json', + }, + JSON.stringify(values), + { + credentials: 'include', + }, + ); + + if (!success) { + throw new Error('๋กœ๊ทธ์ธ ์‹คํŒจ'); + } + + // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ›„ ๋ถ๋งˆํฌ ์ •๋ณด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ + const bookmarkListStr = localStorage.getItem('bookmarkList'); + + // ๋ถ๋งˆํฌ ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๋ฉด ์„œ๋ฒ„์— ์ €์žฅ + if (bookmarkListStr !== null) { + await addBookmarkWhenAuth(bookmarkListStr); + } + + // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ›„ ํšŒ์›์ •๋ณด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ /me + await fetchAndSetUser(); + + const prevPathname = localStorage.getItem('login-trigger-path') || '/'; + + router.push(prevPathname); + + localStorage.removeItem('login-trigger-path'); + } catch (e) { + // TODO: ๋กœ๊ทธ์ธ ์‹คํŒจ์‹œ ์—๋Ÿฌ์ฝ”๋“œ ๋งž์ถฐ์„œ ์„ค์ •ํ•ด์ฃผ๊ธฐ + setIsLoginFailed(true); + console.log(e); + setDisabled(false); + } + }; + + return ( + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> + <InputTextField + form={form} + name="email" + label="์ด๋ฉ”์ผ" + type="email" + placeholder="์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" + /> + + <InputTextField + form={form} + name="password" + label="๋น„๋ฐ€๋ฒˆํ˜ธ" + type="password" + placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" + /> + + {isLoginFailed && <p className="text-red-600">๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค</p>} + <Button className="w-full bg-gray-400" disabled={disabled}> + ๋กœ๊ทธ์ธ + </Button> + </form> + </Form> + ); +}; + +export default LoginForm; diff --git a/src/components/organisms/recommend-group/index.tsx b/src/components/organisms/recommend-group/index.tsx new file mode 100644 index 00000000..3f82c825 --- /dev/null +++ b/src/components/organisms/recommend-group/index.tsx @@ -0,0 +1,102 @@ +'use client'; + +import { request } from '@/api/request'; +import { ErrorBoundary } from '@/components/error-boundary'; +import { handleError } from '@/components/error-boundary/error-handler'; +import { RecommendGroupCard } from '@/components/molecules/recommend-group/recommend-group-card'; +import { Button } from '@/components/ui/button'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Group } from '@/types'; +import { isBeforeToday } from '@/utils/dateUtils'; +import { useQuery } from '@tanstack/react-query'; +import { useState } from 'react'; +import { Empty } from '../empty'; + +export default function RecommendGroup() { + const { data: items = [], isLoading } = useQuery<Group[]>({ + queryKey: ['recommendGroups'], + queryFn: async () => { + const response = await request.get('/v2/groups/recommend'); + return response.items; + }, + }); + + const validItems = items + .filter((item) => !isBeforeToday(item.deadline)) + .slice(0, 10); + + const [currentIndex, setCurrentIndex] = useState(0); + const maxIndex = Math.max(validItems.length - 1, 0); + + const handlePrev = () => setCurrentIndex((prev) => Math.max(prev - 1, 0)); + const handleNext = () => + setCurrentIndex((prev) => Math.min(prev + 1, maxIndex)); + + if (isLoading) { + return ( + <div className="flex gap-4 mt-4 overflow-x-auto whitespace-nowrap scrollbar-hide py-2"> + {Array.from({ length: 10 }).map((_, i) => ( + <Skeleton + key={i} + className="inline-block w-[210px] h-[120px] lg:w-[276px] lg:h-[160px] flex-shrink-0 p-4 lg:p-6 ring-2 ring-gray-400/30 rounded-xl" + /> + ))} + </div> + ); + } + + if (validItems.length === 0) { + return ( + <Empty + mainText="์ถ”์ฒœํ•  ๋ชจ์ž„์ด ์—†์Šต๋‹ˆ๋‹ค." + subText="" + className="h-[120px] lg:h-[160px]" + /> + ); + } + + return ( + <ErrorBoundary + fallback={({ error, resetErrorBoundary }) => + handleError({ + error, + resetErrorBoundary, + defaultMessage: '์ถ”์ฒœ ๊ทธ๋ฃน์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค', + }) + } + > + <div className="relative mt-4"> + <div className="overflow-hidden px-1 py-2"> + <ul + className="flex transition-transform duration-300 ease-in-out" + style={{ transform: `translateX(-${currentIndex * 220}px)` }} + > + {validItems.map((group) => ( + <li + key={group.id} + className="w-[210px] h-[120px] lg:w-[276px] lg:h-[160px] mr-4 bg-white rounded-lg flex-shrink-0" + > + <RecommendGroupCard item={group} /> + </li> + ))} + </ul> + </div> + + <Button + onClick={handlePrev} + disabled={currentIndex === 0} + className="w-9 h-9 absolute top-0 left-[-10px] bottom-0 m-auto rounded-full" + > + โ—€ + </Button> + <Button + onClick={handleNext} + disabled={currentIndex === maxIndex} + className="w-9 h-9 absolute top-0 right-[-10px] bottom-0 m-auto rounded-full" + > + โ–ถ + </Button> + </div> + </ErrorBoundary> + ); +} diff --git a/src/components/organisms/register-form/index.tsx b/src/components/organisms/register-form/index.tsx new file mode 100644 index 00000000..bc056dc9 --- /dev/null +++ b/src/components/organisms/register-form/index.tsx @@ -0,0 +1,153 @@ +'use client'; + +import { z } from 'zod'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { InputTextField } from '@/components/molecules/input-text-field'; +import { Button } from '@/components/ui/button'; +import { Form } from '@/components/ui/form'; +import { useState } from 'react'; +import useAuthStore from '@/stores/useAuthStore'; +import { request } from '@/api/request'; +import addBookmarkWhenAuth from '@/features/auth/utils/addBookmarkWhenAuth'; + +// ํšŒ์›๊ฐ€์ž…์— ์“ฐ์ด๋Š” ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์œ ํšจ์„ฑ +const registerFormSchema = z + .object({ + email: z.string().nonempty({ message: '์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”' }).email({ + message: '์œ ํšจํ•œ ์ด๋ฉ”์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค', + }), + password: z + .string() + .nonempty({ message: '๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”' }) + .regex(/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[\W_]).{8,}$/, { + message: '์˜์–ด, ์ˆซ์ž, ํŠน์ˆ˜๋ฌธ์ž๋ฅผ ํ˜ผํ•ฉํ•˜์—ฌ 8์ž๋ฆฌ ์ด์ƒ', + }), + passwordConfirm: z.string().nonempty({ + message: '๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ค์‹œ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”', + }), + }) + .superRefine(({ password, passwordConfirm }, ctx) => { + if (password && passwordConfirm && password !== passwordConfirm) { + ctx.addIssue({ + path: ['passwordConfirm'], + message: '๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค', + code: z.ZodIssueCode.custom, + }); + } + }); + +const RegisterForm = () => { + const [isRegisterFailed, setIsRegisterFailed] = useState(false); + + const registerForm = useForm<z.infer<typeof registerFormSchema>>({ + resolver: zodResolver(registerFormSchema), + defaultValues: { + email: '', + password: '', + passwordConfirm: '', + }, + }); + + const [disabled, setDisabled] = useState(false); + + const fetchAndSetUser = useAuthStore((s) => s.fetchAndSetUser); + + // ํšŒ์›๊ฐ€์ž… + const onRegisterSubmit = async ( + values: z.infer<typeof registerFormSchema>, + ) => { + try { + setDisabled(true); + // ํšŒ์›๊ฐ€์ž… ๋กœ์ง ์ž‘์„ฑ /user/signup + // ์—๋Ÿฌ์ฒ˜๋ฆฌ ๋ณ„๋„๋กœ ํ•ด์ค˜์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Œ + const { + status: { success: registerSuccess }, + } = await request.post( + '/v1/user/signup', + { + 'Content-Type': 'application/json', + }, + JSON.stringify({ + email: values.email, + password: values.password, + }), + ); + + if (!registerSuccess) throw new Error('ํšŒ์›๊ฐ€์ž… ์‹คํŒจ'); + + const { + status: { success: loginSuccess }, + } = await request.post( + '/v1/user/login', + { + 'Content-Type': 'application/json', + }, + JSON.stringify(values), + { + credentials: 'include', + }, + ); + + if (!loginSuccess) throw new Error('๋กœ๊ทธ์ธ ์‹คํŒจ'); + + // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ›„ ๋ถ๋งˆํฌ ์ •๋ณด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ + const bookmarkListStr = localStorage.getItem('bookmarkList'); + + // ๋ถ๋งˆํฌ ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๋ฉด ์„œ๋ฒ„์— ์ €์žฅ + if (bookmarkListStr !== null) { + await addBookmarkWhenAuth(bookmarkListStr); + } + + // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ›„ ํšŒ์›์ •๋ณด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ /me + await fetchAndSetUser(); + } catch (e) { + setIsRegisterFailed(true); + console.log(e); + setDisabled(false); + } + }; + + return ( + <Form {...registerForm}> + <form + onSubmit={registerForm.handleSubmit(onRegisterSubmit)} + className="space-y-8" + role="register-form" + > + <InputTextField + form={registerForm} + name="email" + placeholder="์ด๋ฉ”์ผ" + type="email" + label="์ด๋ฉ”์ผ" + /> + + <InputTextField + form={registerForm} + name="password" + placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ" + type="password" + label="๋น„๋ฐ€๋ฒˆํ˜ธ" + /> + + <InputTextField + form={registerForm} + name="passwordConfirm" + label="๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ" + placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ" + type="password" + /> + + {isRegisterFailed && ( + <p className="text-red-600">์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํšŒ์›์ž…๋‹ˆ๋‹ค</p> + )} + <Button className="w-full bg-gray-400" disabled={disabled}> + ํšŒ์›๊ฐ€์ž… + </Button> + </form> + </Form> + ); +}; + +export default RegisterForm; diff --git a/src/components/organisms/register-optional-form/index.tsx b/src/components/organisms/register-optional-form/index.tsx new file mode 100644 index 00000000..080454da --- /dev/null +++ b/src/components/organisms/register-optional-form/index.tsx @@ -0,0 +1,137 @@ +'use client'; + +import { InputTextField } from '@/components/molecules/input-text-field'; +import { FormRadioGroupField } from '@/components/molecules/input-radiogroup-field'; +import { FormCheckboxGroupField } from '@/components/molecules/input-checkbox-field'; +import { Button } from '@/components/ui/button'; +import { Form } from '@/components/ui/form'; +import { Position, Skill } from '@/types/enums'; +import { z } from 'zod'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import useAuthStore from '@/stores/useAuthStore'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { request } from '@/api/request'; + +const positions = Object.keys(Position).filter((k) => isNaN(Number(k))) as [ + string, + ...string[], +]; +const skills = Object.keys(Skill).filter((k) => isNaN(Number(k))) as [ + string, + ...string[], +]; + +// ํšŒ์›๊ฐ€์ž… ํ›„ ์˜ต์…”๋„ ์ •๋ณด ์œ ํšจ์„ฑ +// ๊ทœ์น™์„ ๋„ค์ด๋ฒ„์™€ ๊ฐ™์ด ํ–ˆ์Šต๋‹ˆ๋‹ค +const optionalFormSchema = z.object({ + nickname: z.string().max(30, '๋‹‰๋„ค์ž„์€ 30์ž๋ฆฌ ์ดํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.'), + position: z.enum(positions, { + message: 'ํฌ์ง€์…˜์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”', + }), + skills: z.array(z.enum(skills)), +}); + +const RegisterOptionalForm = () => { + const fetchAndSetUser = useAuthStore((s) => s.fetchAndSetUser); + // ์ด ์ปดํฌ๋„ŒํŠธ๋Š” user๊ฐ€ ์กด์žฌํ•  ๋•Œ๋งŒ ๋ Œ๋”๋ง๋˜๋ฏ€๋กœ, useAuthStore์—์„œ user๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ๋Š” !๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค. + const currentUser = useAuthStore((s) => s.user)!; + + const optionalForm = useForm<z.infer<typeof optionalFormSchema>>({ + resolver: zodResolver(optionalFormSchema), + defaultValues: { + nickname: '', + position: '', + skills: [], + }, + }); + + const [disabled, setDisabled] = useState(false); + + const router = useRouter(); + + // ์˜ต์…˜ ์„ค์ • + const onOptionalSubmit = async ( + values: z.infer<typeof optionalFormSchema>, + ) => { + try { + setDisabled(true); + // ํ”„๋กœํ•„ ์˜ต์…˜ ์„ค์ • + const newValues: z.infer<typeof optionalFormSchema> = { + ...values, + nickname: values.nickname || currentUser.email, // ๋‹‰๋„ค์ž„์ด ๋น„์–ด์žˆ์œผ๋ฉด ์ด๋ฉ”์ผ๋กœ ์„ค์ • + }; + + // request๋Š” json ํ˜•ํƒœ์ธ๋ฐ api๋Š” form-data๋กœ ๋ฐ›์•„์„œ ๋ณ„๋„์˜ fetch๋กœ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. + const formData = new FormData(); + formData.append('nickname', newValues.nickname); + formData.append( + 'position', + String(Position[newValues.position as keyof typeof Position]), + ); + + if (newValues.skills.length > 0) { + const skillValues = newValues.skills.map( + (skillKey) => Skill[skillKey as keyof typeof Skill], + ); + formData.append('skills', skillValues.join(',')); + } + + const { + status: { success }, + } = await request.patch(`/v1/user/edit`, {}, formData, { + credentials: 'include', + }); + + if (!success) throw new Error('ํ”„๋กœํ•„ ์„ค์ • ์‹คํŒจ!'); + + // ๋ฐ”๋€ ํ”„๋กœํ•„ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์™€์„œ ์„ค์ • + await fetchAndSetUser(); + + const prevPathname = localStorage.getItem('login-trigger-path') || '/'; + router.push(prevPathname); + + localStorage.removeItem('login-trigger-path'); + } catch (e) { + // TODO: ํ”„๋กœํ•„ ์—๋Ÿฌ ์„ค์ • // + console.log(e); + setDisabled(false); + } + }; + return ( + <Form {...optionalForm}> + <form + onSubmit={optionalForm.handleSubmit(onOptionalSubmit)} + className="space-y-8" + > + <InputTextField + form={optionalForm} + name="nickname" + label="๋‹‰๋„ค์ž„" + type="text" + placeholder="์ž…๋ ฅํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์ด๋ฉ”์ผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค." + /> + + <FormRadioGroupField + form={optionalForm} + name="position" + label="ํฌ์ง€์…˜" + options={positions} + /> + + <FormCheckboxGroupField + form={optionalForm} + name="skills" + label="๊ธฐ์ˆ  ์Šคํƒ" + options={skills} + /> + <Button className="w-full bg-gray-400" disabled={disabled}> + ํ”„๋กœํ•„ ์„ค์ •ํ•˜๊ธฐ + </Button> + </form> + </Form> + ); +}; + +export default RegisterOptionalForm; diff --git a/src/components/organisms/write-form/index.tsx b/src/components/organisms/write-form/index.tsx new file mode 100644 index 00000000..d5cee5af --- /dev/null +++ b/src/components/organisms/write-form/index.tsx @@ -0,0 +1,238 @@ +'use client'; + +import { request } from '@/api/request'; +import { CategoryName } from '@/components/atoms/write-form/categoryName'; +import { ErrorBoundary } from '@/components/error-boundary'; +import { handleError } from '@/components/error-boundary/error-handler'; +import { AutoAllow } from '@/components/molecules/write-form/autoAllow'; +import { DeadlineCalendar } from '@/components/molecules/write-form/deadlineCalendar'; +import { EndDateCalendar } from '@/components/molecules/write-form/endDateCalendar'; +import { MaxParticipants } from '@/components/molecules/write-form/maxParticipants'; +import { SelectType } from '@/components/molecules/write-form/selcetType'; +import { SelectPosition } from '@/components/molecules/write-form/selectPosition'; +import { SelectSkill } from '@/components/molecules/write-form/selectSkill'; +import { StartDateCalendar } from '@/components/molecules/write-form/startDateCalendar'; +import { Description } from '@/components/molecules/write-form/tiptap/desctiption'; +import { Title } from '@/components/molecules/write-form/title'; +import { Button } from '@/components/ui/button'; +import { Form } from '@/components/ui/form'; +import { + DEFAULT_POSITION_NAMES, + DEFAULT_SKILL_NAMES, + GroupType, +} from '@/types'; +import { Position, Skill } from '@/types/enums'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useQueryClient } from '@tanstack/react-query'; +import { addDays, isAfter } from 'date-fns'; +import { useRouter } from 'next/navigation'; +import { useMemo, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { toast } from 'sonner'; +import { z } from 'zod'; + +const formSchema = z + .object({ + title: z + .string() + .trim() + .nonempty({ message: '์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.' }) + .max(100, { + message: '์ œ๋ชฉ์ด ๋„ˆ๋ฌด ๊ธธ์–ด์š”. 100์ž ์ด๋‚ด๋กœ ์ค„์—ฌ์ฃผ์„ธ์š”.', + }), + maxParticipants: z.coerce + .number({ message: '๋ชจ์ž„์˜ ์ •์›์„ ์„ค์ •ํ•ด์ฃผ์„ธ์š”.' }) + .min(2, { + message: '์ตœ์†Œ ์ธ์›์€ 2๋ช…์ด์ƒ์ด์–ด์•ผ ํ•ด์š”.', + }) + .max(30, { + message: '์ตœ๋Œ€ ์ธ์›์€ 30๋ช…๊นŒ์ง€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.', + }), + deadline: z.date().min(addDays(new Date(), 0), { + message: '๋ชจ์ง‘ ๋งˆ๊ฐ์ผ์€ ์˜ค๋Š˜๋กœ๋ถ€ํ„ฐ 1์ผ ์ดํ›„๋ถ€ํ„ฐ ์„ค์ • ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.', + }), + startDate: z.date(), + endDate: z.date(), + description: z + .string() + .min(20, { message: '๋‚ด์šฉ์„ ์ข€ ๋” ์ž์„ธํ•˜๊ฒŒ ์ ์–ด์ฃผ์„ธ์š”.' }), + autoAllow: z.boolean(), + type: z.enum([GroupType.STUDY, GroupType.PROJECT]), + skills: z + .array( + z.union([ + z.enum(DEFAULT_SKILL_NAMES), // ๋ฏธ๋ฆฌ ์ •ํ•ด์ง„ skill๊ณผ + z.string(), // ์œ ์ €๊ฐ€ ์ž…๋ ฅํ•œ ์ปค์Šคํ…€ skill์„ ํ•ฉ์นœ union ํƒ€์ž… ํ˜•ํƒœ๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ + ]), + { required_error: '์‚ฌ์šฉ ๊ธฐ์ˆ ์„ ํ•œ๊ฐ€์ง€ ์ด์ƒ ์„ ํƒํ•ด์ฃผ์„ธ์š”.' }, // ์•„์˜ˆ ์„ ํƒ๋„ ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ + ) + .min(1, { message: '์‚ฌ์šฉ ๊ธฐ์ˆ ์„ ํ•œ๊ฐ€์ง€ ์ด์ƒ ์„ ํƒํ•ด์ฃผ์„ธ์š”.' }), // ์„ ํƒํ–ˆ๋‹ค๊ฐ€ ์ง€์›Œ์„œ [] ์ธ ๊ฒฝ์šฐ + position: z + .array( + z.union([ + z.enum(DEFAULT_POSITION_NAMES), // ๋ฏธ๋ฆฌ ์ •ํ•ด์ง„ position๊ณผ + z.string(), // ์œ ์ €๊ฐ€ ์ž…๋ ฅํ•œ ์ปค์Šคํ…€ skill์„ ํ•ฉ์นœ union ํƒ€์ž… ํ˜•ํƒœ๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ + ]), + { required_error: 'ํฌ์ง€์…˜์„ ํ•œ ๊ฐ€์ง€ ์ด์ƒ ์„ ํƒํ•ด์ฃผ์„ธ์š”.' }, // ์•„์˜ˆ ์„ ํƒ๋„ ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ + ) + .min(1, { message: 'ํฌ์ง€์…˜์„ ํ•œ ๊ฐ€์ง€ ์ด์ƒ ์„ ํƒํ•ด์ฃผ์„ธ์š”.' }), // ์„ ํƒํ–ˆ๋‹ค๊ฐ€ ์ง€์›Œ์„œ [] ์ธ ๊ฒฝ์šฐ + }) + .refine((data) => isAfter(data.startDate, addDays(data.deadline, 0)), { + message: '๋ชจ์ž„ ์‹œ์ž‘์ผ์€ ๋ชจ์ง‘ ๋งˆ๊ฐ์ผ๋กœ๋ถ€ํ„ฐ 1์ผ ์ดํ›„์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.', + path: ['startDate'], + }) + .refine((data) => isAfter(data.endDate, addDays(data.startDate, 6)), { + message: '๋ชจ์ž„ ์ข…๋ฃŒ์ผ์€ ๋ชจ์ง‘ ์‹œ์ž‘์ผ๋กœ๋ถ€ํ„ฐ 7์ผ ์ดํ›„์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.', + path: ['endDate'], + }); + +export const WriteForm = () => { + const [isDeadlineCalendarOpen, setIsDeadlineCalendarOpen] = useState(false); + const [isStartDateCalendarOpen, setIsStartDateCalendarOpen] = useState(false); + const [isEndDateCalendarOpen, setIsEndDateCalendarOpen] = useState(false); + const router = useRouter(); + const queryClient = useQueryClient(); + const validDeadline = addDays(new Date(), 7); + + const [selectedDeadline, setSelectedDeadline] = useState(validDeadline); + + const validStartDate = useMemo( + () => addDays(selectedDeadline, 1), + [selectedDeadline], + ); + + const validEndDate = useMemo( + () => addDays(validStartDate, 7), + [validStartDate], + ); + + const deadlineSelectHandler = (date: Date) => { + setSelectedDeadline(date); + }; + + const form = useForm<z.infer<typeof formSchema>>({ + resolver: zodResolver(formSchema), // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” zodResolver๋กœ ํ•œ๋‹ค + reValidateMode: 'onSubmit', // submit ์‹œ์—๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ + defaultValues: { + title: '', + description: '', + autoAllow: false, + type: GroupType.STUDY, + deadline: validDeadline, // ์„ ํƒํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ validDeadline ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ + startDate: validStartDate, // ์„ ํƒํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ validStartDate ์ผ์ž ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ + endDate: validEndDate, // ์„ ํƒํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ validEndDate ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ + }, + }); + + const cancelClickHandler = () => { + router.push('/'); + }; + + const formSubmit = async (values: z.infer<typeof formSchema>) => { + const skills = values.skills.map( + (skill) => Skill[skill as keyof typeof Skill], + ); // server์— ๋ณด๋‚ผ๋•Œ enum์˜ ์ธ๋ฑ์Šค๋กœ ๋ณด๋‚ด๊ธฐ๋กœ ํ–ˆ์œผ๋ฏ€๋กœ string์„ enum์˜ ์ธ๋ฑ์Šค๋กœ ๋ณ€ํ™˜ + + const position = values.position.map( + (position) => Position[position as keyof typeof Position], + ); // server์— ๋ณด๋‚ผ๋•Œ enum์˜ ์ธ๋ฑ์Šค๋กœ ๋ณด๋‚ด๊ธฐ๋กœ ํ–ˆ์œผ๋ฏ€๋กœ string์„ enum์˜ ์ธ๋ฑ์Šค๋กœ ๋ณ€ํ™˜ + + try { + const result = await request.post( + `/v2/groups`, + { 'Content-Type': 'application/json' }, + JSON.stringify({ ...values, skills, position }), + { credentials: 'include' }, + ); + + if (result.status.success) { + await queryClient.invalidateQueries({ + queryKey: ['items', '/v2/groups'], + }); // ํ™ˆ์œผ๋กœ ์ด๋™ ํ›„ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋กœ ๊ฐฑ์‹ ํ•˜๊ธฐ ์œ„ํ•ด ๋ฌดํšจํ™” ์‹œํ‚ด + + router.push('/'); + } else { + toast.error('์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.'); + console.log( + `Error code ${result.status.code} : ${result.status.message}`, + ); + } + } catch (error) { + throw new Error( + `Group create error(server): ${ + error instanceof Error ? error.message : 'Unexpected Error' + }`, + ); + } + }; + + return ( + <ErrorBoundary + fallback={({ error, resetErrorBoundary }) => + handleError({ + error, + resetErrorBoundary, + defaultMessage: '๊ทธ๋ฃน ์ƒ์„ฑ ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', + }) + } + > + <div className="px-4 py-14"> + <Form {...form}> + <form + onSubmit={form.handleSubmit(formSubmit)} + className="flex flex-col gap-7" + > + <CategoryName number={1} text="๋ชจ์ž„ ๊ธฐ๋ณธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”." /> + <SelectType form={form} /> + <MaxParticipants form={form} /> + <DeadlineCalendar + form={form} + isDeadlineCalendarOpen={isDeadlineCalendarOpen} + deadlineSelect={deadlineSelectHandler} + openDeadlineCalendar={() => setIsDeadlineCalendarOpen(true)} + closeDeadlineCalendar={() => setIsDeadlineCalendarOpen(false)} + validDeadline={validDeadline} + /> + <StartDateCalendar + form={form} + isStartDateCalendarOpen={isStartDateCalendarOpen} + openStartDateCalendar={() => setIsStartDateCalendarOpen(true)} + closeStartDateCalendar={() => setIsStartDateCalendarOpen(false)} + validStartDate={validStartDate} + /> + <EndDateCalendar + form={form} + isEndDateCalendarOpen={isEndDateCalendarOpen} + openEndDateCalendar={() => setIsEndDateCalendarOpen(true)} + closeEndDateCalendar={() => setIsEndDateCalendarOpen(false)} + validEndDate={validEndDate} + /> + <SelectSkill form={form} /> + <SelectPosition form={form} /> + <AutoAllow form={form} /> + <CategoryName number={2} text="๋ชจ์ž„์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”." /> + <Title form={form} /> + <Description form={form} /> + <div className="flex md:justify-end gap-2"> + <Button + variant={'outline'} + type="button" + onClick={cancelClickHandler} + className="flex-1 md:flex-none cursor-pointer" + > + ์ทจ์†Œํ•˜๊ธฐ + </Button> + <Button + type="submit" + className="flex-1 md:flex-none cursor-pointer" + > + ๋“ฑ๋กํ•˜๊ธฐ + </Button> + </div> + </form> + </Form> + </div> + </ErrorBoundary> + ); +}; diff --git a/src/components/query-error-boundary/index.tsx b/src/components/query-error-boundary/index.tsx new file mode 100644 index 00000000..546e89cf --- /dev/null +++ b/src/components/query-error-boundary/index.tsx @@ -0,0 +1,45 @@ +'use client'; + +import { QueryErrorResetBoundary } from '@tanstack/react-query'; +import { ErrorBoundary } from '@/components/error-boundary'; +import { handleError } from '@/components/error-boundary/error-handler'; + +type QueryErrorBoundaryProps = { + children: React.ReactNode; + fallback?: React.ReactNode; +}; + +/** + * ์ฟผ๋ฆฌ ์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ + * + * useSuspenseQuery, useSuspenseInfiniteQuery ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ + * ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ํ›„ ์—๋Ÿฌ๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ ์ฟผ๋ฆฌ์˜ ์—๋Ÿฌ ์ƒํƒœ๋„ ์ดˆ๊ธฐํ™”ํ•ด์•ผ ํ•œ๋‹ค. + * + * + * @param children ์ž์‹ ์ปดํฌ๋„ŒํŠธ + * @returns ์ฟผ๋ฆฌ ์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ + */ +export const QueryErrorBoundary = ({ + children, + fallback, +}: QueryErrorBoundaryProps) => { + return ( + <QueryErrorResetBoundary> + {({ reset }) => ( + <ErrorBoundary + fallback={({ error, resetErrorBoundary }) => { + return fallback + ? fallback + : handleError({ + error, + resetErrorBoundary, + }); + }} + onReset={reset} + > + {children} + </ErrorBoundary> + )} + </QueryErrorResetBoundary> + ); +}; diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..0863e40d --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +function AlertDialog({ + ...props +}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) { + return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} /> +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) { + return ( + <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} /> + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) { + return ( + <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} /> + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) { + return ( + <AlertDialogPrimitive.Overlay + data-slot="alert-dialog-overlay" + className={cn( + "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", + className + )} + {...props} + /> + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) { + return ( + <AlertDialogPortal> + <AlertDialogOverlay /> + <AlertDialogPrimitive.Content + data-slot="alert-dialog-content" + className={cn( + "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", + className + )} + {...props} + /> + </AlertDialogPortal> + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( + <div + data-slot="alert-dialog-header" + className={cn("flex flex-col gap-2 text-center sm:text-left", className)} + {...props} + /> + ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( + <div + data-slot="alert-dialog-footer" + className={cn( + "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", + className + )} + {...props} + /> + ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) { + return ( + <AlertDialogPrimitive.Title + data-slot="alert-dialog-title" + className={cn("text-lg font-semibold", className)} + {...props} + /> + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) { + return ( + <AlertDialogPrimitive.Description + data-slot="alert-dialog-description" + className={cn("text-muted-foreground text-sm", className)} + {...props} + /> + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) { + return ( + <AlertDialogPrimitive.Action + className={cn(buttonVariants(), className)} + {...props} + /> + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) { + return ( + <AlertDialogPrimitive.Cancel + className={cn(buttonVariants({ variant: "outline" }), className)} + {...props} + /> + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx new file mode 100644 index 00000000..e3831623 --- /dev/null +++ b/src/components/ui/avatar.tsx @@ -0,0 +1,52 @@ +'use client'; + +import { cn } from '@/lib/utils'; +import * as AvatarPrimitive from '@radix-ui/react-avatar'; +import * as React from 'react'; + +function Avatar({ + className, + ...props +}: React.ComponentProps<typeof AvatarPrimitive.Root>) { + return ( + <AvatarPrimitive.Root + data-slot="avatar" + className={cn( + 'relative flex size-8 shrink-0 overflow-hidden rounded-full', + className, + )} + {...props} + /> + ); +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps<typeof AvatarPrimitive.Image>) { + return ( + <AvatarPrimitive.Image + data-slot="avatar-image" + className={cn('aspect-square size-full', className)} + {...props} + /> + ); +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) { + return ( + <AvatarPrimitive.Fallback + data-slot="avatar-fallback" + className={cn( + 'bg-muted flex size-full items-center justify-center rounded-full', + className, + )} + {...props} + /> + ); +} + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 00000000..02054139 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps<typeof badgeVariants> & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span" + + return ( + <Comp + data-slot="badge" + className={cn(badgeVariants({ variant }), className)} + {...props} + /> + ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 00000000..5165dd19 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,58 @@ +import { cn } from '@/lib/utils'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import * as React from 'react'; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[12px] text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: + 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90', + destructive: + 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'border border-primary text-primary bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', + secondary: + 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80', + ghost: + 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2 has-[>svg]:px-3', + sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', + lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', + icon: 'size-9', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +); + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<'button'> & + VariantProps<typeof buttonVariants> & { + asChild?: boolean; + }) { + const Comp = asChild ? Slot : 'button'; + + return ( + <Comp + data-slot="button" + className={cn(buttonVariants({ variant, size, className }))} + {...props} + /> + ); +} + +export { Button, buttonVariants }; diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx new file mode 100644 index 00000000..7cd3a94e --- /dev/null +++ b/src/components/ui/calendar.tsx @@ -0,0 +1,75 @@ +'use client'; + +import * as React from 'react'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { DayPicker } from 'react-day-picker'; + +import { cn } from '@/lib/utils'; +import { buttonVariants } from '@/components/ui/button'; + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...props +}: React.ComponentProps<typeof DayPicker>) { + return ( + <DayPicker + showOutsideDays={showOutsideDays} + className={cn('p-3', className)} + classNames={{ + months: 'flex flex-col sm:flex-row gap-2', + month: 'flex flex-col gap-4', + caption: 'flex justify-center pt-1 relative items-center w-full', + caption_label: 'text-sm font-medium', + nav: 'flex items-center gap-1', + nav_button: cn( + buttonVariants({ variant: 'outline' }), + 'size-7 bg-transparent p-0 opacity-50 hover:opacity-100', + ), + nav_button_previous: 'absolute left-1', + nav_button_next: 'absolute right-1', + table: 'w-full border-collapse space-x-1', + head_row: 'flex', + head_cell: + 'text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]', + row: 'flex w-full mt-2', + cell: cn( + 'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md', + props.mode === 'range' + ? '[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md' + : '[&:has([aria-selected])]:rounded-md', + ), + day: cn( + buttonVariants({ variant: 'ghost' }), + 'size-8 p-0 font-normal aria-selected:opacity-100', + ), + day_range_start: + 'day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground', + day_range_end: + 'day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground', + day_selected: + 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground', + day_today: 'bg-accent text-accent-foreground', + day_outside: + 'day-outside text-muted-foreground aria-selected:text-muted-foreground', + day_disabled: 'text-muted-foreground opacity-50', + day_range_middle: + 'aria-selected:bg-accent aria-selected:text-accent-foreground', + day_hidden: 'invisible', + ...classNames, + }} + components={{ + IconLeft: ({ className, ...props }) => ( + <ChevronLeft className={cn('size-4', className)} {...props} /> + ), + IconRight: ({ className, ...props }) => ( + <ChevronRight className={cn('size-4', className)} {...props} /> + ), + }} + {...props} + /> + ); +} + +export { Calendar }; diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 00000000..fa0e4b59 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,32 @@ +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { CheckIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Checkbox({ + className, + ...props +}: React.ComponentProps<typeof CheckboxPrimitive.Root>) { + return ( + <CheckboxPrimitive.Root + data-slot="checkbox" + className={cn( + "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", + className + )} + {...props} + > + <CheckboxPrimitive.Indicator + data-slot="checkbox-indicator" + className="flex items-center justify-center text-current transition-none" + > + <CheckIcon className="size-3.5" /> + </CheckboxPrimitive.Indicator> + </CheckboxPrimitive.Root> + ) +} + +export { Checkbox } diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 00000000..ee466ba0 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,134 @@ +'use client'; + +import { cn } from '@/lib/utils'; +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { XIcon } from 'lucide-react'; +import * as React from 'react'; + +function Dialog({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Root>) { + return <DialogPrimitive.Root data-slot="dialog" {...props} />; +} + +function DialogTrigger({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Trigger>) { + return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />; +} + +function DialogPortal({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Portal>) { + return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />; +} + +function DialogClose({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Close>) { + return <DialogPrimitive.Close data-slot="dialog-close" {...props} />; +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Overlay>) { + return ( + <DialogPrimitive.Overlay + data-slot="dialog-overlay" + className={cn( + 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50', + className, + )} + {...props} + /> + ); +} + +function DialogContent({ + className, + children, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Content>) { + return ( + <DialogPortal data-slot="dialog-portal"> + <DialogOverlay /> + <DialogPrimitive.Content + data-slot="dialog-content" + className={cn( + 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg', + className, + )} + {...props} + > + {children} + <DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"> + <XIcon /> + <span className="sr-only">Close</span> + </DialogPrimitive.Close> + </DialogPrimitive.Content> + </DialogPortal> + ); +} + +function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( + <div + data-slot="dialog-header" + className={cn('flex flex-col gap-2 text-center sm:text-left', className)} + {...props} + /> + ); +} + +function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) { + return ( + <div + data-slot="dialog-footer" + className={cn( + 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', + className, + )} + {...props} + /> + ); +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Title>) { + return ( + <DialogPrimitive.Title + data-slot="dialog-title" + className={cn('text-lg leading-none font-semibold', className)} + {...props} + /> + ); +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Description>) { + return ( + <DialogPrimitive.Description + data-slot="dialog-description" + className={cn('text-muted-foreground text-sm', className)} + {...props} + /> + ); +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..ec51e9cc --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,257 @@ +"use client" + +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function DropdownMenu({ + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) { + return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} /> +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) { + return ( + <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} /> + ) +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) { + return ( + <DropdownMenuPrimitive.Trigger + data-slot="dropdown-menu-trigger" + {...props} + /> + ) +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) { + return ( + <DropdownMenuPrimitive.Portal> + <DropdownMenuPrimitive.Content + data-slot="dropdown-menu-content" + sideOffset={sideOffset} + className={cn( + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md", + className + )} + {...props} + /> + </DropdownMenuPrimitive.Portal> + ) +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) { + return ( + <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} /> + ) +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + <DropdownMenuPrimitive.Item + data-slot="dropdown-menu-item" + data-inset={inset} + data-variant={variant} + className={cn( + "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + className + )} + {...props} + /> + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) { + return ( + <DropdownMenuPrimitive.CheckboxItem + data-slot="dropdown-menu-checkbox-item" + className={cn( + "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + className + )} + checked={checked} + {...props} + > + <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> + <DropdownMenuPrimitive.ItemIndicator> + <CheckIcon className="size-4" /> + </DropdownMenuPrimitive.ItemIndicator> + </span> + {children} + </DropdownMenuPrimitive.CheckboxItem> + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) { + return ( + <DropdownMenuPrimitive.RadioGroup + data-slot="dropdown-menu-radio-group" + {...props} + /> + ) +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) { + return ( + <DropdownMenuPrimitive.RadioItem + data-slot="dropdown-menu-radio-item" + className={cn( + "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + className + )} + {...props} + > + <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> + <DropdownMenuPrimitive.ItemIndicator> + <CircleIcon className="size-2 fill-current" /> + </DropdownMenuPrimitive.ItemIndicator> + </span> + {children} + </DropdownMenuPrimitive.RadioItem> + ) +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { + inset?: boolean +}) { + return ( + <DropdownMenuPrimitive.Label + data-slot="dropdown-menu-label" + data-inset={inset} + className={cn( + "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", + className + )} + {...props} + /> + ) +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) { + return ( + <DropdownMenuPrimitive.Separator + data-slot="dropdown-menu-separator" + className={cn("bg-border -mx-1 my-1 h-px", className)} + {...props} + /> + ) +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + <span + data-slot="dropdown-menu-shortcut" + className={cn( + "text-muted-foreground ml-auto text-xs tracking-widest", + className + )} + {...props} + /> + ) +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) { + return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} /> +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { + inset?: boolean +}) { + return ( + <DropdownMenuPrimitive.SubTrigger + data-slot="dropdown-menu-sub-trigger" + data-inset={inset} + className={cn( + "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8", + className + )} + {...props} + > + {children} + <ChevronRightIcon className="ml-auto size-4" /> + </DropdownMenuPrimitive.SubTrigger> + ) +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) { + return ( + <DropdownMenuPrimitive.SubContent + data-slot="dropdown-menu-sub-content" + className={cn( + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg", + className + )} + {...props} + /> + ) +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +} diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx new file mode 100644 index 00000000..657c64a6 --- /dev/null +++ b/src/components/ui/form.tsx @@ -0,0 +1,168 @@ +'use client'; + +import * as React from 'react'; +import * as LabelPrimitive from '@radix-ui/react-label'; +import { Slot } from '@radix-ui/react-slot'; +import { + Controller, + FormProvider, + useFormContext, + useFormState, + type ControllerProps, + type FieldPath, + type FieldValues, +} from 'react-hook-form'; + +import { cn } from '@/lib/utils'; +import { Label } from '@/components/ui/label'; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, +> = { + name: TName; +}; + +const FormFieldContext = React.createContext<FormFieldContextValue>( + {} as FormFieldContextValue, +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, +>({ + ...props +}: ControllerProps<TFieldValues, TName>) => { + return ( + <FormFieldContext.Provider value={{ name: props.name }}> + <Controller {...props} /> + </FormFieldContext.Provider> + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState } = useFormContext(); + const formState = useFormState({ name: fieldContext.name }); + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error('useFormField should be used within <FormField>'); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext<FormItemContextValue>( + {} as FormItemContextValue, +); + +function FormItem({ className, ...props }: React.ComponentProps<'div'>) { + const id = React.useId(); + + return ( + <FormItemContext.Provider value={{ id }}> + <div + data-slot="form-item" + className={cn('grid gap-2', className)} + {...props} + /> + </FormItemContext.Provider> + ); +} + +function FormLabel({ + className, + ...props +}: React.ComponentProps<typeof LabelPrimitive.Root>) { + const { error, formItemId } = useFormField(); + + return ( + <Label + data-slot="form-label" + data-error={!!error} + className={cn('data-[error=true]:text-destructive', className)} + htmlFor={formItemId} + {...props} + /> + ); +} + +function FormControl({ ...props }: React.ComponentProps<typeof Slot>) { + const { error, formItemId, formDescriptionId, formMessageId } = + useFormField(); + + return ( + <Slot + data-slot="form-control" + id={formItemId} + aria-describedby={ + !error + ? `${formDescriptionId}` + : `${formDescriptionId} ${formMessageId}` + } + aria-invalid={!!error} + {...props} + /> + ); +} + +function FormDescription({ className, ...props }: React.ComponentProps<'p'>) { + const { formDescriptionId } = useFormField(); + + return ( + <p + data-slot="form-description" + id={formDescriptionId} + className={cn('text-muted-foreground text-sm', className)} + {...props} + /> + ); +} + +function FormMessage({ className, ...props }: React.ComponentProps<'p'>) { + const { error, formMessageId } = useFormField(); + const body = error ? String(error?.message ?? '') : props.children; + + if (!body) { + return null; + } + + return ( + <p + data-slot="form-message" + id={formMessageId} + className={cn('text-destructive text-sm', className)} + {...props} + > + {body} + </p> + ); +} + +export { + useFormField, + Form, + FormItem, + FormLabel, + FormControl, + FormDescription, + FormMessage, + FormField, +}; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 00000000..3c1cfcaf --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; + +import { cn } from '@/lib/utils'; + +function Input({ className, type, ...props }: React.ComponentProps<'input'>) { + return ( + <input + type={type} + data-slot="input" + className={cn( + 'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm', + 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]', + 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive', + className, + )} + {...props} + /> + ); +} + +export { Input }; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 00000000..1f7ae512 --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +'use client'; + +import * as React from 'react'; +import * as LabelPrimitive from '@radix-ui/react-label'; + +import { cn } from '@/lib/utils'; + +function Label({ + className, + ...props +}: React.ComponentProps<typeof LabelPrimitive.Root>) { + return ( + <LabelPrimitive.Root + data-slot="label" + className={cn( + 'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50', + className, + )} + {...props} + /> + ); +} + +export { Label }; diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 00000000..0e285c1d --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,48 @@ +'use client'; + +import * as React from 'react'; +import * as PopoverPrimitive from '@radix-ui/react-popover'; + +import { cn } from '@/lib/utils'; + +function Popover({ + ...props +}: React.ComponentProps<typeof PopoverPrimitive.Root>) { + return <PopoverPrimitive.Root data-slot="popover" {...props} />; +} + +function PopoverTrigger({ + ...props +}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) { + return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />; +} + +function PopoverContent({ + className, + align = 'center', + sideOffset = 4, + ...props +}: React.ComponentProps<typeof PopoverPrimitive.Content>) { + return ( + <PopoverPrimitive.Portal> + <PopoverPrimitive.Content + data-slot="popover-content" + align={align} + sideOffset={sideOffset} + className={cn( + 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden', + className, + )} + {...props} + /> + </PopoverPrimitive.Portal> + ); +} + +function PopoverAnchor({ + ...props +}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) { + return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />; +} + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx new file mode 100644 index 00000000..3cd10c17 --- /dev/null +++ b/src/components/ui/progress.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { cn } from '@/lib/utils'; +import * as ProgressPrimitive from '@radix-ui/react-progress'; +import * as React from 'react'; + +function Progress({ + className, + value, + ...props +}: React.ComponentProps<typeof ProgressPrimitive.Root>) { + return ( + <ProgressPrimitive.Root + data-slot="progress" + className={cn( + 'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full', + className, + )} + {...props} + > + <ProgressPrimitive.Indicator + data-slot="progress-indicator" + className="bg-primary h-full w-full flex-1 transition-all duration-500" + style={{ transform: `translateX(-${100 - (value || 0)}%)` }} + /> + </ProgressPrimitive.Root> + ); +} + +export { Progress }; diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx new file mode 100644 index 00000000..5e6778cb --- /dev/null +++ b/src/components/ui/radio-group.tsx @@ -0,0 +1,45 @@ +"use client" + +import * as React from "react" +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" +import { CircleIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function RadioGroup({ + className, + ...props +}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) { + return ( + <RadioGroupPrimitive.Root + data-slot="radio-group" + className={cn("grid gap-3", className)} + {...props} + /> + ) +} + +function RadioGroupItem({ + className, + ...props +}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) { + return ( + <RadioGroupPrimitive.Item + data-slot="radio-group-item" + className={cn( + "border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", + className + )} + {...props} + > + <RadioGroupPrimitive.Indicator + data-slot="radio-group-indicator" + className="relative flex items-center justify-center" + > + <CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" /> + </RadioGroupPrimitive.Indicator> + </RadioGroupPrimitive.Item> + ) +} + +export { RadioGroup, RadioGroupItem } diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 00000000..dcbbc0ca --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,185 @@ +"use client" + +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Select({ + ...props +}: React.ComponentProps<typeof SelectPrimitive.Root>) { + return <SelectPrimitive.Root data-slot="select" {...props} /> +} + +function SelectGroup({ + ...props +}: React.ComponentProps<typeof SelectPrimitive.Group>) { + return <SelectPrimitive.Group data-slot="select-group" {...props} /> +} + +function SelectValue({ + ...props +}: React.ComponentProps<typeof SelectPrimitive.Value>) { + return <SelectPrimitive.Value data-slot="select-value" {...props} /> +} + +function SelectTrigger({ + className, + size = "default", + children, + ...props +}: React.ComponentProps<typeof SelectPrimitive.Trigger> & { + size?: "sm" | "default" +}) { + return ( + <SelectPrimitive.Trigger + data-slot="select-trigger" + data-size={size} + className={cn( + "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + className + )} + {...props} + > + {children} + <SelectPrimitive.Icon asChild> + <ChevronDownIcon className="size-4 opacity-50" /> + </SelectPrimitive.Icon> + </SelectPrimitive.Trigger> + ) +} + +function SelectContent({ + className, + children, + position = "popper", + ...props +}: React.ComponentProps<typeof SelectPrimitive.Content>) { + return ( + <SelectPrimitive.Portal> + <SelectPrimitive.Content + data-slot="select-content" + className={cn( + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md", + position === "popper" && + "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", + className + )} + position={position} + {...props} + > + <SelectScrollUpButton /> + <SelectPrimitive.Viewport + className={cn( + "p-1", + position === "popper" && + "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1" + )} + > + {children} + </SelectPrimitive.Viewport> + <SelectScrollDownButton /> + </SelectPrimitive.Content> + </SelectPrimitive.Portal> + ) +} + +function SelectLabel({ + className, + ...props +}: React.ComponentProps<typeof SelectPrimitive.Label>) { + return ( + <SelectPrimitive.Label + data-slot="select-label" + className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} + {...props} + /> + ) +} + +function SelectItem({ + className, + children, + ...props +}: React.ComponentProps<typeof SelectPrimitive.Item>) { + return ( + <SelectPrimitive.Item + data-slot="select-item" + className={cn( + "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", + className + )} + {...props} + > + <span className="absolute right-2 flex size-3.5 items-center justify-center"> + <SelectPrimitive.ItemIndicator> + <CheckIcon className="size-4" /> + </SelectPrimitive.ItemIndicator> + </span> + <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> + </SelectPrimitive.Item> + ) +} + +function SelectSeparator({ + className, + ...props +}: React.ComponentProps<typeof SelectPrimitive.Separator>) { + return ( + <SelectPrimitive.Separator + data-slot="select-separator" + className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)} + {...props} + /> + ) +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) { + return ( + <SelectPrimitive.ScrollUpButton + data-slot="select-scroll-up-button" + className={cn( + "flex cursor-default items-center justify-center py-1", + className + )} + {...props} + > + <ChevronUpIcon className="size-4" /> + </SelectPrimitive.ScrollUpButton> + ) +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) { + return ( + <SelectPrimitive.ScrollDownButton + data-slot="select-scroll-down-button" + className={cn( + "flex cursor-default items-center justify-center py-1", + className + )} + {...props} + > + <ChevronDownIcon className="size-4" /> + </SelectPrimitive.ScrollDownButton> + ) +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx new file mode 100644 index 00000000..275381ca --- /dev/null +++ b/src/components/ui/separator.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps<typeof SeparatorPrimitive.Root>) { + return ( + <SeparatorPrimitive.Root + data-slot="separator" + decorative={decorative} + orientation={orientation} + className={cn( + "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", + className + )} + {...props} + /> + ) +} + +export { Separator } diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx new file mode 100644 index 00000000..32ea0ef7 --- /dev/null +++ b/src/components/ui/skeleton.tsx @@ -0,0 +1,13 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="skeleton" + className={cn("bg-accent animate-pulse rounded-md", className)} + {...props} + /> + ) +} + +export { Skeleton } diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx new file mode 100644 index 00000000..564a6547 --- /dev/null +++ b/src/components/ui/sonner.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { useTheme } from 'next-themes'; +import { Toaster as Sonner, ToasterProps } from 'sonner'; + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = 'system' } = useTheme(); + + return ( + <Sonner + theme={theme as ToasterProps['theme']} + className="toaster group" + style={ + { + '--normal-bg': 'var(--popover)', + '--normal-text': 'var(--popover-foreground)', + '--normal-border': 'var(--border)', + } as React.CSSProperties + } + {...props} + /> + ); +}; + +export { Toaster }; diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 00000000..c481005c --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,66 @@ +'use client'; + +import * as TabsPrimitive from '@radix-ui/react-tabs'; +import * as React from 'react'; + +import { cn } from '@/lib/utils'; + +function Tabs({ + className, + ...props +}: React.ComponentProps<typeof TabsPrimitive.Root>) { + return ( + <TabsPrimitive.Root + data-slot="tabs" + className={cn('flex flex-col gap-2', className)} + {...props} + /> + ); +} + +function TabsList({ + className, + ...props +}: React.ComponentProps<typeof TabsPrimitive.List>) { + return ( + <TabsPrimitive.List + data-slot="tabs-list" + className={cn( + 'text-muted-foreground inline-flex w-fit items-center justify-center rounded-lg p-[3px]', + className, + )} + {...props} + /> + ); +} + +function TabsTrigger({ + className, + ...props +}: React.ComponentProps<typeof TabsPrimitive.Trigger>) { + return ( + <TabsPrimitive.Trigger + data-slot="tabs-trigger" + className={cn( + "data-[state=active]:text-foreground dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-gray-500 dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 ", + className, + )} + {...props} + /> + ); +} + +function TabsContent({ + className, + ...props +}: React.ComponentProps<typeof TabsPrimitive.Content>) { + return ( + <TabsPrimitive.Content + data-slot="tabs-content" + className={cn('flex-1 outline-none', className)} + {...props} + /> + ); +} + +export { Tabs, TabsContent, TabsList, TabsTrigger }; diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 00000000..98415dfb --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,68 @@ +'use client'; + +import * as TooltipPrimitive from '@radix-ui/react-tooltip'; +import * as React from 'react'; + +import { cn } from '@/lib/utils'; + +function TooltipProvider({ + delayDuration = 0, + ...props +}: React.ComponentProps<typeof TooltipPrimitive.Provider>) { + return ( + <TooltipPrimitive.Provider + data-slot="tooltip-provider" + delayDuration={delayDuration} + {...props} + /> + ); +} + +function Tooltip({ + ...props +}: React.ComponentProps<typeof TooltipPrimitive.Root>) { + return ( + <TooltipProvider> + <TooltipPrimitive.Root data-slot="tooltip" {...props} /> + </TooltipProvider> + ); +} + +function TooltipTrigger({ + ...props +}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) { + return ( + <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} /> + ); +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}: React.ComponentProps<typeof TooltipPrimitive.Content>) { + return ( + <TooltipPrimitive.Portal> + <TooltipPrimitive.Content + data-slot="tooltip-content" + sideOffset={sideOffset} + className={cn( + 'bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance', + className, + )} + {...props} + > + {children} + <TooltipPrimitive.Arrow + className={cn( + 'bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]', + className, + )} + /> + </TooltipPrimitive.Content> + </TooltipPrimitive.Portal> + ); +} + +export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }; diff --git a/src/features/auth/components/AutoLoginClientManager.tsx b/src/features/auth/components/AutoLoginClientManager.tsx new file mode 100644 index 00000000..659f57ac --- /dev/null +++ b/src/features/auth/components/AutoLoginClientManager.tsx @@ -0,0 +1,41 @@ +'use client'; + +import useAuthStore from '@/stores/useAuthStore'; +import { useEffect } from 'react'; + +type AuthClientProviderProps = { + hasRefreshToken: boolean; +}; + +// localStorage์™€ ์„œ๋ฒ„๊ฐ€ ๋ถˆ์ผ์น˜ ํ•  ์ˆ˜๋„ ์žˆ์–ด์„œ ์žฌ์ ‘์†์‹œ ํ”„๋กœํ•„ 1ํšŒ ์—…๋ฐ์ดํŠธ +const AutoLoginClientManager = ({ + hasRefreshToken, +}: AuthClientProviderProps) => { + const fetchAndSetUser = useAuthStore((s) => s.fetchAndSetUser); + const clearUser = useAuthStore((s) => s.clearUser); + + // ์žฌ์ ‘์†์‹œ ๋ฌด์กฐ๊ฑด ํ•œ๋ฒˆ๋งŒ ์‹คํ–‰๋˜์•ผํ•จ!! 0๋ฒˆ๋„ ์•ˆ๋˜๊ณ  2๋ฒˆ๋„ ์•ˆ๋จ + useEffect(() => { + const getProfile = async () => { + try { + // ํšŒ์›์ •๋ณด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ /me + await fetchAndSetUser(); + } catch (e) { + console.log(e); + clearUser(); + } + }; + + if (hasRefreshToken) { + getProfile(); + } else { + clearUser(); + } + + // eslint-disable-next-line + }, []); + + return null; +}; + +export default AutoLoginClientManager; diff --git a/src/features/auth/components/AutoLoginManager.tsx b/src/features/auth/components/AutoLoginManager.tsx new file mode 100644 index 00000000..19402e74 --- /dev/null +++ b/src/features/auth/components/AutoLoginManager.tsx @@ -0,0 +1,11 @@ +import AutoLoginClientManager from '@/features/auth/components/AutoLoginClientManager'; +import { cookies } from 'next/headers'; + +// ์‚ฌ์šฉ์ž ์ ‘์†์‹œ http only ์ฟ ํ‚ค ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค +// refresh ํŒ๋‹จํ›„ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์ธ AutoLoginClient์—์„œ ์ž๋™๋กœ๊ทธ์ธ์„ ํ• ์ง€ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค +export const AutoLoginManager = async () => { + const cookieStore = await cookies(); + const hasRefreshToken = cookieStore.has('refreshToken'); + + return <AutoLoginClientManager hasRefreshToken={hasRefreshToken} />; +}; diff --git a/src/features/auth/components/ClientAuthGuard.tsx b/src/features/auth/components/ClientAuthGuard.tsx new file mode 100644 index 00000000..5d9ba120 --- /dev/null +++ b/src/features/auth/components/ClientAuthGuard.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useEffect } from 'react'; +import useAuthStore from '@/stores/useAuthStore'; + +type ClientAuthGuardProps = { + children: React.ReactNode; +}; + +export default function ClientAuthGuard({ children }: ClientAuthGuardProps) { + const user = useAuthStore((state) => state.user); + const router = useRouter(); + + useEffect(() => { + if (!user) { + router.replace('/login'); + } + // eslint-disable-next-line + }, [user]); + + return <>{children}</>; +} diff --git a/src/features/auth/components/LoginTriggerManager.tsx b/src/features/auth/components/LoginTriggerManager.tsx new file mode 100644 index 00000000..e2aedfd8 --- /dev/null +++ b/src/features/auth/components/LoginTriggerManager.tsx @@ -0,0 +1,33 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useEffect } from 'react'; + +type TriggerManagerProps = { + prevPathname: string; +}; + +const LoginTriggerManager = ({ prevPathname }: TriggerManagerProps) => { + const router = useRouter(); + useEffect(() => { + // ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ๊ฐ™์€ ๊ฒฝ๋กœ๋กœ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์ ‘๊ทผ์‹œ layout์ด ์บ์‰ฌ๋œ๊ฑธ ๋ถˆ๋Ÿฌ์™€์„œ ์ฟ ํ‚ค ๋‹ค์‹œ ํ™•์ธ์šฉ + router.refresh(); + + // ์ธ์ฆ ํŽ˜์ด์ง€๋“ค ์‚ฌ์ด์—์„œ ์ด๋™ํ•˜๋Š” ๊ฒฝ์šฐ ์ €์žฅํ•˜์ง€ ์•Š๋„๋ก ์ฒ˜๋ฆฌ!!!!!!!!!!!!!!! + if ( + prevPathname === '/login' || + prevPathname === '/register' || + prevPathname === '/find-email' || + prevPathname === '/find-password' + ) { + return; + } + + localStorage.setItem('login-trigger-path', prevPathname); + // eslint-disable-next-line + }, []); + + return null; +}; + +export default LoginTriggerManager; diff --git a/src/features/auth/index.ts b/src/features/auth/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/features/auth/utils/addBookmarkWhenAuth.ts b/src/features/auth/utils/addBookmarkWhenAuth.ts new file mode 100644 index 00000000..f684fcb5 --- /dev/null +++ b/src/features/auth/utils/addBookmarkWhenAuth.ts @@ -0,0 +1,24 @@ +import { request } from '@/api/request'; + +// ๋ถ๋งˆํฌ ์ •๋ณด๋ฅผ ๋กœ๊ทธ์ธ ํ›„ ์„œ๋ฒ„์— ์ €์žฅํ•˜๋Š” ํ•จ์ˆ˜ +const addBookmarkWhenAuth = async (bookmarkListStr: string) => { + let bookmarkList: number[] = []; + bookmarkList = JSON.parse(bookmarkListStr) as number[]; + + try { + // TODO: api ์ˆ˜์ • ์š”์ฒญ + await request.post( + `/v2/bookmark/addids`, + { 'Content-Type': 'application/json' }, + JSON.stringify({ groupIds: bookmarkList }), + { credentials: 'include' }, + ); + + // db์— ๋ถ๋งˆํฌ ์ถ”๊ฐ€ ์„ฑ๊ณต ํ›„ ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์—์„œ ๋ถ๋งˆํฌ ์ •๋ณด ์‚ญ์ œ + localStorage.removeItem('bookmarkList'); + } catch (e) { + console.error('๋ถ๋งˆํฌ ์ถ”๊ฐ€ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', e); + } +}; + +export default addBookmarkWhenAuth; diff --git a/src/features/auth/utils/checkAuthCookie.ts b/src/features/auth/utils/checkAuthCookie.ts new file mode 100644 index 00000000..7df4fe2c --- /dev/null +++ b/src/features/auth/utils/checkAuthCookie.ts @@ -0,0 +1,56 @@ +import { cookies } from 'next/headers'; +import { UserInfoResponse } from '@/types/response'; + +// ์•ˆ์”€ +const checkAuthCookie = async () => { + const cookieStore = await cookies(); + + const accessToken = cookieStore.get('accessToken'); + const refreshToken = cookieStore.get('refreshToken'); + + // ๋‘๊ฐœ๋‹ค ์žˆ๋Š” ๊ฒฝ์šฐ + if (accessToken && refreshToken) { + let cookieString = ''; + + if (accessToken) { + cookieString += `${process.env.ACCESS_TOKEN}=${accessToken.value}; `; + } + if (refreshToken) { + cookieString += `${process.env.REFRESH_TOKEN}=${refreshToken.value}; `; + } + + try { + const response = await fetch( + process.env.NEXT_PUBLIC_API_BASE_URL + '/v1/user/info', + { + headers: { + Cookie: cookieString, + }, + }, + ); + + if (!response.ok) { + throw new Error('Failed to fetch user info'); + } + + const responseBody: UserInfoResponse = await response.json(); + + return responseBody.status.success; + } catch (e) { + // refreshToken์ด ๋งŒ๋ฃŒ๋˜์—ˆ๊ฑฐ๋‚˜, accessToken์ด ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ + // TODO: refresh token์„ ์ด์šฉํ•ด์„œ accessToken์„ ๊ฐฑ์‹ ํ•˜๋Š” ๋กœ์ง ์ถ”๊ฐ€ ํ•„์š” + console.log(e); + return false; + } + } + + // accessToken์ด ์—†๊ณ  refreshToken์ด ์žˆ๋Š” ๊ฒฝ์šฐ + if (!accessToken && refreshToken) { + // refreshToken์ด ๋งŒ๋ฃŒ๋˜์—ˆ๊ฑฐ๋‚˜, accessToken์ด ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ + return false; + } + + return false; +}; + +export default checkAuthCookie; diff --git a/src/features/auth/utils/fetchRefreshToken.ts b/src/features/auth/utils/fetchRefreshToken.ts new file mode 100644 index 00000000..49c1f9cd --- /dev/null +++ b/src/features/auth/utils/fetchRefreshToken.ts @@ -0,0 +1,38 @@ +'use client'; + +// ๋ฌด์กฐ๊ฑด ํด๋ผ์ด์–ธํŠธ์—์„œ +// ์„ฑ๊ณต ์—ฌ๋ถ€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜ +const fetchRefreshToken = async () => { + try { + // ์ฟ ํ‚ค๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๊ธฐ ์œ„ํ•œ ์š”์ฒญ + const response = await fetch( + process.env.NEXT_PUBLIC_API_BASE_URL + '/v1/user/refresh', + { + method: 'POST', + credentials: 'include', // ์ฟ ํ‚ค๋ฅผ ํฌํ•จํ•˜์—ฌ ์š”์ฒญ + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + if (!response.ok) { + throw new Error('ํ† ํฐ ๊ฐฑ์‹  ์š”์ฒญ ์‹คํŒจ'); + } + + const { + status: { success }, + } = await response.json(); + + if (!success) { + throw new Error('ํ† ํฐ ๊ฐฑ์‹  ์‹คํŒจ'); + } + + return success as boolean; + } catch (error) { + console.error('token ์ƒˆ๋กœ ๊ณ ์นจ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error); + return false; + } +}; + +export default fetchRefreshToken; diff --git a/src/features/auth/utils/setUserInfo.ts b/src/features/auth/utils/setUserInfo.ts new file mode 100644 index 00000000..a038f078 --- /dev/null +++ b/src/features/auth/utils/setUserInfo.ts @@ -0,0 +1,32 @@ +import { request } from '@/api/request'; +import { User } from '@/types'; +import { UserInfoResponse } from '@/types/response'; + +// ISSUE: ์Šคํ† ์–ด์— ๋„ฃ์œผ๋ฉด ์ข‹์„๊ฑฐ ๊ฐ™์•„์š” ์ผ๋‹จ ์ œ๊ฐ€ ์ž‘์—…ํ•œ๊ฑฐ๋ผ ๋ถ„๋ฆฌํ•ด๋’€์–ด์š” +export const fetchAndSetUser = async ( + setUser: (user: User) => void, +): Promise<void> => { + try { + const responseBody: UserInfoResponse = await request.get( + '/v1/user/info', + {}, + { + credentials: 'include', + }, + ); + + if (responseBody.status.success) { + setUser({ + //@ts-expect-error ๋ฐฑ์—”๋“œ์—์„œ ์ œ๊ณตํ•˜๋Š” ํƒ€์ž…์ด ์ด์ƒํ•ด์„œ ์ž„์‹œ๋กœ ์ฒ˜๋ฆฌ + userId: responseBody.items.items.id, + ...responseBody.items.items, + }); + } else { + throw new Error('์œ ์ € ์ •๋ณด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํŒจ'); + } + } catch (error) { + console.error('์œ ์ € ์ •๋ณด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํŒจ', error); + // ํ•„์š”์‹œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋กœ์ง ์ถ”๊ฐ€ + throw error; + } +}; diff --git a/src/features/bookmark/components/bookmark-button-container.tsx b/src/features/bookmark/components/bookmark-button-container.tsx new file mode 100644 index 00000000..0ee8ce43 --- /dev/null +++ b/src/features/bookmark/components/bookmark-button-container.tsx @@ -0,0 +1,67 @@ +'use client'; + +import { request } from '@/api/request'; +import { BookmarkButton } from '@/features/bookmark/components/bookmark-button'; +import useAuthStore from '@/stores/useAuthStore'; +import { useMutation } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; +import { toast } from 'sonner'; +import { addBookmarkItem, getBookmarkList, removeBookmarkItem } from '../utils'; + +type BookmarkButtonContainerProps = { + groupId: number; + isBookmark: boolean; +}; + +/** + * ๋ถ๋งˆํฌ ๋ฒ„ํŠผ์˜ ์ƒํƒœ(isBookmark)๋ฅผ ๋‚ด๋ถ€์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ ์ปดํฌ๋„ŒํŠธ + * + * - ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž์˜ ๊ฒฝ์šฐ, localStorage์—์„œ ๋ถ๋งˆํฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + */ +export const BookmarkButtonContainer = ({ + groupId, + isBookmark: initialIsBookmark, +}: BookmarkButtonContainerProps) => { + const user = useAuthStore((state) => state.user); + const [isBookmark, setIsBookmark] = useState(initialIsBookmark); + + const { mutate } = useMutation({ + mutationFn: (nextBookmarkStatus: boolean) => + request.patch( + '/v2/bookmark', + { 'Content-Type': 'application/json' }, + { groupId, isBookmark: nextBookmarkStatus }, + { credentials: 'include' }, + ), + onError: () => { + toast.error('์ฐœํ•˜๊ธฐ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); + setIsBookmark((prev) => !prev); + }, + }); + + useEffect(() => { + if (!user) { + const bookmarkList = getBookmarkList(); + setIsBookmark(bookmarkList.includes(groupId)); + } + }, [groupId, user]); + + const toggleBookmark = async () => { + const nextBookmarkState = !isBookmark; + setIsBookmark(nextBookmarkState); + + if (!user) { + if (nextBookmarkState) addBookmarkItem(groupId); + else removeBookmarkItem(groupId); + } else { + mutate(nextBookmarkState); + } + }; + + return ( + <BookmarkButton + isBookmark={isBookmark} + bookmarkToggleHandler={toggleBookmark} + /> + ); +}; diff --git a/src/features/bookmark/components/bookmark-button.tsx b/src/features/bookmark/components/bookmark-button.tsx new file mode 100644 index 00000000..11178d95 --- /dev/null +++ b/src/features/bookmark/components/bookmark-button.tsx @@ -0,0 +1,24 @@ +'use client'; + +import Image from 'next/image'; + +type Props = { + isBookmark: boolean; + bookmarkToggleHandler: () => void; +}; + +export const BookmarkButton = ({ + isBookmark, + bookmarkToggleHandler, +}: Props) => { + return ( + <button onClick={bookmarkToggleHandler} className="cursor-pointer"> + <Image + src={`/icons/bookmark-${isBookmark ? 'active' : 'default'}.svg`} + alt="์ฐœํ•˜๊ธฐ" + width={24} + height={24} + /> + </button> + ); +}; diff --git a/src/features/bookmark/utils/index.ts b/src/features/bookmark/utils/index.ts new file mode 100644 index 00000000..aa1c7b20 --- /dev/null +++ b/src/features/bookmark/utils/index.ts @@ -0,0 +1,25 @@ +/** localStorage์—์„œ ์ฐœ ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๋Š” ํ•จ์ˆ˜ */ +export const getBookmarkList = () => { + let bookmarkList: number[] = []; + const bookmarkListStr = localStorage.getItem('bookmarkList'); + + if (bookmarkListStr !== null) { + bookmarkList = JSON.parse(bookmarkListStr) as number[]; + } + + return bookmarkList; +}; + +/** ํ•ด๋‹น ์š”์†Œ๋ฅผ localStorage ์ฐœ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•˜๋Š” ํ•จ์ˆ˜ */ +export const addBookmarkItem = (itemId: number) => { + const bookmarkList = getBookmarkList(); + bookmarkList.push(itemId); + localStorage.setItem('bookmarkList', JSON.stringify(bookmarkList)); +}; + +/** ํ•ด๋‹น ์š”์†Œ๋ฅผ localStorage ์ฐœ ๋ชฉ๋ก์—์„œ ์ œ๊ฑฐํ•˜๋Š” ํ•จ์ˆ˜ */ +export const removeBookmarkItem = (itemId: number) => { + let bookmarkList = getBookmarkList(); + bookmarkList = bookmarkList.filter((bookmark) => bookmark !== itemId); + localStorage.setItem('bookmarkList', JSON.stringify(bookmarkList)); +}; diff --git a/src/features/group/components/apply-join-button.tsx b/src/features/group/components/apply-join-button.tsx new file mode 100644 index 00000000..46628a5b --- /dev/null +++ b/src/features/group/components/apply-join-button.tsx @@ -0,0 +1,37 @@ +'use client'; + +import { request } from '@/api/request'; +import { LoginRequireButton } from '@/components/atoms/login-require-button'; +import { useMutation } from '@tanstack/react-query'; +import { useParams } from 'next/navigation'; +import { toast } from 'sonner'; + +export const ApplyJoinButton = ({ onSuccess }: { onSuccess: () => void }) => { + const { groupId } = useParams<{ groupId: string }>(); + const { mutate, isPending } = useMutation({ + mutationFn: () => + request.post( + `/v2/groups/${groupId}/applications`, + {}, + JSON.stringify({}), + { credentials: 'include' }, + ), + onError: () => { + toast.error('์ฐธ์—ฌ ์‹ ์ฒญ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.'); + }, + onSuccess: () => { + onSuccess(); + toast.success('์ฐธ์—ฌ ์‹ ์ฒญ์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'); + }, + }); + + const joinGroupHandler = () => { + mutate(); + }; + + return ( + <LoginRequireButton onClick={joinGroupHandler} disabled={isPending}> + ์ฐธ์—ฌ ์‹ ์ฒญ + </LoginRequireButton> + ); +}; diff --git a/src/features/group/components/cancel-group-button.tsx b/src/features/group/components/cancel-group-button.tsx new file mode 100644 index 00000000..f2584b29 --- /dev/null +++ b/src/features/group/components/cancel-group-button.tsx @@ -0,0 +1,38 @@ +'use client'; + +import { request } from '@/api/request'; +import { Button } from '@/components/ui/button'; +import { useMutation } from '@tanstack/react-query'; +import { useParams, useRouter } from 'next/navigation'; +import { toast } from 'sonner'; + +export const CancelGroupButton = () => { + const router = useRouter(); + const { groupId } = useParams<{ groupId: string }>(); + + const { mutate, isPending } = useMutation({ + mutationFn: () => + request.delete(`/v2/groups/${groupId}`, { credentials: 'include' }), + onError: () => { + toast.error('๋ชจ์ž„ ์ทจ์†Œ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.'); + }, + onSuccess: () => { + router.push('/'); + toast.success('๋ชจ์ž„์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'); + }, + }); + + const cancelGroupHandler = () => { + mutate(); + }; + + return ( + <Button + onClick={cancelGroupHandler} + disabled={isPending} + className="cursor-pointer" + > + ๋ชจ์ž„ ์ทจ์†Œ + </Button> + ); +}; diff --git a/src/features/group/components/cancel-join-button.tsx b/src/features/group/components/cancel-join-button.tsx new file mode 100644 index 00000000..a89ca702 --- /dev/null +++ b/src/features/group/components/cancel-join-button.tsx @@ -0,0 +1,38 @@ +import { request } from '@/api/request'; +import { Button } from '@/components/ui/button'; +import { useMutation } from '@tanstack/react-query'; +import { useParams } from 'next/navigation'; +import { toast } from 'sonner'; + +export const CancelJoinButton = ({ onSuccess }: { onSuccess: () => void }) => { + const { groupId } = useParams<{ groupId: string }>(); + const { mutate, isPending } = useMutation({ + mutationFn: () => + request.delete(`/v2/groups/${groupId}/applications`, { + credentials: 'include', + }), + onError: () => { + toast.error( + '์ฐธ์—ฌ ์‹ ์ฒญ ์ทจ์†Œ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.', + ); + }, + onSuccess: () => { + onSuccess(); + toast.success('์ฐธ์—ฌ ์‹ ์ฒญ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'); + }, + }); + + const cancelJoinHandler = () => { + mutate(); + }; + + return ( + <Button + onClick={cancelJoinHandler} + disabled={isPending} + className="cursor-pointer" + > + ์ฐธ์—ฌ ์‹ ์ฒญ ์ทจ์†Œ + </Button> + ); +}; diff --git a/src/features/group/components/group-action-buttons.tsx b/src/features/group/components/group-action-buttons.tsx new file mode 100644 index 00000000..527fc2bd --- /dev/null +++ b/src/features/group/components/group-action-buttons.tsx @@ -0,0 +1,68 @@ +'use client'; + +import { invalidateTag } from '@/actions/invalidate'; +import { ShareButton } from '@/components/atoms/share-button'; +import { ApplyJoinButton } from '@/features/group/components/apply-join-button'; +import { CancelGroupButton } from '@/features/group/components/cancel-group-button'; +import { CancelJoinButton } from '@/features/group/components/cancel-join-button'; +import useAuthStore from '@/stores/useAuthStore'; +import { useState } from 'react'; + +type GroupActionButtonsProps = { + groupId: number; + hostId: number; + isApplicant: boolean; + isJoined: boolean; + autoAllow: boolean; +}; + +export const GroupActionButtons = ({ + groupId, + hostId, + isApplicant, + isJoined, + autoAllow, +}: GroupActionButtonsProps) => { + const user = useAuthStore((state) => state.user); + const [userType, setUserType] = useState< + 'none' | 'host' | 'applicant' | 'nonApplicant' + >(() => { + if (user === null) return 'none'; + if (user?.userId === hostId) return 'host'; + if (isApplicant) return 'applicant'; + return 'nonApplicant'; + }); + + const successApply = () => { + setUserType('applicant'); + + // ์ž๋™ ์ˆ˜๋ฝ์ผ ๊ฒฝ์šฐ, ๋ชจ์ž„ ์ƒ์„ธ ํŽ˜์ด์ง€ ์ƒˆ๋กœ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ + if (autoAllow) { + invalidateTag(`group-detail-${groupId}`); + } + }; + + const successCancelApply = () => { + setUserType('nonApplicant'); + + // ์ˆ˜๋ฝ๋œ ์ฐธ์—ฌ์ž์ผ ๊ฒฝ์šฐ, ๋ชจ์ž„ ์ƒ์„ธ ํŽ˜์ด์ง€ ์ƒˆ๋กœ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ + if (isJoined) { + invalidateTag(`group-detail-${groupId}`); + } + }; + + if (userType === 'host') { + return ( + <> + <CancelGroupButton /> + <ShareButton /> + </> + ); + } + + if (userType === 'applicant') { + return <CancelJoinButton onSuccess={successCancelApply} />; + } + + return <ApplyJoinButton onSuccess={successApply} />; +}; diff --git a/src/features/group/components/group-description.tsx b/src/features/group/components/group-description.tsx new file mode 100644 index 00000000..9923ac89 --- /dev/null +++ b/src/features/group/components/group-description.tsx @@ -0,0 +1,26 @@ +'use client'; + +import { GroupType, GroupTypeName } from '@/types'; +import DOMPurify from 'isomorphic-dompurify'; + +const sanitizeHTML = (markdown: string) => { + const renderedHTML = DOMPurify.sanitize(markdown); + return { __html: renderedHTML }; +}; + +export const GroupDescription = ({ + description, + groupType, +}: { + description: string; + groupType: GroupType; +}) => { + return ( + <div className="flex flex-col gap-10 mt-15 mb-25"> + <h2 className="text-2xl font-bold pb-6 border-b-2 border-gray-100"> + {GroupTypeName[groupType]} ์†Œ๊ฐœ + </h2> + <div dangerouslySetInnerHTML={sanitizeHTML(description)} /> + </div> + ); +}; diff --git a/src/features/group/components/group-detail-card.tsx b/src/features/group/components/group-detail-card.tsx new file mode 100644 index 00000000..885d1b7c --- /dev/null +++ b/src/features/group/components/group-detail-card.tsx @@ -0,0 +1,148 @@ +import { Avatar } from '@/components/atoms/avatar'; +import { Badge } from '@/components/atoms/badge'; +import { GroupProgress } from '@/components/atoms/group/particiapant-progress'; +import { PositionBadge } from '@/components/molecules/position-badge'; +import { SkillBadge } from '@/components/molecules/skill-badge'; +import { BookmarkButtonContainer } from '@/features/bookmark/components/bookmark-button-container'; +import { ParticipantListModal } from '@/features/group/components/participant-list-modal'; +import { GroupDetail, GroupTypeName } from '@/types'; +import { Position, Skill } from '@/types/enums'; +import { formatYearMonthDayWithDot } from '@/utils/dateUtils'; +import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback'; +import Link from 'next/link'; + +type GroupDetailCardProps = { + info: GroupDetail; + isRecruiting: boolean; +}; + +export const GroupDetailCard = ({ + info, + isRecruiting, +}: GroupDetailCardProps) => { + return ( + <article className="flex flex-col gap-5 w-full items-center"> + <header className="flex flex-col gap-8 w-full max-w-[900px] px-3"> + <div className="flex justify-between items-start"> + <h1 className="font-bold text-3xl flex flex-col gap-2"> + <Badge + text={isRecruiting ? '๋ชจ์ง‘ ์ค‘' : '๋ชจ์ง‘ ๋งˆ๊ฐ'} + className="w-[fit-content] text-sm font-semibold bg-primary" + /> + {info.group.title} + </h1> + <BookmarkButtonContainer + groupId={info.group.id} + isBookmark={info.group.isBookmark} + /> + </div> + <div className="flex gap-2 items-center"> + <Link href={`/users/${info.host.userId}`} className="flex gap-2"> + <Avatar + imageSrc={getDisplayProfileImage(info.host.profileImage)} + fallback={getDisplayNickname(info.host.nickname, info.host.email)} + /> + <span className="font-semibold"> + {getDisplayNickname(info.host.nickname, info.host.email)} + </span> + </Link> + </div> + </header> + + <section className="border border-gray-200 rounded-2xl bg-white flex flex-col gap-5 py-8 px-6 w-full max-w-[900px]"> + <GroupInfoItem label="๋ชจ์ง‘ ๊ตฌ๋ถ„"> + <Badge + text={GroupTypeName[info.group.type]} + className="w-[fit-content] text-sm font-semibold bg-gray-200" + /> + </GroupInfoItem> + + <GroupInfoItem label="๋ชจ์ง‘ ๋งˆ๊ฐ"> + {formatYearMonthDayWithDot(new Date(info.group.deadline))} + </GroupInfoItem> + + <GroupInfoItem label="์˜ˆ์ƒ ์ผ์ •"> + {formatYearMonthDayWithDot(new Date(info.group.startDate))} ~{' '} + {formatYearMonthDayWithDot(new Date(info.group.endDate))} + </GroupInfoItem> + + <GroupInfoItem label="๋ชจ์ง‘ ๋ถ„์•ผ"> + <ul className="flex gap-2 w-full flex-wrap"> + {info.group.position.map((position, i) => ( + <li key={i}> + <PositionBadge name={Position[position]} /> + </li> + ))} + </ul> + </GroupInfoItem> + + <GroupInfoItem label="๊ธฐ์ˆ  ์Šคํƒ"> + <ul className="flex gap-2 w-full flex-wrap"> + {info.group.skills?.map((skill, i) => ( + <li key={i}> + <SkillBadge name={Skill[skill]} /> + </li> + ))} + </ul> + </GroupInfoItem> + + <div className="font-bold"> + <div className="whitespace-nowrap text-gray-600"> + ๋ชจ์ง‘ ํ˜„ํ™ฉ ({isRecruiting ? '๋ชจ์ง‘ ์ค‘' : '๋ชจ์ง‘ ๋งˆ๊ฐ'}) + </div> + <div className="-mt-3"> + <GroupProgress + participantsCount={info.group.participants.length} + maxParticipants={info.group.maxParticipants} + /> + </div> + </div> + + <GroupInfoItem label="์ฐธ์—ฌ์ž"> + <div className="flex justify-between"> + <div className="flex"> + {info.group.participants + .slice(0, 5) + .map(({ userId, profileImage, email, nickname }, index) => ( + <Avatar + key={userId} + imageSrc={getDisplayProfileImage(profileImage)} + fallback={getDisplayNickname(nickname, email)} + className={`${index !== 0 ? '-ml-3' : ''} ${getZIndexClass( + index, + )}`} + /> + ))} + <ParticipantListModal + participants={info.group.participants} + className={`z-100 ${ + info.group.participants.length > 0 ? '-ml-3' : '' + }`} + /> + </div> + </div> + </GroupInfoItem> + </section> + </article> + ); +}; + +const getZIndexClass = (index: number) => { + const zIndexMap = ['z-10', 'z-20', 'z-30', 'z-40', 'z-50']; + return zIndexMap[index] ?? 'z-0'; +}; + +const GroupInfoItem = ({ + label, + children, +}: { + label: string; + children: React.ReactNode; +}) => { + return ( + <div className="flex gap-4 font-bold items-center"> + <div className="whitespace-nowrap text-gray-600">{label}</div> + <div>{children}</div> + </div> + ); +}; diff --git a/src/features/group/components/participant-list-modal.tsx b/src/features/group/components/participant-list-modal.tsx new file mode 100644 index 00000000..300b5de8 --- /dev/null +++ b/src/features/group/components/participant-list-modal.tsx @@ -0,0 +1,51 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { MemberInfo } from '@/features/user/group/components/member-list-modal/member-info'; +import { UserSummary } from '@/types'; +import { PlusIcon } from 'lucide-react'; + +type ParticipantListModalProps = { + participants: UserSummary[]; + className?: string; +}; + +export const ParticipantListModal = ({ + participants, + className = '', +}: ParticipantListModalProps) => { + return ( + <Dialog> + <DialogTrigger asChild> + <button className={`rounded-full border p-2 bg-white ${className}`}> + <PlusIcon className="w-3 h-3" /> + </button> + </DialogTrigger> + <DialogContent className="max-w-80 p-0 gap-0"> + <DialogHeader className="sticky top-0 -z-10 rounded-t-lg bg-white p-4 border-b"> + <DialogTitle>์ฐธ์—ฌ์ž ๋ชฉ๋ก</DialogTitle> + <DialogDescription className="sr-only"> + ์ด ๋ชจ๋‹ฌ์€ ์ฐธ์—ฌ์ž ๋ชฉ๋ก์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. + </DialogDescription> + </DialogHeader> + <div className="overflow-y-auto max-h-[60vh] px-4 pt-6 pb-4 -z-20"> + <ul className="flex flex-col gap-4"> + {participants.map((participant) => ( + <li + key={participant.userId} + className="pb-5 border-b-2 border-gray-300 last:border-none border-dashed flex justify-between" + > + <MemberInfo {...participant} /> + </li> + ))} + </ul> + </div> + </DialogContent> + </Dialog> + ); +}; diff --git a/src/features/rating/index.test.ts b/src/features/rating/index.test.ts new file mode 100644 index 00000000..c5a82c42 --- /dev/null +++ b/src/features/rating/index.test.ts @@ -0,0 +1,53 @@ +import { request } from "../../api/request"; +import { setRating, updateRating } from "./index"; +import { server } from '../../mocks/server'; + +describe("setRating", () => { + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + + beforeEach(() => { + jest.clearAllMocks(); + jest.spyOn(request, 'post'); + jest.spyOn(request, 'patch'); + }); + + it("์ ์ˆ˜ ๋“ฑ๋ก์ด ์„ฑ๊ณตํ•˜๋ฉด success: true๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค", async () => { + const targetUserId = 1; + const rate = 4.5; + const response = await setRating(targetUserId, rate); + + expect(request.post).toHaveBeenCalledWith( + '/rating', + { 'Content-Type': 'application/json' }, + JSON.stringify({ rate, targetUserId }) + ); + expect(response.result.success).toBe(true); + }); + + it("5์ ์„ ์ดˆ๊ณผํ•˜๋Š” ํ‰์ ์„ ๋“ฑ๋กํ•˜๋ฉด success: false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค", async () => { + const targetUserId = 1; + const rate = 5.5; // 5์  ์ดˆ๊ณผ + + const response = await setRating(targetUserId, rate); + + expect(request.post).toHaveBeenCalled(); + expect(response.result.success).toBe(false); + }); + + it("ํ‰์  ์ˆ˜์ •์ด ์„ฑ๊ณตํ•˜๋ฉด success: true๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค", async () => { + const ratingId = 1; + const rate = 4.5; + + const response = await updateRating(ratingId, rate); + + expect(request.patch).toHaveBeenCalledWith( + '/ratings', + { rate }, + String(ratingId) + ); + expect(response.result.success).toBe(true); + }); + +}); diff --git a/src/features/rating/index.ts b/src/features/rating/index.ts new file mode 100644 index 00000000..78b4d8c1 --- /dev/null +++ b/src/features/rating/index.ts @@ -0,0 +1,22 @@ +import { request } from '@/api/request'; + +export const setRating = async (targetUserId: number, rate: number) => { + return await request.post( + '/rating', + { + 'Content-Type': 'application/json', + }, + JSON.stringify({ + rate, + targetUserId, + }), + ); +}; + +export const updateRating = async (ratingId: number, rate: number) => { + return await request.patch( + `/ratings/${String(ratingId)}`, + { 'Content-Type': 'application/json' }, + { rate }, + ); +}; diff --git a/src/features/reply/components/add-rereply-button.tsx b/src/features/reply/components/add-rereply-button.tsx new file mode 100644 index 00000000..32a7a786 --- /dev/null +++ b/src/features/reply/components/add-rereply-button.tsx @@ -0,0 +1,11 @@ +'use client'; + +export const AddRereplyButton = ({ onClick }: { onClick: () => void }) => { + return ( + <div className="flex justify-center border-t py-1"> + <button className="cursor-pointer" onClick={onClick}> + ๋Œ€๋Œ“๊ธ€ ๋‹ฌ๊ธฐ + </button> + </div> + ); +}; diff --git a/src/features/reply/components/reply-content.tsx b/src/features/reply/components/reply-content.tsx new file mode 100644 index 00000000..819f97cb --- /dev/null +++ b/src/features/reply/components/reply-content.tsx @@ -0,0 +1,132 @@ +'use client'; + +import { request } from '@/api/request'; +import { Button } from '@/components/ui/button'; +import useAuthStore from '@/stores/useAuthStore'; +import { Reply } from '@/types'; +import { useMutation } from '@tanstack/react-query'; +import { useParams } from 'next/navigation'; +import { useState } from 'react'; +import { toast } from 'sonner'; +import { ReplyMeta } from './reply-meta'; + +type ReplyContentProps = Reply & { parentId?: number; onDelete?: () => void }; + +export const ReplyContent = ({ + content: initalContent, + replyId, + writer, + createdAt, + parentId, + deleted: isDeleted, // ์‚ญ์ œ๋œ ๋Œ“๊ธ€์ธ์ง€ ์—ฌ๋ถ€ + onDelete, +}: ReplyContentProps) => { + const { groupId } = useParams(); + const [isEditing, setIsEditing] = useState<boolean>(false); + const [isLocallyDeleted, setIsLocallyDeleted] = useState<boolean>(isDeleted); + const [content, setContent] = useState<string>(initalContent); + const user = useAuthStore((state) => state.user); + + const isWriter = user && writer.userId == user.userId; + + const { mutate: updateReply } = useMutation({ + mutationFn: async (enteredContent: string) => + request.patch( + `/v2/groups/${groupId}/replies/${replyId}`, + { 'Content-Type': 'application/json' }, + { + content: enteredContent, + }, + { credentials: 'include' }, + ), + onSuccess: () => { + setIsEditing(false); + }, + onError: () => { + toast.error('๋Œ“๊ธ€ ์ˆ˜์ •์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.'); + }, + }); + + const { mutate: deleteReply } = useMutation({ + mutationFn: async () => + request.delete(`/v2/groups/${groupId}/replies/${replyId}`, { + credentials: 'include', + }), + onSuccess: () => { + // ์„œ๋ฒ„์—์„œ ์‚ญ์ œ ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด UI์—์„œ๋„ ๋ฐ˜์˜ + onDelete?.(); + setIsLocallyDeleted(true); + }, + onError: () => { + toast.error('๋Œ“๊ธ€ ์‚ญ์ œ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.'); + }, + }); + + const editButtonClickHandler = () => { + setIsEditing(true); + }; + + const saveButtonClickHandler = () => { + if (!content.trim() || content === initalContent) return; + updateReply(content); + }; + + const deleteButtonClickHandler = () => { + setIsEditing(false); + deleteReply(); + }; + + if (isLocallyDeleted && parentId) return null; + + return ( + <div className="p-5 flex flex-col gap-8"> + <header className="flex justify-between items-center max-[500px]:flex-col-reverse max-[500px]:items-start"> + <ReplyMeta writer={writer} createdAt={createdAt} /> + {isWriter && !isLocallyDeleted && ( + <div className="flex gap-2 items-center max-[500px]:w-full max-[500px]:justify-start max-[500px]:mb-5 "> + {isEditing && ( + <Button + onClick={() => setIsEditing(false)} + variant="outline" + className="cursor-pointer" + > + ์ทจ์†Œ + </Button> + )} + <Button + onClick={ + isEditing ? saveButtonClickHandler : editButtonClickHandler + } + variant="outline" + className="cursor-pointer" + > + {isEditing ? '์ €์žฅ' : '์ˆ˜์ •'} + </Button> + <Button + onClick={deleteButtonClickHandler} + variant="outline" + className="cursor-pointer" + > + ์‚ญ์ œ + </Button> + </div> + )} + </header> + {isEditing ? ( + <textarea + value={content} + onChange={(e) => setContent(e.currentTarget.value)} + className="max-h-20 w-full border-2 border-slate-800 rounded-sm p-3 resize-none" + /> + ) : ( + <h3 + className={`${ + isLocallyDeleted ? 'text-gray-500' : '' + } break-words whitespace-pre-wrap`} + > + {isLocallyDeleted ? '์‚ญ์ œ๋œ ๋Œ“๊ธ€์ž…๋‹ˆ๋‹ค.' : content} + </h3> + )} + </div> + ); +}; diff --git a/src/features/reply/components/reply-form.tsx b/src/features/reply/components/reply-form.tsx new file mode 100644 index 00000000..ebbe3312 --- /dev/null +++ b/src/features/reply/components/reply-form.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { request } from '@/api/request'; +import { LoginRequireButton } from '@/components/atoms/login-require-button'; +import { useTargetReplyStore } from '@/stores/useTargetReply'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useParams } from 'next/navigation'; +import { useState } from 'react'; +import { toast } from 'sonner'; + +type ReplyFormProps = { + onSuccess?: () => void; + parentReplyId?: number; + isOpenRereplyList?: boolean; +}; + +export const ReplyForm = ({ + onSuccess, + parentReplyId, + isOpenRereplyList, +}: ReplyFormProps) => { + const { groupId } = useParams(); + const [replyContent, setReplyContent] = useState<string>(''); + const setTargetReply = useTargetReplyStore((state) => state.setTargetReply); + + const endpoint = + parentReplyId === undefined + ? `/v2/groups/${groupId}/replies` + : `/v2/groups/${groupId}/replies/${parentReplyId}`; + + const queryClient = useQueryClient(); + + const { mutate } = useMutation({ + mutationFn: (content: string) => + request.post( + endpoint, + { 'Content-Type': 'application/json' }, + JSON.stringify({ content }), + { credentials: 'include' }, + ), + onSuccess: (data) => { + // ๋ชฉ๋ก ๋ฌดํšจํ™” ํ›„, ์ž‘์„ฑํ•œ ๋Œ“๊ธ€๋กœ ์ด๋™ + queryClient.invalidateQueries({ + queryKey: ['items', endpoint], + }); + + onSuccess?.(); + setReplyContent(''); + + if (parentReplyId === undefined) { + setTargetReply({ targetReplyId: data.items, targetRereplyId: null }); + } else if (!isOpenRereplyList) { + setTargetReply({ + targetReplyId: parentReplyId, + targetRereplyId: data.items, + }); + } + }, + onError: () => { + toast.error('๋Œ“๊ธ€ ๋“ฑ๋ก์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.'); + }, + }); + + const submitReplyButtonClickHandler = async () => { + if (!replyContent.trim()) return; + mutate(replyContent); + }; + + return ( + <div className="space-y-2"> + <textarea + placeholder="๋Œ“๊ธ€์„ ์ž…๋ ฅํ•˜์„ธ์š”." + className="w-full p-2 border rounded-lg h-20 resize-none" + value={replyContent} + onChange={(e) => setReplyContent(e.target.value)} + /> + <div className="flex justify-end"> + <LoginRequireButton onClick={submitReplyButtonClickHandler}> + ๋“ฑ๋ก + </LoginRequireButton> + </div> + </div> + ); +}; diff --git a/src/features/reply/components/reply-item.tsx b/src/features/reply/components/reply-item.tsx new file mode 100644 index 00000000..47bedac6 --- /dev/null +++ b/src/features/reply/components/reply-item.tsx @@ -0,0 +1,24 @@ +import { Reply } from '@/types'; +import { ReplyContent } from './reply-content'; +import { RereplySection } from './rereply-section'; + +export const ReplyItem = ({ + content, + writer, + createdAt, + replyId, + deleted, +}: Reply) => { + return ( + <section className="border-2 rounded-lg"> + <ReplyContent + content={content} + writer={writer} + createdAt={createdAt} + replyId={replyId} + deleted={deleted} + /> + <RereplySection parentReplyId={replyId} /> + </section> + ); +}; diff --git a/src/features/reply/components/reply-list.tsx b/src/features/reply/components/reply-list.tsx new file mode 100644 index 00000000..67530b97 --- /dev/null +++ b/src/features/reply/components/reply-list.tsx @@ -0,0 +1,94 @@ +'use client'; + +import { useReplyScrollIntoView } from '@/features/reply/hooks/useReplyScrollIntoView'; +import { useTargetReplyParams } from '@/features/reply/hooks/useTargetReplyParams '; +import { useFetchInView } from '@/hooks/useFetchInView'; +import { useFetchItems } from '@/hooks/useFetchItems'; +import { ReplyItem } from './reply-item'; + +import { Loading } from '@/components/organisms/loading'; +import { useTargetReplyStore } from '@/stores/useTargetReply'; +import { Reply } from '@/types'; +import flattenPages from '@/utils/flattenPages'; +import { useParams } from 'next/navigation'; +import { useEffect } from 'react'; + +const DATA_SIZE = 20; + +export const ReplyList = () => { + const { groupId } = useParams(); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = + useFetchItems<Reply>({ + url: `/v2/groups/${groupId}/replies`, + queryParams: { + size: DATA_SIZE, + }, + options: { + staleTime: 0, + }, + }); + + const { ref } = useFetchInView({ + fetchNextPage, + isLoading, + isFetchingNextPage, + }); + + const { notificationTargetReplyId } = useTargetReplyParams(); + const setTargetReply = useTargetReplyStore((state) => state.setTargetReply); + + useEffect(() => { + if (notificationTargetReplyId) { + setTargetReply({ + targetReplyId: notificationTargetReplyId, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [notificationTargetReplyId]); + + const { itemRefs: replyRefs, bottomRef } = useReplyScrollIntoView({ + data, + replyType: 'reply', + hasNextPage, + }); + + const replies = flattenPages(data.pages); + + if (isLoading) { + return ( + <div className="my-15"> + <Loading /> + </div> + ); + } + + if (replies.length === 0) { + return ( + <div className="my-15 text-center text-gray-500"> + ์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. + </div> + ); + } + + return ( + <div className="my-15"> + <ul className="flex flex-col gap-10"> + {replies.map((reply) => ( + <li + key={reply.replyId} + className="space-y-2" + ref={(el) => { + replyRefs.current[reply.replyId] = el; + }} + > + <ReplyItem {...reply} /> + </li> + ))} + </ul> + <div ref={bottomRef} id="reply-list-bottom" /> + {hasNextPage && !isFetchingNextPage && ( + <div ref={ref} className="h-2 -translate-y-100 bg-transparent" /> + )} + </div> + ); +}; diff --git a/src/features/reply/components/reply-meta.tsx b/src/features/reply/components/reply-meta.tsx new file mode 100644 index 00000000..ffd41f4f --- /dev/null +++ b/src/features/reply/components/reply-meta.tsx @@ -0,0 +1,27 @@ +'use client'; + +import { Avatar } from '@/components/atoms/avatar'; +import { Reply } from '@/types'; +import { formatDateTime } from '@/utils/dateUtils'; +import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback'; + +type ReplyMetaProps = Pick<Reply, 'writer' | 'createdAt'>; + +export const ReplyMeta = ({ writer, createdAt }: ReplyMetaProps) => { + return ( + <div className="flex gap-3 items-center"> + <Avatar + imageSrc={getDisplayProfileImage(writer.profileImage)} + fallback={getDisplayNickname(writer.nickname, writer.email)} + className="w-10 h-10 cursor-pointer" + onClick={() => {}} + /> + <div className="flex flex-col"> + <div>{getDisplayNickname(writer.nickname, writer.email)}</div> + <div className="text-gray-500 text-xs sm:text-sm"> + {formatDateTime(createdAt)} + </div> + </div> + </div> + ); +}; diff --git a/src/features/reply/components/reply-section.tsx b/src/features/reply/components/reply-section.tsx new file mode 100644 index 00000000..7111830e --- /dev/null +++ b/src/features/reply/components/reply-section.tsx @@ -0,0 +1,28 @@ +'use client'; + +import { ErrorBoundary } from '@/components/error-boundary'; +import { handleError } from '@/components/error-boundary/error-handler'; +import { ReplyForm } from './reply-form'; +import { ReplyList } from './reply-list'; + +export const ReplySection = () => { + return ( + <div className="flex flex-col gap-10"> + <h2 className="text-2xl font-bold pb-6 border-b-2 border-gray-100"> + ๋Œ“๊ธ€ + </h2> + <ErrorBoundary + fallback={({ error, resetErrorBoundary }) => + handleError({ + error, + resetErrorBoundary, + defaultMessage: '๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค', + }) + } + > + <ReplyForm /> + <ReplyList /> + </ErrorBoundary> + </div> + ); +}; diff --git a/src/features/reply/components/rereply-form-toggle.tsx b/src/features/reply/components/rereply-form-toggle.tsx new file mode 100644 index 00000000..e61cab4a --- /dev/null +++ b/src/features/reply/components/rereply-form-toggle.tsx @@ -0,0 +1,36 @@ +'use client'; + +import { useState } from 'react'; +import { AddRereplyButton } from './add-rereply-button'; +import { ReplyForm } from './reply-form'; + +type RereplyFormToggleProps = { + parentReplyId: number; + openRereplyList: () => void; + isOpenRereplyList: boolean; +}; + +export const RereplyFormToggle = ({ + parentReplyId, + openRereplyList, + isOpenRereplyList, +}: RereplyFormToggleProps) => { + const [isWriting, setIsWriting] = useState<boolean>(false); + + const rereplyFormSuccessHandler = () => { + setIsWriting(false); + openRereplyList(); + }; + + return isWriting ? ( + <div className="px-5 py-5"> + <ReplyForm + parentReplyId={parentReplyId} + onSuccess={rereplyFormSuccessHandler} + isOpenRereplyList={isOpenRereplyList} + /> + </div> + ) : ( + <AddRereplyButton onClick={() => setIsWriting(true)} /> + ); +}; diff --git a/src/features/reply/components/rereply-item.tsx b/src/features/reply/components/rereply-item.tsx new file mode 100644 index 00000000..b42ba913 --- /dev/null +++ b/src/features/reply/components/rereply-item.tsx @@ -0,0 +1,18 @@ +import { Reply } from '@/types'; +import { forwardRef, useState } from 'react'; +import { ReplyContent } from './reply-content'; + +/** ๋Œ€๋Œ“๊ธ€ ์‚ญ์ œ๋ฅผ ์œ„ํ•ด ReplyContent๋ฅผ ๋‘˜๋Ÿฌ์‹ธ๊ธฐ ์œ„ํ•ด ๋งŒ๋“  ์ปดํฌ๋„ŒํŠธ */ +export const RereplyItem = forwardRef<HTMLLIElement, Reply>((props, ref) => { + const [isDeleted, setIsDeleted] = useState(props.deleted); + + if (isDeleted) return null; + + return ( + <li ref={ref} className="border-[1px] rounded-md mx-2 first:mt-6 last:mb-3"> + <ReplyContent {...props} onDelete={() => setIsDeleted(true)} /> + </li> + ); +}); + +RereplyItem.displayName = 'RereplyItem'; diff --git a/src/features/reply/components/rereply-list.tsx b/src/features/reply/components/rereply-list.tsx new file mode 100644 index 00000000..ff885f63 --- /dev/null +++ b/src/features/reply/components/rereply-list.tsx @@ -0,0 +1,67 @@ +'use client'; + +import { useReplyScrollIntoView } from '@/features/reply/hooks/useReplyScrollIntoView'; +import { useFetchInView } from '@/hooks/useFetchInView'; +import { useFetchItems } from '@/hooks/useFetchItems'; +import { Reply } from '@/types'; +import flattenPages from '@/utils/flattenPages'; +import { useParams } from 'next/navigation'; +import { RereplyItem } from './rereply-item'; + +type RereplyListProps = { + parentReplyId: number; +}; + +const DATA_SIZE = 20; + +export const RereplyList = ({ parentReplyId }: RereplyListProps) => { + const { groupId } = useParams(); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = + useFetchItems<Reply & { parentId: number }>({ + url: `/v2/groups/${groupId}/replies/${parentReplyId}`, + queryParams: { + size: DATA_SIZE, + }, + options: { + staleTime: 0, + }, + }); + + const { ref } = useFetchInView({ + fetchNextPage, + isLoading, + isFetchingNextPage, + }); + + const { itemRefs: rereplyRefs, bottomRef } = useReplyScrollIntoView({ + data, + replyType: 'rereply', + hasNextPage, + }); + + const rereplies = flattenPages(data.pages).filter( + (rereply) => !rereply.deleted, + ); + + if (rereplies.length === 0) return null; + + return ( + <div className="mx-3"> + <ul className="flex flex-col gap-3"> + {rereplies.map((rereply) => ( + <RereplyItem + key={rereply.replyId} + ref={(el) => { + rereplyRefs.current[rereply.replyId] = el; + }} + {...rereply} + /> + ))} + </ul> + <div ref={bottomRef} /> + {hasNextPage && !isFetchingNextPage && ( + <div ref={ref} className="h-2 -translate-y-50 bg-transparent" /> + )} + </div> + ); +}; diff --git a/src/features/reply/components/rereply-section.tsx b/src/features/reply/components/rereply-section.tsx new file mode 100644 index 00000000..675dbe9d --- /dev/null +++ b/src/features/reply/components/rereply-section.tsx @@ -0,0 +1,56 @@ +'use client'; + +import { useTargetReplyParams } from '@/features/reply/hooks/useTargetReplyParams '; +import { useTargetReplyStore } from '@/stores/useTargetReply'; +import { useEffect, useState } from 'react'; +import { RereplyFormToggle } from './rereply-form-toggle'; +import { RereplyList } from './rereply-list'; + +export const RereplySection = ({ + parentReplyId, +}: { + parentReplyId: number; +}) => { + const [isOpen, setIsOpen] = useState<boolean>(false); + const { notificationTargetReplyId, notificationTargetRereplyId } = + useTargetReplyParams(); + const setTargetReply = useTargetReplyStore((state) => state.setTargetReply); + + useEffect(() => { + if ( + notificationTargetRereplyId && + parentReplyId === notificationTargetReplyId + ) { + setIsOpen(true); + setTargetReply({ targetRereplyId: notificationTargetRereplyId }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [notificationTargetReplyId, notificationTargetRereplyId, parentReplyId]); + + const toggleRereplyListHandler = () => { + setTargetReply({ targetReplyId: null, targetRereplyId: null }); + setIsOpen(true); + }; + + return ( + <section> + <div> + <div className="flex justify-between mb-2 pt-3 px-5"> + <div className="font-semibold text-gray-500">๋Œ€๋Œ“๊ธ€</div> + <button + className="cursor-pointer text-gray-500 text-sm" + onClick={() => setIsOpen((prev) => !prev)} + > + {isOpen ? '์ ‘๊ธฐ' : '๋ณด๊ธฐ'} + </button> + </div> + {isOpen && <RereplyList parentReplyId={parentReplyId} />} + </div> + <RereplyFormToggle + parentReplyId={parentReplyId} + openRereplyList={toggleRereplyListHandler} + isOpenRereplyList={isOpen} + /> + </section> + ); +}; diff --git a/src/features/reply/hooks/useReplyScrollIntoView.ts b/src/features/reply/hooks/useReplyScrollIntoView.ts new file mode 100644 index 00000000..89f7d01f --- /dev/null +++ b/src/features/reply/hooks/useReplyScrollIntoView.ts @@ -0,0 +1,104 @@ +import { useTargetReplyStore } from '@/stores/useTargetReply'; +import { Reply } from '@/types'; +import { Page } from '@/utils/flattenPages'; +import type { InfiniteData } from '@tanstack/react-query'; +import { usePathname, useSearchParams } from 'next/navigation'; +import { useEffect, useRef } from 'react'; +import { toast } from 'sonner'; + +interface UseReplyScrollIntoViewProps { + data: InfiniteData<Page<Reply>>; + replyType: 'reply' | 'rereply'; + hasNextPage: boolean; +} + +/** + * useTargetReplyStore์— ์ €์žฅ๋œ ๋ชฉํ‘œ ๋Œ“๊ธ€๋กœ ์Šคํฌ๋กค ์ด๋™์‹œํ‚ค๊ธฐ ์œ„ํ•œ ์ปค์Šคํ…€ ํ›… + * + * @param data - useInfiniteQuery๋กœ ๊ฐ€์ ธ์˜จ ํŽ˜์ด์ง€๋„ค์ด์…˜๋œ ๋Œ“๊ธ€ ๋ฐ์ดํ„ฐ + * @param replyType - ๋ชฉํ‘œ๋ฌผ์ด ๋Œ“๊ธ€์ธ์ง€ ๋Œ€๋Œ“๊ธ€์ธ์ง€ ํƒ€์ž… + * @param hasNextPage - ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋Š”์ง€ ์—ฌ๋ถ€ + * + * @returns itemRefs - ๋Œ“๊ธ€ ๊ฐ๊ฐ์— ๋Œ€์‘๋˜๋Š” ref ๊ฐ์ฒด + * @returns bottomRef - ์š”์†Œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์Šคํฌ๋กคํ•  fallback ์œ„์น˜ + */ +export const useReplyScrollIntoView = ({ + data, + replyType, + hasNextPage, +}: UseReplyScrollIntoViewProps) => { + const itemRefs = useRef<Record<number, HTMLLIElement | null>>({}); + const bottomRef = useRef<HTMLDivElement | null>(null); + const { targetReplyId, targetRereplyId, setTargetReply } = + useTargetReplyStore(); + + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const hasSetToastRef = useRef<boolean>(false); + + const getTargetId = (): number | undefined => { + if (replyType === 'reply') return targetReplyId ?? undefined; + if (replyType === 'rereply') return targetRereplyId ?? undefined; + }; + + const clearTargetQueryAndState = () => { + const params = new URLSearchParams(searchParams.toString()); + + if (replyType === 'reply') { + params.delete('replyId'); + setTargetReply({ targetReplyId: null }); + } else { + params.delete('replyId'); + params.delete('rereplyId'); + setTargetReply({ targetRereplyId: null }); + } + + const query = params.toString(); + const newUrl = query ? `${pathname}?${query}` : pathname; + window.history.replaceState(null, '', newUrl); + }; + + const scrollToElement = (element: HTMLElement | null) => { + element?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }; + + const showReplyNotFoundToast = () => { + const params = new URLSearchParams(searchParams.toString()); + + if (params.has('replyId')) { + toast.error('์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋Œ“๊ธ€์ž…๋‹ˆ๋‹ค.'); + } else if (params.has('rereplyId')) { + toast.error('์‚ญ์ œ๋œ ๋Œ€๋Œ“๊ธ€์ž…๋‹ˆ๋‹ค.'); + } + + clearTargetQueryAndState(); + setTargetReply({ targetReplyId: null, targetRereplyId: null }); + }; + + useEffect(() => { + const targetId = getTargetId(); + if (!targetId) return; + + const targetElement = itemRefs.current[targetId]; + + if (targetElement) { + scrollToElement(targetElement); + clearTargetQueryAndState(); + } else { + scrollToElement(bottomRef.current); + + if (!hasNextPage && !hasSetToastRef.current) { + hasSetToastRef.current = true; + setTimeout(showReplyNotFoundToast, 1000); + } + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [targetReplyId, targetRereplyId, data]); + + return { + itemRefs, + bottomRef, + }; +}; diff --git a/src/features/reply/hooks/useTargetReplyParams .ts b/src/features/reply/hooks/useTargetReplyParams .ts new file mode 100644 index 00000000..6978ffe2 --- /dev/null +++ b/src/features/reply/hooks/useTargetReplyParams .ts @@ -0,0 +1,18 @@ +import { useSearchParams } from 'next/navigation'; + +export const useTargetReplyParams = () => { + const searchParams = useSearchParams(); + + const replyIdParam = searchParams.get('replyId'); + const replyId = + typeof replyIdParam === 'string' ? Number(replyIdParam) : null; + + const rereplyIdParam = searchParams.get('rereplyId'); + const rereplyId = + typeof rereplyIdParam === 'string' ? Number(rereplyIdParam) : null; + + return { + notificationTargetReplyId: replyId, + notificationTargetRereplyId: rereplyId, + }; +}; diff --git a/src/features/user/components/account-settings-dialog.tsx b/src/features/user/components/account-settings-dialog.tsx new file mode 100644 index 00000000..175cbcca --- /dev/null +++ b/src/features/user/components/account-settings-dialog.tsx @@ -0,0 +1,53 @@ +'use client'; + +import { useState } from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { ChangePasswordForm } from './account-settings/change-password-form'; + +/** + * ๊ณ„์ • ์„ค์ • ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ + * + * ๊ณ„์ • ์„ค์ • ๋ชจ๋‹ฌ์—์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค. + * + * @returns ๊ณ„์ • ์„ค์ • ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ + */ +export const AccountSettingsDialog = () => { + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const closeDialog = () => { + setIsDialogOpen(false); + }; + + return ( + <> + <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> + <DialogTrigger asChild> + <button + type="button" + className="inline-flex items-center gap-x-1 text-sm font-medium text-gray-500 cursor-pointer" + > + ๊ณ„์ • ์„ค์ • + </button> + </DialogTrigger> + <DialogContent className="sm:max-w-[425px]"> + <DialogHeader> + <DialogTitle>๊ณ„์ • ์„ค์ •</DialogTitle> + <DialogDescription> + ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + </DialogDescription> + </DialogHeader> + <div className="flex flex-col divide-y-1 divide-gray-200"> + <ChangePasswordForm closeDialog={closeDialog} /> + </div> + </DialogContent> + </Dialog> + </> + ); +}; diff --git a/src/features/user/components/account-settings/change-password-form.tsx b/src/features/user/components/account-settings/change-password-form.tsx new file mode 100644 index 00000000..43cec2cd --- /dev/null +++ b/src/features/user/components/account-settings/change-password-form.tsx @@ -0,0 +1,87 @@ +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Button } from '@/components/ui/button'; +import { Form } from '@/components/ui/form'; +import { InputTextField } from '@/components/molecules/input-text-field'; +import { useChangePassword } from '@/features/user/hooks/useChangePassword'; + +const schema = z.object({ + newPassword: z + .string() + .nonempty({ message: '์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”' }) + .regex(/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[\W_]).{8,}$/, { + message: + '์˜์–ด ๋Œ€/์†Œ๋ฌธ์ž, ์ˆซ์ž, ํŠน์ˆ˜๋ฌธ์ž๋ฅผ ํ˜ผํ•ฉํ•˜์—ฌ 8์ž๋ฆฌ ์ด์ƒ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.', + }), + currentPassword: z + .string() + .nonempty({ message: '๊ธฐ์กด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”' }), +}); + +type ChangePasswordFormProps = { + closeDialog: () => void; +}; + +/** + * ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ํผ ์ปดํฌ๋„ŒํŠธ + * + * @param closeDialog ๋ชจ๋‹ฌ ๋‹ซ๋Š” ํ•จ์ˆ˜ + * @returns ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ํผ ์ปดํฌ๋„ŒํŠธ + */ +export const ChangePasswordForm = ({ + closeDialog, +}: ChangePasswordFormProps) => { + const formMethods = useForm<z.infer<typeof schema>>({ + resolver: zodResolver(schema), + defaultValues: { + newPassword: '', + currentPassword: '', + }, + }); + + const { mutateAsync: changePassword } = useChangePassword(); + + const onSubmit = async (data: z.infer<typeof schema>) => { + try { + await changePassword({ + newPassword: data.newPassword, + currentPassword: data.currentPassword, + }); + closeDialog(); + } catch (error) { + console.error(error); + } + }; + + return ( + <Form {...formMethods}> + <form + className="flex flex-col gap-y-4" + onSubmit={formMethods.handleSubmit(onSubmit)} + > + <InputTextField + label="๊ธฐ์กด ๋น„๋ฐ€๋ฒˆํ˜ธ" + name="currentPassword" + form={formMethods} + type="password" + /> + + <InputTextField + label="์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ" + name="newPassword" + form={formMethods} + type="password" + /> + + <Button + className="self-end" + type="submit" + disabled={formMethods.formState.isSubmitting} + > + ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ + </Button> + </form> + </Form> + ); +}; diff --git a/src/features/user/components/current-user-profile.tsx b/src/features/user/components/current-user-profile.tsx new file mode 100644 index 00000000..dcf833d0 --- /dev/null +++ b/src/features/user/components/current-user-profile.tsx @@ -0,0 +1,82 @@ +'use client'; + +import { Avatar } from '@/components/atoms/avatar'; +import useAuthStore from '@/stores/useAuthStore'; +import { getSkill } from '@/types/enums'; +import { EditUserProfileDialog } from '@/features/user/components/edit-user-profile-dialog'; +import { AccountSettingsDialog } from '@/features/user/components/account-settings-dialog'; +import { WithdrawDialog } from '@/features/user/components/withdraw-dialog'; +import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback'; +import { Separator } from '@/components/ui/separator'; + +/** + * ํ˜„์žฌ ๋กœ๊ทธ์ธ ํ•œ ์œ ์ €์˜ ํ”„๋กœํ•„ ์ปดํฌ๋„ŒํŠธ + * + * ํ˜„์žฌ ๋กœ๊ทธ์ธ ํ•œ ์œ ์ €์˜ ํ”„๋กœํ•„์„ ๋ณด์—ฌ์ค€๋‹ค. + * + * @returns ํ˜„์žฌ ๋กœ๊ทธ์ธ ํ•œ ์œ ์ €์˜ ํ”„๋กœํ•„ ์ปดํฌ๋„ŒํŠธ + */ + +export const CurrentUserProfile = () => { + const user = useAuthStore((state) => state.user); + + if (!user) { + return <div>๋‚˜์˜ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์–ด์š”. ๋‹ค์‹œ ๋กœ๊ทธ์ธ ํ•ด์ฃผ์„ธ์š”.</div>; + } + + const { nickname, email, profileImage, skills } = user; + + return ( + <> + <div className="flex absolute top-4 left-6 right-6 gap-x-3"> + <div className="flex flex-col items-center gap-y-4"> + <Avatar + className="size-[4.75rem]" + imageSrc={getDisplayProfileImage(profileImage)} + fallback={getDisplayNickname(nickname, email)} + /> + <EditUserProfileDialog /> + </div> + <div className="flex flex-col gap-y-9 mt-4 flex-1 min-w-0"> + <div className="h-[28px] flex items-center"> + <span className="font-semibold"> + {getDisplayNickname(nickname, email)} + </span> + </div> + <div className="flex flex-col gap-y-1 min-w-0"> + <div className="flex gap-x-2 min-w-0"> + <span className="text-sm font-medium shrink-0">๊ธฐ์ˆ ์Šคํƒ</span> + {skills && skills.length === 0 ? ( + <p className="text-gray-700 text-sm"> + ์„ค์ •๋œ ๊ธฐ์ˆ ์Šคํƒ์ด ์—†์–ด์š”. + </p> + ) : ( + <ul className="flex gap-x-3 overflow-x-auto flex-nowrap flex-1 min-w-0 scrollbar-hide"> + {skills?.map((skill) => ( + <li + className="text-sm font-normal text-gray-700 whitespace-nowrap shrink-0 relative after:content-[''] after:absolute after:right-[-0.375rem] after:top-1/2 after:-translate-y-1/2 after:w-[1px] after:h-3 after:bg-gray-300 last:after:hidden" + key={skill} + > + {getSkill(skill)} + </li> + ))} + </ul> + )} + </div> + <div className="flex gap-x-1.5"> + <span className="text-sm font-medium">์ด๋ฉ”์ผ</span> + <span className="text-sm font-normal text-gray-700 line-clamp-1"> + {email} + </span> + </div> + <div className="flex gap-x-1.5 justify-end mr-3"> + <AccountSettingsDialog /> + <Separator orientation="vertical" /> + <WithdrawDialog /> + </div> + </div> + </div> + </div> + </> + ); +}; diff --git a/src/features/user/components/edit-user-profile-dialog.tsx b/src/features/user/components/edit-user-profile-dialog.tsx new file mode 100644 index 00000000..8d6dbeea --- /dev/null +++ b/src/features/user/components/edit-user-profile-dialog.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { useState } from 'react'; +import Image from 'next/image'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { EditUserProfileForm } from '@/features/user/components/edit-user-profile-form/edit-user-profile-form'; + +/** + * ํ”„๋กœํ•„ ์ˆ˜์ • ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ + * + * @returns ํ”„๋กœํ•„ ์ˆ˜์ • ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ + */ + +export const EditUserProfileDialog = () => { + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const closeDialog = () => { + setIsDialogOpen(false); + }; + + return ( + <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> + <DialogTrigger asChild> + <button type="button"> + <Image src="/icons/btn_edit.svg" alt="edit" width={32} height={32} /> + </button> + </DialogTrigger> + <DialogContent className="max-w-[350px]! md:max-w-[450px]! p-0"> + <DialogHeader> + <DialogTitle className='text-center pt-4'>ํ”„๋กœํ•„ ์ˆ˜์ •ํ•˜๊ธฐ</DialogTitle> + </DialogHeader> + <div className={'flex flex-col gap-y-4 mt-4'}> + <EditUserProfileForm closeDialog={closeDialog} /> + </div> + </DialogContent> + </Dialog> + ); +}; diff --git a/src/features/user/components/edit-user-profile-form/edit-user-profile-form.tsx b/src/features/user/components/edit-user-profile-form/edit-user-profile-form.tsx new file mode 100644 index 00000000..af590a02 --- /dev/null +++ b/src/features/user/components/edit-user-profile-form/edit-user-profile-form.tsx @@ -0,0 +1,126 @@ +'use client'; + +import { FormProvider, useForm, SubmitHandler } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { EditableAvatar } from '@/features/user/components/edit-user-profile-form/editable-avatar'; +import { Form } from '@/components/ui/form'; +import { InputTextField } from '@/components/molecules/input-text-field'; +import { InputSelectField } from '@/components/molecules/input-select-field'; +import { SkillSelector } from '@/features/user/components/edit-user-profile-form/skill-selector'; +import { Button } from '@/components/ui/button'; +import { Position, Skill } from '@/types/enums'; +import useAuthStore from '@/stores/useAuthStore'; +import { useUpdateProfileMutation } from '@/features/user/hooks/useUpdateProfileMutation'; +import { getDisplayProfileImage, getDisplayNickname } from '@/utils/fallback'; + +const schema = z.object({ + nickname: z.string().nonempty('๋‹‰๋„ค์ž„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.'), + profileImageFile: z.custom<File>().nullable(), + position: z.string().nullable(), + skills: z.array(z.nativeEnum(Skill)), +}); + +type EditUserProfileFormProps = { + closeDialog: () => void; +}; + +export type FormData = z.infer<typeof schema>; + +/** + * ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์ˆ˜์ • ํผ + * + * @param closeDialog ๋ชจ๋‹ฌ ๋‹ซ๋Š” ํ•จ์ˆ˜ + * @returns ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์ˆ˜์ • ํผ ์ปดํฌ๋„ŒํŠธ + */ +export const EditUserProfileForm = ({ + closeDialog, +}: EditUserProfileFormProps) => { + const user = useAuthStore((state) => state.user); + + const formMethods = useForm({ + resolver: zodResolver(schema), + defaultValues: { + nickname: user?.nickname ?? user?.email.split('@')[0], + profileImageFile: null, + position: user?.position ? String(user.position) : null, + skills: user?.skills ?? [], + }, + }); + + const { mutateAsync: updateProfile } = useUpdateProfileMutation(); + const formSubmitHandler: SubmitHandler<FormData> = async (data) => { + const { nickname, position, skills, profileImageFile } = data; + + try { + await updateProfile({ + nickname, + position: Number(position), + skills, + ...(profileImageFile && { file: profileImageFile }), + }); + closeDialog(); + } catch (error) { + console.error(error); + } + }; + + return ( + <FormProvider {...formMethods}> + <Form {...formMethods}> + <form + className="flex flex-col gap-y-7" + onSubmit={formMethods.handleSubmit(formSubmitHandler)} + > + <div className="flex gap-x-6 px-6"> + <EditableAvatar + imageSrc={getDisplayProfileImage(user?.profileImage ?? null)} + fallback={getDisplayNickname( + user?.nickname ?? '', + user?.email ?? '', + )} + /> + <div className="flex flex-col gap-y-[20px] flex-grow min-w-0"> + <InputTextField + label="๋‹‰๋„ค์ž„" + name="nickname" + form={formMethods} + placeholder="์ˆ˜์ •ํ•  ๋‹‰๋„ค์ž„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”." + /> + <InputSelectField + label="ํฌ์ง€์…˜" + name="position" + form={formMethods} + placeholder="ํฌ์ง€์…˜ ์„ ํƒ" + options={Object.entries(Position) + .filter(([key]) => isNaN(Number(key))) + .map(([key, value]) => ({ + label: key, + value: String(value), + }))} + selectTriggerClassName="bg-gray-50 w-full border-none" + /> + </div> + </div> + <SkillSelector /> + <div className="flex gap-x-2 justify-end border-t-[6px] border-[#f5f6f7] py-[20px] px-6"> + <Button + className="w-1/2 shrink-0 h-12 text-sm rounded-lg font-semibold border-gray-300 text-gray-600" + type="button" + onClick={closeDialog} + variant="outline" + > + ์ทจ์†Œํ•˜๊ธฐ + </Button> + <Button + className="w-1/2 shrink-0 h-12 text-sm rounded-lg font-semibold" + disabled={formMethods.formState.isSubmitting} + > + ์ˆ˜์ •ํ•˜๊ธฐ + </Button> + </div> + </form> + </Form> + </FormProvider> + ); +}; diff --git a/src/features/user/components/edit-user-profile-form/editable-avatar.tsx b/src/features/user/components/edit-user-profile-form/editable-avatar.tsx new file mode 100644 index 00000000..2eab7a91 --- /dev/null +++ b/src/features/user/components/edit-user-profile-form/editable-avatar.tsx @@ -0,0 +1,117 @@ +'use client'; + +import { Avatar } from '@/components/atoms/avatar'; +import { Button } from '@/components/ui/button'; +import { validateImageFile } from '@/features/user/utils/validateImageFile'; +import { useRef, useState } from 'react'; +import { toast } from 'sonner'; +import { useFormContext } from 'react-hook-form'; +import { type FormData } from '@/features/user/components/edit-user-profile-form/edit-user-profile-form'; +import { request } from '@/api/request'; + +type EditableAvatarProps = { + imageSrc: string; + fallback: string; +}; + +/** + * ์ˆ˜์ • ๊ฐ€๋Šฅํ•œ ์•„๋ฐ”ํƒ€ ์ปดํฌ๋„ŒํŠธ + * + * @param imageSrc ์ด๋ฏธ์ง€ ์ฃผ์†Œ(๋ฌธ์ž์—ด) + * @param fallback ์•„๋ฐ”ํƒ€ ํด๋ฐฑ ๋ฌธ์ž์—ด(๋ฌธ์ž์—ด) + * @returns ์ˆ˜์ • ๊ฐ€๋Šฅํ•œ ์•„๋ฐ”ํƒ€ ์ปดํฌ๋„ŒํŠธ + */ +export const EditableAvatar = ({ imageSrc, fallback }: EditableAvatarProps) => { + const { register, setValue } = useFormContext<FormData>(); + + const { ref: registerRef, ...rest } = register('profileImageFile'); + + const [currentImageSrc, setCurrentImageSrc] = useState(imageSrc); + const fileInputRef = useRef<HTMLInputElement>(null); + + /** + * ํŒŒ์ผ์ด ๋ณ€๊ฒฝ๋  ๋•Œ ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜ + * + * ์œ ํšจํ•œ ์ด๋ฏธ์ง€ ํŒŒ์ผ์ธ ๊ฒฝ์šฐ, ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€ ์ฃผ์†Œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ , ํŒŒ์ผ์„ ํผ ๋ฐ์ดํ„ฐ์— ์ถ”๊ฐ€ํ•œ๋‹ค. + * ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ, ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œ. + * + * @param e ํŒŒ์ผ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ + */ + const fileChangeHandler: React.ChangeEventHandler<HTMLInputElement> = (e) => { + const file = e.target.files?.[0]; + if (file) { + const { isValid, errorMessage } = validateImageFile(file); + if (isValid) { + setCurrentImageSrc(URL.createObjectURL(file)); + setValue('profileImageFile', file); + } else { + toast.error(errorMessage); + } + } + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + + /** + * ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ œ๊ฑฐ ๋ฒ„ํŠผ ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ + * + * ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , ์ˆ˜์ • ํผ์—์„œ ๋ณด์ด๋Š” ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ๋ฅผ ์—…๋ฐ์ดํŠธํ•œ๋‹ค. + * ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ œ๊ฑฐ ์š”์ฒญ์ด ์‹คํŒจํ•˜๋ฉด, ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•œ๋‹ค. + */ + const removeProfileImgButtonClickHandler = async () => { + try { + await request.delete('/v1/user/profile-image-delete', { + credentials: 'include', + }); + setCurrentImageSrc('/images/default-profile.png'); + setValue('profileImageFile', null); + } catch { + toast.error('ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์‚ญ์ œ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.', { + description: '์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.', + }); + } + }; + + return ( + <div className={'relative'}> + <div className="flex flex-col gap-y-3 items-center"> + <Avatar + imageSrc={currentImageSrc} + fallback={fallback} + className={'size-20'} + /> + <div className="flex flex-col gap-y-2"> + <Button + type="button" + onClick={() => { + fileInputRef.current?.click(); + }} + className="h-8 rounded-[6px] text-sm font-medium bg-white text-gray-500 border cursor-pointer border-gray-300 shadow-none hover:bg-gray-50" + > + ์‚ฌ์ง„ ์—…๋กœ๋“œ + </Button> + <button + type="button" + onClick={removeProfileImgButtonClickHandler} + className="h-8 text-sm font-medium bg-white text-gray-400 shadow-none hover:bg-none cursor-pointer" + > + ์‚ฌ์ง„ ์ œ๊ฑฐ + </button> + </div> + </div> + <input + type={'file'} + accept={'image/*'} + className={'absolute inset-0 w-full h-full hidden'} + multiple={false} + {...rest} + ref={(e) => { + registerRef(e); + fileInputRef.current = e; + }} + onChange={fileChangeHandler} + /> + </div> + ); +}; diff --git a/src/features/user/components/edit-user-profile-form/skill-selector.tsx b/src/features/user/components/edit-user-profile-form/skill-selector.tsx new file mode 100644 index 00000000..69c45c6e --- /dev/null +++ b/src/features/user/components/edit-user-profile-form/skill-selector.tsx @@ -0,0 +1,48 @@ +'use client'; + +import { useController, useFormContext } from 'react-hook-form'; +import { Skill } from '@/types/enums'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { type FormData } from '@/features/user/components/edit-user-profile-form/edit-user-profile-form'; + +export const SkillSelector = () => { + const { control } = useFormContext<FormData>(); + + const { field } = useController({ + control, + name: 'skills', + }); + + const itemClickHandler = (enumValue: Skill) => { + if (field.value.includes(enumValue)) { + field.onChange(field.value.filter((skill) => skill !== enumValue)); + return; + } + field.onChange([...field.value, enumValue]); + }; + + return ( + <div className="flex flex-col gap-y-2 px-6"> + <Label className='font-medium text-gray-900 text-lg'>Skills</Label> + <p className='text-sm font-medium text-gray-500'>์‚ฌ์šฉ ์ค‘์ธ ๊ธฐ์ˆ  ํƒœ๊ทธ๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”.</p> + <ul className="flex flex-wrap gap-2 items-center"> + {Object.entries(Skill) + .filter(([key]) => isNaN(Number(key))) + .map(([key, value]) => ( + <li key={key}> + <Button + variant={ + field.value.includes(Number(value)) ? 'default' : 'outline' + } + onClick={() => itemClickHandler(Number(value))} + type="button" + > + {key} + </Button> + </li> + ))} + </ul> + </div> + ); +}; diff --git a/src/features/user/components/other-user-profile.tsx b/src/features/user/components/other-user-profile.tsx new file mode 100644 index 00000000..1ec2fd5e --- /dev/null +++ b/src/features/user/components/other-user-profile.tsx @@ -0,0 +1,115 @@ +'use client'; + +import { request } from '@/api/request'; +import { Avatar } from '@/components/atoms/avatar'; +import { UserProfileLoading } from '@/features/user/components/user-profile-loading'; +import { ToggleFollowButton } from '@/features/user/follow/components/toggle-follow-button'; +import useAuthStore from '@/stores/useAuthStore'; +import { User } from '@/types'; +import { getSkill } from '@/types/enums'; +import { CommonResponse } from '@/types/response'; +import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback'; +import { useQuery } from '@tanstack/react-query'; +import { notFound, useParams } from 'next/navigation'; + +/** + * ํ˜„์žฌ ๋กœ๊ทธ์ธ ํ•œ ์œ ์ €๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ์œ ์ €์˜ ํ”„๋กœํ•„ ์ปดํฌ๋„ŒํŠธ + * + * ๋‹ค๋ฅธ ์œ ์ €์˜ ํ”„๋กœํ•„์„ ๋ณด์—ฌ์ค€๋‹ค. + * + * @returns ํ˜„์žฌ ๋กœ๊ทธ์ธ ํ•œ ์œ ์ €๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ์œ ์ €์˜ ํ”„๋กœํ•„ ์ปดํฌ๋„ŒํŠธ + */ +export const OtherUserProfile = () => { + const { id } = useParams(); + + const currentUser = useAuthStore((state) => state.user); + + const { + data: userResponse, + isLoading, + isError, + } = useQuery<CommonResponse<User>>({ + queryKey: ['user', id], + queryFn: () => + request.get( + `/v1/user/${id}`, + {}, + { + credentials: 'include', + }, + ), + staleTime: 0, + }); + + const user = userResponse?.data; + + if (isLoading) return <UserProfileLoading />; + if (isError) return <div>Error</div>; + + // ์œ ์ €๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด, 404 Not Found ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ๋‹ค. + if (!user) notFound(); + + const { nickname, email, profileImage, skills } = user; + + // @ts-expect-error ํŠน์ • ์œ ์ € ์ •๋ณด ์กฐํšŒ์˜ ๊ฒฝ์šฐ ๋ฐฑ์—”๋“œ์—์„œ ํŒ”๋กœ์ž‰ ์—ฌ๋ถ€๋ฅผ ์ฃผ์ง€ ์•Š์Œ. + const isFollowing = user?.followers.some( + // @ts-expect-error ํŠน์ • ์œ ์ € ์ •๋ณด ์กฐํšŒ์˜ ๊ฒฝ์šฐ ๋ฐฑ์—”๋“œ์—์„œ ํŒ”๋กœ์ž‰ ์—ฌ๋ถ€๋ฅผ ์ฃผ์ง€ ์•Š์Œ. + (follower) => follower.id === currentUser?.id, + ); + + return ( + <> + <div className="flex absolute top-4 left-6 right-6 gap-x-3"> + <div className="flex flex-col items-center gap-y-4"> + <Avatar + className="size-[4.75rem]" + imageSrc={getDisplayProfileImage(profileImage)} + fallback={getDisplayNickname(nickname, email)} + /> + </div> + <div className="flex flex-col gap-y-9 mt-4 flex-1 min-w-0"> + <div className="flex items-center justify-between md:gap-x-5 md:justify-start flex-1"> + <span className="font-semibold truncate"> + {getDisplayNickname(nickname, email)} + </span> + <ToggleFollowButton + isFollowing={isFollowing} + usedIn="profile" + className={`${ + isFollowing + ? 'bg-red-600 hover:bg-red-700' + : 'bg-black hover:bg-black/70' + } text-white h-[28px] text-sm font-semibold rounded-lg py-1 px-3 gap-x-[6px]`} + /> + </div> + <div className="flex flex-col gap-y-1 min-w-0"> + <div className="flex gap-x-2 min-w-0"> + <span className="text-sm font-medium shrink-0">๊ธฐ์ˆ ์Šคํƒ</span> + <ul className="flex gap-x-3 overflow-x-auto flex-nowrap flex-1 min-w-0 scrollbar-hide"> + {skills?.length === 0 && ( + <p className="text-gray-700 text-sm"> + ์„ค์ •๋œ ๊ธฐ์ˆ ์Šคํƒ์ด ์—†์–ด์š”. + </p> + )} + {skills?.map((skill) => ( + <li + className="text-sm font-normal text-gray-700 whitespace-nowrap shrink-0 relative after:content-[''] after:absolute after:right-[-0.375rem] after:top-1/2 after:-translate-y-1/2 after:w-[1px] after:h-3 after:bg-gray-300 last:after:hidden" + key={skill} + > + {getSkill(skill)} + </li> + ))} + </ul> + </div> + <div className="flex gap-x-1.5"> + <span className="text-sm font-medium min-w-fit">์ด๋ฉ”์ผ</span> + <span className="text-sm font-normal text-gray-700 truncate"> + {email} + </span> + </div> + </div> + </div> + </div> + </> + ); +}; diff --git a/src/features/user/components/user-page-tabs.tsx b/src/features/user/components/user-page-tabs.tsx new file mode 100644 index 00000000..f6ae2646 --- /dev/null +++ b/src/features/user/components/user-page-tabs.tsx @@ -0,0 +1,42 @@ +'use client'; + +import { useParams } from 'next/navigation'; +import { usePathname } from 'next/navigation'; + +import Link from 'next/link'; + +const getIsActive = (pathname: string, href: string) => { + if (pathname.includes('social') && href.includes('social')) { + return true; + } + + return pathname === href; +}; + +export const UserPageTabs = () => { + const { id } = useParams<{ id: string }>(); + + const pathname = usePathname(); + + return ( + <ul className="flex gap-3 px-4 w-full overflow-x-auto scrollbar-hide"> + {[ + { label: 'ํ™ˆ', href: `/users/${id}` }, + { label: '์†Œ์…œ', href: `/users/${id}/social/followings` }, + { label: '๊ฐœ์„คํ•œ ๋ชจ์ž„', href: `/users/${id}/groups/created` }, + { label: '์ข…๋ฃŒ๋œ ๋ชจ์ž„', href: `/users/${id}/groups/ended` }, + ].map(({ label, href }) => { + return ( + <li className="relative shrink-0" key={label}> + <Link + className={`text-lg font-semibold ${getIsActive(pathname, href) ? 'after:content-[""] after:absolute after:-bottom-1 after:left-0 after:w-full after:h-[2px] after:bg-gray-900' : 'text-gray-400'}`} + href={href} + > + {label} + </Link> + </li> + ); + })} + </ul> + ); +}; diff --git a/src/features/user/components/user-profile-loading.tsx b/src/features/user/components/user-profile-loading.tsx new file mode 100644 index 00000000..e5582cec --- /dev/null +++ b/src/features/user/components/user-profile-loading.tsx @@ -0,0 +1,21 @@ +import { Skeleton } from '@/components/ui/skeleton'; + +export const UserProfileLoading = () => { + return ( + <div className="flex absolute top-4 left-6 right-6 gap-x-3"> + <div className="flex flex-col items-center"> + <Skeleton className="size-[4.75rem] rounded-full" /> + </div> + <div className="flex flex-col gap-y-9 mt-4 flex-1 min-w-0"> + <div className="flex items-center justify-between md:gap-x-5 md:justify-start"> + <Skeleton className="h-5 w-20" /> + <Skeleton className="h-7 w-23" /> + </div> + <div className='flex flex-col gap-y-1'> + <Skeleton className='h-4 w-40'/> + <Skeleton className='h-4 w-35'/> + </div> + </div> + </div> + ); +}; diff --git a/src/features/user/components/user-profile.tsx b/src/features/user/components/user-profile.tsx new file mode 100644 index 00000000..da13a1dd --- /dev/null +++ b/src/features/user/components/user-profile.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { useParams } from 'next/navigation'; +import useAuthStore from '@/stores/useAuthStore'; +import { CurrentUserProfile } from '@/features/user/components/current-user-profile'; +import { OtherUserProfile } from '@/features/user/components/other-user-profile'; + +/** + * ์œ ์ € ํ”„๋กœํ•„ ์ปดํฌ๋„ŒํŠธ + * + * ๋งŒ์•ฝ ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ id์™€ id ์„ธ๊ทธ๋จผํŠธ ๊ฐ’์ด ๊ฐ™๋‹ค๋ฉด, CurrentUserProfile ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. + * ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด, OtherUserProfile ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. + */ +export const UserProfile = () => { + const { id } = useParams<{ id: string }>(); + const user = useAuthStore((state) => state.user); + + return ( + <div className='relative h-[168px] bg-white rounded-t-2xl'> + <div className='rounded-t-2xl h-[73px] bg-green-300'/> + {/* @ts-expect-error ๋ฐฑ์—”๋“œ์—์„œ ์ฃผ๋Š” ์‚ฌ์šฉ์ž ์•„์ด๋”” ํ”„๋กœํผํ‹ฐ๊ฐ€ userId๊ฐ€ ์•„๋‹Œ id์—ฌ์„œ ์ผ๋‹จ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณ€๊ฒฝ */} + {String(user?.id) === id ? <CurrentUserProfile /> : <OtherUserProfile />} + </div> + ); +}; diff --git a/src/features/user/components/withdraw-dialog.tsx b/src/features/user/components/withdraw-dialog.tsx new file mode 100644 index 00000000..eefb8b2d --- /dev/null +++ b/src/features/user/components/withdraw-dialog.tsx @@ -0,0 +1,74 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { toast } from 'sonner'; +import { + AlertDialog, + AlertDialogTrigger, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; +import { request } from '@/api/request'; +import useAuthStore from '@/stores/useAuthStore'; + +/** + * ํšŒ์› ํƒˆํ‡ด ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ + * + * @returns ํšŒ์› ํƒˆํ‡ด ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ + */ +export const WithdrawDialog = () => { + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const router = useRouter(); + const clearUser = useAuthStore((state) => state.clearUser); + + const withdrawButtonClickHandler = async () => { + try { + await request.delete('/v1/user/delete', { + credentials: 'include', + }); + toast.success('ํšŒ์› ํƒˆํ‡ด ์™„๋ฃŒ', { + description: 'ํšŒ์› ํƒˆํ‡ด๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.', + }); + clearUser(); + router.push('/'); + } catch (error) { + console.error(error); + toast.error('ํšŒ์› ํƒˆํ‡ด ์‹คํŒจ', { + description: 'ํšŒ์› ํƒˆํ‡ด์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.', + }); + } + }; + + return ( + <> + <AlertDialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> + <AlertDialogTrigger asChild> + <button type="button" className="text-sm font-medium cursor-pointer text-gray-500 underline underline-offset-3"> + ํšŒ์› ํƒˆํ‡ด + </button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle>์ •๋ง๋กœ ํƒˆํ‡ดํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?</AlertDialogTitle> + <AlertDialogDescription> + ํšŒ์› ํƒˆํ‡ด ํ›„ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel className='border-gray-300 text-gray-600'>์ทจ์†Œ</AlertDialogCancel> + <AlertDialogAction onClick={withdrawButtonClickHandler}> + ํƒˆํ‡ด + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + </> + ); +}; diff --git a/src/features/user/follow/components/follow-list-items-loading.tsx b/src/features/user/follow/components/follow-list-items-loading.tsx new file mode 100644 index 00000000..9221e335 --- /dev/null +++ b/src/features/user/follow/components/follow-list-items-loading.tsx @@ -0,0 +1,29 @@ +'use client'; + +import { Skeleton } from '@/components/ui/skeleton'; + +type FollowListItemsLoadingProps = { + itemCount?: number; +}; + +export default function FollowListItemsLoading({ + itemCount = 6, +}: FollowListItemsLoadingProps) { + return ( + <> + {Array.from({ length: itemCount }).map((_, i) => ( + <li key={i} className="pb-6"> + <div className="flex justify-between"> + <div className="flex gap-x-6 items-start"> + <Skeleton className="size-[4.75rem] rounded-full" /> + <div className="flex flex-col justify-start gap-y-2"> + <Skeleton className="w-42 h-4" /> + <Skeleton className="w-28 h-3" /> + </div> + </div> + </div> + </li> + ))} + </> + ); +} diff --git a/src/features/user/follow/components/follow-tabs.tsx b/src/features/user/follow/components/follow-tabs.tsx new file mode 100644 index 00000000..697f507f --- /dev/null +++ b/src/features/user/follow/components/follow-tabs.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { useParams, usePathname } from 'next/navigation'; + +import Link from 'next/link'; + +export const FollowTabs = () => { + const { id } = useParams<{ id: string }>(); + + const pathname = usePathname(); + + const type = pathname.split('/').at(-1); + + return ( + <ul className="flex gap-3"> + {[ + { + label: 'ํŒ”๋กœ์ž‰', + value: 'followings', + href: `/users/${id}/social/followings`, + }, + { + label: 'ํŒ”๋กœ์›Œ', + value: 'followers', + href: `/users/${id}/social/followers`, + }, + ].map(({ label, value, href }) => ( + <li className="relative" key={label}> + <Link + className={`font-semibold ${type === value ? 'text-gray-900' : 'text-gray-400'} transition-all`} + href={href} + > + {label} + </Link> + </li> + ))} + </ul> + ); +}; diff --git a/src/features/user/follow/components/followers-list.tsx b/src/features/user/follow/components/followers-list.tsx new file mode 100644 index 00000000..d0ca3070 --- /dev/null +++ b/src/features/user/follow/components/followers-list.tsx @@ -0,0 +1,147 @@ +'use client'; + +import { Avatar } from '@/components/atoms/avatar'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import FollowListItemsLoading from '@/features/user/follow/components/follow-list-items-loading'; +import { RemoveFollowerButton } from '@/features/user/follow/components/remove-follower-button'; +import { ToggleFollowButton } from '@/features/user/follow/components/toggle-follow-button'; +import { useFetchInView } from '@/hooks/useFetchInView'; +import { useFetchItems } from '@/hooks/useFetchItems'; +import useAuthStore from '@/stores/useAuthStore'; +import { User } from '@/types/index'; +import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback'; +import flattenPages, { Page } from '@/utils/flattenPages'; +import Image from 'next/image'; +import Link from 'next/link'; +import { useParams, useSearchParams } from 'next/navigation'; + +export const FollowersList = () => { + const { id } = useParams(); + const user = useAuthStore((state) => state.user); + const isCurrentUser = id === String(user?.userId); + + const searchParams = useSearchParams(); + + const search = searchParams.get('search'); + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = + useFetchItems<User>({ + url: `/v1/follow/${id}/followers`, + ...(search && { queryParams: { name: search } }), + options: { + refetchOnMount: true, + staleTime: 0, + retry: 0, + gcTime: 0, + }, + }); + + const followersCount = (data.pages[0] as Page<User> & { totalCount: number }) + .totalCount; + + const { ref } = useFetchInView({ + fetchNextPage, + isLoading, + isFetchingNextPage, + }); + + const followersList = flattenPages<User>(data.pages); + + return ( + <div className="flex flex-col gap-y-6 pb-4 rounded-b-2xl flex-1 mt-3"> + <div className="flex items-center"> + <div className="flex gap-x-1 items-center"> + <span className="text-sm font-semibold text-gray-500"> + ์ด ํŒ”๋กœ์›Œ ์ˆ˜ :{' '} + </span> + <span className="text-sm font-semibold text-gray-500"> + {followersCount ?? 0} + </span> + </div> + </div> + {followersList.length === 0 ? ( + <div className="flex flex-1 justify-center items-center"> + <p className="text-center font-medium text-gray-400"> + ์•„์ง ํŒ”๋กœ์›Œ๊ฐ€ ์—†์–ด์š”. + </p> + </div> + ) : ( + <ul className="grid grid-cols-1 lg:grid-cols-2 gap-6"> + <h1 className="hidden">ํŒ”๋กœ์›Œ {followersCount ?? null}</h1> + {followersList.map( + // @ts-expect-error ํ˜„์žฌ User ํƒ€์ž…์—๋Š” id ํ”„๋กœํผํ‹ฐ๊ฐ€ ์—†์Œ -> ์ถ”ํ›„ ์ˆ˜์ • ํ•„์š” + ({ id: userId, nickname, profileImage, email, isFollowing }) => ( + <li + className="border-b-2 border-gray-200 border-dashed pb-6 " + key={userId} + > + <Link href={`/users/${userId}`}> + <div className="flex justify-between"> + <div className="flex gap-x-6"> + <Avatar + imageSrc={getDisplayProfileImage(profileImage)} + fallback={getDisplayNickname(nickname, email)} + className="size-[4.75rem]" + /> + <div className="flex flex-col justify-start"> + <span className="text-gray-900 text-lg font-semibold"> + {getDisplayNickname(nickname, email)} + </span> + <span className="text-gray-300 text-sm font-medium"> + {email} + </span> + </div> + </div> + {user && String(user?.userId) !== String(userId) && ( + <DropdownMenu> + <DropdownMenuTrigger asChild> + <button + type="button" + className="size-6 self-start inline-flex justify-center" + > + <Image + src="/icons/more.svg" + alt="more" + width={3.5} + height={3.5} + /> + </button> + </DropdownMenuTrigger> + <DropdownMenuContent className="min-w-[110px]"> + {isCurrentUser && ( + <RemoveFollowerButton userId={String(userId)} /> + )} + + {!isCurrentUser && ( + <ToggleFollowButton + userId={String(userId)} + isFollowing={isFollowing} + usedIn="followers" + className={`shadow-none hover:bg-white! ${ + isFollowing + ? 'text-red-600 [&>svg]:text-red-600!' + : 'text-black [&_svg]:text-black' + } bg-white h-[28px] cursor-pointer text-sm font-semibold rounded-lg py-1 px-3 gap-x-[6px]`} + /> + )} + </DropdownMenuContent> + </DropdownMenu> + )} + </div> + </Link> + </li> + ), + )} + {isFetchingNextPage && <FollowListItemsLoading itemCount={4} />} + {!isFetchingNextPage && hasNextPage && ( + <div ref={ref} className="h-10" /> + )} + </ul> + )} + </div> + ); +}; diff --git a/src/features/user/follow/components/following-list.tsx b/src/features/user/follow/components/following-list.tsx new file mode 100644 index 00000000..5cea4cb8 --- /dev/null +++ b/src/features/user/follow/components/following-list.tsx @@ -0,0 +1,139 @@ +'use client'; + +import { Avatar } from '@/components/atoms/avatar'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import FollowListItemsLoading from '@/features/user/follow/components/follow-list-items-loading'; +import { ToggleFollowButton } from '@/features/user/follow/components/toggle-follow-button'; +import { useFetchInView } from '@/hooks/useFetchInView'; +import { useFetchItems } from '@/hooks/useFetchItems'; +import useAuthStore from '@/stores/useAuthStore'; +import { User } from '@/types'; +import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback'; +import flattenPages, { Page } from '@/utils/flattenPages'; +import Image from 'next/image'; +import Link from 'next/link'; +import { useParams, useSearchParams } from 'next/navigation'; + +export const FollowingList = () => { + const searchParams = useSearchParams(); + const { id } = useParams(); + const user = useAuthStore((state) => state.user); + + const search = searchParams.get('search'); + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = + useFetchItems<User>({ + url: `/v1/follow/${id}/following`, + ...(search && { queryParams: { name: search } }), + options: { + refetchOnMount: true, + staleTime: 0, + retry: 0, + gcTime: 0, + }, + }); + + const followingCount = (data.pages[0] as Page<User> & { totalCount: number }) + .totalCount; + + const { ref } = useFetchInView({ + fetchNextPage, + isLoading, + isFetchingNextPage, + }); + + const followingList = flattenPages<User>(data.pages); + + return ( + <div className="flex flex-col gap-y-6 pb-4 rounded-b-2xl flex-1 mt-3"> + <div className="flex items-center"> + <div className="flex gap-x-1 items-center"> + <span className="text-sm font-semibold text-gray-500"> + ์ด ํŒ”๋กœ์ž‰ ์ˆ˜ :{' '} + </span> + <span className="text-sm font-semibold text-gray-500"> + {followingCount ?? 0} + </span> + </div> + </div> + {followingList.length === 0 ? ( + <div className="flex flex-1 justify-center items-center"> + <p className="text-center font-medium text-gray-400"> + ์•„์ง ํŒ”๋กœ์šฐ ํ•˜๋Š” ์‚ฌ๋žŒ์ด ์—†์–ด์š”. + </p> + </div> + ) : ( + <ul className="grid grid-cols-1 lg:grid-cols-2 gap-6"> + {followingList?.map( + /* @ts-expect-error ํ˜„์žฌ User ํƒ€์ž…์—๋Š” id ํ”„๋กœํผํ‹ฐ๊ฐ€ ์—†์Œ -> ์ถ”ํ›„ ์ˆ˜์ • ํ•„์š” */ + ({ id: userId, nickname, profileImage, email, isFollowing }) => ( + <li + className="border-b-2 border-gray-200 border-dashed pb-6" + key={userId} + > + <Link href={`/users/${userId}`}> + <div className="flex justify-between"> + <div className="flex gap-x-6"> + <Avatar + imageSrc={getDisplayProfileImage(profileImage)} + fallback={getDisplayNickname(nickname, email)} + className="size-[4.75rem]" + /> + <div className="flex flex-col justify-start"> + <span className="text-gray-900 text-lg font-semibold"> + {getDisplayNickname(nickname, email)} + </span> + <span className="text-gray-300 text-sm font-medium"> + {email} + </span> + </div> + </div> + {user && String(user?.userId) !== String(userId) && ( + <DropdownMenu> + <DropdownMenuTrigger asChild> + <button + type="button" + className="size-6 self-start inline-flex justify-center" + > + <Image + src="/icons/more.svg" + alt="more" + width={3.5} + height={3.5} + /> + </button> + </DropdownMenuTrigger> + <DropdownMenuContent className="min-w-[110px] h-[40px] flex items-center rounded-lg!"> + { + <ToggleFollowButton + userId={String(userId)} + isFollowing={isFollowing} + usedIn="following" + className={`shadow-none hover:bg-white! ${ + isFollowing + ? 'text-red-600 [&>svg]:text-red-600!' + : 'text-black [&_svg]:text-black' + } bg-white h-[28px] cursor-pointer text-sm font-semibold rounded-lg py-1 px-3 gap-x-[6px]`} + /> + } + </DropdownMenuContent> + </DropdownMenu> + )} + </div> + </Link> + </li> + ), + )} + {isFetchingNextPage && <FollowListItemsLoading itemCount={4} />} + {!isFetchingNextPage && hasNextPage && ( + <div ref={ref} className="h-10" /> + )} + </ul> + )} + </div> + ); +}; diff --git a/src/features/user/follow/components/remove-follower-button.tsx b/src/features/user/follow/components/remove-follower-button.tsx new file mode 100644 index 00000000..2c1a623a --- /dev/null +++ b/src/features/user/follow/components/remove-follower-button.tsx @@ -0,0 +1,29 @@ +import { useRemoveFollower } from '@/features/user/follow/hooks/useRemoveFollower'; +import { Button } from '@/components/ui/button'; +import { X } from 'lucide-react'; + +type RemoveFollowerButtonProps = { + userId: string; +}; + +export const RemoveFollowerButton = ({ userId }: RemoveFollowerButtonProps) => { + const { mutate: removeFollower, isPending } = useRemoveFollower({ userId }); + + const removeFollowerButtonClickHandler: React.MouseEventHandler< + HTMLButtonElement + > = (e) => { + e.preventDefault(); + removeFollower(); + }; + + return ( + <Button + onClick={removeFollowerButtonClickHandler} + disabled={isPending} + className={`shadow-none hover:bg-white! cursor-pointer border-none text-red-600 [&>svg]:text-red-600! bg-white h-[28px] text-sm font-semibold rounded-lg py-1 px-3 gap-x-[6px]`} + > + <X className='size-4' /> + ์‚ญ์ œ + </Button> + ); +}; diff --git a/src/features/user/follow/components/toggle-follow-button.tsx b/src/features/user/follow/components/toggle-follow-button.tsx new file mode 100644 index 00000000..19f39fbb --- /dev/null +++ b/src/features/user/follow/components/toggle-follow-button.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useToggleFollow } from '@/features/user/follow/hooks/useToggleFollow'; +import { Button } from '@/components/ui/button'; +import { X, Plus } from 'lucide-react'; +import useAuthStore from '@/stores/useAuthStore'; + +type ToggleFollowButtonProps = { + userId?: string; + isFollowing: boolean; + usedIn: string; + className?: string; +}; + +export const ToggleFollowButton = ({ + userId, + isFollowing, + usedIn, + className, +}: ToggleFollowButtonProps) => { + const user = useAuthStore((state) => state.user); + + const router = useRouter(); + + const { mutate: toggleFollow, isPending } = useToggleFollow({ + ...(userId && { userId }), + isFollowing, + usedIn, + }); + + const toggleFollowButtonClickHandler = ( + e: React.MouseEvent<HTMLButtonElement>, + ) => { + e.preventDefault(); + if (!user) { + router.push('/login'); + return; + } + toggleFollow(); + }; + + return ( + <Button + onClick={toggleFollowButtonClickHandler} + disabled={isPending} + className={className} + > + {isFollowing ? ( + <> + <X className='size-4' /> + ์–ธํŒ”ํ•˜๊ธฐ + </> + ) : ( + <> + <Plus className='size-4' /> + ํŒ”๋กœ์šฐ + </> + )} + </Button> + ); +}; diff --git a/src/features/user/follow/hooks/useRemoveFollower.ts b/src/features/user/follow/hooks/useRemoveFollower.ts new file mode 100644 index 00000000..1ec2792f --- /dev/null +++ b/src/features/user/follow/hooks/useRemoveFollower.ts @@ -0,0 +1,34 @@ +'use client'; + +import { useParams } from 'next/navigation'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { request } from '@/api/request'; +import { toast } from 'sonner'; + +export const useRemoveFollower = ({ userId }: { userId: string }) => { + const queryClient = useQueryClient(); + const { id } = useParams(); + + return useMutation({ + mutationFn() { + return request.delete(`/v1/follow/${userId}/unfollower`, { + credentials: 'include', + }); + }, + onError() { + toast.error('ํŒ”๋กœ์›Œ ์‚ญ์ œ ์‹คํŒจ', { + description: 'ํŒ”๋กœ์›Œ ์‚ญ์ œ์— ์‹คํŒจํ–ˆ์–ด์š”. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.', + }); + }, + onSettled() { + return Promise.all([ + queryClient.invalidateQueries({ + queryKey: ['items', `/v1/follow/${id}/followers`], + }), + queryClient.invalidateQueries({ + queryKey: ['user', id, 'followers count'], + }), + ]); + }, + }); +}; diff --git a/src/features/user/follow/hooks/useToggleFollow.ts b/src/features/user/follow/hooks/useToggleFollow.ts new file mode 100644 index 00000000..5ce8462c --- /dev/null +++ b/src/features/user/follow/hooks/useToggleFollow.ts @@ -0,0 +1,71 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { request } from '@/api/request'; +import { toast } from 'sonner'; +import { useParams } from 'next/navigation'; + +/** + * ํŒ”๋กœ์šฐ, ์–ธํŒ”๋กœ์šฐ ๊ธฐ๋Šฅ์„ ์œ„ํ•œ ์ปค์Šคํ…€ ํ›… + * + * usedIn prop๋ฅผ ์ถ”๊ฐ€ํ•œ ์ด์œ ๋Š” ์ด ์ปค์Šคํ…€ ํ›…์„ ํ˜ธ์ถœํ•œ ๋ฒ„ํŠผ์ด ์‚ฌ์šฉ๋œ ์œ„์น˜์— ๋”ฐ๋ผ ๋ฌดํšจํ™”ํ•  ์ฟผ๋ฆฌ ํ‚ค๋ฅผ ๋‹ค๋ฅด๊ฒŒ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•จ + * + * @param userId: ํŒ”๋กœ์šฐ, ์–ธํŒ”๋กœ์šฐ ๋Œ€์ƒ ์œ ์ € ID + * @param isFollowing: ํŒ”๋กœ์šฐ ์ƒํƒœ + * @param usedIn: ํ•ด๋‹น ์ปค์Šคํ…€ ํ›…์„ ํ˜ธ์ถœํ•œ ๋ฒ„ํŠผ์ด ์‚ฌ์šฉ๋œ ์œ„์น˜ (ํ”„๋กœํ•„, ํŒ”๋กœ์ž‰ ๋ชฉ๋ก, ํŒ”๋กœ์›Œ ๋ชฉ๋ก) + * @returns useMutation ๋ฐ˜ํ™˜๊ฐ’ + */ +export const useToggleFollow = ({ + userId, + isFollowing, + usedIn, +}: { + userId?: string; + isFollowing: boolean; + usedIn: string; +}) => { + const { id } = useParams(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn() { + if (isFollowing) { + return request.delete(`/v1/follow/${userId ?? id}/unfollow`, { + credentials: 'include', + }); + } + return request.post( + `/v1/follow/${userId ?? id}`, + { + 'Content-Type': 'application/json', + }, + JSON.stringify({ id: userId ?? id }), + { credentials: 'include' }, + ); + }, + onError() { + toast.error('์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์–ด์š”.', { + description: + '์š”์ฒญ ์‚ฌํ•ญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์–ด์š”. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.', + }); + }, + onSettled() { + if (usedIn === 'profile') + return Promise.all([ + queryClient.invalidateQueries({ + queryKey: ['user', id], + exact: true, + }), + queryClient.invalidateQueries({ + queryKey: ['items', `/v1/follow/${id}/followers`], + }), + ]); + return Promise.all([ + queryClient.invalidateQueries({ + queryKey: ['user', id, `${usedIn} count`], + }), + queryClient.invalidateQueries({ + queryKey: ['items', `/v1/follow/${id}/${usedIn}`], + }), + ]); + }, + }); +}; diff --git a/src/features/user/group/components/group-list-item-loading.tsx b/src/features/user/group/components/group-list-item-loading.tsx new file mode 100644 index 00000000..a8434f12 --- /dev/null +++ b/src/features/user/group/components/group-list-item-loading.tsx @@ -0,0 +1,47 @@ +import { Skeleton } from '@/components/ui/skeleton'; + +type GroupListItemLoadingProps = { + itemCount?: number; +}; + +export const GroupListItemLoading = ({ + itemCount = 4, +}: GroupListItemLoadingProps) => { + return ( + <> + {Array.from({ length: itemCount }).map((_, index) => ( + <li key={index} className="h-60 p-4 border border-gray-200 rounded-2xl"> + <div className="flex flex-col justify-between h-full"> + <div className="flex items-center justify-between"> + <Skeleton className="w-40 h-5.5" /> + <Skeleton className="w-17 h-6 rounded-lg" /> + </div> + <div className="flex flex-col gap-y-2.5"> + <Skeleton className="w-50 h-3.5" /> + <ul className="flex items-center gap-x-2"> + {[1, 2, 3].map((i) => ( + <li key={i}> + <Skeleton className="size-[30px] rounded-full" /> + </li> + ))} + </ul> + <ul className="flex items-center gap-x-2"> + {[1, 2, 3, 4].map((i) => ( + <li key={i}> + <Skeleton className="size-7 rounded-sm" /> + </li> + ))} + </ul> + <div className="flex items-center gap-x-3 pt-3"> + <div className="flex items-center gap-x-1.5"> + <Skeleton className="h-4 w-14" /> + <Skeleton className="size-8 rounded-full" /> + </div> + </div> + </div> + </div> + </li> + ))} + </> + ); +}; diff --git a/src/features/user/group/components/group-list-item.tsx b/src/features/user/group/components/group-list-item.tsx new file mode 100644 index 00000000..a82dc457 --- /dev/null +++ b/src/features/user/group/components/group-list-item.tsx @@ -0,0 +1,119 @@ +'use client'; + +import Link from 'next/link'; +import { Group } from '@/types'; +import { formatYearMonthDayWithDot, isBeforeToday } from '@/utils/dateUtils'; +import { MemberListDialog } from '@/features/user/group/components/member-list-modal/member-list-dialog'; +import { CalendarDays, UsersRound } from 'lucide-react'; +import { Avatar } from '@/components/atoms/avatar'; +import { getDisplayProfileImage, getDisplayNickname } from '@/utils/fallback'; +import { GroupSkills } from '@/components/atoms/group/group-skills'; +import { GroupPositions } from '@/components/atoms/group/group-positions'; + +type GroupListItemProps = { + group: Group; + isCurrentUser: boolean; + status: 'PARTICIPATING' | 'RECRUITING' | 'ENDED'; +}; + +const getIsRecruiting = (group: Group) => { + if (!isBeforeToday(group.deadline)) { + return group.participants.length < group.maxParticipants; + } + return false; +}; + +export const GroupListItem = ({ + group, + isCurrentUser, + status, +}: GroupListItemProps) => { + const { id, startDate, endDate, title, participants, maxParticipants, type } = + group; + + return ( + <li className="border border-gray-200 rounded-2xl h-60 p-4 hover:bg-gray-50 transition-colors relative"> + <Link href={`/groups/${id}`}> + <div className="flex flex-col justify-between h-full"> + <div className="flex items-center justify-between"> + <h3 className="text-lg font-semibold truncate">{title}</h3> + {status === 'RECRUITING' ? ( + <span + className={`text-sm inline-block font-medium px-3 py-1 rounded-lg shrink-0 ${ + getIsRecruiting(group) + ? 'bg-gray-50 text-gray-600' + : 'bg-green-50 text-green-500' + }`} + > + {getIsRecruiting(group) ? '๋ชจ์ง‘์ค‘' : '๋ชจ์ง‘์™„๋ฃŒ'} + </span> + ) : ( + <span + className={`text-sm inline-block font-medium px-3 py-1 rounded-lg bg-green-50 text-green-500 shrink-0`} + > + {type === 'study' ? '์Šคํ„ฐ๋””' : 'ํ”„๋กœ์ ํŠธ'} + </span> + )} + </div> + <div className="flex flex-col gap-y-2.5"> + <div className="flex items-center gap-x-1.5"> + <div className="flex items-center gap-x-0.5"> + <CalendarDays className="w-4 h-4 text-gray-600" /> + <span className="text-sm text-gray-600 font-medium"> + ์ง„ํ–‰ ๊ธฐ๊ฐ„ + </span> + </div> + <span className="text-sm text-gray-600 font-medium"> + {formatYearMonthDayWithDot(startDate)} ~{' '} + {formatYearMonthDayWithDot(endDate)} + </span> + </div> + <GroupSkills skills={group.skills} /> + <GroupPositions positions={group.position} /> + <div className="flex items-center gap-x-3 border-t border-gray-200 pt-3"> + <div className="flex items-center gap-x-1.5"> + <UsersRound className="w-4 h-4 text-gray-600" /> + <span className="text-sm text-gray-600 font-medium"> + {participants.length} / {maxParticipants}๋ช… + </span> + </div> + <div className="items-center hidden md:flex"> + {participants.map((participant, i) => { + if (i > 3) { + return ( + <div key={'indicator'} className="size-8 bg-gray-100 border border-gray-200 text-gray-600 rounded-full flex items-center justify-center z-10 -ml-3"> + <span className="text-xs font-medium"> + +{participants.length - 4} + </span> + </div> + ); + } + return ( + <Avatar + key={participant.userId} + imageSrc={getDisplayProfileImage( + participant.profileImage, + )} + fallback={getDisplayNickname( + participant.nickname, + participant.email, + )} + className={`border border-gray-200 size-8 ${i >= 1 ? '-ml-3' : 'ml-0'} z-10`} + /> + ); + })} + </div> + </div> + </div> + </div> + </Link> + {isCurrentUser && status === 'RECRUITING' && ( + <MemberListDialog groupId={String(id)} groupTitle={title} /> + )} + { + isCurrentUser && status === 'ENDED' && false + // TODO: ๋ณ„์ ์„ ๋‹ฌ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋‹ฌ ๊ฐ™์€ ๊ฒƒ์„ ์ถ”๊ฐ€ + } + </li> + ); +}; diff --git a/src/features/user/group/components/group-list-loading.tsx b/src/features/user/group/components/group-list-loading.tsx new file mode 100644 index 00000000..4bd5c0af --- /dev/null +++ b/src/features/user/group/components/group-list-loading.tsx @@ -0,0 +1,9 @@ +import { GroupListItemLoading } from '@/features/user/group/components/group-list-item-loading'; + +export const GroupListLoading = () => { + return ( + <ul className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-3 mt-7.5"> + <GroupListItemLoading /> + </ul> + ) +} \ No newline at end of file diff --git a/src/features/user/group/components/group-list.tsx b/src/features/user/group/components/group-list.tsx new file mode 100644 index 00000000..97f0a20a --- /dev/null +++ b/src/features/user/group/components/group-list.tsx @@ -0,0 +1,97 @@ +'use client'; + +import { GroupListItem } from '@/features/user/group/components/group-list-item'; +import { useFetchInView } from '@/hooks/useFetchInView'; +import { useFetchItems } from '@/hooks/useFetchItems'; +import useAuthStore from '@/stores/useAuthStore'; +import { Group } from '@/types'; +import { isBeforeToday } from '@/utils/dateUtils'; +import flattenPages from '@/utils/flattenPages'; +import { useParams, useSearchParams } from 'next/navigation'; + +type GroupListProps = { + status: 'PARTICIPATING' | 'RECRUITING' | 'ENDED'; +}; + +/** + * ๋ชจ์ž„ ๋ชฉ๋ก ์ปดํฌ๋„ŒํŠธ + * + * ๋ชจ์ž„ ๋ชฉ๋ก์„ ๋ณด์—ฌ์ค€๋‹ค. + * + * @returns ๋ชจ์ž„ ๋ชฉ๋ก ์ปดํฌ๋„ŒํŠธ + */ +export const GroupList = ({ status }: GroupListProps) => { + const user = useAuthStore((state) => state.user); + + const { id } = useParams(); + + const isCurrentUser = String(user?.userId) === id; + + const searchParams = useSearchParams(); + + const { search, type, order } = Object.fromEntries(searchParams.entries()); + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = + useFetchItems<Group>({ + url: `/v2/groups/usergroup/${id}`, + queryParams: { + ...(status !== 'PARTICIPATING' && { type: type ?? 'study' }), + status: 'PARTICIPATING', + size: status !== 'ENDED' ? 10 : 50, + ...(search && { search }), + order: order === 'latest' || !order ? 'desc' : 'asc', + }, + options: { + staleTime: 0, + refetchOnMount: true, + refetchOnWindowFocus: false, + retry: 0, + }, + }); + + const { ref } = useFetchInView({ + fetchNextPage, + isLoading, + isFetchingNextPage, + }); + + // @ts-expect-error ๊ฐ์ฒด ์•ˆ์— ๋˜ group ํ”„๋กœํผํ‹ฐ๊ฐ€ ์กด์žฌํ•จ. + let groupList = flattenPages<Group>(data.pages).map((e) => e.group); + if (status === 'ENDED') { + groupList = groupList.filter((group) => isBeforeToday(group.endDate)); + } + + if (status === 'RECRUITING') { + groupList = groupList.filter((group) => group.createUserId === Number(id)); + } + + if (status === 'PARTICIPATING') { + groupList = groupList.filter((group) => !isBeforeToday(group.endDate)); + } + + return ( + <> + {groupList.length === 0 ? ( + <div className="flex flex-col items-center justify-center mt-30"> + <p className="text-center font-medium text-gray-500"> + {status === 'RECRUITING' && '๋ชจ์ง‘ ์ค‘์ธ ๋ชจ์ž„์ด ์—†์–ด์š”.'} + {status === 'PARTICIPATING' && '์ฐธ์—ฌ ์ค‘์ธ ๋ชจ์ž„์ด ์—†์–ด์š”.'} + {status === 'ENDED' && '์ข…๋ฃŒ๋œ ๋ชจ์ž„์ด ์—†์–ด์š”.'} + </p> + </div> + ) : ( + <ul className="grid grid-cols-1 md:grid-cols-2 mt-7.5 gap-4 mb-3"> + {groupList.map((group) => ( + <GroupListItem + key={group.id} + group={group} + isCurrentUser={isCurrentUser} + status={status} + /> + ))} + {hasNextPage && <div ref={ref} className="h-10" />} + </ul> + )} + </> + ); +}; diff --git a/src/features/user/group/components/member-list-modal/applicants-list.tsx b/src/features/user/group/components/member-list-modal/applicants-list.tsx new file mode 100644 index 00000000..4da3094c --- /dev/null +++ b/src/features/user/group/components/member-list-modal/applicants-list.tsx @@ -0,0 +1,101 @@ +'use client'; + +import { request } from '@/api/request'; +import { Button } from '@/components/ui/button'; +import { MemberInfo } from '@/features/user/group/components/member-list-modal/member-info'; +import { useManageParticipation } from '@/features/user/group/hooks/useManageParticipation'; +import { UserSummary } from '@/types'; +import { useQuery } from '@tanstack/react-query'; +import { MemberListLoading } from '@/features/user/group/components/member-list-modal/member-list-loading'; + +type ApplicantsListProps = { + groupId: string; +}; + +/** + * ๋ชจ์ž„์— ์ฐธ์—ฌ ์‹ ์ฒญํ•œ ์‚ฌ์šฉ์ž ๋ชฉ๋ก ์ปดํฌ๋„ŒํŠธ + * + * ๋ชจ์ž„์— ์ฐธ์—ฌ ์‹ ์ฒญํ•œ ์‚ฌ์šฉ์ž์˜ ๋ชฉ๋ก์„ ๋ณด์—ฌ์ค€๋‹ค. + * + * @param groupId ๋ชจ์ž„ id + * @returns ๋ชจ์ž„์— ์ฐธ์—ฌ ์‹ ์ฒญํ•œ ์‚ฌ์šฉ์ž ๋ชฉ๋ก ์ปดํฌ๋„ŒํŠธ +ํŠธ */ +export const ApplicantsList = ({ groupId }: ApplicantsListProps) => { + const { mutate: manageParticipation } = useManageParticipation(); + + const { + data: applicantsList, + isLoading, + isError, + } = useQuery({ + queryKey: ['group-member-list', groupId, 'applicants'], + queryFn() { + return request + .get(`/v2/groups/${groupId}/waitinglist`) + .then((res) => res.items); + }, + staleTime: 0, + refetchOnWindowFocus: false, + gcTime: 0, + }); + + return ( + <div className="flex-1"> + {isLoading && <MemberListLoading />} + {isError && <>Error</>} + {applicantsList && applicantsList.length === 0 && ( + <div className="flex flex-col items-center justify-center h-full "> + <p className="text-center font-medium text-gray-400"> + ๋ชจ์ž„์— ์ฐธ์—ฌ ์‹ ์ฒญํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์—†์–ด์š”. + </p> + </div> + )} + {applicantsList && applicantsList.length > 0 && ( + <ul className="flex flex-col gap-y-3"> + {applicantsList.map((applicant: UserSummary) => ( + <li + key={applicant.userId} + className="pb-5 border-b-2 border-gray-300 last:border-none border-dashed flex justify-between" + > + <MemberInfo + userId={applicant.userId} + nickname={applicant.nickname} + email={applicant.email} + profileImage={applicant.profileImage} + /> + <div className="flex gap-x-2"> + <Button + variant="outline" + type="button" + className="h-9 w-12 border-gray-300! text-gray-500! text-sm font-medium" + onClick={() => + manageParticipation({ + groupId, + userId: String(applicant.userId), + status: 'deny', + }) + } + > + ๊ฑฐ์ ˆ + </Button> + <Button + type="button" + className="h-9 w-12 text-sm font-medium" + onClick={() => + manageParticipation({ + groupId, + userId: String(applicant.userId), + status: 'approve', + }) + } + > + ์ˆ˜๋ฝ + </Button> + </div> + </li> + ))} + </ul> + )} + </div> + ); +}; diff --git a/src/features/user/group/components/member-list-modal/group-member-list.tsx b/src/features/user/group/components/member-list-modal/group-member-list.tsx new file mode 100644 index 00000000..ca609b7e --- /dev/null +++ b/src/features/user/group/components/member-list-modal/group-member-list.tsx @@ -0,0 +1,64 @@ +'use client'; + +import { ApplicantsList } from '@/features/user/group/components/member-list-modal/applicants-list'; +import { ParticipantsList } from '@/features/user/group/components/member-list-modal/participants-list'; +import { useQueryClient } from '@tanstack/react-query'; +import { useParams } from 'next/navigation'; +import { useEffect, useState } from 'react'; + +type GroupMemberListProps = { + groupId: string; +}; + +/** + * ๋ชจ์ž„ ์ฐธ์—ฌ/์‹ ์ฒญ์ž ๋ชฉ๋ก ์ปดํฌ๋„ŒํŠธ + * + * ํƒญ์— ๋”ฐ๋ผ ๋ชจ์ž„ ์ฐธ์—ฌ/์‹ ์ฒญ์ž ๋ชฉ๋ก์„ ๋ณด์—ฌ์ค€๋‹ค. + * + * @param groupId ๋ชจ์ž„ id + * @returns ๋ชจ์ž„ ์ฐธ์—ฌ/์‹ ์ฒญ์ž ๋ชฉ๋ก ์ปดํฌ๋„ŒํŠธ + */ +export const GroupMemberList = ({ groupId }: GroupMemberListProps) => { + const { id } = useParams(); + + const queryClient = useQueryClient(); + + const [currentTab, setCurrentTab] = useState<'participants' | 'applicants'>( + 'applicants', + ); + + useEffect(() => { + return () => { + queryClient.invalidateQueries({ + queryKey: ['items', `/v2/groups/usergroup/${id}`], + }); + }; + // eslint-disable-next-line + }, []); + + return ( + <div className="flex flex-col gap-y-4 flex-1"> + <ul className="flex items-center gap-x-4"> + {['applicants', 'participants'].map((tab) => ( + <li key={tab} className="relative"> + <button + type="button" + className={`font-medium ${ + tab === currentTab + ? 'after:content-[""] after:absolute after:-bottom-1 after:left-0 after:w-full after:h-[2px] after:bg-gray-900' + : 'text-gray-400' + }`} + onClick={() => + setCurrentTab(tab as 'participants' | 'applicants') + } + > + {tab === 'participants' ? 'ํ™•์ •๋ฉค๋ฒ„' : '์ˆ˜๋ฝ ๋Œ€๊ธฐ์ค‘'} + </button> + </li> + ))} + </ul> + {currentTab === 'applicants' && <ApplicantsList groupId={groupId} />} + {currentTab === 'participants' && <ParticipantsList groupId={groupId} />} + </div> + ); +}; diff --git a/src/features/user/group/components/member-list-modal/member-info.tsx b/src/features/user/group/components/member-list-modal/member-info.tsx new file mode 100644 index 00000000..10dc01d6 --- /dev/null +++ b/src/features/user/group/components/member-list-modal/member-info.tsx @@ -0,0 +1,29 @@ +'use client'; + +import { Avatar } from '@/components/atoms/avatar'; +import { UserSummary } from '@/types'; +import { getDisplayNickname, getDisplayProfileImage } from '@/utils/fallback'; +import Link from 'next/link'; + +export const MemberInfo = ({ + userId, + nickname, + email, + profileImage, +}: UserSummary) => ( + <div className="flex gap-x-6"> + <Link href={`/users/${userId}`}> + <Avatar + imageSrc={getDisplayProfileImage(profileImage)} + fallback={getDisplayNickname(nickname, email)} + className="size-14" + /> + </Link> + <div className="flex flex-col"> + <Link href={`/users/${userId}`} className="text-lg font-semibold"> + {getDisplayNickname(nickname, email)} + </Link> + <span className="text-sm text-gray-400">{email}</span> + </div> + </div> +); diff --git a/src/features/user/group/components/member-list-modal/member-list-dialog.tsx b/src/features/user/group/components/member-list-modal/member-list-dialog.tsx new file mode 100644 index 00000000..76705f46 --- /dev/null +++ b/src/features/user/group/components/member-list-modal/member-list-dialog.tsx @@ -0,0 +1,53 @@ +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { GroupMemberList } from '@/features/user/group/components/member-list-modal/group-member-list'; +import { ArrowUpRight } from 'lucide-react'; + +type MemberListDialogProps = { + groupId: string; + groupTitle: string; +}; + +/** + * ๋ชจ์ž„ ์ฐธ์—ฌ/์‹ ์ฒญ์ž ๋ชฉ๋ก ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ + * + * ๋ชจ์ž„ ์ฐธ์—ฌ/์‹ ์ฒญ์ž ๋ชฉ๋ก์„ ๋ณด์—ฌ์ค€๋‹ค. + * + * @param groupId ๋ชจ์ž„ id + * @param groupTitle ๋ชจ์ž„ ์ œ๋ชฉ + * @returns ๋ชจ์ž„ ์ฐธ์—ฌ/์‹ ์ฒญ์ž ๋ชฉ๋ก ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ + */ +export const MemberListDialog = ({ + groupId, + groupTitle, +}: MemberListDialogProps) => { + return ( + <div className="absolute bottom-3 right-4"> + <Dialog> + <DialogTrigger asChild> + <button + className="inline-flex items-center gap-x-0.5 cursor-pointer self-end text-sm text-gray-500" + type="button" + > + <ArrowUpRight className="size-5" /> + ์ฐธ์—ฌ/์‹ ์ฒญ์ž ๋ชฉ๋ก + </button> + </DialogTrigger> + <DialogContent className="max-w-80 h-[25rem] overflow-y-auto scrollbar-hide flex flex-col"> + <DialogHeader> + <DialogTitle className="mt-2"> + <span className="text-green-500">{`"${groupTitle}"`}</span> ๋ชจ์ž„์˜ + ์ฐธ์—ฌ/์‹ ์ฒญ์ž ๋ชฉ๋ก + </DialogTitle> + </DialogHeader> + <GroupMemberList groupId={groupId} /> + </DialogContent> + </Dialog> + </div> + ); +}; diff --git a/src/features/user/group/components/member-list-modal/member-list-loading.tsx b/src/features/user/group/components/member-list-modal/member-list-loading.tsx new file mode 100644 index 00000000..7cece3b9 --- /dev/null +++ b/src/features/user/group/components/member-list-modal/member-list-loading.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { Skeleton } from '@/components/ui/skeleton'; + +export const MemberListLoading = () => { + return ( + <ul className="flex flex-col gap-y-3"> + {Array.from({ length: 3 }).map((_, index) => ( + <li key={index} className="flex gap-x-6 pb-5"> + <Skeleton className="size-14 rounded-full" /> + <div className="flex flex-col gap-y-1"> + <Skeleton className="w-20 h-6" /> + <Skeleton className="w-40 h-4" /> + </div> + </li> + ))} + </ul> + ); +}; diff --git a/src/features/user/group/components/member-list-modal/participants-list.tsx b/src/features/user/group/components/member-list-modal/participants-list.tsx new file mode 100644 index 00000000..dd70e871 --- /dev/null +++ b/src/features/user/group/components/member-list-modal/participants-list.tsx @@ -0,0 +1,61 @@ +'use client'; + +import { request } from '@/api/request'; +import { MemberInfo } from '@/features/user/group/components/member-list-modal/member-info'; +import { UserSummary } from '@/types'; +import { useQuery } from '@tanstack/react-query'; +import { MemberListLoading } from '@/features/user/group/components/member-list-modal/member-list-loading'; + +type ParticipantsListProps = { + groupId: string; +}; + +/** + * ๋ชจ์ž„์— ์ฐธ์—ฌํ•œ ์‚ฌ์šฉ์ž ๋ชฉ๋ก ์ปดํฌ๋„ŒํŠธ + * + * ๋ชจ์ž„์— ์ฐธ์—ฌํ•œ ์‚ฌ์šฉ์ž์˜ ๋ชฉ๋ก์„ ๋ณด์—ฌ์ค€๋‹ค. + * + * @param groupId ๋ชจ์ž„ id + * @returns ๋ชจ์ž„์— ์ฐธ์—ฌํ•œ ์‚ฌ์šฉ์ž ๋ชฉ๋ก ์ปดํฌ๋„ŒํŠธ + */ +export const ParticipantsList = ({ groupId }: ParticipantsListProps) => { + const { + data: participantsList, + isLoading, + isError, + } = useQuery({ + queryKey: ['group-member-list', groupId, 'participants'], + queryFn() { + return request + .get(`/v2/groups/${groupId}/participants`) + .then((res) => res.items.participants); + }, + staleTime: 0, + refetchOnWindowFocus: false, + gcTime: 0, + }); + + return ( + <div> + {isLoading && <MemberListLoading />} + {isError && <>Error</>} + {participantsList && ( + <ul className="flex flex-col gap-y-3"> + {participantsList.map((participant: UserSummary) => ( + <li + key={participant.userId} + className="pb-5 border-b-2 border-gray-300 last:border-none border-dashed" + > + <MemberInfo + userId={participant.userId} + nickname={participant.nickname} + email={participant.email} + profileImage={participant.profileImage} + /> + </li> + ))} + </ul> + )} + </div> + ); +}; diff --git a/src/features/user/group/hooks/useManageParticipation.tsx b/src/features/user/group/hooks/useManageParticipation.tsx new file mode 100644 index 00000000..fdf2078d --- /dev/null +++ b/src/features/user/group/hooks/useManageParticipation.tsx @@ -0,0 +1,50 @@ +import { useMutation } from '@tanstack/react-query'; +import { request } from '@/api/request'; +import { toast } from 'sonner'; +import { useQueryClient } from '@tanstack/react-query'; + +/** + * ์‚ฌ์šฉ์ž์˜ ๋ชจ์ž„ ์ฐธ๊ฐ€๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ปค์Šคํ…€ ํ›… + * + * ๋ชจ์ž„ ์ฐธ๊ฐ€ ์š”์ฒญ ์Šน์ธ ๋ฐ ๊ฑฐ์ ˆ ์ฒ˜๋ฆฌ + * + * @returns useMutation ํ›… ๋ฐ˜ํ™˜๊ฐ’ + */ +export const useManageParticipation = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn({ + groupId, + userId, + status, + }: { + groupId: string; + userId: string; + status: 'approve' | 'deny'; + }) { + return request.post( + `/v2/groups/${groupId}/join`, + { + 'Content-Type': 'application/json', + }, + JSON.stringify({ + userId: Number(userId), + status, + }), + { + credentials: 'include', + }, + ); + }, + onSuccess() { + return queryClient.invalidateQueries({ + queryKey: ['group-member-list'], + }); + }, + onError(error) { + const errMessage = JSON.parse(error.message.split('-')[1]).message; + toast.error(errMessage); + }, + }); +}; diff --git a/src/features/user/hooks/useChangePassword.ts b/src/features/user/hooks/useChangePassword.ts new file mode 100644 index 00000000..b145035b --- /dev/null +++ b/src/features/user/hooks/useChangePassword.ts @@ -0,0 +1,43 @@ +import { useMutation } from '@tanstack/react-query'; +import { toast } from 'sonner'; +import { request } from '@/api/request'; + +/** + * ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์ปค์Šคํ…€ ํ›… + */ +export const useChangePassword = () => { + return useMutation({ + mutationFn: ({ + newPassword, + currentPassword, + }: { + newPassword: string; + currentPassword: string; + }) => { + return request.patch( + '/v1/user/password-change', + { + 'Content-Type': 'application/json', + }, + { + newPassword, + confirmPassword: currentPassword, + }, + { + credentials: 'include', + }, + ); + }, + onSuccess() { + toast.success('๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์„ฑ๊ณต', { + description: '๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์–ด์š”', + }); + }, + onError(error) { + const errInfo = JSON.parse(error.message.split('-')[1]); + toast.error('๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์‹คํŒจ', { + description: errInfo.status.message, + }); + }, + }); +}; diff --git a/src/features/user/hooks/useUpdateProfileMutation.ts b/src/features/user/hooks/useUpdateProfileMutation.ts new file mode 100644 index 00000000..36cd511d --- /dev/null +++ b/src/features/user/hooks/useUpdateProfileMutation.ts @@ -0,0 +1,58 @@ +import { useMutation } from '@tanstack/react-query'; +import useAuthStore from '@/stores/useAuthStore'; +import { Position, Skill } from '@/types/enums'; +import { toast } from 'sonner'; +import { User } from '@/types'; +import { CommonResponse } from '@/types/response'; +import { request } from '@/api/request'; + +/** + * ํ”„๋กœํ•„ ์ˆ˜์ • ์ปค์Šคํ…€ ํ›… + * + * @param userId ์‚ฌ์šฉ์ž ์•„์ด๋”” + * @returns ํ”„๋กœํ•„ ์ˆ˜์ • ์ปค์Šคํ…€ ํ›… + */ +export const useUpdateProfileMutation = () => { + const { setUser } = useAuthStore((state) => state); + + return useMutation({ + mutationFn: (data: { + nickname: string; + position: Position | null; + skills: Skill[] | null; + file?: File; + }) => { + const formData = new FormData(); + formData.append('nickname', data.nickname); + + if (data.position) { + formData.append('position', JSON.stringify(data.position)); + } + + if (data.skills) { + formData.append('skills', data.skills.join(',')); + } + + if (data.file) { + formData.append('image', data.file); + } + + return request.patch(`/v1/user/edit`, {}, formData, { + credentials: 'include', + }); + }, + onSuccess(response: CommonResponse<User>) { + const user = response.data; + // @ts-expect-error ๋ฐฑ์—”๋“œ์—์„œ ์‘๋‹ตํ•˜๋Š” ๊ฐ์ฒด์— userId ํ”„๋กœํผํ‹ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Œ. -> ์ถ”ํ›„ ํƒ€์ž… ์ˆ˜์ • ํ•„์š” + setUser({ ...user, userId: user.id }); + toast.success('ํ”„๋กœํ•„ ์ˆ˜์ • ์„ฑ๊ณต', { + description: 'ํ”„๋กœํ•„์ด ์ˆ˜์ •๋˜์—ˆ์–ด์š”', + }); + }, + onError() { + toast.error('ํ”„๋กœํ•„ ์ˆ˜์ • ์‹คํŒจ', { + description: 'ํ”„๋กœํ•„ ์ˆ˜์ •์— ์‹คํŒจํ–ˆ์–ด์š”. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.', + }); + }, + }); +}; diff --git a/src/features/user/utils/validateImageFile.ts b/src/features/user/utils/validateImageFile.ts new file mode 100644 index 00000000..774e9d90 --- /dev/null +++ b/src/features/user/utils/validateImageFile.ts @@ -0,0 +1,35 @@ +/** + * ์ด๋ฏธ์ง€ ํŒŒ์ผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ + * + * ๋‹ค์Œ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•ด์•ผ ์œ ํšจํ•œ ์ด๋ฏธ์ง€๋กœ ๊ฐ„์ฃผ + * - MIME ํƒ€์ž…์ด image/๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•จ + * - ํŒŒ์ผ ํฌ๊ธฐ๊ฐ€ maxFileSize ์ดํ•˜์—ฌ์•ผ ์ด๋ฏธ์ง€ ํŒŒ์ผ์ด ์œ ํšจํ•จ + * + * @param file ๊ฒ€์‚ฌํ•  ์ด๋ฏธ์ง€ ํŒŒ์ผ + * @param maxFileSize ์ตœ๋Œ€ ํŒŒ์ผ ํฌ๊ธฐ(๊ธฐ๋ณธ๊ฐ’: 5MB) + * @returns ์ด๋ฏธ์ง€ ํŒŒ์ผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ฒฐ๊ณผ ๊ฐ์ฒด + * - isValid: ์œ ํšจํ•œ ์ด๋ฏธ์ง€ ํŒŒ์ผ์ธ ๊ฒฝ์šฐ true, ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ false + * - errorMessage: ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ + */ +export const validateImageFile = ( + file: File, + maxFileSize = 1024 * 1024, +): { isValid: boolean; errorMessage?: string } => { + if (!file.type.startsWith('image/')) { + return { + isValid: false, + errorMessage: '์ด๋ฏธ์ง€ ํŒŒ์ผ๋งŒ ์—…๋กœ๋“œํ•  ์ˆ˜ ์žˆ์–ด์š”.', + }; + } + + const fileSize = file.size; + if (fileSize > maxFileSize) { + return { + isValid: false, + errorMessage: `ํŒŒ์ผ ํฌ๊ธฐ๋Š” ${ + maxFileSize / 1024 / 1024 + }MB๋ฅผ ์ดˆ๊ณผํ•  ์ˆ˜ ์—†์–ด์š”.`, + }; + } + return { isValid: true }; +}; diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts new file mode 100644 index 00000000..8aac9dc1 --- /dev/null +++ b/src/hooks/useDebounce.ts @@ -0,0 +1,39 @@ +import { useCallback, useEffect, useRef } from 'react'; + +/** + * ๋””๋ฐ”์šด์Šค ํ›… + * + * @param callback ํ˜ธ์ถœํ•  ํ•จ์ˆ˜ + * @param delay ์ง€์—ฐ ์‹œ๊ฐ„ + * @returns ๋””๋ฐ”์šด์Šค๋œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜ + */ + +export const useDebounce = <T extends (...args: Parameters<T>) => void>( + callback: T, + delay: number, +): ((...args: Parameters<T>) => void) => { + const timerRef = useRef<number | null>(null); + + useEffect(() => { + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + }; + }, []); + + const debouncedCallback = useCallback( + (...args: Parameters<T>) => { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + + timerRef.current = window.setTimeout(() => { + callback(...args); + }, delay); + }, + [callback, delay], + ); + + return debouncedCallback; +}; diff --git a/src/hooks/useFetchInView.ts b/src/hooks/useFetchInView.ts new file mode 100644 index 00000000..c6155f9f --- /dev/null +++ b/src/hooks/useFetchInView.ts @@ -0,0 +1,26 @@ +import { FetchNextPageOptions } from '@tanstack/react-query'; +import { useEffect } from 'react'; +import { IntersectionOptions, useInView } from 'react-intersection-observer'; + +export const useFetchInView = ({ + fetchNextPage, + options, + isLoading, + isFetchingNextPage, +}: { + fetchNextPage: (options?: FetchNextPageOptions) => void; + options?: IntersectionOptions; + isLoading?: boolean; + isFetchingNextPage?: boolean; +}) => { + const { ref, inView } = useInView({ ...options }); + + useEffect(() => { + if (inView && !isLoading && !isFetchingNextPage) { + fetchNextPage(); + } + // eslint-disable-next-line + }, [inView]); + + return { ref }; +}; diff --git a/src/hooks/useFetchItems.ts b/src/hooks/useFetchItems.ts new file mode 100644 index 00000000..4ae5b571 --- /dev/null +++ b/src/hooks/useFetchItems.ts @@ -0,0 +1,44 @@ +import { request } from '@/api/request'; +import { Page } from '@/utils/flattenPages'; +import { + InfiniteData, + useSuspenseInfiniteQuery, + UseSuspenseInfiniteQueryOptions, +} from '@tanstack/react-query'; + +export const useFetchItems = <T>({ + url, + queryParams = {}, + options = {}, + getNextPageParam, +}: { + url: string; + queryParams?: Record<string, string | number | null>; + options?: Partial< + UseSuspenseInfiniteQueryOptions<Page<T>, Error, InfiniteData<Page<T>>> + >; + getNextPageParam?: (lastPage: Page<T>) => number | null; +}) => { + const isCursorNull = queryParams.order === 'desc'; + + return useSuspenseInfiniteQuery<Page<T>, Error, InfiniteData<Page<T>>>({ + queryKey: ['items', url, queryParams ?? {}], + queryFn: async ({ pageParam }): Promise<Page<T>> => + request.get( + url, + { + ...queryParams, + cursor: + isCursorNull && pageParam === 0 + ? 'null' + : (pageParam as number | string), + }, + { credentials: 'include' }, + ), + initialPageParam: 0, + getNextPageParam(lastPage) { + return getNextPageParam ? getNextPageParam(lastPage) : lastPage.hasNext ? lastPage.cursor : null; + }, + ...options, + }); +}; diff --git a/src/hooks/useIsClient.ts b/src/hooks/useIsClient.ts new file mode 100644 index 00000000..b548d1c7 --- /dev/null +++ b/src/hooks/useIsClient.ts @@ -0,0 +1,14 @@ +import { useEffect, useState } from 'react'; + +// ๋งˆ์šดํŠธ ํ›„์— ํด๋ผ์ด์–ธํŠธ์ธ์ง€ ํ™•์ธํ•˜๋Š” ํ›… +const useIsClient = () => { + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + setIsClient(true); + }, []); + + return isClient; +}; + +export default useIsClient; diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 00000000..2819a830 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/src/mocks/browser.ts b/src/mocks/browser.ts new file mode 100644 index 00000000..ab00d904 --- /dev/null +++ b/src/mocks/browser.ts @@ -0,0 +1,20 @@ +import { setupWorker } from 'msw/browser'; +import { applicationsHandlers } from './handler/applications'; +import { authenticationsHandlers } from './handler/authentications'; +import { followHandlers } from './handler/follow'; +import { groupsHandlers } from './handler/groups'; +import { notificationsHandlers } from './handler/notifications'; +import { ratingHandlers } from './handler/rating'; +import { repliesHandlers } from './handler/replies'; +import { userHandlers } from './handler/user'; + +export const worker = setupWorker( + ...groupsHandlers, + ...notificationsHandlers, + ...followHandlers, + ...authenticationsHandlers, + ...repliesHandlers, + ...ratingHandlers, + ...userHandlers, + ...applicationsHandlers, +); diff --git a/src/mocks/handler/applications.ts b/src/mocks/handler/applications.ts new file mode 100644 index 00000000..75e9fd63 --- /dev/null +++ b/src/mocks/handler/applications.ts @@ -0,0 +1,16 @@ +import { http, HttpResponse } from 'msw'; + +export const applicationsHandlers = [ + http.post( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/applications`, + () => { + return HttpResponse.json({}); + }, + ), + http.delete( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/applications`, + () => { + return HttpResponse.json({}); + }, + ), +]; diff --git a/src/mocks/handler/authentications.ts b/src/mocks/handler/authentications.ts new file mode 100644 index 00000000..b0f526aa --- /dev/null +++ b/src/mocks/handler/authentications.ts @@ -0,0 +1,81 @@ +import { http, HttpResponse } from 'msw'; +import { User } from '@/types'; +import { Position, Skill } from '@/types/enums'; + +let user: User = { + userId: 1, + email: 'me@example.com', + nickname: null, + position: null, + skills: null, + profileImage: null, + isFollower: false, + isFollowing: true, + rate: 4, +}; + +export const authenticationsHandlers = [ + http.post('http://localhost:4000/api/login', () => { + return new HttpResponse(JSON.stringify({ success: true }), { + headers: new Headers([ + ['Set-Cookie', 'accessToken=myAccess; Path=/'], + ['Set-Cookie', 'refreshToken=myRefresh; Path=/'], + ]), + }); + }), + http.post('http://localhost:4000/api/register', () => { + return new HttpResponse(JSON.stringify({ success: true }), { + headers: new Headers([ + ['Set-Cookie', 'accessToken=myAccess; Path=/'], + ['Set-Cookie', 'refreshToken=myRefresh; Path=/'], + ]), + }); + }), + http.get('http://localhost:4000/api/me', () => { + return HttpResponse.json<{ user: User }>({ + user, + }); + }), + http.post('http://localhost:4000/api/me', async ({ request }) => { + const body = (await request.json()) as { + nickname: string; + position: Position; + skills: Skill[]; + }; + + user = { + ...user, + nickname: body.nickname, + position: body.position, + skills: body.skills, + }; + + return HttpResponse.json({ + success: true, + }); + }), + http.post('http://localhost:4000/api/find-email', () => { + return HttpResponse.json({ + success: true, + }); + }), + http.post('http://localhost:4000/api/find-password', () => { + return HttpResponse.json({ + success: true, + }); + }), + http.post('http://localhost:4000/api/logout', () => { + return new HttpResponse(JSON.stringify({ success: true }), { + headers: new Headers([ + [ + 'Set-Cookie', + 'accessToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT', + ], + [ + 'Set-Cookie', + 'refreshToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT', + ], + ]), + }); + }), +]; diff --git a/src/mocks/handler/follow.ts b/src/mocks/handler/follow.ts new file mode 100644 index 00000000..73235260 --- /dev/null +++ b/src/mocks/handler/follow.ts @@ -0,0 +1,109 @@ +import { http, HttpResponse } from 'msw'; +import { User } from '@/types'; + +export const followHandlers = [ + http.get( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/followings`, + ({ request }) => { + const url = new URL(request.url); + const searchParams = new URLSearchParams(url.search); + const search = searchParams.get('search'); + console.log('in handler', search); + + const items: User[] = Array.from({ length: 10 }).map((_, index) => { + return { + userId: index + 1, + email: `test${index + 1}@gmail.com`, + nickname: `ํ…Œ์ŠคํŠธ ๋‹‰๋„ค์ž„${index + 1}`, + profileImage: `https://github.com/shadcn.png`, + position: 1, + skills: [1, 2, 3], + isFollowing: true, + isFollower: true, + rate: 4, + }; + }); + + return HttpResponse.json({ + hasNext: true, + cursor: 1, + items, + }); + }, + ), + + http.get( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/followers`, + ({ request }) => { + const url = new URL(request.url); + const searchParams = new URLSearchParams(url.search); + const search = searchParams.get('search'); + console.log('in handler', search); + + const items: User[] = Array.from({ length: 10 }).map((_, index) => { + return { + userId: index + 1, + email: `test${index + 1}@gmail.com`, + nickname: `ํ…Œ์ŠคํŠธ ๋‹‰๋„ค์ž„${index + 1}`, + profileImage: `https://github.com/shadcn.png`, + position: 1, + skills: [1, 2, 3], + isFollowing: true, + isFollower: true, + rate: 4, + }; + }); + + return HttpResponse.json({ + hasNext: true, + cursor: 1, + items, + }); + }, + ), + + http.post( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/follow`, + () => { + return HttpResponse.json({ + message: 'ํŒ”๋กœ์šฐ ์„ฑ๊ณต', + }); + }, + ), + + http.delete( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/unfollow`, + () => { + return HttpResponse.json({ + message: '์–ธํŒ”๋กœ์šฐ ์„ฑ๊ณต', + }); + }, + ), + + http.delete( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/unfollower`, + () => { + return HttpResponse.json({ + message: 'ํŒ”๋กœ์›Œ ์‚ญ์ œ ์„ฑ๊ณต', + }); + }, + ), + + http.get( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/followers/count`, + () => { + return HttpResponse.json({ + count: 100, + }); + }, + ), + + http.get( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/users/:userId/followings/count`, + () => { + return HttpResponse.json({ + count: 50, + }); + }, + ), +]; diff --git a/src/mocks/handler/groups.ts b/src/mocks/handler/groups.ts new file mode 100644 index 00000000..3637535f --- /dev/null +++ b/src/mocks/handler/groups.ts @@ -0,0 +1,214 @@ +import { Group, GroupSort, Order } from '@/types'; +import { Position, Skill } from '@/types/enums'; +import { + getRandomItem, + getRandomItems, + groupTypeValues, + positionKeys, + skillKeys, +} from '@/utils/mockUtils'; +import { addDays } from 'date-fns'; +import { http, HttpResponse } from 'msw'; + +const titles = [ + 'React ์Šคํ„ฐ๋””', + 'Node.js ๋ชจ์ž„', + 'TypeScript ์Šคํ„ฐ๋””', + 'Next.js ํด๋Ÿฝ', + 'ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž ๊ทธ๋ฃน', + '๋ฐฑ์—”๋“œ ๋งˆ์Šคํ„ฐ์ฆˆ', + 'ํ’€์Šคํƒ ํ”„๋กœ์ ํŠธํŒ€', + '์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋งˆ๋ผํ†ค', + 'UI/UX ๋””์ž์ธ ์Šคํ„ฐ๋””', + '๋ฐ์ดํ„ฐ ์‚ฌ์ด์–ธ์Šค ํด๋Ÿฝ', +]; + +const GROUP_LIST = [ + { + title: '์Šคํ„ฐ๋””1', + deadline: '2025-05-24', + startDate: '2025-05-20', + endDate: '2025-05-24', + maxParticipants: 10, + description: + '<h1 class="text-xl font-bold capitalize text-green-700" levels="2">Next.js ์Šคํ„ฐ๋””</h1><ul class="list-disc ml-2"><li><p>์˜จ๋ผ์ธ์œผ๋กœ ์ง„ํ–‰</p></li><li><p>์˜คํ›„ 7์‹œ</p></li></ul><p></p><p>โ€”์™„๋ฃŒโ€”</p><h2 class="text-xl font-bold capitalize" levels="2"></h2>', + position: [1, 3], + skills: [1, 2], + createdAt: '2025-05-20', + type: 'study', + autoAllow: true, + host: { + userId: 'abcd123', + nickname: '์‚ฌ์šฉ์ž1', + profileImage: 'https://github.com/shadcn.png', + email: 'qwerty@gmail.com', + }, + isApplicant: false, + isBookmark: false, + participants: [ + { + userId: 'abcd1', + nickname: 'ํŒ€์›1', + profileImage: null, + email: 'member1@gmail.com', + }, + { + userId: 'abcd12', + nickname: null, + profileImage: 'https://github.com/shadcn.png', + email: 'member2@gmail.com', + }, + { + userId: 'abcd123', + nickname: 'ํŒ€์›3', + profileImage: 'https://github.com/shadcn.png', + email: 'member3@gmail.com', + }, + { + userId: 'abcd1234', + nickname: 'ํŒ€์›4', + profileImage: 'https://github.com/shadcn.png', + email: 'member4@gmail.com', + }, + { + userId: 'abcd1235', + nickname: null, + profileImage: 'https://github.com/shadcn.png', + email: 'member5@naver.com', + }, + ], + }, +]; + +export const groupsHandlers = [ + http.get(`${process.env.NEXT_PUBLIC_API_BASE_URL}/groups`, ({ request }) => { + const url = new URL(request.url); + + const typeParam = url.searchParams.get('type'); + const skillParam = url.searchParams.get('skill'); + const skillNumber = skillParam ? Number(skillParam) : null; + const positionParam = url.searchParams.get('position'); + const positionNumber = positionParam ? Number(positionParam) : null; + const searchKeyword = url.searchParams.get('search')?.toLowerCase() ?? ''; + const sortParam = url.searchParams.get('sort') as GroupSort | null; + const orderParam = url.searchParams.get('order') as Order | null; + + const cursorParam = url.searchParams.get('cursor'); + const cursor = cursorParam ? Number(cursorParam) : 0; + const limit = 10; // ํŽ˜์ด์ง€๋‹น ์•„์ดํ…œ ์ˆ˜ + + const allItems: Group[] = Array.from({ length: 100 }, (_, index) => { + const offset = index * 2; + const baseDate = new Date(2025, 4, 26); + const createdAt = addDays(baseDate, offset); + const deadline = addDays(baseDate, offset + 1).toISOString(); + const startDate = addDays(baseDate, offset + 5).toISOString(); + const endDate = addDays(baseDate, offset + 10).toISOString(); + + const positions = getRandomItems( + positionKeys, + Math.floor(Math.random() * 3) + 1, + ).map((key) => Position[key]); + const skills = getRandomItems( + skillKeys, + Math.floor(Math.random() * 3) + 1, + ).map((key) => Skill[key]); + const type = getRandomItem(groupTypeValues.slice(0, 2)); + + const maxParticipants = Math.floor(Math.random() * (30 - 2 + 1)) + 2; + const participants = Array.from( + { length: Math.floor(Math.random() * maxParticipants) }, + () => ({ + userId: 1, + nickname: '๋ชจ์—ฌ๋ผ์ž‡์œ ์ €', + profileImage: null, + email: 'user@yopmail.com', + }), + ); + + const title = titles[index % titles.length]; + + return { + id: index + 1, + title, + description: `<h2 class="text-xl font-bold capitalize">${title} ๋ชจ์ง‘ํ•ฉ๋‹ˆ๋‹ค</h2><p>๋ชจ๋‘ ์ฆ๊ฒ๊ฒŒ ๊ณต๋ถ€ํ•ด์š”!</p>`, + position: positions, + skills: skills, + participants, + maxParticipants, + autoAllow: true, + isBookmark: false, + createdAt, + deadline, + startDate, + endDate, + type, + }; + }); + + const typeFiltered = typeParam + ? allItems.filter((item) => item.type === typeParam) + : allItems; + const skillFiltered = + skillNumber !== null + ? typeFiltered.filter((item) => item.skills.includes(skillNumber)) + : typeFiltered; + const positionFiltered = + positionNumber !== null + ? skillFiltered.filter((item) => item.position.includes(positionNumber)) + : skillFiltered; + const searchFiltered = searchKeyword + ? positionFiltered.filter((item) => + item.title.toLowerCase().includes(decodeURIComponent(searchKeyword)), + ) + : positionFiltered; + + const sortedItems = [...searchFiltered].sort((a, b) => { + if (!sortParam) return 0; + const aDate = new Date(a[sortParam]); + const bDate = new Date(b[sortParam]); + if (orderParam === 'asc') return aDate.getTime() - bDate.getTime(); + if (orderParam === 'desc') return bDate.getTime() - aDate.getTime(); + return 0; + }); + + const paginatedItems = sortedItems.slice(cursor, cursor + limit); + const nextCursor = + cursor + limit < sortedItems.length ? cursor + limit : null; + + return HttpResponse.json({ + items: paginatedItems, + hasNext: nextCursor !== null, + cursor: nextCursor, + }); + }), + http.post(`${process.env.NEXT_PUBLIC_API_BASE_URL}/groups`, () => { + return HttpResponse.json({ + success: true, + }); + }), + http.get(`${process.env.NEXT_PUBLIC_API_BASE_URL}/groups/:id`, () => { + return HttpResponse.json(GROUP_LIST[0]); + }), + http.patch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/bookmark`, + async ({ request }) => { + const body = (await request.json()) as { + id: number; + isBookmark: boolean; + }; + const { id, isBookmark } = body; + + GROUP_LIST[0].isBookmark = isBookmark; + + if (id === 2) { + return HttpResponse.json({}, { status: 400 }); + } + + return HttpResponse.json({}); + }, + ), + http.delete(`${process.env.NEXT_PUBLIC_API_BASE_URL}/groups/:id`, () => { + return HttpResponse.json({}); + }), +]; diff --git a/src/mocks/handler/notifications.ts b/src/mocks/handler/notifications.ts new file mode 100644 index 00000000..032234ae --- /dev/null +++ b/src/mocks/handler/notifications.ts @@ -0,0 +1,60 @@ +import { Notification } from '@/types'; +import { eNotification } from '@/types/enums'; +import { InfiniteResponse } from '@/types/response'; +import { http, HttpResponse } from 'msw'; + +export const NOTIFICATIONS = [ + { + id: 9999, + message: '์•Œ๋ฆผ ๋ฉ”์‹œ์ง€1', + isRead: false, + createdAt: '2025-05-20', + type: eNotification.GROUP_HAS_PARTICIPANT, + url: 'http://localhost:3000/groups/1', + }, + { + id: 9998, + message: '์•Œ๋ฆผ ๋ฉ”์‹œ์ง€2', + isRead: true, + createdAt: '2025-05-20', + type: eNotification.FOLLOWER_ADDED, + }, +]; + +export const notificationsHandlers = [ + http.get( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/notifications`, + ({ request }) => { + const url = new URL(request.url); + const searchParams = new URLSearchParams(url.search); + const isRead = searchParams.get('isRead'); + + const filteredNotifications = NOTIFICATIONS.filter((notification) => { + return isRead === 'false' ? !notification.isRead : true; + }).map((notification) => ({ + ...notification, + createdAt: new Date(notification.createdAt), + url: notification.url ?? null, + })); + + return HttpResponse.json<InfiniteResponse<Notification>>({ + status: { + code: 200, + message: 'success', + success: true, + }, + data: filteredNotifications, + hasNext: false, + cursor: 0, + }); + }, + ), + http.get( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/notifications/unread-count`, + () => { + return HttpResponse.json({ + unreadCount: 1, + }); + }, + ), +]; diff --git a/src/mocks/handler/rating.ts b/src/mocks/handler/rating.ts new file mode 100644 index 00000000..b2ce6cf7 --- /dev/null +++ b/src/mocks/handler/rating.ts @@ -0,0 +1,51 @@ +import { http, HttpResponse } from 'msw'; + +type RatingBody = { + targetUserId?: number; + rate: number; +} + +export const ratingHandlers = [ + http.post('http://localhost:4000/api/rating', async ({ request }) => { + const body = await request.json() as RatingBody; + const { rate } = body; + + if (rate > 5) { + return HttpResponse.json({ + result: { + success: false, + message: 'ํ‰์ ์€ 5์ ์„ ์ดˆ๊ณผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.' + } + }); + } + + return HttpResponse.json({ + result: { + success: true, + }, + }); + }), + + http.patch('http://localhost:4000/api/ratings/:id', async ({ request, params }) => { + const body = await request.json() as RatingBody; + const { rate } = body; + const { id } = params; + + console.log('๋ฐ›์€ ํ‰์  ์ˆ˜์ • ์š”์ฒญ:', { id, rate }); + + if (rate > 5) { + return HttpResponse.json({ + result: { + success: false, + message: 'ํ‰์ ์€ 5์ ์„ ์ดˆ๊ณผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.' + } + }); + } + + return HttpResponse.json({ + result: { + success: true, + }, + }); + }), +]; diff --git a/src/mocks/handler/replies.ts b/src/mocks/handler/replies.ts new file mode 100644 index 00000000..8e838c9b --- /dev/null +++ b/src/mocks/handler/replies.ts @@ -0,0 +1,237 @@ +import { Reply } from '@/types'; +import { http, HttpResponse } from 'msw'; + +const REPLY_LIST: Reply[] = Array.from({ length: 33 }, (_, i) => ({ + replyId: i + 1, + content: `๋Œ“๊ธ€ ${i + 1}`, + writer: { + userId: i + 1, + nickname: `w${i + 1}`, + profileImage: null, + email: `w${i + 1}@gmail.com`, + }, + createdAt: '2025-05-31T07:22:02.678Z', + deleted: false, +})); + +const REREPLY_LIST: (Reply & { parentId: number })[] = Array.from( + { length: 500 }, + (_, i) => ({ + replyId: i + 1 + 10000, + parentId: (i % REPLY_LIST.length) + 1, + content: `๋Œ€๋Œ“๊ธ€ ${i + 1}`, + writer: { + userId: i + 1, + nickname: `q${i + 1}`, + profileImage: null, + email: `q${i + 1}@gmail.com`, + }, + createdAt: '2025-06-10T10:39:05.678Z', + deleted: false, + }), +); + +export const repliesHandlers = [ + // ๋Œ“๊ธ€ ๋ฌดํ•œ ์Šคํฌ๋กค๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ + http.get( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies`, + ({ request }) => { + const url = new URL(request.url); + const cursorParam = url.searchParams.get('cursor'); + const sizeParam = url.searchParams.get('size'); + + const cursor = cursorParam ? parseInt(cursorParam, 10) : 0; + const size = sizeParam ? parseInt(sizeParam, 10) : 10; + + const foundIndex = REPLY_LIST.findIndex( + (item) => item.replyId === cursor, + ); + const startIndex = + cursor === 0 ? 0 : foundIndex >= 0 ? foundIndex + 1 : REPLY_LIST.length; + + const paginatedItems = REPLY_LIST.slice(startIndex, startIndex + size); + const nextCursor = + startIndex + size < REPLY_LIST.length + ? paginatedItems.at(-1)?.replyId + : null; + const hasNext = nextCursor !== null; + + return HttpResponse.json({ + items: paginatedItems, + cursor: nextCursor, + hasNext, + }); + }, + ), + // ๋Œ€๋Œ“๊ธ€ ๋ฌดํ•œ ์Šคํฌ๋กค๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ + http.get( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies/:replyId`, + ({ request, params }) => { + const url = new URL(request.url); + const cursorParam = url.searchParams.get('cursor'); + const sizeParam = url.searchParams.get('size'); + + const cursor = cursorParam ? parseInt(cursorParam, 10) : 0; + const size = sizeParam ? parseInt(sizeParam, 10) : 10; + const parentId = Number(params.replyId); // ๋ถ€๋ชจ ๋Œ“๊ธ€ ID + + // ํ•ด๋‹น ๋ถ€๋ชจ ๋Œ“๊ธ€์— ๋‹ฌ๋ฆฐ ๋Œ€๋Œ“๊ธ€๋งŒ ํ•„ํ„ฐ๋ง + const filtered = REREPLY_LIST.filter( + (item) => item.parentId === parentId, + ); + + const foundIndex = filtered.findIndex((item) => item.replyId === cursor); + const startIndex = + cursor === 0 ? 0 : foundIndex >= 0 ? foundIndex + 1 : filtered.length; + + const paginatedItems = filtered.slice(startIndex, startIndex + size); + const nextCursor = + startIndex + size < filtered.length + ? paginatedItems.at(-1)?.replyId + : null; + const hasNext = nextCursor !== null; + + return HttpResponse.json({ + items: paginatedItems, + cursor: nextCursor, + hasNext, + }); + }, + ), + // ๋Œ“๊ธ€ ์ถ”๊ฐ€ + http.post( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies`, + async ({ request }) => { + const body = (await request.json()) as { content: string }; + const { content } = body; + + const newId = REPLY_LIST.length + 1; + const newReplyData = { + replyId: newId, + content, + writer: { + userId: newId, + nickname: null, + profileImage: null, + email: `w${newId}@gmail.com`, + }, + createdAt: '2025-05-23', + deleted: false, + }; + + REPLY_LIST.push(newReplyData); + + return HttpResponse.json({ + replyId: newReplyData.replyId, + }); + }, + ), + // ๋Œ€๋Œ“๊ธ€ ์ถ”๊ฐ€ + http.post( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies/:replyId`, + async ({ request, params }) => { + const body = (await request.json()) as { content: string }; + const { content } = body; + + const parentId = Number(params.replyId); + const newId = REREPLY_LIST.length + 1 + 10000; + const newRereplyData = { + replyId: newId, + parentId, + content, + writer: { + userId: newId, + nickname: `n${newId}`, + profileImage: null, + email: `n${newId}@gmail.com`, + }, + createdAt: '2025-05-23', + deleted: false, + }; + + REREPLY_LIST.push(newRereplyData); + + return HttpResponse.json({ + parentId: 10, + replyId: newRereplyData.replyId, + }); + }, + ), + // ๋Œ“๊ธ€, ๋Œ€๋Œ“๊ธ€ ์ˆ˜์ • + http.patch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies/:replyId`, + async ({ request, params }) => { + const body = (await request.json()) as { content: string }; + const { content } = body; + + const replyId = Number(params.replyId); + + // ๋Œ“๊ธ€ ์ˆ˜์ • + const reply = REPLY_LIST.find((item) => item.replyId === replyId); + + if (reply) { + reply.content = content; + + return HttpResponse.json({}); + } + + // ๋Œ€๋Œ“๊ธ€ ์ˆ˜์ • + const rereply = REREPLY_LIST.find((item) => item.replyId === replyId); + + if (rereply) { + rereply.content = content; + return HttpResponse.json({}); + } + + return new HttpResponse('ํ•ด๋‹น ๋Œ“๊ธ€์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', { status: 404 }); + }, + ), // ๋Œ“๊ธ€, ๋Œ€๋Œ“๊ธ€ ์‚ญ์ œ + http.delete( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v2/groups/:groupId/replies/:replyId`, + async ({ params }) => { + const replyId = Number(params.replyId); + + // ๋Œ“๊ธ€ ์‚ญ์ œ + const replyIdx = REPLY_LIST.findIndex((item) => item.replyId === replyId); + + if (replyIdx !== -1) { + // ๋Œ€๋Œ“๊ธ€์ด ํ•˜๋‚˜๋„ ์—†์œผ๋ฉด ๋Œ“๊ธ€ ์™„์ „ ์‚ญ์ œ, ๋‹ฌ๋ ค ์žˆ์œผ๋ฉด content์™€ isDeleted๋งŒ ๋ณ€๊ฒฝ + if (REREPLY_LIST.some((item) => item.parentId === replyId)) { + REPLY_LIST[replyIdx].content = '์‚ญ์ œ๋œ ๋Œ“๊ธ€์ž…๋‹ˆ๋‹ค.'; + REPLY_LIST[replyIdx].deleted = true; + } else { + REPLY_LIST.splice(replyIdx, 1); + } + + return HttpResponse.json({}); + } + + // ๋Œ€๋Œ“๊ธ€ ์‚ญ์ œ + const rereplyIdx = REREPLY_LIST.findIndex( + (item) => item.replyId === replyId, + ); + + if (rereplyIdx !== -1) { + const parentReplyIdx = REPLY_LIST.findIndex( + (item) => item.replyId === REREPLY_LIST[rereplyIdx].parentId, + ); + + // ๋Œ€๋Œ“๊ธ€ ์‚ญ์ œ + REREPLY_LIST.splice(rereplyIdx, 1); + + // ๋ถ€๋ชจ ๋Œ“๊ธ€๋„ ์‚ญ์ œ๋œ ์ƒํƒœ๋ฉด ๋ถ€๋ชจ ๋Œ“๊ธ€๋„ ์™„์ „ ์‚ญ์ œ + if ( + REPLY_LIST[parentReplyIdx].deleted && + REREPLY_LIST.some((item) => item.parentId === replyId) + ) { + REPLY_LIST.splice(replyIdx, 1); + } + + return HttpResponse.json({}); + } + + // ๋ชป ์ฐพ์œผ๋ฉด 404 + return new HttpResponse('ํ•ด๋‹น ๋Œ“๊ธ€์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', { status: 404 }); + }, + ), +]; diff --git a/src/mocks/handler/user.ts b/src/mocks/handler/user.ts new file mode 100644 index 00000000..da9f6d08 --- /dev/null +++ b/src/mocks/handler/user.ts @@ -0,0 +1,114 @@ +import { http, HttpResponse } from 'msw'; +import { User } from '@/types'; +import { Position, Skill } from '@/types/enums'; +import { Group, GroupType } from '@/types'; + +export const userHandlers = [ + http.get( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/user/:id`, + ({ params }) => { + const { id } = params; + + if (id === '10') { + return HttpResponse.json(null); + } + + return HttpResponse.json<User>({ + userId: 1, + email: 'test@test.com', + nickname: 'ํ…Œ์ŠคํŠธ๋‹‰๋„ค์ž„', + profileImage: 'https://github.com/shadcn.png', + position: Position.FE, + skills: [Skill.Java, Skill.JavaScript, Skill.Spring], + rate: 4.5, + isFollowing: false, + isFollower: false, + }); + }, + ), + + http.delete(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/user/delete`, () => { + if (Math.trunc(Math.random() * 100) % 2) { + return HttpResponse.json({ + success: true, + message: 'ํšŒ์› ํƒˆํ‡ด ์™„๋ฃŒ', + }); + } + return HttpResponse.json({ + success: false, + message: '์—๋Ÿฌ ๋ฉ”์‹œ์ง€', + }); + }), + + http.patch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/user/password`, + async ({ request }) => { + const body = (await request.json()) as { + newPassword: string; + confirmPassword: string; + }; + console.log(body?.newPassword, body?.confirmPassword); + return new HttpResponse(null, { + status: Math.trunc(Math.random() * 100) % 2 === 0 ? 200 : 500, + }); + }, + ), + + http.patch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/user/edit`, + async ({ params, request }) => { + const body = await request.formData(); + const nickname = body.get('nickname') as string; + const position = JSON.parse(body.get('position') as string) as Position; + const skills = JSON.parse(body.get('skills') as string) as Skill[]; + const file = body.get('file') as File; + + const imgSrc = file + ? 'https://github.com/shadcn.png' + : 'https://media.istockphoto.com/id/1337144146/vector/default-avatar-profile-icon-vector.jpg?s=612x612&w=0&k=20&c=BIbFwuv7FxTWvh5S3vB6bkT0Qv8Vn8N5Ffseq84ClGI='; + + return HttpResponse.json<User>({ + userId: Number(params.id), + nickname, + position, + skills, + profileImage: imgSrc, + email: 'me@example.com', + isFollowing: false, + isFollower: false, + rate: 4.5, + }); + }, + ), + + http.get( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/user/groups`, + ({ request }) => { + const url = new URL(request.url); + console.log(url); + + const items: Group[] = Array.from({ length: 10 }, () => ({ + id: Math.floor(Math.random() * 1000000) + 1, + title: '์Šคํ„ฐ๋””1', + deadline: new Date('2025-05-22').toISOString(), + startDate: new Date('2025-05-20').toISOString(), + endDate: new Date('2025-05-24').toISOString(), + maxParticipants: 10, + participants: [], + description: '์Šคํ„ฐ๋””1 ์„ค๋ช…', + position: [1, 3], + skills: [1, 2], + createdAt: new Date('2025-05-20'), + type: GroupType.STUDY, + autoAllow: true, + isBookmark: false, + })); + + return HttpResponse.json({ + hasNext: true, + items, + cursor: 10, + }); + }, + ), +]; diff --git a/src/mocks/handler/ws.ts b/src/mocks/handler/ws.ts new file mode 100644 index 00000000..937efd93 --- /dev/null +++ b/src/mocks/handler/ws.ts @@ -0,0 +1,107 @@ +import { Server } from 'mock-socket'; +import { eNotification } from '@/types/enums'; + +let mockServer: Server | null = null; + +const getRandomNotificationType = () => { + const enumLength = Object.keys(eNotification).length / 2; // enum์€ ํ‚ค์™€ ๊ฐ’์ด ์–‘๋ฐฉํ–ฅ์œผ๋กœ ๋งคํ•‘๋˜์–ด ์‹ค์ œ ๊ธธ์ด์˜ 2๋ฐฐ + return Math.floor(Math.random() * enumLength); +}; + +const createMockNotification = (id: number) => { + const type = getRandomNotificationType(); + console.log('type', type); + let message = `์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ${id}`; + let url = '/v2/groups/1'; // ๊ธฐ๋ณธ URL + + switch (type) { + case eNotification.GROUP_HAS_PARTICIPANT: + message = '์ƒˆ๋กœ์šด ์ฐธ๊ฐ€์ž๊ฐ€ ๊ทธ๋ฃน์— ์ฐธ์—ฌํ–ˆ์Šต๋‹ˆ๋‹ค.'; + url = '/v2/groups/1/participants'; + break; + case eNotification.CONFIRMED_PARTICIPANT_CANCELED: + message = 'ํ™•์ •๋œ ์ฐธ๊ฐ€์ž๊ฐ€ ์ทจ์†Œํ–ˆ์Šต๋‹ˆ๋‹ค.'; + url = '/v2/groups/1'; + break; + case eNotification.APPLY_APPROVED: + message = '๋ชจ์ž„ ์ฐธ๊ฐ€ ์‹ ์ฒญ์ด ์Šน์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'; + url = '/groups/1'; + break; + case eNotification.APPLY_REJECTED: + message = '๋ชจ์ž„ ์ฐธ๊ฐ€ ์‹ ์ฒญ์ด ๊ฑฐ์ ˆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'; + break; + case eNotification.FULL_CAPACITY: + message = '๋ชจ์ž„ ์ •์›์ด ๋งˆ๊ฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'; + url = '/groups/1'; + break; + case eNotification.FOLLOWER_ADDED: + message = '์ƒˆ๋กœ์šด ํŒ”๋กœ์›Œ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'; + url = '/users/1'; + break; + case eNotification.APPLY_CANCELED: + message = '๋ชจ์ž„ ์‹ ์ฒญ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'; + break; + case eNotification.COMMENT_RECEIVED: + message = '์ƒˆ๋กœ์šด ๋Œ“๊ธ€์ด ๋‹ฌ๋ ธ์Šต๋‹ˆ๋‹ค.'; + url = '/groups/1#comment-1'; + break; + case eNotification.FOLLOWER_CREATE_GROUP: + message = 'ํŒ”๋กœ์šฐํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ƒˆ ๋ชจ์ž„์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.'; + url = '/groups/1'; + break; + case eNotification.REJECTED_GROUP: + message = '๋ชจ์ž„ ์‹ ์ฒญ์ด ๊ฑฐ์ ˆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'; + break; + case eNotification.WITHIN_24_HOUR: + message = '๋ถ๋งˆํฌํ•œ ๋ชจ์ž„์ด 24์‹œ๊ฐ„ ํ›„์— ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.'; + url = '/groups/1'; + break; + } + + return { + type, + notification: { + id, + message, + url, + isRead: false, + createdAt: new Date().toISOString(), + }, + }; +}; + +// ๋ชฉ์„œ๋ฒ„ ์ƒ์„ฑ +export const initMockSocket = () => { + if (mockServer) { + console.warn('mock ์„œ๋ฒ„ ์ด๋ฏธ ์‹คํ–‰๋จ'); + return; + } + + mockServer = new Server('ws://localhost:8080'); + + mockServer.on('connection', socket => { + console.log('mock ์—ฐ๊ฒฐ๋จ'); + let notificationId = 1; + + // ์ดˆ๊ธฐ ์•Œ๋ฆผ ์ „์†ก + setTimeout(() => { + socket.send(JSON.stringify(createMockNotification(notificationId++))); + }, 2000); + + // ์ฃผ๊ธฐ์ ์œผ๋กœ ์•Œ๋ฆผ ์ „์†ก (5-15์ดˆ ๋žœ๋ค ๊ฐ„๊ฒฉ) + const sendRandomNotification = () => { + const randomDelay = Math.floor(Math.random() * (15000 - 5000) + 5000); + setTimeout(() => { + socket.send(JSON.stringify(createMockNotification(notificationId++))); + sendRandomNotification(); + }, randomDelay); + }; + + sendRandomNotification(); + + // socket.on('message', data => { + // console.log('์„œ๋ฒ„ ์ˆ˜์‹ :', data); + // socket.send('echo: ' + data); + // }); + }); +}; \ No newline at end of file diff --git a/src/mocks/index.ts b/src/mocks/index.ts new file mode 100644 index 00000000..9a93f176 --- /dev/null +++ b/src/mocks/index.ts @@ -0,0 +1,13 @@ +export async function initMocks() { + if (process.env.NODE_ENV !== 'development') return; + console.log('initMocks'); + if (typeof window === 'undefined') { + // ์„œ๋ฒ„ ์‚ฌ์ด๋“œ + const { server } = await import('./server'); + server.listen(); + } else { + // ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ + const { worker } = await import('./browser'); + await worker.start(); + } +} diff --git a/src/mocks/server.ts b/src/mocks/server.ts new file mode 100644 index 00000000..0859d980 --- /dev/null +++ b/src/mocks/server.ts @@ -0,0 +1,20 @@ +import { setupServer } from 'msw/node'; +import { applicationsHandlers } from './handler/applications'; +import { authenticationsHandlers } from './handler/authentications'; +import { followHandlers } from './handler/follow'; +import { groupsHandlers } from './handler/groups'; +import { notificationsHandlers } from './handler/notifications'; +import { ratingHandlers } from './handler/rating'; +import { repliesHandlers } from './handler/replies'; +import { userHandlers } from './handler/user'; + +export const server = setupServer( + ...groupsHandlers, + ...notificationsHandlers, + ...followHandlers, + ...authenticationsHandlers, + ...repliesHandlers, + ...userHandlers, + ...ratingHandlers, + ...applicationsHandlers, +); diff --git a/src/providers/MSWComponent.tsx b/src/providers/MSWComponent.tsx new file mode 100644 index 00000000..fa6e3798 --- /dev/null +++ b/src/providers/MSWComponent.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { initMocks } from '@/mocks'; +import { useEffect, useState } from 'react'; + +export const MSWComponent = ({ children }: { children: React.ReactNode }) => { + const [mswReady, setMswReady] = useState(false); + useEffect(() => { + const init = async () => { + await initMocks(); + setMswReady(true); + }; + + if (!mswReady) { + init(); + } + }, [mswReady]); + + if (!mswReady) { + return null; + } + return <>{children}</>; +}; diff --git a/src/providers/ReactQueryProvider.tsx b/src/providers/ReactQueryProvider.tsx new file mode 100644 index 00000000..a6dae7a3 --- /dev/null +++ b/src/providers/ReactQueryProvider.tsx @@ -0,0 +1,46 @@ +// In Next.js, this file would be called: app/providers.tsx +'use client'; + +import { + isServer, + QueryClient, + QueryClientProvider, +} from '@tanstack/react-query'; + +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; + +function makeQueryClient() { + return new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, // ๊ธฐ๋ณธ ์บ์‹ฑ ์‹œ๊ฐ„(1๋ถ„) + }, + }, + }); +} + +let browserQueryClient: QueryClient | undefined = undefined; + +export function getQueryClient() { + if (isServer) { + return makeQueryClient(); // ์„œ๋ฒ„์—์„œ ์‹คํ–‰ ์ค‘์ธ ๊ฒฝ์šฐ ์ƒˆ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋ฐ˜ํ™˜ + } else { + if (!browserQueryClient) browserQueryClient = makeQueryClient(); // ๋ธŒ๋ผ์šฐ์ €์—์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ์ƒˆ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑ + return browserQueryClient; // ๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋ฐ˜ํ™˜ + } +} + +export function ReactQueryProvider({ + children, +}: { + children: React.ReactNode; +}) { + const queryClient = getQueryClient(); + + return ( + <QueryClientProvider client={queryClient}> + {children} + <ReactQueryDevtools /> + </QueryClientProvider> + ); +} diff --git a/src/providers/WSProvider.tsx b/src/providers/WSProvider.tsx new file mode 100644 index 00000000..a1549d4d --- /dev/null +++ b/src/providers/WSProvider.tsx @@ -0,0 +1,90 @@ +// providers/WebSocketProvider.tsx +'use client'; + +import useAuthStore from '@/stores/useAuthStore'; +import useNotificationStore from '@/stores/useNotificationStore'; +import { eNotification } from '@/types/enums'; +import { createContext, useContext, useEffect, useState } from 'react'; +import { Socket, io } from 'socket.io-client'; + +type SocketMessage = { + id: number; + message: string; + notificationType: keyof typeof eNotification; + targetUserId: number; + url: string; +}; + +const SocketContext = createContext<Socket | null>(null); + +export const SocketProvider = ({ children }: { children: React.ReactNode }) => { + const [socket, setSocket] = useState<Socket | null>(null); + const user = useAuthStore((state) => state.user); + + const { addNotification } = useNotificationStore(); + + useEffect(() => { + if (!user) { + socket?.disconnect(); + setSocket(null); + return; + } + const newSocket = io(`${process.env.NEXT_PUBLIC_SOCKET_URL}`, { + withCredentials: true, + autoConnect: false, + transports: ['websocket', 'polling'], + }); + + // ์—ฐ๊ฒฐ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ + newSocket.on('connect', () => { + console.log('socket.io ์—ฐ๊ฒฐ๋จ', user.userId); + // ๋กœ๊ทธ์ธ ์ด๋ฒคํŠธ์— ์ฝœ๋ฐฑ ์ถ”๊ฐ€ + newSocket.emit('login', user.userId); + newSocket.on('loginSuccess', (response: { userId: number }) => { + console.log('์„œ๋ฒ„ ์‘๋‹ต:', response.userId); + if (response.userId !== user.userId) { + newSocket.disconnect(); + setSocket(null); + return; + } + }); + }); + + // ๋ฉ”์‹œ์ง€ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ - connect ๋ฐ–์—์„œ ๋“ฑ๋ก + newSocket.on('notification', (message: SocketMessage) => { + addNotification({ + id: Math.random(), + message: message.message, + isRead: false, + createdAt: new Date(), + type: eNotification[ + message.notificationType as keyof typeof eNotification + ], + url: message.url, + }); + console.log('messageC ์ด๋ฒคํŠธ ๋ฐœ์ƒ!', message); + console.log('store', useNotificationStore.getState().notifications); + + console.log('๋ฐ›์€ ๋ฉ”์‹œ์ง€ ์ƒ์„ธ:', { + notificationType: message.notificationType, + targetUserId: message.targetUserId, + url: message.url, + }); + }); + + newSocket.connect(); + setSocket(newSocket); + + return () => { + newSocket.removeAllListeners(); // ๋ชจ๋“  ๋ฆฌ์Šค๋„ˆ ์ œ๊ฑฐ + newSocket.disconnect(); + }; + // eslint-disable-next-line + }, [user]); + + return ( + <SocketContext.Provider value={socket}>{children}</SocketContext.Provider> + ); +}; + +export const useSocket = () => useContext(SocketContext); diff --git a/src/stores/useAuthStore.ts b/src/stores/useAuthStore.ts new file mode 100644 index 00000000..87dd1995 --- /dev/null +++ b/src/stores/useAuthStore.ts @@ -0,0 +1,61 @@ +import { request } from '@/api/request'; +import { User } from '@/types'; +import { UserInfoResponse } from '@/types/response'; +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +export type UserStore = { + user: User | null; + setUser: (user: User) => void; + clearUser: () => void; + fetchAndSetUser: () => Promise<void>; +}; + +/** + * Zustand๋ฅผ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ „์—ญ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ํ›… + * - user: ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž ์ •๋ณด, ์ดˆ๊ธฐ๊ฐ’(๋กœ๊ทธ์ธ ์•ˆ๋œ ์ƒํƒœ)์€ null์ž…๋‹ˆ๋‹ค + * - setUser: ๋กœ๊ทธ์ธํ•œ ์œ ์ € ์ •๋ณด๋ฅผ user param์œผ๋กœ ๋ฐ›์•„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค + * !!ํ”„๋กœํ•„ ์ˆ˜์ •์‹œ setUser ํ•จ์ˆ˜๋กœ ์„ค์ •ํ•ด์ฃผ์…”์•ผ ํ•ฉ๋‹ˆ๋‹ค!! + * - clearUser: ํ˜„์žฌ ๋กœ๊ทธ์ธ ์œ ์ € ์ •๋ณด๋ฅผ null๋กœ ์„ค์ •ํ•˜์—ฌ ์ง€์›๋‹ˆ๋‹ค. + */ +const useAuthStore = create<UserStore>()( + persist( + (set) => ({ + user: null, + setUser: (user: User) => set({ user }), + clearUser: () => set({ user: null }), + fetchAndSetUser: async () => { + try { + const responseBody: UserInfoResponse = await request.get( + '/v1/user/info', + {}, + { + credentials: 'include', + }, + ); + + if (responseBody.status.success) { + set({ + user: { + //@ts-expect-error ๋ฐฑ์—”๋“œ์—์„œ ์ œ๊ณตํ•˜๋Š” ํƒ€์ž…์ด ์ด์ƒํ•ด์„œ ์ž„์‹œ๋กœ ์ฒ˜๋ฆฌ + userId: responseBody.items.items.id, + ...responseBody.items.items, + profileImage: responseBody.items.items.profileImage ?? '/images/default-profile.png', + }, + }); + } else { + throw new Error('์œ ์ € ์ •๋ณด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํŒจ'); + } + } catch (error) { + console.error('์œ ์ € ์ •๋ณด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํŒจ', error); + throw error; // ํ•„์š”์‹œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋กœ์ง ์ถ”๊ฐ€ + } + }, + }), + { + name: 'user-store', + }, + ), +); + +export default useAuthStore; diff --git a/src/stores/useNotificationStore.test.ts b/src/stores/useNotificationStore.test.ts new file mode 100644 index 00000000..e7172101 --- /dev/null +++ b/src/stores/useNotificationStore.test.ts @@ -0,0 +1,49 @@ +import { NOTIFICATIONS } from '@/mocks/handler/notifications'; +import { http, HttpResponse } from 'msw'; +import { server } from '@/mocks/server'; +import useNotificationStore from './useNotificationStore'; + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); + +describe('useNotificationStore ํ…Œ์ŠคํŠธ', () => { + it('clearAllNotifications: ์„œ๋ฒ„์— DELETE ์š”์ฒญ ํ›„ ์ƒํƒœ ์ดˆ๊ธฐํ™”', async () => { + server.use( + http.delete('/api/v1/notification', () => { + return HttpResponse.json({ result: { success: true } }); + }) + ); + + useNotificationStore.setState({ + notifications: NOTIFICATIONS.map(n => ({ + ...n, + createdAt: new Date(n.createdAt), + url: n.url ?? null + })), + unreadCount: 1, + }); + + await useNotificationStore.getState().clearAllNotifications(); + + const { notifications, unreadCount } = useNotificationStore.getState(); + expect(notifications).toEqual([]); + expect(unreadCount).toBe(0); + }); + + it('์ฝ์Œ ์ฒ˜๋ฆฌ ํ›„ ์•ˆ ์ฝ์€ ์•Œ๋ฆผ ๊ฐœ์ˆ˜ ๊ฐ์†Œ', () => { + useNotificationStore.setState({ + notifications: NOTIFICATIONS.map(n => ({ + ...n, + createdAt: new Date(n.createdAt), + url: n.url ?? null + })), + unreadCount: 1, + }); + + useNotificationStore.getState().setReadNotification(NOTIFICATIONS[0].id); + + const { unreadCount } = useNotificationStore.getState(); + expect(unreadCount).toBe(0); + }); +}); \ No newline at end of file diff --git a/src/stores/useNotificationStore.ts b/src/stores/useNotificationStore.ts new file mode 100644 index 00000000..83cd3673 --- /dev/null +++ b/src/stores/useNotificationStore.ts @@ -0,0 +1,68 @@ +import { create } from 'zustand'; +import { Notification } from '@/types'; +import { request } from '@/api/request'; + +export type NotificationState = { + notifications: Notification[]; + unreadCount: number; + } + +export type NotificationActions = { + init: () => void; + addNotification: (notification: Notification) => void; + setNotifications: (notifications: Notification[]) => void; + setReadNotification: (id: number) => void; + setUnreadCount: (count: number) => void; + clearAllNotifications: () => Promise<void>; +} + +type NotificationStore = NotificationState & NotificationActions; + +const useNotificationStore = create<NotificationStore>((set, get) => ({ + notifications: [], + unreadCount: 0, + addNotification: (notification: Notification) => { + const { notifications } = get(); + const exists = notifications.some(n => n?.id === notification.id); // ์ด๋ฏธ ๊ฐ™์€ ID์˜ ์•Œ๋ฆผ์ด ์žˆ๋Š”์ง€ ํ™•์ธ + + if (exists) return; // ์ด๋ฏธ ์กด์žฌํ•˜๋ฉด ์ƒํƒœ ๋ณ€๊ฒฝ ์—†์Œ + set((state) => ({ + notifications: [notification, ...state.notifications], + unreadCount: state.unreadCount + 1, + })); + }, + setNotifications: (notifications: Notification[]) => set(() => ({ + notifications, + })), + setReadNotification: async (id: number) => { + const { notifications } = get(); + await request.patch(`/v1/notification/${id}/read`, {},{}, { credentials: 'include' }); + set((state) => { + const updatedNotifications = notifications.map((notification: Notification) => ({ + ...notification, + isRead: notification.id === id ? true : notification.isRead, + })); + return { + unreadCount: state.unreadCount - 1, + notifications: updatedNotifications, + }; + }); + }, + setUnreadCount: (count: number) => set(() => ({ + unreadCount: count, + })), + clearAllNotifications: async () => { + console.log('clearAllNotifications'); + await fetch('/api/v1/notification', { method: 'DELETE' }); + set(() => ({ + notifications: [], + unreadCount: 0, + })); + }, + init: () => set(() => ({ + notifications: [], + unreadCount: 0, + })), +})); + +export default useNotificationStore; diff --git a/src/stores/useTargetReply.ts b/src/stores/useTargetReply.ts new file mode 100644 index 00000000..bc9a6616 --- /dev/null +++ b/src/stores/useTargetReply.ts @@ -0,0 +1,16 @@ +import { create } from 'zustand'; + +export type TargetReplyStore = { + targetReplyId: null | number; + targetRereplyId: null | number; + setTargetReply: (input: { + targetReplyId?: number | null; + targetRereplyId?: number | null; + }) => void; +}; + +export const useTargetReplyStore = create<TargetReplyStore>((set) => ({ + targetReplyId: null, + targetRereplyId: null, + setTargetReply: (input) => set((state) => ({ ...state, ...input })), +})); diff --git a/src/types/enums.ts b/src/types/enums.ts new file mode 100644 index 00000000..b59f5834 --- /dev/null +++ b/src/types/enums.ts @@ -0,0 +1,44 @@ +export enum Position { + 'PM', + 'PL', + 'AA', + 'TA', + 'DA', + 'QA', + 'FE', + 'BE', + 'FS', +} + +export enum Skill { + 'Java', + 'JavaScript', + 'HTML_CSS', + 'REACT', + 'Vue', + 'Kotlin', + 'Spring', +} + +/** ์•Œ๋ฆผ ํƒ€์ž… */ +export enum eNotification { + GROUP_HAS_PARTICIPANT, // ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๊ธ€์— ์ฐธ์—ฌ์ž ์ƒ๊น€ -> ์‹ ์ฒญ ์ˆ˜๋ฝ ํŽ˜์ด์ง€, ์ƒ์„ฑ์ž(๋ชจ์ž„ ์ƒ์„ฑ์ž)ํ•œํ…Œ ์•Œ๋žŒ, ๋ชจ์ž„ ID + CONFIRMED_PARTICIPANT_CANCELED, // ํ™•์ • ์ฐธ์—ฌ์ž ์ทจ์†Œ -> ์ƒ์„ธํŽ˜์ด์ง€, ์ƒ์„ฑ์žํ•œํ…Œ ์•Œ๋žŒ, ๋ชจ์ž„ ID + APPLY_APPROVED, // ๋‚ด๊ฐ€ ์‹ ์ฒญํ•œ ๋ชจ์ž„ ์Šน์ธ-> ์ƒ์„ธํŽ˜์ด์ง€, ์‹ ์ฒญ์žํ•œํ…Œ ์•Œ๋žŒ + APPLY_REJECTED, // ๋‚ด๊ฐ€ ์‹ ์ฒญํ•œ ๋ชจ์ž„ ๊ฑฐ์ ˆ -> x, ์‹ ์ฒญ์žํ•œํ…Œ ์•Œ๋žŒ + FULL_CAPACITY, // ๋ชจ์ž„ ์ •์› ๋งˆ๊ฐ-> ์ƒ์„ธํŽ˜์ด์ง€, ์ƒ์„ฑ์žํ•œํ…Œ ์•Œ๋žŒ + FOLLOWER_ADDED, // ํŒ”๋กœ์›Œ ์ถ”๊ฐ€๋œ ๊ฒฝ์šฐ ์•Œ๋žŒ -> ์œ ์ € ์ƒ์„ธ ํŽ˜์ด์ง€, ํŒ”๋กœ์›Œ๋œ ์‚ฌ๋žŒํ•œํ…Œ ์•Œ๋žŒ + APPLY_CANCELED, // ๋‚ด๊ฐ€ ์‹ ์ฒญํ•œ ๋ชจ์ž„ ์ทจ์†Œ -> x, ์‹ ์ฒญ์žํ•œํ…Œ ์•Œ๋žŒ + COMMENT_RECEIVED, // ๋Œ“๊ธ€ ๋‹ฌ๋ฆฐ ๊ฒฝ์šฐ -> ํ•ด๋‹น ํŽ˜์ด์ง€ ๋Œ“๊ธ€๋กœ ๋ฐ”๋กœ, ์ƒ์„ธํŽ˜์ด์ง€ id๋ž‘, ํ•ด๋‹น ๋Œ“๊ธ€ id ํ•„์š”, ์ƒ์„ฑ์žํ•œํ…Œ ์•Œ๋žŒ, ๋Œ€๋Œ“๊ธ€์˜ ๊ฒฝ์šฐ(์ƒ์„ฑ์ž, ์›๋Œ“๊ธ€์ž์—๊ฒŒ ์•Œ๋žŒ) + FOLLOWER_CREATE_GROUP, // ํŒ”๋กœ์›Œ๊ฐ€ ๋ชจ์ž„์„ ๋งŒ๋“  ๊ฒฝ์šฐ ์•Œ๋žŒ -> ์ƒ์„ธํŽ˜์ด์ง€, ํŒ”๋กœ์šฐํ•œ์‚ฌ๋žŒํ•œํ…Œ ์•Œ๋žŒ + REJECTED_GROUP, // ๋ชจ์ž„ ์‹ ์ฒญ์ด ๊ฑฐ์ ˆ๋œ ๊ฒฝ์šฐ, ์‹ ์ฒญ์žํ•œํ…Œ์•Œ๋žŒ + WITHIN_24_HOUR, // ๋ถ๋งˆํฌํ•œ ๋ชจ์ž„์ด 24์‹œ๊ฐ„ ๋‚จ์€ ๊ฒฝ์šฐ ์•Œ๋žŒ -> ์ƒ์„ธํŽ˜์ด์ง€, ๋ถ๋งˆํฌํ•œ ์‚ฌ๋žŒํ•œํ…Œ ์•Œ๋žŒ +} + +export function getPosition(position: Position): string { + return Position[position]; +} + +export function getSkill(skill: Skill): string { + return Skill[skill]; +} diff --git a/src/types/index.ts b/src/types/index.ts index c2d2409d..f02a4d03 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,9 +1,138 @@ //์ „์—ญ ํŒŒ์ผ ํƒ€์ž… ์ •๋ฆฌ - +import { eNotification, Position, Skill } from './enums'; export type User = { - id: string; - name: string; + userId: number; + nickname: string | null; email: string; - profileImage: string; -}; \ No newline at end of file + profileImage: string | null; + position: Position | null; + skills: Skill[] | null; + isFollowing: boolean; + isFollower: boolean; + rate: number; +}; + +export type UserSummary = Pick< + User, + 'userId' | 'nickname' | 'profileImage' | 'email' +>; + +export enum GroupType { + STUDY = 'study', + PROJECT = 'project', +} +export const GroupTypeName = { + study: '์Šคํ„ฐ๋””', + project: 'ํ”„๋กœ์ ํŠธ', +}; + +export type WriteFormWithCreatedAt = WriteForm & { createdAt: Date }; +/** ์ œ๊ณตํ•ด์ฃผ๋Š” ๊ธฐ๋ณธ skill์˜ ์ด๋ฆ„๋“ค. enum Skill๊ณผ ๋™๊ธฐํ™”๋˜์–ด์•ผ ํ•จ */ +export const DEFAULT_SKILL_NAMES = [ + 'Java', + 'JavaScript', + 'HTML_CSS', + 'REACT', + 'Vue', + 'Kotlin', + 'Spring', +] as const; +/** ์ œ๊ณตํ•ด์ฃผ๋Š” ๊ธฐ๋ณธ skill์˜ ์ด๋ฆ„๋“ค์˜ ํƒ€์ž…. UI์šฉ */ +export type DefaultSkillName = (typeof DEFAULT_SKILL_NAMES)[number]; +/** ์œ ์ €๊ฐ€ ์ž…๋ ฅํ•œ skill๋„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“  ํƒ€์ž…. UI์šฉ */ +export type SkillName = DefaultSkillName | string; + +/** ์ œ๊ณตํ•ด์ฃผ๋Š” ๊ธฐ๋ณธ position์˜ ์ด๋ฆ„๋“ค. enum Position๊ณผ ๋™๊ธฐํ™”๋˜์–ด์•ผ ํ•จ */ +export const DEFAULT_POSITION_NAMES = [ + 'PM', + 'PL', + 'AA', + 'TA', + 'DA', + 'QA', + 'FE', + 'BE', + 'FS', +] as const; +/** ์ œ๊ณตํ•ด์ฃผ๋Š” ๊ธฐ๋ณธ position์˜ ์ด๋ฆ„๋“ค์˜ ํƒ€์ž…. UI์šฉ */ +export type DefaultPositionName = (typeof DEFAULT_POSITION_NAMES)[number]; +/** ์œ ์ €๊ฐ€ ์ž…๋ ฅํ•œ skill๋„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“  ํƒ€์ž…. UI์šฉ */ +export type PositionName = DefaultPositionName | string; + +/** ๋ชจ์ž„ ๋งŒ๋“ค๊ธฐ ํผ์— ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ๋“ค์˜ ํƒ€์ž… */ +export type WriteForm = { + title: string; + maxParticipants: number; + deadline: Date; + startDate: Date; + endDate: Date; + description: string; + autoAllow: boolean; + type: GroupType; + skills: SkillName[]; + position: PositionName[]; +}; + +export type Group = { + id: number; + title: string; + deadline: string; + startDate: string; // ๋ชจ์ž„์˜ ์‹œ์ž‘์ผ + endDate: string; // ๋ชจ์ž„์˜ ์ข…๋ฃŒ์ผ + maxParticipants: number; + participants: UserSummary[]; + description: string; + position: Position[]; + skills: Skill[]; + createdAt: Date; + isBookmark: boolean; + autoAllow: boolean; + type: GroupType; +}; + +export type GroupSort = 'createdAt' | 'deadline'; + +export type Order = 'asc' | 'desc'; + +export type Notification = { + id: number; + message: string | null; // ์ถ”ํ›„ ์ฑ„ํŒ…์˜ ๊ฒฝ์šฐ ๊ฐ€์žฅ ๊ฐ„๋žตํ•œ ์ฑ„ํŒ…๋ฉ”์„ธ์ง€ ์ „๋‹ฌ์šฉ ํ˜„์žฌ๋Š” ๋นˆ๊ฐ’์œผ๋กœ ๋ฆฌํ„ด + content?: string | null; // TODO: socket, notification ํƒ€์ž… ํ†ต์ผ ํ›„ ์ œ๊ฑฐ + isRead: boolean; // ์ฝ์Œ ์—ฌ๋ถ€ default: false + read?: boolean; // TODO: socket, notification ํƒ€์ž… ํ†ต์ผ ํ›„ ์ œ๊ฑฐ + created_at?: Date; // ์•Œ๋žŒ ์ƒ์„ฑ๋‚ ์งœ + createdAt: Date; // ์•Œ๋žŒ ์ƒ์„ฑ๋‚ ์งœ + type: eNotification; + url: string | null; // ์—ฐ๊ฒฐ๋˜๋Š” url -> NotificationType์— ๋”ฐ๋ผ ํ•„์š”ํ•œ ๋ถ€๋ถ„ ๋‹ค๋ฆ„ +}; + +export type Reply = { + replyId: number; + content: string; + writer: UserSummary; + createdAt: string; + deleted: boolean; // ์‚ญ์ œ๋œ ๋Œ“๊ธ€์ธ์ง€ ์—ฌ๋ถ€ +}; + +export type GroupDetail = { + group: { + id: number; + title: string; + description: string; + autoAllow: boolean; + maxParticipants: number; + type: GroupType; + skills: Skill[]; + position: Position[]; + deadline: string; + startDate: string; + endDate: string; + createdAt: string; + participants: UserSummary[]; + isBookmark: boolean; + }; + host: UserSummary; + isApplicant: boolean; + isJoined: boolean; +}; diff --git a/src/types/response.ts b/src/types/response.ts new file mode 100644 index 00000000..fb247c5a --- /dev/null +++ b/src/types/response.ts @@ -0,0 +1,32 @@ +import { User } from '.'; + + +// ISSUE: ๋ฐฑ์—”๋“œ ์‘๋‹ต ํƒ€์ž… ์ˆ˜์ •ํ•„์š” +export type UserInfoResponse = { + status: { code: number; message: string; success: boolean }; + items: { + averageRating: number; + items: User; + }; + userId?: string; // ์ž„์‹œ ํ•„๋“œ, ๋ฐฑ์—”๋“œ ์‘๋‹ต ํƒ€์ž… ์ˆ˜์ • ํ›„ ์ œ๊ฑฐ +}; + +export type InfiniteResponse<T> = { + status: { + code: number; + message: string; + success: boolean; + }; + data: T[]; + hasNext: boolean; + cursor: number; +}; + +export type CommonResponse<T> = { + status: { + code: number; + message: string; + success: boolean; + }; + data: T; +}; diff --git a/src/utils/cookie.ts b/src/utils/cookie.ts new file mode 100644 index 00000000..aa8a8515 --- /dev/null +++ b/src/utils/cookie.ts @@ -0,0 +1,17 @@ +import { cookies } from 'next/headers'; + +export const getAuthCookieHeader = async () => { + const ACCESS_TOKEN_KEY = process.env.ACCESS_TOKEN ?? 'accessToken'; + const REFRESH_TOKEN_KEY = process.env.REFRESH_TOKEN ?? 'refreshToken'; + + const cookieStore = await cookies(); + const accessToken = cookieStore.get(ACCESS_TOKEN_KEY); + const refreshToken = cookieStore.get(REFRESH_TOKEN_KEY); + + return [ + accessToken && `${ACCESS_TOKEN_KEY}=${accessToken.value}`, + refreshToken && `${REFRESH_TOKEN_KEY}=${refreshToken.value}`, + ] + .filter(Boolean) + .join('; '); +}; diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts new file mode 100644 index 00000000..da0479ca --- /dev/null +++ b/src/utils/dateUtils.ts @@ -0,0 +1,69 @@ +import { + differenceInDays, + differenceInHours, + differenceInMinutes, + differenceInSeconds, + format, + isBefore, +} from 'date-fns'; + +/** ๋‚ ์งœ๋ฅผ 0000.00.00 ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜ */ +export const formatYearMonthDayWithDot = (utcTime: string | Date) => { + const date = typeof utcTime === 'string' ? new Date(utcTime) : utcTime; + return format(date, 'yyyy.MM.dd'); +}; + +/** ํ˜„์žฌ ๋‚ ์งœ๋ณด๋‹ค ์ด์ „๋‚ ์งœ์ธ์ง€ ํ™•์ธ */ +export const isBeforeToday = (utcTime: string) => { + const date = new Date(utcTime); + const now = new Date(); // ํ˜„์žฌ ์‹œ๊ฐ ํฌํ•จ + return isBefore(date, now); +}; + +/** + * UTC ๋ฌธ์ž์—ด์„ ๋ฐ›์•„ ํ˜„์žฌ ์‹œ๊ฐ๊ณผ ๋น„๊ตํ•˜์—ฌ ์ƒ๋Œ€ ์‹œ๊ฐ„ ๋˜๋Š” ๋‚ ์งœ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜ + * - 1๋ถ„ ๋ฏธ๋งŒ: n์ดˆ ์ „ + * - 1์‹œ๊ฐ„ ๋ฏธ๋งŒ: n๋ถ„ ์ „ + * - 1์ผ ๋ฏธ๋งŒ: n์‹œ๊ฐ„ ์ „ + * - 7์ผ ์ดํ•˜: n์ผ ์ „ + * - ๊ทธ ์ดํ›„: 'yyyy๋…„ M์›” d์ผ' ํ˜•์‹์œผ๋กœ ํ‘œ์‹œ + * @param utcTime UTC ISO ๋ฌธ์ž์—ด (์˜ˆ: '2025-05-27T08:00:00Z') + * @returns ์ƒ๋Œ€ ์‹œ๊ฐ„ ๋˜๋Š” 'yyyy๋…„ M์›” d์ผ' + */ +export const formatRelativeTime = (utcTime: string): string => { + const date = new Date(utcTime); + const now = new Date(); + + const seconds = Math.abs(differenceInSeconds(now, date)); + const minutes = Math.abs(differenceInMinutes(now, date)); + const hours = Math.abs(differenceInHours(now, date)); + const days = Math.abs(differenceInDays(now, date)); + + if (seconds < 60) return `${seconds}์ดˆ ์ „`; + if (minutes < 60) return `${minutes}๋ถ„ ์ „`; + if (hours < 24) return `${hours}์‹œ๊ฐ„ ์ „`; + if (days <= 7) return `${days}์ผ ์ „`; + + return format(date, 'yyyy๋…„ M์›” d์ผ'); +}; + +/** + * UTC ๋ฌธ์ž์—ด์„ "0000๋…„ 0์›” 0์ผ ์˜ค์ „/์˜คํ›„ 0:00" ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•ฉ์ˆ˜ + * + * ์˜ˆ: '2025-06-01T02:10:00Z' โ†’ '2025๋…„ 6์›” 1์ผ ์˜ค์ „ 11:10' + * + * @param utcTime - UTC ISO ๋ฌธ์ž์—ด (์˜ˆ: '2025-05-27T08:00:00Z') + * @returns ํ•œ๊ตญ์–ด ํฌ๋งท์˜ ๋‚ ์งœ ๋ฐ ์‹œ๊ฐ„ ๋ฌธ์ž์—ด + */ +export const formatDateTime = (utcTime: string): string => { + const date = new Date(utcTime); + + return new Intl.DateTimeFormat('ko-KR', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: '2-digit', + hour12: true, + }).format(date); +}; diff --git a/src/utils/fallback.ts b/src/utils/fallback.ts new file mode 100644 index 00000000..60fa7e98 --- /dev/null +++ b/src/utils/fallback.ts @@ -0,0 +1,22 @@ +/** + * ํ™”๋ฉด์— ํ‘œ์‹œํ•  ์‚ฌ์šฉ์ž ๋‹‰๋„ค์ž„์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + * ๋‹‰๋„ค์ž„์ด ์กด์žฌํ•˜๋ฉด ํ•ด๋‹น ๋‹‰๋„ค์ž„์„, ์ด๋ฉ”์ผ์˜ @ ์•ž๋ถ€๋ถ„์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + * @param nickname + * @param email + * @returns ํ™”๋ฉด ํ‘œ์‹œ์šฉ ์‚ฌ์šฉ์ž ๋‹‰๋„ค์ž„ + */ +export const getDisplayNickname = (nickname: string | null, email: string) => { + if(!email) return '์ด๋ฆ„ ์—†์Œ'; + return nickname || email.split('@')[0]; +}; + +/** + * ํ™”๋ฉด์— ํ‘œ์‹œํ•  ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + * ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๊ฐ€ ์กด์žฌํ•˜๋ฉด ํ•ด๋‹น ์ด๋ฏธ์ง€๋ฅผ, ์—†์œผ๋ฉด ๊ธฐ๋ณธ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + * + * @param profileImage - ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL + * @returns ํ™”๋ฉด ํ‘œ์‹œ์šฉ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL + */ +export const getDisplayProfileImage = (profileImage: string | null) => { + return profileImage || '/images/default-profile.png'; +}; diff --git a/src/utils/fileUtils.ts b/src/utils/fileUtils.ts index 6c9f3c21..766cc55c 100644 --- a/src/utils/fileUtils.ts +++ b/src/utils/fileUtils.ts @@ -1,2 +1,2 @@ -// ํŒŒ์ผ ์—…๋กœ๋“œ ๋กœ์ง -// ์œ ์ € ํ”„๋กœํ•„... \ No newline at end of file +// ํŒŒ์ผ ์—…๋กœ๋“œ ๋กœ์ง +// ์œ ์ € ํ”„๋กœํ•„... diff --git a/src/utils/flattenPages.ts b/src/utils/flattenPages.ts new file mode 100644 index 00000000..1718aa62 --- /dev/null +++ b/src/utils/flattenPages.ts @@ -0,0 +1,11 @@ +export type Page<T> = { + status: {code:number, message:string, success:boolean}, + data?:T[], + items?:T[], + hasNext: boolean, + cursor: number | null +}; + +export default function flattenPages<T>(pages: Page<T>[]) { + return pages.length ? pages.flatMap((page) => page.data || page.items || []) : []; +} diff --git a/src/utils/mockUtils.ts b/src/utils/mockUtils.ts new file mode 100644 index 00000000..8993f58d --- /dev/null +++ b/src/utils/mockUtils.ts @@ -0,0 +1,27 @@ +import { GroupType } from '@/types'; +import { Position, Skill } from '@/types/enums'; + +/** enum Position ํ‚ค */ +export const positionKeys = Object.keys(Position).filter((key) => + isNaN(Number(key)), +) as (keyof typeof Position)[]; + +/** enum Skill ํ‚ค */ +export const skillKeys = Object.keys(Skill).filter((key) => + isNaN(Number(key)), +) as (keyof typeof Skill)[]; + +/** enum Group ํ‚ค */ +export const groupTypeValues = Object.values(GroupType); + +/** ์•„์ดํ…œ์„ ๋žœ๋คํ•˜๊ฒŒ ์ •๋ ฌํ•ด์„œ ๋ฐ›๊ธฐ */ +export const getRandomItems = <T>(arr: T[], count: number): T[] => { + const mixedArr = [...arr].sort(() => 0.5 - Math.random()); // ๋žœ๋คํ•œ ์ˆซ์ž๋กœ ์ •๋ ฌ ์„ž๊ธฐ + + return mixedArr.slice(0, count); +}; + +/** ๋ฐฐ์—ด์—์„œ ๊ฐ’์„ ๋žœ๋คํ•˜๊ฒŒ ๋ฐ›๊ธฐ */ +export const getRandomItem = <T>(arr: T[]): T => { + return arr[Math.floor(Math.random() * arr.length)]; +}; diff --git a/src/utils/routes.ts b/src/utils/routes.ts new file mode 100644 index 00000000..3acd7ef7 --- /dev/null +++ b/src/utils/routes.ts @@ -0,0 +1,23 @@ +export const routes = { + // (auth) + main: '/', + findEmail: '/find-email', + findPassword: '/find-password', + login: '/login', + register: '/register', + // (user) + userPage: (userId: string) => `/users/${userId}`, + followers: (userId: string) => `/users/${userId}/social/followers`, + followings: (userId: string) => `/users/${userId}/social/followings`, + userCreatedGroups: (userId: string) => `/users/${userId}/groups/created`, + userEndedGroups: (userId: string) => `/users/${userId}/groups/ended`, + + // bookmark + bookmark: '/bookmark', + + // group + groupDetail: (groupId: number) => `/groups/${groupId}`, + + // write + write: '/write', +}; diff --git a/tsconfig.json b/tsconfig.json index c1334095..8d33a6d3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,5 +23,5 @@ } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "cypress", "**/*.cy.ts"] } diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 73616a2f..00000000 --- a/yarn.lock +++ /dev/null @@ -1,5207 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@alloc/quick-lru@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" - integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== - -"@ampproject/remapping@^2.2.0", "@ampproject/remapping@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/compat-data@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.2.tgz#4183f9e642fd84e74e3eea7ffa93a412e3b102c9" - integrity sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.1.tgz#89de51e86bd12246003e3524704c49541b16c3e6" - integrity sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.27.1" - "@babel/helper-compilation-targets" "^7.27.1" - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helpers" "^7.27.1" - "@babel/parser" "^7.27.1" - "@babel/template" "^7.27.1" - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.27.1", "@babel/generator@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.1.tgz#862d4fad858f7208edd487c28b58144036b76230" - integrity sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w== - dependencies: - "@babel/parser" "^7.27.1" - "@babel/types" "^7.27.1" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - -"@babel/helper-compilation-targets@^7.27.1": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== - dependencies: - "@babel/compat-data" "^7.27.2" - "@babel/helper-validator-option" "^7.27.1" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helper-module-transforms@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz#e1663b8b71d2de948da5c4fb2a20ca4f3ec27a6f" - integrity sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g== - dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== - -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - -"@babel/helper-validator-identifier@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" - integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== - -"@babel/helper-validator-option@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" - integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== - -"@babel/helpers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.1.tgz#ffc27013038607cdba3288e692c3611c06a18aa4" - integrity sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ== - dependencies: - "@babel/template" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.1", "@babel/parser@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127" - integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw== - dependencies: - "@babel/types" "^7.27.1" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" - integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" - integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" - integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/runtime@^7.12.5": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.1.tgz#9fce313d12c9a77507f264de74626e87fd0dc541" - integrity sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog== - -"@babel/template@^7.27.1", "@babel/template@^7.3.3": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" - -"@babel/traverse@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291" - integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.27.1" - "@babel/parser" "^7.27.1" - "@babel/template" "^7.27.1" - "@babel/types" "^7.27.1" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.3.3": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@emnapi/core@^1.4.0", "@emnapi/core@^1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.4.3.tgz#9ac52d2d5aea958f67e52c40a065f51de59b77d6" - integrity sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g== - dependencies: - "@emnapi/wasi-threads" "1.0.2" - tslib "^2.4.0" - -"@emnapi/runtime@^1.4.0", "@emnapi/runtime@^1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.4.3.tgz#c0564665c80dc81c448adac23f9dfbed6c838f7d" - integrity sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ== - dependencies: - tslib "^2.4.0" - -"@emnapi/wasi-threads@1.0.2", "@emnapi/wasi-threads@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz#977f44f844eac7d6c138a415a123818c655f874c" - integrity sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA== - dependencies: - tslib "^2.4.0" - -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.7.0": - version "4.7.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" - integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== - dependencies: - eslint-visitor-keys "^3.4.3" - -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== - -"@eslint/config-array@^0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.20.0.tgz#7a1232e82376712d3340012a2f561a2764d1988f" - integrity sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ== - dependencies: - "@eslint/object-schema" "^2.1.6" - debug "^4.3.1" - minimatch "^3.1.2" - -"@eslint/config-helpers@^0.2.1": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.2.2.tgz#3779f76b894de3a8ec4763b79660e6d54d5b1010" - integrity sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg== - -"@eslint/core@^0.13.0": - version "0.13.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.13.0.tgz#bf02f209846d3bf996f9e8009db62df2739b458c" - integrity sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw== - dependencies: - "@types/json-schema" "^7.0.15" - -"@eslint/eslintrc@^3", "@eslint/eslintrc@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" - integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^10.0.1" - globals "^14.0.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" - -"@eslint/js@9.26.0": - version "9.26.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.26.0.tgz#1e13126b67a3db15111d2dcc61f69a2acff70bd5" - integrity sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ== - -"@eslint/object-schema@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" - integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== - -"@eslint/plugin-kit@^0.2.8": - version "0.2.8" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz#47488d8f8171b5d4613e833313f3ce708e3525f8" - integrity sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA== - dependencies: - "@eslint/core" "^0.13.0" - levn "^0.4.1" - -"@humanfs/core@^0.19.1": - version "0.19.1" - resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" - integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== - -"@humanfs/node@^0.16.6": - version "0.16.6" - resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" - integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== - dependencies: - "@humanfs/core" "^0.19.1" - "@humanwhocodes/retry" "^0.3.0" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/retry@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" - integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== - -"@humanwhocodes/retry@^0.4.2": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" - integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== - -"@img/sharp-darwin-arm64@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz#e79a4756bea9a06a7aadb4391ee53cb154a4968c" - integrity sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A== - optionalDependencies: - "@img/sharp-libvips-darwin-arm64" "1.1.0" - -"@img/sharp-darwin-x64@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz#f1f1d386719f6933796415d84937502b7199a744" - integrity sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q== - optionalDependencies: - "@img/sharp-libvips-darwin-x64" "1.1.0" - -"@img/sharp-libvips-darwin-arm64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz#843f7c09c7245dc0d3cfec2b3c83bb08799a704f" - integrity sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA== - -"@img/sharp-libvips-darwin-x64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz#1239c24426c06a8e833815562f78047a3bfbaaf8" - integrity sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ== - -"@img/sharp-libvips-linux-arm64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz#20d276cefd903ee483f0441ba35961679c286315" - integrity sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew== - -"@img/sharp-libvips-linux-arm@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz#067c0b566eae8063738cf1b1db8f8a8573b5465c" - integrity sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA== - -"@img/sharp-libvips-linux-ppc64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz#682334595f2ca00e0a07a675ba170af165162802" - integrity sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ== - -"@img/sharp-libvips-linux-s390x@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz#82fcd68444b3666384235279c145c2b28d8ee302" - integrity sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA== - -"@img/sharp-libvips-linux-x64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz#65b2b908bf47156b0724fde9095676c83a18cf5a" - integrity sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q== - -"@img/sharp-libvips-linuxmusl-arm64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz#72accf924e80b081c8db83b900b444a67c203f01" - integrity sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w== - -"@img/sharp-libvips-linuxmusl-x64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz#1fa052737e203f46bf44192acd01f9faf11522d7" - integrity sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A== - -"@img/sharp-linux-arm64@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz#c36ef964499b8cfc2d2ed88fe68f27ce41522c80" - integrity sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ== - optionalDependencies: - "@img/sharp-libvips-linux-arm64" "1.1.0" - -"@img/sharp-linux-arm@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz#c96e38ff028d645912bb0aa132a7178b96997866" - integrity sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA== - optionalDependencies: - "@img/sharp-libvips-linux-arm" "1.1.0" - -"@img/sharp-linux-s390x@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz#8ac58d9a49dcb08215e76c8d450717979b7815c3" - integrity sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA== - optionalDependencies: - "@img/sharp-libvips-linux-s390x" "1.1.0" - -"@img/sharp-linux-x64@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz#3d8652efac635f0dba39d5e3b8b49515a2b2dee1" - integrity sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA== - optionalDependencies: - "@img/sharp-libvips-linux-x64" "1.1.0" - -"@img/sharp-linuxmusl-arm64@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz#b267e6a3e06f9e4d345cde471e5480c5c39e6969" - integrity sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-arm64" "1.1.0" - -"@img/sharp-linuxmusl-x64@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz#a8dee4b6227f348c4bbacaa6ac3dc584a1a80391" - integrity sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-x64" "1.1.0" - -"@img/sharp-wasm32@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz#f7dfd66b6c231269042d3d8750c90f28b9ddcba1" - integrity sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg== - dependencies: - "@emnapi/runtime" "^1.4.0" - -"@img/sharp-win32-ia32@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz#4bc293705df76a5f0a02df66ca3dc12e88f61332" - integrity sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw== - -"@img/sharp-win32-x64@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz#8a7922fec949f037c204c79f6b83238d2482384b" - integrity sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw== - -"@isaacs/fs-minipass@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" - integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== - dependencies: - minipass "^7.0.4" - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - 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" - -"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" - integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - -"@jest/core@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" - integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== - dependencies: - "@jest/console" "^29.7.0" - "@jest/reporters" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@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.7.0" - jest-config "^29.7.0" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-resolve-dependencies "^29.7.0" - jest-runner "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - jest-watcher "^29.7.0" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/environment@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" - integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== - dependencies: - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== - dependencies: - jest-get-type "^29.6.3" - -"@jest/expect@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" - integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== - dependencies: - expect "^29.7.0" - jest-snapshot "^29.7.0" - -"@jest/fake-timers@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== - dependencies: - "@jest/types" "^29.6.3" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -"@jest/globals@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" - integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/types" "^29.6.3" - jest-mock "^29.7.0" - -"@jest/reporters@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" - integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@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 "^6.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - jest-worker "^29.7.0" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - v8-to-istanbul "^9.0.1" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/source-map@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" - integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" - integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== - dependencies: - "@jest/console" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" - integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== - dependencies: - "@jest/test-result" "^29.7.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - slash "^3.0.0" - -"@jest/transform@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" - integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.3" - "@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.7.0" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@modelcontextprotocol/sdk@^1.8.0": - version "1.11.2" - resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.11.2.tgz#d81784c140d1a9cc937f61af9f071d8b78befe30" - integrity sha512-H9vwztj5OAqHg9GockCQC06k1natgcxWQSRpQcPJf6i5+MWBzfKkRtxGbjQf0X2ihii0ffLZCRGbYV2f2bjNCQ== - dependencies: - content-type "^1.0.5" - cors "^2.8.5" - cross-spawn "^7.0.3" - eventsource "^3.0.2" - express "^5.0.1" - express-rate-limit "^7.5.0" - pkce-challenge "^5.0.0" - raw-body "^3.0.0" - zod "^3.23.8" - zod-to-json-schema "^3.24.1" - -"@napi-rs/wasm-runtime@^0.2.9": - version "0.2.9" - resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz#7278122cf94f3b36d8170a8eee7d85356dfa6a96" - integrity sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg== - dependencies: - "@emnapi/core" "^1.4.0" - "@emnapi/runtime" "^1.4.0" - "@tybys/wasm-util" "^0.9.0" - -"@next/env@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/env/-/env-15.3.2.tgz#7143eafa9b11cfdf3d3c7318b0facb9dfdb2948f" - integrity sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g== - -"@next/eslint-plugin-next@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.2.tgz#6a371b022d6de47f2bafc868c7c2220f4e6a2903" - integrity sha512-ijVRTXBgnHT33aWnDtmlG+LJD+5vhc9AKTJPquGG5NKXjpKNjc62woIhFtrAcWdBobt8kqjCoaJ0q6sDQoX7aQ== - dependencies: - fast-glob "3.3.1" - -"@next/swc-darwin-arm64@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.2.tgz#1a7b36bf3c439f899065c878a580bc57a3630ec7" - integrity sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g== - -"@next/swc-darwin-x64@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.2.tgz#3742026344f49128cf1b0f43814c67e880db7361" - integrity sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w== - -"@next/swc-linux-arm64-gnu@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.2.tgz#fb29d45c034e3d2eef89b0e2801d62eb86155823" - integrity sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA== - -"@next/swc-linux-arm64-musl@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.2.tgz#396784ef312666600ab1ae481e34cb1f6e3ae730" - integrity sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg== - -"@next/swc-linux-x64-gnu@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz#ac01fda376878e02bc6b57d1e88ab8ceae9f868e" - integrity sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg== - -"@next/swc-linux-x64-musl@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz#327a5023003bcb3ca436efc08733f091bba2b1e8" - integrity sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w== - -"@next/swc-win32-arm64-msvc@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.2.tgz#ce3a6588bd9c020960704011ab20bd0440026965" - integrity sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ== - -"@next/swc-win32-x64-msvc@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.2.tgz#43cc36097ac27639e9024a5ceaa6e7727fa968c8" - integrity sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@nolyfill/is-core-module@1.0.39": - version "1.0.39" - resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e" - integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== - -"@rtsao/scc@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" - integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== - -"@rushstack/eslint-patch@^1.10.3": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz#75dce8e972f90bba488e2b0cc677fb233aa357ab" - integrity sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ== - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sinonjs/commons@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" - integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@swc/counter@0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" - integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== - -"@swc/helpers@0.5.15": - version "0.5.15" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" - integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== - dependencies: - tslib "^2.8.0" - -"@tailwindcss/node@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.1.6.tgz#693304dce91d88a1ea8e0d87cf5b64b5feb0fc6a" - integrity sha512-ed6zQbgmKsjsVvodAS1q1Ld2BolEuxJOSyyNc+vhkjdmfNUDCmQnlXBfQkHrlzNmslxHsQU/bFmzcEbv4xXsLg== - dependencies: - "@ampproject/remapping" "^2.3.0" - enhanced-resolve "^5.18.1" - jiti "^2.4.2" - lightningcss "1.29.2" - magic-string "^0.30.17" - source-map-js "^1.2.1" - tailwindcss "4.1.6" - -"@tailwindcss/oxide-android-arm64@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.6.tgz#b7632044a47250112f9ea9da4a4fdb5f7550b9f8" - integrity sha512-VHwwPiwXtdIvOvqT/0/FLH/pizTVu78FOnI9jQo64kSAikFSZT7K4pjyzoDpSMaveJTGyAKvDjuhxJxKfmvjiQ== - -"@tailwindcss/oxide-darwin-arm64@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.6.tgz#8d94e40fee9fb3214b1cf4f4d9341738a812871a" - integrity sha512-weINOCcqv1HVBIGptNrk7c6lWgSFFiQMcCpKM4tnVi5x8OY2v1FrV76jwLukfT6pL1hyajc06tyVmZFYXoxvhQ== - -"@tailwindcss/oxide-darwin-x64@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.6.tgz#281ab262cfde170dd4e977126e259b58eaab3bd3" - integrity sha512-3FzekhHG0ww1zQjQ1lPoq0wPrAIVXAbUkWdWM8u5BnYFZgb9ja5ejBqyTgjpo5mfy0hFOoMnMuVDI+7CXhXZaQ== - -"@tailwindcss/oxide-freebsd-x64@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.6.tgz#4d5b7e13ff8ab47aabf7d4613faf051cfd540398" - integrity sha512-4m5F5lpkBZhVQJq53oe5XgJ+aFYWdrgkMwViHjRsES3KEu2m1udR21B1I77RUqie0ZYNscFzY1v9aDssMBZ/1w== - -"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.6.tgz#88dc4f20e6e75ded01aee85b398494adcaef85e8" - integrity sha512-qU0rHnA9P/ZoaDKouU1oGPxPWzDKtIfX7eOGi5jOWJKdxieUJdVV+CxWZOpDWlYTd4N3sFQvcnVLJWJ1cLP5TA== - -"@tailwindcss/oxide-linux-arm64-gnu@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.6.tgz#6b848009eec017a4feb1d7f763d37540b20eef16" - integrity sha512-jXy3TSTrbfgyd3UxPQeXC3wm8DAgmigzar99Km9Sf6L2OFfn/k+u3VqmpgHQw5QNfCpPe43em6Q7V76Wx7ogIQ== - -"@tailwindcss/oxide-linux-arm64-musl@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.6.tgz#5b5a27013fd801d471998fc371812fdf1156be24" - integrity sha512-8kjivE5xW0qAQ9HX9reVFmZj3t+VmljDLVRJpVBEoTR+3bKMnvC7iLcoSGNIUJGOZy1mLVq7x/gerVg0T+IsYw== - -"@tailwindcss/oxide-linux-x64-gnu@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.6.tgz#81e06ade4eef09141504bb35b8e4aa18349b7ced" - integrity sha512-A4spQhwnWVpjWDLXnOW9PSinO2PTKJQNRmL/aIl2U/O+RARls8doDfs6R41+DAXK0ccacvRyDpR46aVQJJCoCg== - -"@tailwindcss/oxide-linux-x64-musl@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.6.tgz#a01bb576581269e8c996b19c594d0b0d6673fdc3" - integrity sha512-YRee+6ZqdzgiQAHVSLfl3RYmqeeaWVCk796MhXhLQu2kJu2COHBkqlqsqKYx3p8Hmk5pGCQd2jTAoMWWFeyG2A== - -"@tailwindcss/oxide-wasm32-wasi@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.6.tgz#7e45eb7aafec0406477a05403689198a9f062b4d" - integrity sha512-qAp4ooTYrBQ5pk5jgg54/U1rCJ/9FLYOkkQ/nTE+bVMseMfB6O7J8zb19YTpWuu4UdfRf5zzOrNKfl6T64MNrQ== - dependencies: - "@emnapi/core" "^1.4.3" - "@emnapi/runtime" "^1.4.3" - "@emnapi/wasi-threads" "^1.0.2" - "@napi-rs/wasm-runtime" "^0.2.9" - "@tybys/wasm-util" "^0.9.0" - tslib "^2.8.0" - -"@tailwindcss/oxide-win32-arm64-msvc@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.6.tgz#9b445635928a43b92ffb7b52bb063a549d7df980" - integrity sha512-nqpDWk0Xr8ELO/nfRUDjk1pc9wDJ3ObeDdNMHLaymc4PJBWj11gdPCWZFKSK2AVKjJQC7J2EfmSmf47GN7OuLg== - -"@tailwindcss/oxide-win32-x64-msvc@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.6.tgz#2d0405b733a5fcbe44554601a71f907142738ced" - integrity sha512-5k9xF33xkfKpo9wCvYcegQ21VwIBU1/qEbYlVukfEIyQbEA47uK8AAwS7NVjNE3vHzcmxMYwd0l6L4pPjjm1rQ== - -"@tailwindcss/oxide@4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-4.1.6.tgz#1ddabeb360385f04742c887e081352ab7469a668" - integrity sha512-0bpEBQiGx+227fW4G0fLQ8vuvyy5rsB1YIYNapTq3aRsJ9taF3f5cCaovDjN5pUGKKzcpMrZst/mhNaKAPOHOA== - dependencies: - detect-libc "^2.0.4" - tar "^7.4.3" - optionalDependencies: - "@tailwindcss/oxide-android-arm64" "4.1.6" - "@tailwindcss/oxide-darwin-arm64" "4.1.6" - "@tailwindcss/oxide-darwin-x64" "4.1.6" - "@tailwindcss/oxide-freebsd-x64" "4.1.6" - "@tailwindcss/oxide-linux-arm-gnueabihf" "4.1.6" - "@tailwindcss/oxide-linux-arm64-gnu" "4.1.6" - "@tailwindcss/oxide-linux-arm64-musl" "4.1.6" - "@tailwindcss/oxide-linux-x64-gnu" "4.1.6" - "@tailwindcss/oxide-linux-x64-musl" "4.1.6" - "@tailwindcss/oxide-wasm32-wasi" "4.1.6" - "@tailwindcss/oxide-win32-arm64-msvc" "4.1.6" - "@tailwindcss/oxide-win32-x64-msvc" "4.1.6" - -"@tailwindcss/postcss@^4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@tailwindcss/postcss/-/postcss-4.1.6.tgz#c37ae6fc324ae4ec291976f477fac092357058b4" - integrity sha512-ELq+gDMBuRXPJlpE3PEen+1MhnHAQQrh2zF0dI1NXOlEWfr2qWf2CQdr5jl9yANv8RErQaQ2l6nIFO9OSCVq/g== - dependencies: - "@alloc/quick-lru" "^5.2.0" - "@tailwindcss/node" "4.1.6" - "@tailwindcss/oxide" "4.1.6" - postcss "^8.4.41" - tailwindcss "4.1.6" - -"@tanstack/query-core@5.76.0": - version "5.76.0" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.76.0.tgz#3b4d5d34ce307ba0cf7d1a3e90d7adcdc6c46be0" - integrity sha512-FN375hb8ctzfNAlex5gHI6+WDXTNpe0nbxp/d2YJtnP+IBM6OUm7zcaoCW6T63BawGOYZBbKC0iPvr41TteNVg== - -"@tanstack/react-query@^5.76.1": - version "5.76.1" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.76.1.tgz#ac8a19f99dfec1452a44fe22d46680c396c21152" - integrity sha512-YxdLZVGN4QkT5YT1HKZQWiIlcgauIXEIsMOTSjvyD5wLYK8YVvKZUPAysMqossFJJfDpJW3pFn7WNZuPOqq+fw== - dependencies: - "@tanstack/query-core" "5.76.0" - -"@testing-library/dom@^10.4.0": - version "10.4.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8" - integrity sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" - "@types/aria-query" "^5.0.1" - aria-query "5.3.0" - chalk "^4.1.0" - dom-accessibility-api "^0.5.9" - lz-string "^1.5.0" - pretty-format "^27.0.2" - -"@testing-library/react@^16.3.0": - version "16.3.0" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.0.tgz#3a85bb9bdebf180cd76dba16454e242564d598a6" - integrity sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw== - dependencies: - "@babel/runtime" "^7.12.5" - -"@tybys/wasm-util@^0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz#3e75eb00604c8d6db470bf18c37b7d984a0e3355" - integrity sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw== - dependencies: - tslib "^2.4.0" - -"@types/aria-query@^5.0.1": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" - integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== - -"@types/babel__core@^7.1.14": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" - integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz#968cdc2366ec3da159f61166428ee40f370e56c2" - integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng== - dependencies: - "@babel/types" "^7.20.7" - -"@types/estree@^1.0.6": - version "1.0.7" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8" - integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== - -"@types/graceful-fs@^4.1.3": - version "4.1.9" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" - integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" - integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== - -"@types/istanbul-lib-report@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" - integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" - integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/json-schema@^7.0.15": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== - -"@types/node@*": - version "22.15.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.18.tgz#2f8240f7e932f571c2d45f555ba0b6c3f7a75963" - integrity sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg== - dependencies: - undici-types "~6.21.0" - -"@types/node@^20": - version "20.17.47" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.47.tgz#f9cb375993fffdae609c8e17d2b3dd8d3c4bfa14" - integrity sha512-3dLX0Upo1v7RvUimvxLeXqwrfyKxUINk0EAM83swP2mlSUcwV73sZy8XhNz8bcZ3VbsfQyC/y6jRdL5tgCNpDQ== - dependencies: - undici-types "~6.19.2" - -"@types/react-dom@^19": - version "19.1.5" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.5.tgz#cdfe2c663742887372f54804b16e8dbc26bd794a" - integrity sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg== - -"@types/react@^19": - version "19.1.4" - resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.4.tgz#4d125f014d6ac26b4759775698db118701e314fe" - integrity sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g== - dependencies: - csstype "^3.0.2" - -"@types/stack-utils@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" - integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== - -"@types/yargs-parser@*": - version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" - integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== - -"@types/yargs@^17.0.8": - version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" - integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== - dependencies: - "@types/yargs-parser" "*" - -"@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz#9185b3eaa3b083d8318910e12d56c68b3c4f45b4" - integrity sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg== - dependencies: - "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.32.1" - "@typescript-eslint/type-utils" "8.32.1" - "@typescript-eslint/utils" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" - graphemer "^1.4.0" - ignore "^7.0.0" - natural-compare "^1.4.0" - ts-api-utils "^2.1.0" - -"@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.32.1.tgz#18b0e53315e0bc22b2619d398ae49a968370935e" - integrity sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg== - dependencies: - "@typescript-eslint/scope-manager" "8.32.1" - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/typescript-estree" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz#9a6bf5fb2c5380e14fe9d38ccac6e4bbe17e8afc" - integrity sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA== - dependencies: - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" - -"@typescript-eslint/type-utils@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz#b9292a45f69ecdb7db74d1696e57d1a89514d21e" - integrity sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA== - dependencies: - "@typescript-eslint/typescript-estree" "8.32.1" - "@typescript-eslint/utils" "8.32.1" - debug "^4.3.4" - ts-api-utils "^2.1.0" - -"@typescript-eslint/types@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.32.1.tgz#b19fe4ac0dc08317bae0ce9ec1168123576c1d4b" - integrity sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg== - -"@typescript-eslint/typescript-estree@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz#9023720ca4ecf4f59c275a05b5fed69b1276face" - integrity sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg== - dependencies: - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" - debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^2.1.0" - -"@typescript-eslint/utils@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.32.1.tgz#4d6d5d29b9e519e9a85e9a74e9f7bdb58abe9704" - integrity sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA== - dependencies: - "@eslint-community/eslint-utils" "^4.7.0" - "@typescript-eslint/scope-manager" "8.32.1" - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/typescript-estree" "8.32.1" - -"@typescript-eslint/visitor-keys@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz#4321395cc55c2eb46036cbbb03e101994d11ddca" - integrity sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w== - dependencies: - "@typescript-eslint/types" "8.32.1" - eslint-visitor-keys "^4.2.0" - -"@unrs/resolver-binding-darwin-arm64@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.2.tgz#12eed2bd9865d1f55bb79d76072330b6032441d7" - integrity sha512-vxtBno4xvowwNmO/ASL0Y45TpHqmNkAaDtz4Jqb+clmcVSSl8XCG/PNFFkGsXXXS6AMjP+ja/TtNCFFa1QwLRg== - -"@unrs/resolver-binding-darwin-x64@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.2.tgz#97e0212a85c56e156a272628ec55da7aff992161" - integrity sha512-qhVa8ozu92C23Hsmv0BF4+5Dyyd5STT1FolV4whNgbY6mj3kA0qsrGPe35zNR3wAN7eFict3s4Rc2dDTPBTuFQ== - -"@unrs/resolver-binding-freebsd-x64@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.2.tgz#07594a9d1d83e84b52908800459273ea00caf595" - integrity sha512-zKKdm2uMXqLFX6Ac7K5ElnnG5VIXbDlFWzg4WJ8CGUedJryM5A3cTgHuGMw1+P5ziV8CRhnSEgOnurTI4vpHpg== - -"@unrs/resolver-binding-linux-arm-gnueabihf@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.2.tgz#9ef6031bb1136ee7862a6f94a2a53c395d2b6fae" - integrity sha512-8N1z1TbPnHH+iDS/42GJ0bMPLiGK+cUqOhNbMKtWJ4oFGzqSJk/zoXFzcQkgtI63qMcUI7wW1tq2usZQSb2jxw== - -"@unrs/resolver-binding-linux-arm-musleabihf@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.2.tgz#24910379ab39da1b15d65b1a06b4bfb4c293ca0c" - integrity sha512-tjYzI9LcAXR9MYd9rO45m1s0B/6bJNuZ6jeOxo1pq1K6OBuRMMmfyvJYval3s9FPPGmrldYA3mi4gWDlWuTFGA== - -"@unrs/resolver-binding-linux-arm64-gnu@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.2.tgz#49b6a8fb8f42f7530f51bc2e60fc582daed31ffb" - integrity sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA== - -"@unrs/resolver-binding-linux-arm64-musl@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.2.tgz#3a9707a6afda534f30c8de8a5de6c193b1b6d164" - integrity sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA== - -"@unrs/resolver-binding-linux-ppc64-gnu@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.2.tgz#659831ff2bfe8157d806b69b6efe142265bf9f0f" - integrity sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg== - -"@unrs/resolver-binding-linux-riscv64-gnu@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.2.tgz#e75abebd53cdddb3d635f6efb7a5ef6e96695717" - integrity sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q== - -"@unrs/resolver-binding-linux-riscv64-musl@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.2.tgz#e99b5316ee612b180aff5a7211717f3fc8c3e54e" - integrity sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ== - -"@unrs/resolver-binding-linux-s390x-gnu@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.2.tgz#36646d5f60246f0eae650fc7bcd79b3cbf7dcff1" - integrity sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA== - -"@unrs/resolver-binding-linux-x64-gnu@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.2.tgz#e720adc2979702c62f4040de05c854f186268c27" - integrity sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg== - -"@unrs/resolver-binding-linux-x64-musl@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.2.tgz#684e576557d20deb4ac8ea056dcbe79739ca2870" - integrity sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw== - -"@unrs/resolver-binding-wasm32-wasi@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.2.tgz#5b138ce8d471f5d0c8d6bfab525c53b80ca734e0" - integrity sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g== - dependencies: - "@napi-rs/wasm-runtime" "^0.2.9" - -"@unrs/resolver-binding-win32-arm64-msvc@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.2.tgz#bd772db4e8a02c31161cf1dfa33852eb7ef22df6" - integrity sha512-gtYTh4/VREVSLA+gHrfbWxaMO/00y+34htY7XpioBTy56YN2eBjkPrY1ML1Zys89X3RJDKVaogzwxlM1qU7egg== - -"@unrs/resolver-binding-win32-ia32-msvc@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.2.tgz#a6955ccdc43e809a158c4fe2d54931d34c3f7b51" - integrity sha512-Ywv20XHvHTDRQs12jd3MY8X5C8KLjDbg/jyaal/QLKx3fAShhJyD4blEANInsjxW3P7isHx1Blt56iUDDJO3jg== - -"@unrs/resolver-binding-win32-x64-msvc@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.2.tgz#7fd81d89e34a711d398ca87f6d5842735d49721e" - integrity sha512-friS8NEQfHaDbkThxopGk+LuE5v3iY0StruifjQEt7SLbA46OnfgMO15sOTkbpJkol6RB+1l1TYPXh0sCddpvA== - -accepts@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" - integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== - dependencies: - mime-types "^3.0.0" - negotiator "^1.0.0" - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^8.14.0: - version "8.14.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" - integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== - -ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -anymatch@^3.0.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -aria-query@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" - integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== - dependencies: - dequal "^2.0.3" - -aria-query@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" - integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== - -array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" - integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== - dependencies: - call-bound "^1.0.3" - is-array-buffer "^3.0.5" - -array-includes@^3.1.6, array-includes@^3.1.8: - version "3.1.8" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" - integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.4" - is-string "^1.0.7" - -array.prototype.findlast@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" - integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-shim-unscopables "^1.0.2" - -array.prototype.findlastindex@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz#cfa1065c81dcb64e34557c9b81d012f6a421c564" - integrity sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.4" - define-properties "^1.2.1" - es-abstract "^1.23.9" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - es-shim-unscopables "^1.1.0" - -array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: - version "1.3.3" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5" - integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== - dependencies: - call-bind "^1.0.8" - define-properties "^1.2.1" - es-abstract "^1.23.5" - es-shim-unscopables "^1.0.2" - -array.prototype.flatmap@^1.3.2, array.prototype.flatmap@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" - integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== - dependencies: - call-bind "^1.0.8" - define-properties "^1.2.1" - es-abstract "^1.23.5" - es-shim-unscopables "^1.0.2" - -array.prototype.tosorted@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" - integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.3" - es-errors "^1.3.0" - es-shim-unscopables "^1.0.2" - -arraybuffer.prototype.slice@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" - integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== - dependencies: - array-buffer-byte-length "^1.0.1" - call-bind "^1.0.8" - define-properties "^1.2.1" - es-abstract "^1.23.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - is-array-buffer "^3.0.4" - -ast-types-flow@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" - integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== - -async-function@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" - integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -axe-core@^4.10.0: - version "4.10.3" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.3.tgz#04145965ac7894faddbac30861e5d8f11bfd14fc" - integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg== - -axobject-query@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" - integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== - -babel-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" - integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== - dependencies: - "@jest/transform" "^29.7.0" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.6.3" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - 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" - -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" - integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-import-attributes" "^7.24.7" - "@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-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== - dependencies: - babel-plugin-jest-hoist "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -body-parser@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa" - integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg== - dependencies: - bytes "^3.1.2" - content-type "^1.0.5" - debug "^4.4.0" - http-errors "^2.0.0" - iconv-lite "^0.6.3" - on-finished "^2.4.1" - qs "^6.14.0" - raw-body "^3.0.0" - type-is "^2.0.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browserslist@^4.24.0: - version "4.24.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.5.tgz#aa0f5b8560fe81fde84c6dcb38f759bafba0e11b" - integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw== - dependencies: - caniuse-lite "^1.0.30001716" - electron-to-chromium "^1.5.149" - node-releases "^2.0.19" - update-browserslist-db "^1.1.3" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -busboy@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" - integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== - dependencies: - streamsearch "^1.1.0" - -bytes@3.1.2, bytes@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.7, call-bind@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" - set-function-length "^1.2.2" - -call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001716: - version "1.0.30001718" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz#dae13a9c80d517c30c6197515a96131c194d8f82" - integrity sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw== - -chalk@^4.0.0, chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -chownr@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" - integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== - -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - -cjs-module-lexer@^1.0.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" - integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== - -class-variance-authority@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz#4008a798a0e4553a781a57ac5177c9fb5d043787" - integrity sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg== - dependencies: - clsx "^2.1.1" - -client-only@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" - integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - 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" - -clsx@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" - integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" - integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== - dependencies: - color-convert "^2.0.1" - color-string "^1.9.0" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -content-disposition@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2" - integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg== - dependencies: - safe-buffer "5.2.1" - -content-type@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -cookie-signature@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" - integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== - -cookie@^0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" - integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -create-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" - integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-config "^29.7.0" - jest-util "^29.7.0" - prompts "^2.0.1" - -cross-spawn@^7.0.3, cross-spawn@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -csstype@^3.0.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== - -damerau-levenshtein@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" - integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== - -data-view-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" - integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== - dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - is-data-view "^1.0.2" - -data-view-byte-length@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" - integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== - dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - is-data-view "^1.0.2" - -data-view-byte-offset@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" - integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0: - version "4.4.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" - integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== - dependencies: - ms "^2.1.3" - -dedent@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" - integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -define-data-property@^1.0.1, define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-properties@^1.1.3, define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -depd@2.0.0, depd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -dequal@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" - integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== - -detect-libc@^2.0.3, detect-libc@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8" - integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -dom-accessibility-api@^0.5.9: - version "0.5.16" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" - integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== - -dunder-proto@^1.0.0, dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.5.149: - version "1.5.153" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.153.tgz#edf1e4d2c00bb88bc642ac01dec2b836d4cfedfe" - integrity sha512-4bwluTFwjXZ0/ei1qDpHDGzVveuBfx4wiZ9VQ8j/30+T2JxSF2TfZ00d1X+wNMeDyUdZXgLkJFbarJdAMtd+/w== - -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -encodeurl@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -enhanced-resolve@^5.18.1: - version "5.18.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz#728ab082f8b7b6836de51f1637aab5d3b9568faf" - integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9: - version "1.23.9" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.9.tgz#5b45994b7de78dada5c1bebf1379646b32b9d606" - integrity sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA== - dependencies: - array-buffer-byte-length "^1.0.2" - arraybuffer.prototype.slice "^1.0.4" - available-typed-arrays "^1.0.7" - call-bind "^1.0.8" - call-bound "^1.0.3" - data-view-buffer "^1.0.2" - data-view-byte-length "^1.0.2" - data-view-byte-offset "^1.0.1" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.1.0" - es-to-primitive "^1.3.0" - function.prototype.name "^1.1.8" - get-intrinsic "^1.2.7" - get-proto "^1.0.0" - get-symbol-description "^1.1.0" - globalthis "^1.0.4" - gopd "^1.2.0" - has-property-descriptors "^1.0.2" - has-proto "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - internal-slot "^1.1.0" - is-array-buffer "^3.0.5" - is-callable "^1.2.7" - is-data-view "^1.0.2" - is-regex "^1.2.1" - is-shared-array-buffer "^1.0.4" - is-string "^1.1.1" - is-typed-array "^1.1.15" - is-weakref "^1.1.0" - math-intrinsics "^1.1.0" - object-inspect "^1.13.3" - object-keys "^1.1.1" - object.assign "^4.1.7" - own-keys "^1.0.1" - regexp.prototype.flags "^1.5.3" - safe-array-concat "^1.1.3" - safe-push-apply "^1.0.0" - safe-regex-test "^1.1.0" - set-proto "^1.0.0" - string.prototype.trim "^1.2.10" - string.prototype.trimend "^1.0.9" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.3" - typed-array-byte-length "^1.0.3" - typed-array-byte-offset "^1.0.4" - typed-array-length "^1.0.7" - unbox-primitive "^1.1.0" - which-typed-array "^1.1.18" - -es-define-property@^1.0.0, es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-iterator-helpers@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz#d1dd0f58129054c0ad922e6a9a1e65eef435fe75" - integrity sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.3" - define-properties "^1.2.1" - es-abstract "^1.23.6" - es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" - function-bind "^1.1.2" - get-intrinsic "^1.2.6" - globalthis "^1.0.4" - gopd "^1.2.0" - has-property-descriptors "^1.0.2" - has-proto "^1.2.0" - has-symbols "^1.1.0" - internal-slot "^1.1.0" - iterator.prototype "^1.1.4" - safe-array-concat "^1.1.3" - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -es-shim-unscopables@^1.0.2, es-shim-unscopables@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz#438df35520dac5d105f3943d927549ea3b00f4b5" - integrity sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== - dependencies: - hasown "^2.0.2" - -es-to-primitive@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" - integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== - dependencies: - is-callable "^1.2.7" - is-date-object "^1.0.5" - is-symbol "^1.0.4" - -escalade@^3.1.1, escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-html@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-config-next@15.3.2: - version "15.3.2" - resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-15.3.2.tgz#ed2bc6607f54555bbeaf23a032b4e430e453b63c" - integrity sha512-FerU4DYccO4FgeYFFglz0SnaKRe1ejXQrDb8kWUkTAg036YWi+jUsgg4sIGNCDhAsDITsZaL4MzBWKB6f4G1Dg== - dependencies: - "@next/eslint-plugin-next" "15.3.2" - "@rushstack/eslint-patch" "^1.10.3" - "@typescript-eslint/eslint-plugin" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" - "@typescript-eslint/parser" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" - eslint-import-resolver-node "^0.3.6" - eslint-import-resolver-typescript "^3.5.2" - eslint-plugin-import "^2.31.0" - eslint-plugin-jsx-a11y "^6.10.0" - eslint-plugin-react "^7.37.0" - eslint-plugin-react-hooks "^5.0.0" - -eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9: - version "0.3.9" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" - integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== - dependencies: - debug "^3.2.7" - is-core-module "^2.13.0" - resolve "^1.22.4" - -eslint-import-resolver-typescript@^3.5.2: - version "3.10.1" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz#23dac32efa86a88e2b8232eb244ac499ad636db2" - integrity sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ== - dependencies: - "@nolyfill/is-core-module" "1.0.39" - debug "^4.4.0" - get-tsconfig "^4.10.0" - is-bun-module "^2.0.0" - stable-hash "^0.0.5" - tinyglobby "^0.2.13" - unrs-resolver "^1.6.2" - -eslint-module-utils@^2.12.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz#fe4cfb948d61f49203d7b08871982b65b9af0b0b" - integrity sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg== - dependencies: - debug "^3.2.7" - -eslint-plugin-import@^2.31.0: - version "2.31.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz#310ce7e720ca1d9c0bb3f69adfd1c6bdd7d9e0e7" - integrity sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A== - dependencies: - "@rtsao/scc" "^1.1.0" - array-includes "^3.1.8" - array.prototype.findlastindex "^1.2.5" - array.prototype.flat "^1.3.2" - array.prototype.flatmap "^1.3.2" - debug "^3.2.7" - doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.9" - eslint-module-utils "^2.12.0" - hasown "^2.0.2" - is-core-module "^2.15.1" - is-glob "^4.0.3" - minimatch "^3.1.2" - object.fromentries "^2.0.8" - object.groupby "^1.0.3" - object.values "^1.2.0" - semver "^6.3.1" - string.prototype.trimend "^1.0.8" - tsconfig-paths "^3.15.0" - -eslint-plugin-jsx-a11y@^6.10.0: - version "6.10.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz#d2812bb23bf1ab4665f1718ea442e8372e638483" - integrity sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q== - dependencies: - aria-query "^5.3.2" - array-includes "^3.1.8" - array.prototype.flatmap "^1.3.2" - ast-types-flow "^0.0.8" - axe-core "^4.10.0" - axobject-query "^4.1.0" - damerau-levenshtein "^1.0.8" - emoji-regex "^9.2.2" - hasown "^2.0.2" - jsx-ast-utils "^3.3.5" - language-tags "^1.0.9" - minimatch "^3.1.2" - object.fromentries "^2.0.8" - safe-regex-test "^1.0.3" - string.prototype.includes "^2.0.1" - -eslint-plugin-react-hooks@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3" - integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== - -eslint-plugin-react@^7.37.0: - version "7.37.5" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065" - integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== - dependencies: - array-includes "^3.1.8" - array.prototype.findlast "^1.2.5" - array.prototype.flatmap "^1.3.3" - array.prototype.tosorted "^1.1.4" - doctrine "^2.1.0" - es-iterator-helpers "^1.2.1" - estraverse "^5.3.0" - hasown "^2.0.2" - jsx-ast-utils "^2.4.1 || ^3.0.0" - minimatch "^3.1.2" - object.entries "^1.1.9" - object.fromentries "^2.0.8" - object.values "^1.2.1" - prop-types "^15.8.1" - resolve "^2.0.0-next.5" - semver "^6.3.1" - string.prototype.matchall "^4.0.12" - string.prototype.repeat "^1.0.0" - -eslint-scope@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.3.0.tgz#10cd3a918ffdd722f5f3f7b5b83db9b23c87340d" - integrity sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint-visitor-keys@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" - integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== - -eslint@^9: - version "9.26.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.26.0.tgz#978fe029adc2aceed28ab437bca876e83461c3b4" - integrity sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.20.0" - "@eslint/config-helpers" "^0.2.1" - "@eslint/core" "^0.13.0" - "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.26.0" - "@eslint/plugin-kit" "^0.2.8" - "@humanfs/node" "^0.16.6" - "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.2" - "@modelcontextprotocol/sdk" "^1.8.0" - "@types/estree" "^1.0.6" - "@types/json-schema" "^7.0.15" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.6" - debug "^4.3.2" - escape-string-regexp "^4.0.0" - eslint-scope "^8.3.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" - esquery "^1.5.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^8.0.0" - find-up "^5.0.0" - glob-parent "^6.0.2" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - json-stable-stringify-without-jsonify "^1.0.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - zod "^3.24.2" - -espree@^10.0.1, espree@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" - integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== - dependencies: - acorn "^8.14.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.0" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eventsource-parser@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz#5e358dba9a55ba64ca90da883c4ca35bd82467bd" - integrity sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA== - -eventsource@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-3.0.7.tgz#1157622e2f5377bb6aef2114372728ba0c156989" - integrity sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA== - dependencies: - eventsource-parser "^3.0.1" - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expect@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== - dependencies: - "@jest/expect-utils" "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - -express-rate-limit@^7.5.0: - version "7.5.0" - resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz#6a67990a724b4fbbc69119419feef50c51e8b28f" - integrity sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg== - -express@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9" - integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA== - dependencies: - accepts "^2.0.0" - body-parser "^2.2.0" - content-disposition "^1.0.0" - content-type "^1.0.5" - cookie "^0.7.1" - cookie-signature "^1.2.1" - debug "^4.4.0" - encodeurl "^2.0.0" - escape-html "^1.0.3" - etag "^1.8.1" - finalhandler "^2.1.0" - fresh "^2.0.0" - http-errors "^2.0.0" - merge-descriptors "^2.0.0" - mime-types "^3.0.0" - on-finished "^2.4.1" - once "^1.4.0" - parseurl "^1.3.3" - proxy-addr "^2.0.7" - qs "^6.14.0" - range-parser "^1.2.1" - router "^2.2.0" - send "^1.1.0" - serve-static "^2.2.0" - statuses "^2.0.1" - type-is "^2.0.1" - vary "^1.1.2" - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== - 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" - -fast-glob@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - 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.8" - -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fastq@^1.6.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" - integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== - dependencies: - reusify "^1.0.4" - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -fdir@^6.4.4: - version "6.4.4" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.4.tgz#1cfcf86f875a883e19a8fab53622cfe992e8d2f9" - integrity sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg== - -file-entry-cache@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" - integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== - dependencies: - flat-cache "^4.0.0" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f" - integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q== - dependencies: - debug "^4.4.0" - encodeurl "^2.0.0" - escape-html "^1.0.3" - on-finished "^2.4.1" - parseurl "^1.3.3" - statuses "^2.0.1" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" - integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.4" - -flatted@^3.2.9: - version "3.3.3" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" - integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== - -for-each@^0.3.3, for-each@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" - integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== - dependencies: - is-callable "^1.2.7" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" - integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" - integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.3" - define-properties "^1.2.1" - functions-have-names "^1.2.3" - hasown "^2.0.2" - is-callable "^1.2.7" - -functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-proto@^1.0.0, get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -get-symbol-description@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" - integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== - dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - -get-tsconfig@^4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.0.tgz#403a682b373a823612475a4c2928c7326fc0f6bb" - integrity sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A== - dependencies: - resolve-pkg-maps "^1.0.0" - -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - 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" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" - integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== - -globalthis@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" - integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== - dependencies: - define-properties "^1.2.1" - gopd "^1.0.1" - -gopd@^1.0.1, gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.2.4, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -has-bigints@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" - integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-proto@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" - integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== - dependencies: - dunder-proto "^1.0.0" - -has-symbols@^1.0.3, has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -http-errors@2.0.0, http-errors@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@0.6.3, iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -ignore@^5.2.0: - version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -ignore@^7.0.0: - version "7.0.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.4.tgz#a12c70d0f2607c5bf508fb65a40c75f037d7a078" - integrity sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A== - -import-fresh@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" - integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -internal-slot@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" - integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== - dependencies: - es-errors "^1.3.0" - hasown "^2.0.2" - side-channel "^1.1.0" - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" - integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.3" - get-intrinsic "^1.2.6" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-async-function@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" - integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== - dependencies: - async-function "^1.0.0" - call-bound "^1.0.3" - get-proto "^1.0.1" - has-tostringtag "^1.0.2" - safe-regex-test "^1.1.0" - -is-bigint@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" - integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== - dependencies: - has-bigints "^1.0.2" - -is-boolean-object@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" - integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== - dependencies: - call-bound "^1.0.3" - has-tostringtag "^1.0.2" - -is-bun-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-bun-module/-/is-bun-module-2.0.0.tgz#4d7859a87c0fcac950c95e666730e745eae8bddd" - integrity sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ== - dependencies: - semver "^7.7.1" - -is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-core-module@^2.13.0, is-core-module@^2.15.1, is-core-module@^2.16.0: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" - integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== - dependencies: - hasown "^2.0.2" - -is-data-view@^1.0.1, is-data-view@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" - integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== - dependencies: - call-bound "^1.0.2" - get-intrinsic "^1.2.6" - is-typed-array "^1.1.13" - -is-date-object@^1.0.5, is-date-object@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" - integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== - dependencies: - call-bound "^1.0.2" - has-tostringtag "^1.0.2" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-finalizationregistry@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" - integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== - dependencies: - call-bound "^1.0.3" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-generator-function@^1.0.10: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" - integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== - dependencies: - call-bound "^1.0.3" - get-proto "^1.0.0" - has-tostringtag "^1.0.2" - safe-regex-test "^1.1.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-map@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" - integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== - -is-number-object@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" - integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== - dependencies: - call-bound "^1.0.3" - has-tostringtag "^1.0.2" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-promise@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" - integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== - -is-regex@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" - integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== - dependencies: - call-bound "^1.0.2" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -is-set@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" - integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== - -is-shared-array-buffer@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" - integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== - dependencies: - call-bound "^1.0.3" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-string@^1.0.7, is-string@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" - integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== - dependencies: - call-bound "^1.0.3" - has-tostringtag "^1.0.2" - -is-symbol@^1.0.4, is-symbol@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" - integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== - dependencies: - call-bound "^1.0.2" - has-symbols "^1.1.0" - safe-regex-test "^1.1.0" - -is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: - version "1.1.15" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" - integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== - dependencies: - which-typed-array "^1.1.16" - -is-weakmap@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" - integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== - -is-weakref@^1.0.2, is-weakref@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" - integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== - dependencies: - call-bound "^1.0.3" - -is-weakset@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" - integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== - dependencies: - call-bound "^1.0.3" - get-intrinsic "^1.2.6" - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== - -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - 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" - -istanbul-lib-instrument@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" - integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== - dependencies: - "@babel/core" "^7.23.9" - "@babel/parser" "^7.23.9" - "@istanbuljs/schema" "^0.1.3" - istanbul-lib-coverage "^3.2.0" - semver "^7.5.4" - -istanbul-lib-report@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" - integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -iterator.prototype@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39" - integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== - dependencies: - define-data-property "^1.1.4" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.6" - get-proto "^1.0.0" - has-symbols "^1.1.0" - set-function-name "^2.0.2" - -jest-changed-files@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" - integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== - dependencies: - execa "^5.0.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - -jest-circus@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" - integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^1.0.0" - is-generator-fn "^2.0.0" - jest-each "^29.7.0" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - pretty-format "^29.7.0" - pure-rand "^6.0.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" - integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== - dependencies: - "@jest/core" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - chalk "^4.0.0" - create-jest "^29.7.0" - exit "^0.1.2" - import-local "^3.0.2" - jest-config "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - yargs "^17.3.1" - -jest-config@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" - integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.7.0" - "@jest/types" "^29.6.3" - babel-jest "^29.7.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.7.0" - jest-environment-node "^29.7.0" - jest-get-type "^29.6.3" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-runner "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" - integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.6.3" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-docblock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" - integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" - integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - jest-get-type "^29.6.3" - jest-util "^29.7.0" - pretty-format "^29.7.0" - -jest-environment-node@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" - integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== - -jest-haste-map@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" - integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== - dependencies: - "@jest/types" "^29.6.3" - "@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.6.3" - jest-util "^29.7.0" - jest-worker "^29.7.0" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" - integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== - dependencies: - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-matcher-utils@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" - integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== - dependencies: - chalk "^4.0.0" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - -jest-resolve-dependencies@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" - integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== - dependencies: - jest-regex-util "^29.6.3" - jest-snapshot "^29.7.0" - -jest-resolve@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" - integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-pnp-resolver "^1.2.2" - jest-util "^29.7.0" - jest-validate "^29.7.0" - resolve "^1.20.0" - resolve.exports "^2.0.0" - slash "^3.0.0" - -jest-runner@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" - integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== - dependencies: - "@jest/console" "^29.7.0" - "@jest/environment" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.13.1" - graceful-fs "^4.2.9" - jest-docblock "^29.7.0" - jest-environment-node "^29.7.0" - jest-haste-map "^29.7.0" - jest-leak-detector "^29.7.0" - jest-message-util "^29.7.0" - jest-resolve "^29.7.0" - jest-runtime "^29.7.0" - jest-util "^29.7.0" - jest-watcher "^29.7.0" - jest-worker "^29.7.0" - p-limit "^3.1.0" - source-map-support "0.5.13" - -jest-runtime@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" - integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/globals" "^29.7.0" - "@jest/source-map" "^29.6.3" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@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.7.0" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" - integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== - 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.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^29.7.0" - graceful-fs "^4.2.9" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - natural-compare "^1.4.0" - pretty-format "^29.7.0" - semver "^7.5.3" - -jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" - integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== - dependencies: - "@jest/types" "^29.6.3" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.6.3" - leven "^3.1.0" - pretty-format "^29.7.0" - -jest-watcher@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" - integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== - dependencies: - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.13.1" - jest-util "^29.7.0" - string-length "^4.0.1" - -jest-worker@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== - dependencies: - "@types/node" "*" - jest-util "^29.7.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" - integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== - dependencies: - "@jest/core" "^29.7.0" - "@jest/types" "^29.6.3" - import-local "^3.0.2" - jest-cli "^29.7.0" - -jiti@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560" - integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== - dependencies: - minimist "^1.2.0" - -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: - version "3.3.5" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" - integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== - dependencies: - array-includes "^3.1.6" - array.prototype.flat "^1.3.1" - object.assign "^4.1.4" - object.values "^1.1.6" - -keyv@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -language-subtag-registry@^0.3.20: - version "0.3.23" - resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" - integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== - -language-tags@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" - integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== - dependencies: - language-subtag-registry "^0.3.20" - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -lightningcss-darwin-arm64@1.29.2: - version "1.29.2" - resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz#6ceff38b01134af48e859394e1ca21e5d49faae6" - integrity sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA== - -lightningcss-darwin-x64@1.29.2: - version "1.29.2" - resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz#891b6f9e57682d794223c33463ca66d3af3fb038" - integrity sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w== - -lightningcss-freebsd-x64@1.29.2: - version "1.29.2" - resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz#8a95f9ab73b2b2b0beefe1599fafa8b058938495" - integrity sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg== - -lightningcss-linux-arm-gnueabihf@1.29.2: - version "1.29.2" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz#5c60bbf92b39d7ed51e363f7b98a7111bf5914a1" - integrity sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg== - -lightningcss-linux-arm64-gnu@1.29.2: - version "1.29.2" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz#e73d7608c4cce034c3654e5e8b53be74846224de" - integrity sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ== - -lightningcss-linux-arm64-musl@1.29.2: - version "1.29.2" - resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz#a95a18d5a909831c092e0a8d2de4b9ac1a8db151" - integrity sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ== - -lightningcss-linux-x64-gnu@1.29.2: - version "1.29.2" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz#551ca07e565394928642edee92acc042e546cb78" - integrity sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg== - -lightningcss-linux-x64-musl@1.29.2: - version "1.29.2" - resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz#2fd164554340831bce50285b57101817850dd258" - integrity sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w== - -lightningcss-win32-arm64-msvc@1.29.2: - version "1.29.2" - resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz#da43ea49fafc5d2de38e016f1a8539d5eed98318" - integrity sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw== - -lightningcss-win32-x64-msvc@1.29.2: - version "1.29.2" - resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz#ddefaa099a39b725b2f5bbdcb9fc718435cc9797" - integrity sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA== - -lightningcss@1.29.2: - version "1.29.2" - resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.29.2.tgz#f5f0fd6e63292a232697e6fe709da5b47624def3" - integrity sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA== - dependencies: - detect-libc "^2.0.3" - optionalDependencies: - lightningcss-darwin-arm64 "1.29.2" - lightningcss-darwin-x64 "1.29.2" - lightningcss-freebsd-x64 "1.29.2" - lightningcss-linux-arm-gnueabihf "1.29.2" - lightningcss-linux-arm64-gnu "1.29.2" - lightningcss-linux-arm64-musl "1.29.2" - lightningcss-linux-x64-gnu "1.29.2" - lightningcss-linux-x64-musl "1.29.2" - lightningcss-win32-arm64-msvc "1.29.2" - lightningcss-win32-x64-msvc "1.29.2" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -loose-envify@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lucide-react@^0.510.0: - version "0.510.0" - resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.510.0.tgz#933b19d893b30ac5cb355e4b91eeefc13088caa3" - integrity sha512-p8SQRAMVh7NhsAIETokSqDrc5CHnDLbV29mMnzaXx+Vc/hnqQzwI2r0FMWCcoTXnbw2KEjy48xwpGdEL+ck06Q== - -lz-string@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" - integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== - -magic-string@^0.30.17: - version "0.30.17" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" - integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -media-typer@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" - integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== - -merge-descriptors@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" - integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4, micromatch@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mime-db@^1.54.0: - version "1.54.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" - integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== - -mime-types@^3.0.0, mime-types@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" - integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== - dependencies: - mime-db "^1.54.0" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.0, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -minipass@^7.0.4, minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== - -minizlib@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.2.tgz#f33d638eb279f664439aa38dc5f91607468cb574" - integrity sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA== - dependencies: - minipass "^7.1.2" - -mkdirp@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" - integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== - -ms@^2.1.1, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -nanoid@^3.3.6, nanoid@^3.3.8: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== - -napi-postinstall@^0.2.2: - version "0.2.4" - resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.2.4.tgz#419697d0288cb524623e422f919624f22a5e4028" - integrity sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -negotiator@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" - integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== - -next@15.3.2: - version "15.3.2" - resolved "https://registry.yarnpkg.com/next/-/next-15.3.2.tgz#97510629e38a058dd154782a5c2ec9c9ab94d0d8" - integrity sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ== - dependencies: - "@next/env" "15.3.2" - "@swc/counter" "0.1.3" - "@swc/helpers" "0.5.15" - busboy "1.6.0" - caniuse-lite "^1.0.30001579" - postcss "8.4.31" - styled-jsx "5.1.6" - optionalDependencies: - "@next/swc-darwin-arm64" "15.3.2" - "@next/swc-darwin-x64" "15.3.2" - "@next/swc-linux-arm64-gnu" "15.3.2" - "@next/swc-linux-arm64-musl" "15.3.2" - "@next/swc-linux-x64-gnu" "15.3.2" - "@next/swc-linux-x64-musl" "15.3.2" - "@next/swc-win32-arm64-msvc" "15.3.2" - "@next/swc-win32-x64-msvc" "15.3.2" - sharp "^0.34.1" - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -object-assign@^4, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.3: - version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.4, object.assign@^4.1.7: - version "4.1.7" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" - integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.3" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - has-symbols "^1.1.0" - object-keys "^1.1.1" - -object.entries@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3" - integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.4" - define-properties "^1.2.1" - es-object-atoms "^1.1.1" - -object.fromentries@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" - integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - -object.groupby@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" - integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - -object.values@^1.1.6, object.values@^1.2.0, object.values@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" - integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.3" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -on-finished@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@^1.3.0, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.5" - -own-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" - integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== - dependencies: - get-intrinsic "^1.2.6" - object-keys "^1.1.1" - safe-push-apply "^1.0.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2, p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - 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" - -parseurl@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" - integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== - -picocolors@^1.0.0, picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -picomatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== - -pirates@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" - integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== - -pkce-challenge@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-5.0.0.tgz#c3a405cb49e272094a38e890a2b51da0228c4d97" - integrity sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -possible-typed-array-names@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" - integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== - -postcss@8.4.31: - version "8.4.31" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" - integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== - dependencies: - nanoid "^3.3.6" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -postcss@^8.4.41, postcss@^8.5.3: - version "8.5.3" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" - integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== - dependencies: - nanoid "^3.3.8" - picocolors "^1.1.1" - source-map-js "^1.2.1" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -pretty-format@^27.0.2: - version "27.5.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" - integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== - dependencies: - ansi-regex "^5.0.1" - ansi-styles "^5.0.0" - react-is "^17.0.1" - -pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -prop-types@^15.8.1: - version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - -proxy-addr@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -pure-rand@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" - integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== - -qs@^6.14.0: - version "6.14.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" - integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== - dependencies: - side-channel "^1.1.0" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -range-parser@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f" - integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.6.3" - unpipe "1.0.0" - -react-hook-form@^7.56.3: - version "7.56.3" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.56.3.tgz#c459f59fead379db30113951152e13db386cc015" - integrity sha512-IK18V6GVbab4TAo1/cz3kqajxbDPGofdF0w7VHdCo0Nt8PrPlOZcuuDq9YYIV1BtjcX78x0XsldbQRQnQXWXmw== - -react-is@^16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -react-is@^17.0.1: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - -react-is@^18.0.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== - -react@^19.0.0: - version "19.1.0" - resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75" - integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== - -reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: - version "1.0.10" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" - integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== - dependencies: - call-bind "^1.0.8" - define-properties "^1.2.1" - es-abstract "^1.23.9" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.7" - get-proto "^1.0.1" - which-builtin-type "^1.2.1" - -regexp.prototype.flags@^1.5.3: - version "1.5.4" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" - integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== - dependencies: - call-bind "^1.0.8" - define-properties "^1.2.1" - es-errors "^1.3.0" - get-proto "^1.0.1" - gopd "^1.2.0" - set-function-name "^2.0.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve-pkg-maps@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" - integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== - -resolve.exports@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" - integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== - -resolve@^1.20.0, resolve@^1.22.4: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== - dependencies: - is-core-module "^2.16.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^2.0.0-next.5: - version "2.0.0-next.5" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" - integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -reusify@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" - integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== - -router@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" - integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== - dependencies: - debug "^4.4.0" - depd "^2.0.0" - is-promise "^4.0.0" - parseurl "^1.3.3" - path-to-regexp "^8.0.0" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-array-concat@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" - integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.2" - get-intrinsic "^1.2.6" - has-symbols "^1.1.0" - isarray "^2.0.5" - -safe-buffer@5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-push-apply@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" - integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== - dependencies: - es-errors "^1.3.0" - isarray "^2.0.5" - -safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" - integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - is-regex "^1.2.1" - -"safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.1: - version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" - integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== - -send@^1.1.0, send@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212" - integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw== - dependencies: - debug "^4.3.5" - encodeurl "^2.0.0" - escape-html "^1.0.3" - etag "^1.8.1" - fresh "^2.0.0" - http-errors "^2.0.0" - mime-types "^3.0.1" - ms "^2.1.3" - on-finished "^2.4.1" - range-parser "^1.2.1" - statuses "^2.0.1" - -serve-static@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9" - integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ== - dependencies: - encodeurl "^2.0.0" - escape-html "^1.0.3" - parseurl "^1.3.3" - send "^1.2.0" - -set-function-length@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -set-function-name@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" - integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - functions-have-names "^1.2.3" - has-property-descriptors "^1.0.2" - -set-proto@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" - integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== - dependencies: - dunder-proto "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -sharp@^0.34.1: - version "0.34.1" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.34.1.tgz#e5922894b0cc7ddf159eeabc6d5668e4e8b11d61" - integrity sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg== - dependencies: - color "^4.2.3" - detect-libc "^2.0.3" - semver "^7.7.1" - optionalDependencies: - "@img/sharp-darwin-arm64" "0.34.1" - "@img/sharp-darwin-x64" "0.34.1" - "@img/sharp-libvips-darwin-arm64" "1.1.0" - "@img/sharp-libvips-darwin-x64" "1.1.0" - "@img/sharp-libvips-linux-arm" "1.1.0" - "@img/sharp-libvips-linux-arm64" "1.1.0" - "@img/sharp-libvips-linux-ppc64" "1.1.0" - "@img/sharp-libvips-linux-s390x" "1.1.0" - "@img/sharp-libvips-linux-x64" "1.1.0" - "@img/sharp-libvips-linuxmusl-arm64" "1.1.0" - "@img/sharp-libvips-linuxmusl-x64" "1.1.0" - "@img/sharp-linux-arm" "0.34.1" - "@img/sharp-linux-arm64" "0.34.1" - "@img/sharp-linux-s390x" "0.34.1" - "@img/sharp-linux-x64" "0.34.1" - "@img/sharp-linuxmusl-arm64" "0.34.1" - "@img/sharp-linuxmusl-x64" "0.34.1" - "@img/sharp-wasm32" "0.34.1" - "@img/sharp-win32-ia32" "0.34.1" - "@img/sharp-win32-x64" "0.34.1" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel-list@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" - integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" - integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - side-channel-list "^1.0.0" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -source-map-js@^1.0.2, source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stable-hash@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stable-hash/-/stable-hash-0.0.5.tgz#94e8837aaeac5b4d0f631d2972adef2924b40269" - integrity sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -statuses@2.0.1, statuses@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -streamsearch@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" - integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - 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" - -string.prototype.includes@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz#eceef21283640761a81dbe16d6c7171a4edf7d92" - integrity sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.3" - -string.prototype.matchall@^4.0.12: - version "4.0.12" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0" - integrity sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.3" - define-properties "^1.2.1" - es-abstract "^1.23.6" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.6" - gopd "^1.2.0" - has-symbols "^1.1.0" - internal-slot "^1.1.0" - regexp.prototype.flags "^1.5.3" - set-function-name "^2.0.2" - side-channel "^1.1.0" - -string.prototype.repeat@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" - integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string.prototype.trim@^1.2.10: - version "1.2.10" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" - integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.2" - define-data-property "^1.1.4" - define-properties "^1.2.1" - es-abstract "^1.23.5" - es-object-atoms "^1.0.0" - has-property-descriptors "^1.0.2" - -string.prototype.trimend@^1.0.8, string.prototype.trimend@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" - integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.2" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string.prototype.trimstart@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -styled-jsx@5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.6.tgz#83b90c077e6c6a80f7f5e8781d0f311b2fe41499" - integrity sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA== - dependencies: - client-only "0.0.1" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tailwind-merge@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-3.3.0.tgz#d9bb4f298801ef6232ced4f97e830fc79bc3d3e7" - integrity sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ== - -tailwindcss@4.1.6, tailwindcss@^4.1.6: - version "4.1.6" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.1.6.tgz#ebe62de22b7d8a8c1f76bd3a07fc37c3fcc36503" - integrity sha512-j0cGLTreM6u4OWzBeLBpycK0WIh8w7kSwcUsQZoGLHZ7xDTdM69lN64AgoIEEwFi0tnhs4wSykUa5YWxAzgFYg== - -tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -tar@^7.4.3: - version "7.4.3" - resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" - integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== - dependencies: - "@isaacs/fs-minipass" "^4.0.0" - chownr "^3.0.0" - minipass "^7.1.2" - minizlib "^3.0.1" - mkdirp "^3.0.1" - yallist "^5.0.0" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -tinyglobby@^0.2.13: - version "0.2.13" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.13.tgz#a0e46515ce6cbcd65331537e57484af5a7b2ff7e" - integrity sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw== - dependencies: - fdir "^6.4.4" - picomatch "^4.0.2" - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -ts-api-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" - integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== - -tsconfig-paths@^3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" - integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tslib@^2.4.0, tslib@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -tw-animate-css@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/tw-animate-css/-/tw-animate-css-1.2.9.tgz#b25d5fb31fd3c3ec6d91c1d1842c0d7c0fdbd999" - integrity sha512-9O4k1at9pMQff9EAcCEuy1UNO43JmaPQvq+0lwza9Y0BQ6LB38NiMj+qHqjoQf40355MX+gs6wtlR6H9WsSXFg== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-is@^2.0.0, type-is@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" - integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== - dependencies: - content-type "^1.0.5" - media-typer "^1.1.0" - mime-types "^3.0.0" - -typed-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" - integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== - dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - is-typed-array "^1.1.14" - -typed-array-byte-length@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" - integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== - dependencies: - call-bind "^1.0.8" - for-each "^0.3.3" - gopd "^1.2.0" - has-proto "^1.2.0" - is-typed-array "^1.1.14" - -typed-array-byte-offset@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" - integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.8" - for-each "^0.3.3" - gopd "^1.2.0" - has-proto "^1.2.0" - is-typed-array "^1.1.15" - reflect.getprototypeof "^1.0.9" - -typed-array-length@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" - integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - is-typed-array "^1.1.13" - possible-typed-array-names "^1.0.0" - reflect.getprototypeof "^1.0.6" - -typescript@^5: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== - -unbox-primitive@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" - integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== - dependencies: - call-bound "^1.0.3" - has-bigints "^1.0.2" - has-symbols "^1.1.0" - which-boxed-primitive "^1.1.1" - -undici-types@~6.19.2: - version "6.19.8" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" - integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== - -undici-types@~6.21.0: - version "6.21.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" - integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== - -unpipe@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -unrs-resolver@^1.6.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.7.2.tgz#a6844bcb9006020b58e718c5522a4f4552632b6b" - integrity sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A== - dependencies: - napi-postinstall "^0.2.2" - optionalDependencies: - "@unrs/resolver-binding-darwin-arm64" "1.7.2" - "@unrs/resolver-binding-darwin-x64" "1.7.2" - "@unrs/resolver-binding-freebsd-x64" "1.7.2" - "@unrs/resolver-binding-linux-arm-gnueabihf" "1.7.2" - "@unrs/resolver-binding-linux-arm-musleabihf" "1.7.2" - "@unrs/resolver-binding-linux-arm64-gnu" "1.7.2" - "@unrs/resolver-binding-linux-arm64-musl" "1.7.2" - "@unrs/resolver-binding-linux-ppc64-gnu" "1.7.2" - "@unrs/resolver-binding-linux-riscv64-gnu" "1.7.2" - "@unrs/resolver-binding-linux-riscv64-musl" "1.7.2" - "@unrs/resolver-binding-linux-s390x-gnu" "1.7.2" - "@unrs/resolver-binding-linux-x64-gnu" "1.7.2" - "@unrs/resolver-binding-linux-x64-musl" "1.7.2" - "@unrs/resolver-binding-wasm32-wasi" "1.7.2" - "@unrs/resolver-binding-win32-arm64-msvc" "1.7.2" - "@unrs/resolver-binding-win32-ia32-msvc" "1.7.2" - "@unrs/resolver-binding-win32-x64-msvc" "1.7.2" - -update-browserslist-db@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -v8-to-istanbul@^9.0.1: - version "9.3.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" - integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" - -vary@^1, vary@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" - integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== - dependencies: - is-bigint "^1.1.0" - is-boolean-object "^1.2.1" - is-number-object "^1.1.1" - is-string "^1.1.1" - is-symbol "^1.1.1" - -which-builtin-type@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" - integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== - dependencies: - call-bound "^1.0.2" - function.prototype.name "^1.1.6" - has-tostringtag "^1.0.2" - is-async-function "^2.0.0" - is-date-object "^1.1.0" - is-finalizationregistry "^1.1.0" - is-generator-function "^1.0.10" - is-regex "^1.2.1" - is-weakref "^1.0.2" - isarray "^2.0.5" - which-boxed-primitive "^1.1.0" - which-collection "^1.0.2" - which-typed-array "^1.1.16" - -which-collection@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" - integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== - dependencies: - is-map "^2.0.3" - is-set "^2.0.3" - is-weakmap "^2.0.2" - is-weakset "^2.0.3" - -which-typed-array@^1.1.16, which-typed-array@^1.1.18: - version "1.1.19" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" - integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.8" - call-bound "^1.0.4" - for-each "^0.3.5" - get-proto "^1.0.1" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -word-wrap@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" - integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.3.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - 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" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zod-to-json-schema@^3.24.1: - version "3.24.5" - resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3" - integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== - -zod@^3.23.8, zod@^3.24.2, zod@^3.24.4: - version "3.24.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f" - integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg== - -zustand@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.4.tgz#33af161f1e337854ccd8b711ef9e92545d6ae53f" - integrity sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==