diff --git a/.env.example b/.env.example index ad7ce014..fb3d6ea4 100644 --- a/.env.example +++ b/.env.example @@ -4,17 +4,10 @@ DATABASE_URL=postgresql://devcard:devcard@localhost:5432/devcard?schema=public # ─── Redis ─── REDIS_URL=redis://localhost:6379 -# ─── Set The Url ─── -PUBLIC_APP_URL= - # ─── JWT ─── -# JWT_SECRET: any long random string, minimum 32 characters -# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" JWT_SECRET=your-super-secret-jwt-key-change-in-production # ─── Encryption (for OAuth tokens) ─── -# ENCRYPTION_KEY: must be exactly 32 bytes = 64 hex characters -# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ENCRYPTION_KEY=your-32-byte-hex-encryption-key-here # ─── GitHub OAuth ─── @@ -32,4 +25,4 @@ MOBILE_REDIRECT_URI=devcard://oauth/callback # ─── Server ─── PORT=3000 -NODE_ENV=development \ No newline at end of file +NODE_ENV=development diff --git a/.github/scripts/ciScript.js b/.github/scripts/ciScript.js deleted file mode 100644 index f8cb346c..00000000 --- a/.github/scripts/ciScript.js +++ /dev/null @@ -1,101 +0,0 @@ -module.exports = async ({ github, context, core }) => { - const owner = context.repo.owner; - const repo = context.repo.repo; - const pr = context.payload.pull_request; - const prNumber = pr.number; - const prState = pr.state; - - const backendFiles = []; - const backendTests = []; - const mobileFiles = []; - const webFiles = []; - - try { - if (prState === 'closed') { - console.log(`PR state is: ${prState}`); - return { - backendChanged: false, - mobileChanged: false, - webChanged: false - }; - } - - const changedFiles = await github.paginate( - github.rest.pulls.listFiles, - { - owner, - repo, - pull_number: prNumber - } - ); - - changedFiles.forEach((file) => { - const fileName = file.filename; - - if (fileName.startsWith('apps/backend/')) { - backendFiles.push(fileName); - - const relative = fileName.replace('apps/backend/src/', ''); - const baseName = relative - .split('/') - .pop() - ?.replace(/\.(ts|tsx|js|jsx)$/, ''); - - if (baseName) { - backendTests.push(`src/__tests__/${baseName}.test.ts`); - } - - } else if (fileName.startsWith('apps/mobile/')) { - mobileFiles.push(fileName); - } else if (fileName.startsWith('apps/web/')) { - webFiles.push(fileName); - } - }); - - console.log({ - backendFiles, - backendTests, - mobileFiles, - webFiles, - }); - - core.setOutput( - "backendFiles", - backendFiles - .map(file => file.replace("apps/backend/", "")) - .join(" ") - ); - - core.setOutput( - "backendTests", - [...new Set(backendTests)].join(" ") - ); - - core.setOutput( - "mobileFiles", - mobileFiles - .map(file => file.replace("apps/mobile/", "")) - .join(" ") - ); - - core.setOutput( - "webFiles", - webFiles - .map(file => file.replace("apps/web/", "")) - .join(" ") - ); - - core.setOutput("backendChanged", backendFiles.length > 0); - core.setOutput("mobileChanged", mobileFiles.length > 0); - core.setOutput("webChanged", webFiles.length > 0); - - } catch (error) { - console.error(error); - - return { - backendChanged: false, - mobileChanged: false, - webChanged: false - }; - } -}; \ No newline at end of file diff --git a/.github/scripts/commentResults.js b/.github/scripts/commentResults.js deleted file mode 100644 index 50cd1395..00000000 --- a/.github/scripts/commentResults.js +++ /dev/null @@ -1,101 +0,0 @@ -module.exports = async ({ - github, - context, - backend, - mobile, - web, - backendLint, - backendTest, - backendTypecheck, - mobileLint, - mobileTest, - webCheck, - webBuild -}) => { - const owner = context.repo.owner; - const repo = context.repo.repo; - const prNumber = context.payload.pull_request.number; - - const emoji = (status) => { - if (status === 'success') return '✅'; - if (status === 'failure') return '❌'; - if (status === 'skipped') return '⏭️'; - return '⚪'; - }; - - const label = (status) => { - if (!status) return '⚪ unknown'; - return `${emoji(status)} ${status}`; - }; - - const anyFailure = [ - backend, - mobile, - web - ].includes('failure'); - - const title = anyFailure - ? '❌ Some checks failed' - : '✅ CI completed'; - - const timestamp = new Date().toUTCString(); - - const body = `## CI Results — ${title} - -### 🖥️ Backend (${label(backend)}) -| Check | Status | -|---|---| -| Lint | ${label(backendLint)} | -| Test | ${label(backendTest)} | -| Typecheck | ${label(backendTypecheck)} | - -### 📱 Mobile (${label(mobile)}) -| Check | Status | -|---|---| -| Lint | ${label(mobileLint)} | -| Test | ${label(mobileTest)} | - -### 🌐 Web (${label(web)}) -| Check | Status | -|---|---| -| Check | ${label(webCheck)} | -| Build | ${label(webBuild)} | - ---- -🕐 Last updated: \`${timestamp}\``; - - const COMMENT_MARKER = '## CI Results —'; - - try { - const comments = await github.paginate( - github.rest.issues.listComments, - { - owner, - repo, - issue_number: prNumber - } - ); - - const existing = comments.find( - c => c.body && c.body.startsWith(COMMENT_MARKER) - ); - - if (existing) { - await github.rest.issues.updateComment({ - owner, - repo, - comment_id: existing.id, - body - }); - } else { - await github.rest.issues.createComment({ - owner, - repo, - issue_number: prNumber, - body - }); - } - } catch (err) { - console.error(err); - } -}; \ No newline at end of file diff --git a/.github/scripts/discordPinReminder.js b/.github/scripts/discordPinReminder.js deleted file mode 100644 index d5724578..00000000 --- a/.github/scripts/discordPinReminder.js +++ /dev/null @@ -1,37 +0,0 @@ -module.exports = async ({ github, context }) => { - const pr = context.payload.pull_request; - const ignoreUsers = [ - 'ShantKhatri', - 'Harxhit', - 'blankirigaya' - ] - try { - // Only continue if merged - if (!pr || !pr.merged) { - console.log('PR not merged.'); - return; - } - - const prNumber = pr.number; - const contributor = pr.user.login; - - if(ignoreUsers.includes(contributor)){ - console.log(`Ignoring PR #${prNumber} by ${contributor}`); - return; - } - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: `Congratulations @${contributor} on getting PR #${prNumber} merged! - - Thank you for your contribution. Please mention @Harxhit in our Discord server to receive the appropriate GSSoC labels and recognition. - ` - }); - - console.log(`Comment added to PR #${prNumber}`); - } catch (error) { - console.error(error) - } -}; diff --git a/.github/scripts/unassignIssues.js b/.github/scripts/unassignIssues.js deleted file mode 100644 index b4886e91..00000000 --- a/.github/scripts/unassignIssues.js +++ /dev/null @@ -1,177 +0,0 @@ -module.exports = async ({ github, context }) => { - const owner = context.repo.owner; - const repo = context.repo.repo; - - const PROTECTED_ASSIGNEES = [ - 'ShantKhatri', - 'Harxhit', - 'blankirigaya' - ]; - - // Fetch all open issues (excluding PRs) - let page = 1; - let issues = []; - - while (true) { - const { data } = await github.rest.issues.listForRepo({ - owner, - repo, - state: 'open', - per_page: 100, - page, - }); - - const onlyIssues = data.filter( - item => !item.pull_request - ); - - issues = issues.concat(onlyIssues); - - if (data.length < 100) break; - page++; - } - - console.log( - `Found ${issues.length} open issue(s) to check.` - ); - - for (const issue of issues) { - const issueNumber = issue.number; - - // Skip if no assignees - if ( - !issue.assignees || - issue.assignees.length === 0 - ) { - console.log( - `Issue #${issueNumber} has no assignees — skipping.` - ); - continue; - } - - const assigneeLogins = - issue.assignees.map(a => a.login); - - // Skip protected assignees - const hasProtectedAssignee = - assigneeLogins.some(login => - PROTECTED_ASSIGNEES.includes(login) - ); - - if (hasProtectedAssignee) { - console.log( - `Issue #${issueNumber} has protected assignee(s) — skipping.` - ); - continue; - } - - let linkedPRFound = false; - let assignedAt = null; - - try { - const timeline = - await github.rest.issues.listEventsForTimeline({ - owner, - repo, - issue_number: issueNumber, - per_page: 100, - }); - - // Check linked PR - linkedPRFound = timeline.data.some(event => { - const pr = event.source?.issue; - - return ( - event.event === 'cross-referenced' && - pr?.pull_request && - pr.state === 'open' - ); - }); - - // Find latest assignment event - const assignEvents = - timeline.data.filter( - event => event.event === 'assigned' - ); - - if (assignEvents.length > 0) { - assignedAt = - assignEvents[ - assignEvents.length - 1 - ].created_at; - } - } catch (err) { - console.log( - `Could not fetch timeline for issue #${issueNumber}: ${err.message}` - ); - continue; - } - - // Skip if no assignment timestamp - if (!assignedAt) { - console.log( - `Issue #${issueNumber} has no assignment timestamp — skipping.` - ); - continue; - } - - const assignedDate = - new Date(assignedAt); - const now = new Date(); - - const daysAssigned = - (now - assignedDate) / - (1000 * 60 * 60 * 24); - - console.log( - `Issue #${issueNumber} assigned for ${daysAssigned.toFixed( - 1 - )} day(s).` - ); - - // Skip if assigned <= 5 days - if (daysAssigned <= 5) { - console.log( - `Issue #${issueNumber} assigned less than 5 days ago — skipping.` - ); - continue; - } - - // Skip if linked PR exists - if (linkedPRFound) { - console.log( - `Issue #${issueNumber} has linked open/draft PR — keeping assignment.` - ); - continue; - } - - // Remove assignees - await github.rest.issues.removeAssignees({ - owner, - repo, - issue_number: issueNumber, - assignees: assigneeLogins, - }); - - const assigneesMention = - assigneeLogins - .map(user => `@${user}`) - .join(', '); - - // Comment - await github.rest.issues.createComment({ - owner, - repo, - issue_number: issueNumber, - body: `Hey @ShantKhatri (Project Admin) and @Harxhit (Maintainer), - -This issue (previously assigned to ${assigneesMention}) has been **automatically unassigned** because no linked pull request was found within 5 days of assignment. - -If work is in progress, please open and link a PR to keep the assignment active.`, - }); - - console.log( - `Issue #${issueNumber} unassigned successfully.` - ); - } -}; diff --git a/.github/scripts/welcomeScript.js b/.github/scripts/welcomeScript.js deleted file mode 100644 index aa48ce7b..00000000 --- a/.github/scripts/welcomeScript.js +++ /dev/null @@ -1,72 +0,0 @@ -module.exports = async ({ github, context }) => { - const owner = context.repo.owner; - const repo = context.repo.repo; - const issueNumber = context.issue.number; - const eventName = context.eventName; - const ghUsername = context.payload.sender.login; - - try { - const issueAssociation = - context.payload.issue?.author_association; - - if ( - eventName === 'issues' && - issueAssociation === 'NONE' - ) { - // Verify this is truly their first issue (listForRepo returns PRs too) - const userIssues = await github.rest.issues.listForRepo({ - owner, - repo, - state: 'all', - creator: ghUsername, - per_page: 10 - }); - - const actualIssues = userIssues.data.filter(issue => !issue.pull_request); - - if (actualIssues.length === 1) { - return await github.rest.issues.createComment({ - owner, - repo, - issue_number: issueNumber, - body: `👋 Thanks for opening your first issue, @${ghUsername}! - -We appreciate your contribution and are excited to have you here. Please make sure to follow the contribution guidelines and provide as much detail as possible. - -To stay updated, ask questions, and connect with maintainers and contributors, please join our Discord community: -https://discord.gg/QueQN83wn - -Looking forward to collaborating with you!` - }); - } - } - - const prAssociation = - context.payload.pull_request?.author_association; - - if ( - eventName === 'pull_request_target' && - ( - prAssociation === 'FIRST_TIMER' || - prAssociation === 'FIRST_TIME_CONTRIBUTOR' - ) - ) { - return await github.rest.issues.createComment({ - owner, - repo, - issue_number: issueNumber, - body: `🎉 Thanks for your first contribution, @${ghUsername}! - -We're excited to have you here. A maintainer will review your PR soon. Please check CI results and review any feedback if needed. - -To stay updated, ask questions, and connect with maintainers and contributors, please join our Discord community: -https://discord.gg/QueQN83wn - -Looking forward to collaborating with you!` - }); - } - - } catch (error) { - console.error(error); - } -}; \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 45b5f7af..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,178 +0,0 @@ -name: CI - -on: - pull_request_target: - types: [opened, synchronize, reopened] - -permissions: - pull-requests: write - -jobs: - detect-changes: - runs-on: ubuntu-latest - - outputs: - backendChanged: ${{ steps.detect.outputs.backendChanged }} - mobileChanged: ${{ steps.detect.outputs.mobileChanged }} - webChanged: ${{ steps.detect.outputs.webChanged }} - backendFiles: ${{ steps.detect.outputs.backendFiles }} - mobileFiles: ${{ steps.detect.outputs.mobileFiles }} - webFiles: ${{ steps.detect.outputs.webFiles }} - backendTests: ${{ steps.detect.outputs.backendTests }} - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - - name: Detect changed files - id: detect - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const script = require('./.github/scripts/ciScript.js'); - return await script({ github, context, core }); - - backend-ci: - needs: detect-changes - if: needs.detect-changes.outputs.backendChanged == 'true' - runs-on: ubuntu-latest - - outputs: - backend_lint: ${{ steps.backend_lint.outcome }} - backend_test: ${{ steps.backend_test.outcome }} - backend_typecheck: ${{ steps.backend_typecheck.outcome }} - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e - with: - node-version: 22 - - - uses: pnpm/action-setup@v6.0.8 - - - run: pnpm install - - - name: Backend lint - id: backend_lint - continue-on-error: true - run: cd apps/backend && pnpm eslint ${{ needs.detect-changes.outputs.backendFiles }} - - - name: Backend test - id: backend_test - continue-on-error: true - run: cd apps/backend && pnpm test ${{ needs.detect-changes.outputs.backendTests }} - - - name: Backend typecheck - id: backend_typecheck - continue-on-error: true - run: cd apps/backend && pnpm typecheck ${{ needs.detect-changes.outputs.backendFiles }} - - - name: Fail backend if checks failed - if: steps.backend_lint.outcome == 'failure' || steps.backend_test.outcome == 'failure' || steps.backend_typecheck.outcome == 'failure' - run: exit 1 - - web-ci: - needs: detect-changes - if: needs.detect-changes.outputs.webChanged == 'true' - runs-on: ubuntu-latest - - outputs: - web_check: ${{ steps.web_check.outcome }} - web_build: ${{ steps.web_build.outcome }} - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e - with: - node-version: 22 - - - uses: pnpm/action-setup@v6.0.8 - - - run: pnpm install - - - name: Web check - id: web_check - continue-on-error: true - run: cd apps/web && pnpm check - - - name: Web build - id: web_build - continue-on-error: true - run: cd apps/web && pnpm build - - - name: Fail web if checks failed - if: steps.web_check.outcome == 'failure' || steps.web_build.outcome == 'failure' - run: exit 1 - - mobile-ci: - needs: detect-changes - if: needs.detect-changes.outputs.mobileChanged == 'true' - runs-on: ubuntu-latest - - outputs: - mobile_lint: ${{ steps.mobile_lint.outcome }} - mobile_test: ${{ steps.mobile_test.outcome }} - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e - with: - node-version: 22 - - - uses: pnpm/action-setup@v6.0.8 - - - run: pnpm install - - - name: Mobile lint - id: mobile_lint - continue-on-error: true - run: cd apps/mobile && pnpm eslint ${{ needs.detect-changes.outputs.mobileFiles }} - - - name: Mobile test - id: mobile_test - continue-on-error: true - run: cd apps/mobile && pnpm test - - - name: Fail mobile if checks failed - if: steps.mobile_lint.outcome == 'failure' || steps.mobile_test.outcome == 'failure' - run: exit 1 - - comment-results: - needs: - - backend-ci - - web-ci - - mobile-ci - if: always() - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - - name: Comment results - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const script = require('./.github/scripts/commentResults.js'); - - await script({ - github, - context, - - backend: '${{ needs.backend-ci.result }}', - web: '${{ needs.web-ci.result }}', - mobile: '${{ needs.mobile-ci.result }}', - - backendLint: '${{ needs.backend-ci.outputs.backend_lint }}', - backendTest: '${{ needs.backend-ci.outputs.backend_test }}', - backendTypecheck: '${{ needs.backend-ci.outputs.backend_typecheck }}', - - mobileLint: '${{ needs.mobile-ci.outputs.mobile_lint }}', - mobileTest: '${{ needs.mobile-ci.outputs.mobile_test }}', - - webCheck: '${{ needs.web-ci.outputs.web_check }}', - webBuild: '${{ needs.web-ci.outputs.web_build }}' - }); \ No newline at end of file diff --git a/.github/workflows/gssoc-discord-pin-reminder.yml b/.github/workflows/gssoc-discord-pin-reminder.yml deleted file mode 100644 index 5c6cd7cb..00000000 --- a/.github/workflows/gssoc-discord-pin-reminder.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: GSSoC Discord Pin Reminder - -on: - pull_request_target: - types: [closed] - workflow_dispatch: - -jobs: - discord-pin-reminder: - if: github.event.pull_request.merged == true - runs-on: ubuntu-latest - - permissions: - pull-requests: write - issues: write - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Notify contributor about Discord GSSoC labels - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const script = require('./.github/scripts/discordPinReminder.js'); - await script({ github, context }); diff --git a/.github/workflows/unassign-unlinked-issues.yml b/.github/workflows/unassign-unlinked-issues.yml deleted file mode 100644 index 40bcab5f..00000000 --- a/.github/workflows/unassign-unlinked-issues.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Unassign Issues Without Linked PR - -on: - schedule: - - cron: '0 9 */5 * *' # Runs every 5 days at 9:00 AM UTC - workflow_dispatch: # Also allows manual triggering - -jobs: - unassign-issues: - runs-on: ubuntu-latest - permissions: - issues: write - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - - - name: Unassign issues with no linked PR and notify - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const script = require('./.github/scripts/unassignIssues.js'); - await script({ github, context }); \ No newline at end of file diff --git a/.github/workflows/welcome-first-time.yml b/.github/workflows/welcome-first-time.yml deleted file mode 100644 index 2f3acc4e..00000000 --- a/.github/workflows/welcome-first-time.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Welcome First-Time Contributors - -on: - issues: - types: [opened] - pull_request_target: - types: [opened] - -permissions: - issues: write - pull-requests: write - -jobs: - welcome: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - - - name: Welcome first-time contributor - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const script = require('./.github/scripts/welcomeScript.js'); - await script({ github, context }); \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f95620b..00cb1e8b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,6 @@ # Contributing to DevCard -

- - Discord Server - -

- -**Join the community** — ask questions, get help, discuss ideas, and meet other contributors on our [Discord server](https://discord.gg/QueQN83wn). +Thank you for your interest in contributing to DevCard! This guide will help you get started. ## Development Setup diff --git a/README.md b/README.md index 26261279..cbe700ae 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,6 @@ GitHub Repo - - Discord Server -

@@ -73,11 +70,6 @@ docker compose up -d # Copy environment config cp .env.example .env -# ⚠️ Replace secret placeholders before starting the server: -# JWT_SECRET → node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -# ENCRYPTION_KEY → node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -# Paste the generated values into your .env file. Never use placeholders in production. - # Run database migrations pnpm db:migrate @@ -278,32 +270,6 @@ New to open source? We've got you covered! Check out our [Good First Issues](htt See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions, coding standards, and PR process. -## Contributors - -Thanks to all the amazing people who contribute to **DevCard** 🚀 - -

- - Contributors - -

- -
- -## Project Support - -

- - Stars - -    - - Forks - -

- ---- - ## License DevCard is licensed under the [Apache License 2.0](./LICENSE). diff --git a/apps/backend/README.md b/apps/backend/README.md deleted file mode 100644 index a807f250..00000000 --- a/apps/backend/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# DevCard Backend - -## Follow Engine Architecture - -DevCard implements a multi-layered Hybrid Follow Engine designed to connect platform professionals seamlessly while maintaining platform policy compliance. - -```mermaid -graph TD - A[User triggers Follow/Connect] --> B{Check Platform Strategy} - B -- api (GitHub) --> C[Layer 1: Direct OAuth API integration] - B -- webview (LinkedIn) --> D[Layer 2: In-app WebView Interaction Engine] - B -- link (GitLab/Devfolio) --> E[Layer 3: Native deep-linking / Browser redirect] - B -- copy (Discord) --> F[Layer 4: Clipboard Copy fallback] -``` - -### Layer 2: WebView Interaction Engine (LinkedIn) - -Due to LinkedIn's modern API restrictions preventing programmatic connection requests, direct API follow (Layer 1) is not viable. Instead, the WebView Interaction Engine routes the action through a secure, native WebView: - -1. **Routing Strategy**: The backend parses the connection request and returns `{ strategy: 'webview', url }` containing the resolved profile link. -2. **Session Persistence**: The mobile WebView loads the target profile URL using system-level OAuth cookie-sharing (`sharedCookiesEnabled={true}`), ensuring the user remains authenticated. -3. **DOM Introspection**: A lightweight JavaScript snippet is injected to continuously poll for the native LinkedIn 'Connect' button, smooth-scrolls it into view, and highlights it visually to encourage action. -4. **Interactive Send**: Users retain full control over actual connection request submission, adhering completely to platform terms of service. -5. **State Detection**: - - URL State Polling: The engine inspects URL transitions containing `invite-sent` or similar sub-routes. - - DOM Observation: The injected Javascript queries for structural indicators of successful invitation (e.g. "Pending" button state or toaster text) and posts a serialized message back to the native layer. -6. **Robust Fallback**: If network or WebView loading times out (>10s), the engine gracefully falls back to native deep links (`linkedin://profile?id={username}`) or launches the default browser with an interactive custom in-app overlay. -7. **Telemetry Logging**: Upon client-side success (detected via state changes or DOM indicators), the mobile app makes a `POST /api/follow/:platform/:targetUsername/log` request to the backend. This writes a record to the `FollowLog` database table for auditing and analytics tracking. diff --git a/apps/backend/eslint.config.js b/apps/backend/eslint.config.js deleted file mode 100644 index 3924db19..00000000 --- a/apps/backend/eslint.config.js +++ /dev/null @@ -1,202 +0,0 @@ -import tseslint from 'typescript-eslint'; -import pluginN from 'eslint-plugin-n'; -import pluginImportX from 'eslint-plugin-import-x'; -import pluginPromise from 'eslint-plugin-promise'; -import pluginSecurity from 'eslint-plugin-security'; -import pluginUnicorn from 'eslint-plugin-unicorn'; - -export default tseslint.config( - - // ─── Global Ignores ────────────────────────────────────────────────────────── - { - ignores: [ - 'dist/**', - 'build/**', - 'node_modules/**', - 'coverage/**', - 'prisma/migrations/**', - '**/*.d.ts', - ], - }, - - // ─── Base: ESLint Recommended + TypeScript ────────────────────────────────── - ...tseslint.configs.recommendedTypeChecked, - - // ─── Main Config ──────────────────────────────────────────────────────────── - { - files: ['src/**/*.ts'], - - languageOptions: { - parserOptions: { - projectService: true, - tsconfigRootDir: import.meta.dirname, - }, - }, - - plugins: { - n: pluginN, - 'import-x': pluginImportX, - promise: pluginPromise, - security: pluginSecurity, - unicorn: pluginUnicorn, - }, - - settings: { - 'import-x/resolver': { - typescript: { project: './tsconfig.json' }, - node: true, - }, - node: { version: '>=18.0.0' }, - }, - - rules: { - - // ── TypeScript: Type Safety: currently off ───────────────────────────────────────────── - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unsafe-argument': 'off', - '@typescript-eslint/no-unsafe-assignment': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/no-unsafe-return': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-unnecessary-type-assertion': 'off', - '@typescript-eslint/prefer-nullish-coalescing': 'off', - '@typescript-eslint/prefer-optional-chain': 'off', - '@typescript-eslint/strict-boolean-expressions': 'off', - - // ── TypeScript: Async / Promises: currently off ──────────────────────────────────────── - '@typescript-eslint/no-floating-promises': 'off', - '@typescript-eslint/no-misused-promises': 'off', - '@typescript-eslint/await-thenable': 'off', - '@typescript-eslint/require-await': 'off', - '@typescript-eslint/return-await': 'off', - - // ── TypeScript: Imports ───────────────────────────────────────────────── - '@typescript-eslint/consistent-type-imports': [ - 'error', - { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, - ], - '@typescript-eslint/consistent-type-exports': 'error', - '@typescript-eslint/no-import-type-side-effects': 'error', - - // ── TypeScript: Code Quality ──────────────────────────────────────────── - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - caughtErrorsIgnorePattern: '^_', - }, - ], - '@typescript-eslint/explicit-function-return-type': [ - 'warn', - { - allowExpressions: true, - allowTypedFunctionExpressions: true, - }, - ], - '@typescript-eslint/prefer-as-const': 'error', - '@typescript-eslint/no-redundant-type-constituents': 'warn', - '@typescript-eslint/no-shadow': 'error', - '@typescript-eslint/no-use-before-define': ['error', { functions: false }], - - // ── Node.js ───────────────────────────────────────────────────────────── - 'n/no-deprecated-api': 'error', - 'n/no-extraneous-import': 'error', - 'n/no-process-exit': 'off', - 'n/prefer-global/buffer': ['error', 'always'], - 'n/prefer-global/process': ['error', 'always'], - 'n/prefer-promises/fs': 'error', - 'n/prefer-promises/dns': 'error', - 'n/no-sync': 'warn', - - // ── Imports (import-x) ────────────────────────────────────────────────── - 'import-x/no-duplicates': 'error', - 'import-x/no-cycle': 'off', - 'import-x/no-self-import': 'error', - 'import-x/first': 'error', - 'import-x/newline-after-import': 'error', - 'import-x/order': [ - 'error', - { - groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'type'], - 'newlines-between': 'always', - alphabetize: { order: 'asc', caseInsensitive: true }, - }, - ], - - // ── Promises ──────────────────────────────────────────────────────────── - 'promise/always-return': 'off', - 'promise/catch-or-return': 'off', - 'promise/no-new-statics': 'error', - 'promise/no-return-wrap': 'error', - 'promise/param-names': 'error', - 'promise/no-promise-in-callback': 'warn', - - // ── Security ──────────────────────────────────────────────────────────── - 'security/detect-object-injection': 'off', - 'security/detect-non-literal-regexp': 'warn', - 'security/detect-non-literal-fs-filename': 'warn', - 'security/detect-eval-with-expression': 'error', - 'security/detect-child-process': 'warn', - 'security/detect-possible-timing-attacks': 'warn', - - // ── Unicorn ───────────────────────────────────────────────────────────── - 'unicorn/prefer-node-protocol': 'error', - 'unicorn/no-process-exit': 'off', - 'unicorn/error-message': 'off', - 'unicorn/throw-new-error': 'off', - 'unicorn/no-useless-undefined': 'off', - 'unicorn/prefer-string-slice': 'warn', - 'unicorn/no-for-loop': 'off', - 'unicorn/prefer-includes': 'warn', - 'unicorn/no-array-for-each': 'off', - 'unicorn/prefer-ternary': 'off', - 'unicorn/prevent-abbreviations': 'off', - - // ── Core ESLint ───────────────────────────────────────────────────────── - 'no-console': 'warn', - 'eqeqeq': ['error', 'always'], - 'no-var': 'error', - 'prefer-const': 'error', - 'no-throw-literal': 'error', - 'curly': ['error', 'all'], - 'object-shorthand': 'error', - 'no-lonely-if': 'warn', - 'no-nested-ternary': 'off', - 'prefer-rest-params': 'error', - 'prefer-spread': 'error', - 'no-param-reassign': [ - 'error', - { - props: true, - ignorePropertyModificationsFor: ['acc', 'request', 'reply'], - }, - ], - }, - }, - - // ─── Test File Overrides ──────────────────────────────────────────────────── - { - files: ['**/*.test.ts', '**/*.spec.ts', 'src/__tests__/**/*.ts'], - rules: { - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unsafe-assignment': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-floating-promises': 'off', - 'security/detect-object-injection': 'off', - 'no-console': 'off', - }, - }, - - // ─── Prisma Seed / Scripts Override ──────────────────────────────────────── - { - files: ['prisma/**/*.ts', 'scripts/**/*.ts'], - rules: { - 'n/no-process-exit': 'off', - 'unicorn/no-process-exit': 'off', - 'no-console': 'off', - }, - }, -); \ No newline at end of file diff --git a/apps/backend/package.json b/apps/backend/package.json index 8bc19bf8..b8d11411 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -9,13 +9,11 @@ "start": "node dist/server.js", "test": "vitest run", "test:watch": "vitest", - "lint:fix": "eslint src/ --fix", "lint": "eslint src/", "db:migrate": "prisma migrate dev", "db:deploy": "prisma migrate deploy", "db:seed": "tsx prisma/seed.ts", - "db:studio": "prisma studio", - "typecheck": "tsc --noEmit" + "db:studio": "prisma studio" }, "dependencies": { "@devcard/shared": "workspace:*", @@ -24,7 +22,6 @@ "@fastify/helmet": "^12.0.0", "@fastify/jwt": "^9.0.0", "@fastify/multipart": "^9.0.0", - "@fastify/rate-limit": "^10.3.0", "@fastify/static": "^8.0.0", "@prisma/client": "^6.0.0", "dotenv": "^16.4.0", @@ -37,18 +34,10 @@ "devDependencies": { "@types/node": "^22.0.0", "@types/qrcode": "^1.5.0", - "eslint": "^10.4.0", - "eslint-import-resolver-typescript": "^4.4.4", - "eslint-plugin-import-x": "^4.16.2", - "eslint-plugin-n": "^18.0.1", - "eslint-plugin-promise": "^7.3.0", - "eslint-plugin-security": "^4.0.0", - "eslint-plugin-unicorn": "^64.0.0", "pino-pretty": "^13.1.3", "prisma": "^6.0.0", "tsx": "^4.0.0", "typescript": "^5.4.0", - "typescript-eslint": "^8.59.3", "vitest": "^2.0.0" } -} \ No newline at end of file +} diff --git a/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma index 28458021..13dec572 100644 --- a/apps/backend/prisma/schema.prisma +++ b/apps/backend/prisma/schema.prisma @@ -1,10 +1,10 @@ generator client { provider = "prisma-client-js" } + datasource db { provider = "postgresql" url = env("DATABASE_URL") - } model User { @@ -29,11 +29,6 @@ model User { ownedViews CardView[] @relation("cardOwner") viewedCards CardView[] @relation("cardViewer") followLogs FollowLog[] - organizer Event[] - attendedEvents EventAttendee[] - - ownedTeams Team[] @relation("TeamOwner") - teamMemberships TeamMember[] @relation("TeamMember") @@unique([provider, providerId]) @@map("users") @@ -129,69 +124,3 @@ model FollowLog { @@map("follow_logs") } - -model Event { - id String @id @default(uuid()) - name String - slug String @unique - location String - description String? - organizerId String - startDate DateTime - endDate DateTime - isPublic Boolean @default(true) - createdAt DateTime @default(now()) @map("created_at") - attendees EventAttendee[] - - organizer User @relation(fields: [organizerId], references: [id]) -} - -model EventAttendee { - id String @id @default(uuid()) - userId String - eventId String - joinedAt DateTime - - event Event @relation(fields: [eventId] , references: [id]) - user User @relation(fields: [userId],references: [id]) - - @@unique([userId, eventId]) -} - -enum TeamRole { - OWNER - ADMIN - MEMBER -} - -model Team{ - id String @id @default(uuid()) - name String - slug String @unique - description String? - avatarUrl String? - ownerId String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - owner User @relation("TeamOwner", fields: [ownerId], references: [id], onDelete: Restrict) - members TeamMember[] @relation("TeamMember") - - @@map("teams") - @@index([slug]) -} - -model TeamMember{ - id String @id @default(uuid()) - teamId String - userId String - role TeamRole - joinedAt DateTime - - team Team @relation("TeamMember",fields: [teamId] , references: [id], onDelete: Cascade) - user User @relation("TeamMember",fields: [userId] , references: [id]) - - @@unique([userId, teamId]) - @@index([userId]) - @@map("team_members") -} \ No newline at end of file diff --git a/apps/backend/prisma/seed.ts b/apps/backend/prisma/seed.ts index f19345d8..a799a3e3 100644 --- a/apps/backend/prisma/seed.ts +++ b/apps/backend/prisma/seed.ts @@ -142,8 +142,8 @@ async function main() { } main() - .catch((error) => { - console.error('❌ Seed failed:', error); + .catch((e) => { + console.error('❌ Seed failed:', e); process.exit(1); }) .finally(async () => { diff --git a/apps/backend/src/__tests__/analytics.test.ts b/apps/backend/src/__tests__/analytics.test.ts deleted file mode 100644 index 4f0d07ae..00000000 --- a/apps/backend/src/__tests__/analytics.test.ts +++ /dev/null @@ -1,466 +0,0 @@ -import { - describe, - it, - expect, - beforeEach, - afterEach, - vi, -} from 'vitest'; - -import Fastify, { - type FastifyInstance, -} from 'fastify'; - -import type { PrismaClient } from '@prisma/client'; - -import { analyticsRoutes } from '../routes/analytics'; - -// ─── Shared mock data ──────────────────────────────────────────────────────── - -const MOCK_USER_ID = 'user-001'; - -// ─── Prisma mock ───────────────────────────────────────────────────────────── - -const prismaMock = { - cardView: { - count: vi.fn(), - findMany: vi.fn(), - groupBy: vi.fn(), - }, - followLog: { - count: vi.fn(), - }, -}; - -// ─── App factory ───────────────────────────────────────────────────────────── - -let mockJwtVerify = vi.fn(); - -async function buildApp(): Promise { - const app = Fastify({ - logger: false, - }); - - app.decorate( - 'prisma', - prismaMock as unknown as PrismaClient - ); - - app.decorateRequest( - 'jwtVerify', - function () { - return mockJwtVerify(); - } - ); - - app.decorate( - 'authenticate', - async function ( - request: any, - reply: any - ) { - try { - const user = - await request.jwtVerify(); - - request.user = user; - } catch (_err) { - return reply.status(401).send({ - error: 'Unauthorized', - }); - } - } - ); - - await app.register( - analyticsRoutes, - { - prefix: '/api/analytics', - } - ); - - await app.ready(); - return app; -} - -// ─── Helpers ───────────────────────────────────────────────────────────────── - -function authHeader(): Record { - return { - Authorization: - 'Bearer mock-token', - }; -} - -// ─── Test Suite ────────────────────────────────────────────────────────────── - -describe( - 'Analytics API', - () => { - let app: FastifyInstance; - - beforeEach( - async () => { - vi.clearAllMocks(); - - mockJwtVerify.mockResolvedValue( - { - id: MOCK_USER_ID, - } - ); - - app = await buildApp(); - } - ); - - afterEach( - async () => { - await app.close(); - } - ); - - // ── GET /overview ─────────────────────────────────────────────────────── - - describe( - 'GET /api/analytics/overview', - () => { - it( - '200 — returns analytics overview', - async () => { - prismaMock.cardView.count - .mockResolvedValueOnce( - 100 - ) - .mockResolvedValueOnce( - 10 - ); - - prismaMock.followLog.count.mockResolvedValue( - 5 - ); - - prismaMock.cardView.findMany.mockResolvedValue( - [ - { - id: 'view-1', - viewer: { - displayName: - 'John', - avatarUrl: - null, - }, - card: { - title: - 'My Card', - }, - }, - ] - ); - - prismaMock.cardView.groupBy.mockResolvedValue( - [ - { - viewerId: - 'u1', - viewerIp: - null, - }, - { - viewerId: - 'u2', - viewerIp: - null, - }, - ] - ); - - const res = - await app.inject( - { - method: - 'GET', - url: - '/api/analytics/overview', - headers: - authHeader(), - } - ); - - expect( - res.statusCode - ).toBe(200); - - const body = - res.json(); - - expect( - body.totalViews - ).toBe(100); - - expect( - body.viewsToday - ).toBe(10); - - expect( - body.totalFollows - ).toBe(5); - - expect( - body.uniqueViewers - ).toBe(2); - - expect( - body.recentViews - ).toHaveLength( - 1 - ); - } - ); - - it( - '401 — rejects unauthenticated request', - async () => { - mockJwtVerify.mockRejectedValue( - new Error( - 'Unauthorized' - ) - ); - - const res = - await app.inject( - { - method: - 'GET', - url: - '/api/analytics/overview', - } - ); - - expect( - res.statusCode - ).toBe(401); - - expect( - res.json() - ).toMatchObject( - { - error: - 'Unauthorized', - } - ); - } - ); - } - ); - - // ── GET /views ────────────────────────────────────────────────────────── - - describe( - 'GET /api/analytics/views', - () => { - it( - '200 — returns paginated views', - async () => { - prismaMock.cardView.count.mockResolvedValue( - 45 - ); - - prismaMock.cardView.findMany.mockResolvedValue( - [ - { - id: - 'view-1', - viewer: - { - id: - 'viewer-1', - username: - 'john', - displayName: - 'John', - avatarUrl: - null, - }, - card: - { - id: - 'card-1', - title: - 'Portfolio', - }, - }, - ] - ); - - const res = - await app.inject( - { - method: - 'GET', - url: - '/api/analytics/views?page=2', - headers: - authHeader(), - } - ); - - expect( - res.statusCode - ).toBe(200); - - const body = - res.json(); - - expect( - body.data - ).toHaveLength( - 1 - ); - - expect( - body.meta - ).toMatchObject( - { - total: - 45, - page: 2, - limit: - 20, - totalPages: - 3, - } - ); - - expect( - prismaMock.cardView.findMany.mock.calls[0][0] - ).toMatchObject( - { - skip: - 20, - take: - 20, - } - ); - } - ); - - it( - '200 — filters by cardId when provided', - async () => { - prismaMock.cardView.count.mockResolvedValue( - 0 - ); - - prismaMock.cardView.findMany.mockResolvedValue( - [] - ); - - const res = - await app.inject( - { - method: - 'GET', - url: - '/api/analytics/views?cardId=card-123', - headers: - authHeader(), - } - ); - - expect( - res.statusCode - ).toBe(200); - - expect( - prismaMock.cardView.count.mock.calls[0][0] - ).toMatchObject( - { - where: - { - ownerId: - MOCK_USER_ID, - cardId: - 'card-123', - }, - } - ); - } - ); - - it( - '200 — defaults to page 1', - async () => { - prismaMock.cardView.count.mockResolvedValue( - 0 - ); - - prismaMock.cardView.findMany.mockResolvedValue( - [] - ); - - const res = - await app.inject( - { - method: - 'GET', - url: - '/api/analytics/views', - headers: - authHeader(), - } - ); - - expect( - res.statusCode - ).toBe(200); - - expect( - prismaMock.cardView.findMany.mock.calls[0][0] - ).toMatchObject( - { - skip: - 0, - take: - 20, - } - ); - } - ); - - it( - '401 — rejects unauthenticated request', - async () => { - mockJwtVerify.mockRejectedValue( - new Error( - 'Unauthorized' - ) - ); - - const res = - await app.inject( - { - method: - 'GET', - url: - '/api/analytics/views', - } - ); - - expect( - res.statusCode - ).toBe(401); - - expect( - res.json() - ).toMatchObject( - { - error: - 'Unauthorized', - } - ); - } - ); - } - ); - } -); \ No newline at end of file diff --git a/apps/backend/src/__tests__/app.test.ts b/apps/backend/src/__tests__/app.test.ts deleted file mode 100644 index 648d98a6..00000000 --- a/apps/backend/src/__tests__/app.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -process.env.NODE_ENV = 'test'; - -import { describe, it, expect } from 'vitest'; -import { buildApp } from '../app'; - -describe('GET /health', () => { - it('should return status ok', async () => { - const app = await buildApp(); - - const res = await app.inject({ - method: 'GET', - url: '/health', - }); - - expect(res.statusCode).toBe(200); - expect(JSON.parse(res.body)).toEqual({ status: 'ok' }); - - await app.close(); - }); -}); \ No newline at end of file diff --git a/apps/backend/src/__tests__/cards.test.ts b/apps/backend/src/__tests__/cards.test.ts deleted file mode 100644 index 813883e8..00000000 --- a/apps/backend/src/__tests__/cards.test.ts +++ /dev/null @@ -1,440 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import Fastify from 'fastify'; -import { cardRoutes } from '../routes/cards.js'; - -const USER_ID = 'user-123'; -const CARD_ID = 'card-abc'; -// Must be valid UUIDs — createCardSchema and updateCardSchema use z.string().uuid() -const OWNED_LINK_ID = '11111111-1111-1111-1111-111111111111'; -const FOREIGN_LINK_ID = '22222222-2222-2222-2222-222222222222'; - -const mockCard = { - id: CARD_ID, - userId: USER_ID, - title: 'My Card', - isDefault: true, - createdAt: new Date(), - updatedAt: new Date(), - cardLinks: [], -}; - -// $transaction executes the callback synchronously against the same mock client, -// mirroring Prisma's interactive-transactions API without a real DB connection. -const mockPrisma = { - card: { - count: vi.fn(), - create: vi.fn(), - findMany: vi.fn(), - findFirst: vi.fn(), - findUnique: vi.fn(), - update: vi.fn(), - updateMany: vi.fn(), - delete: vi.fn(), - }, - cardLink: { - deleteMany: vi.fn(), - createMany: vi.fn(), - }, - platformLink: { - findMany: vi.fn(), - }, - $transaction: vi.fn(), -}; - -// Re-wire $transaction before every test so that it executes the callback -// against the same mock client, preserving existing per-operation mocks. -function wireTransaction() { - mockPrisma.$transaction.mockImplementation( - async (callback: (tx: typeof mockPrisma) => Promise) => callback(mockPrisma), - ); -} - -async function buildApp() { - const app = Fastify({ logger: false }); - app.decorate('prisma', mockPrisma); - app.decorate('authenticate', async (request: any) => { - request.user = { id: USER_ID }; - }); - app.register(cardRoutes, { prefix: '/api/cards' }); - await app.ready(); - return app; -} - -// ───────────────────────────────────────────────────────────────────────────── -// POST /api/cards -// ───────────────────────────────────────────────────────────────────────────── - -describe('POST /api/cards — link ownership validation', () => { - beforeEach(() => { - vi.clearAllMocks(); - wireTransaction(); - }); - - it('returns 403 when a supplied linkId belongs to another user', async () => { - mockPrisma.platformLink.findMany.mockResolvedValue([]); - - const app = await buildApp(); - const res = await app.inject({ - method: 'POST', - url: '/api/cards', - payload: { title: 'Test Card', linkIds: [FOREIGN_LINK_ID] }, - }); - - expect(res.statusCode).toBe(403); - expect(res.json().error).toBe('One or more links do not belong to your account'); - expect(mockPrisma.card.create).not.toHaveBeenCalled(); - }); - - it('returns 403 when a mix of owned and foreign linkIds is supplied', async () => { - // Only 1 of 2 requested IDs is owned — count mismatch triggers 403 - mockPrisma.platformLink.findMany.mockResolvedValue([{ id: OWNED_LINK_ID }]); - - const app = await buildApp(); - const res = await app.inject({ - method: 'POST', - url: '/api/cards', - payload: { title: 'Test Card', linkIds: [OWNED_LINK_ID, FOREIGN_LINK_ID] }, - }); - - expect(res.statusCode).toBe(403); - expect(res.json().error).toBe('One or more links do not belong to your account'); - expect(mockPrisma.card.create).not.toHaveBeenCalled(); - }); - - it('creates the card when all linkIds are owned by the user', async () => { - mockPrisma.platformLink.findMany.mockResolvedValue([{ id: OWNED_LINK_ID }]); - mockPrisma.card.count.mockResolvedValue(0); - mockPrisma.card.create.mockResolvedValue({ ...mockCard, cardLinks: [] }); - - const app = await buildApp(); - const res = await app.inject({ - method: 'POST', - url: '/api/cards', - payload: { title: 'Test Card', linkIds: [OWNED_LINK_ID] }, - }); - - expect(res.statusCode).toBe(201); - expect(mockPrisma.platformLink.findMany).toHaveBeenCalledWith({ - where: { id: { in: [OWNED_LINK_ID] }, userId: USER_ID }, - select: { id: true }, - }); - }); - - it('skips the ownership check and creates the card when linkIds is empty', async () => { - mockPrisma.card.count.mockResolvedValue(1); - mockPrisma.card.create.mockResolvedValue({ ...mockCard, isDefault: false, cardLinks: [] }); - - const app = await buildApp(); - const res = await app.inject({ - method: 'POST', - url: '/api/cards', - payload: { title: 'Empty Card', linkIds: [] }, - }); - - expect(res.statusCode).toBe(201); - expect(mockPrisma.platformLink.findMany).not.toHaveBeenCalled(); - }); - - it('returns 500 when the ownership query throws unexpectedly', async () => { - mockPrisma.platformLink.findMany.mockRejectedValue(new Error('DB connection lost')); - - const app = await buildApp(); - const res = await app.inject({ - method: 'POST', - url: '/api/cards', - payload: { title: 'Test Card', linkIds: [OWNED_LINK_ID] }, - }); - - expect(res.statusCode).toBe(500); - // No write must have been attempted after the read failure - expect(mockPrisma.card.create).not.toHaveBeenCalled(); - }); - - it('returns 500 when card.count throws and no partial write occurs', async () => { - mockPrisma.platformLink.findMany.mockResolvedValue([{ id: OWNED_LINK_ID }]); - mockPrisma.card.count.mockRejectedValue(new Error('Query timeout')); - - const app = await buildApp(); - const res = await app.inject({ - method: 'POST', - url: '/api/cards', - payload: { title: 'Test Card', linkIds: [OWNED_LINK_ID] }, - }); - - expect(res.statusCode).toBe(500); - expect(mockPrisma.card.create).not.toHaveBeenCalled(); - }); - - it('returns 500 when card.create throws', async () => { - mockPrisma.platformLink.findMany.mockResolvedValue([{ id: OWNED_LINK_ID }]); - mockPrisma.card.count.mockResolvedValue(0); - mockPrisma.card.create.mockRejectedValue(new Error('FK constraint violation')); - - const app = await buildApp(); - const res = await app.inject({ - method: 'POST', - url: '/api/cards', - payload: { title: 'Test Card', linkIds: [OWNED_LINK_ID] }, - }); - - expect(res.statusCode).toBe(500); - }); -}); - -// ───────────────────────────────────────────────────────────────────────────── -// PUT /api/cards/:id -// ───────────────────────────────────────────────────────────────────────────── - -describe('PUT /api/cards/:id — link ownership validation', () => { - beforeEach(() => { - vi.clearAllMocks(); - wireTransaction(); - }); - - it('returns 403 when a supplied linkId belongs to another user', async () => { - mockPrisma.card.findFirst.mockResolvedValue(mockCard); - mockPrisma.platformLink.findMany.mockResolvedValue([]); - - const app = await buildApp(); - const res = await app.inject({ - method: 'PUT', - url: `/api/cards/${CARD_ID}`, - payload: { linkIds: [FOREIGN_LINK_ID] }, - }); - - expect(res.statusCode).toBe(403); - expect(res.json().error).toBe('One or more links do not belong to your account'); - // Existing links must not have been touched - expect(mockPrisma.$transaction).not.toHaveBeenCalled(); - expect(mockPrisma.cardLink.deleteMany).not.toHaveBeenCalled(); - expect(mockPrisma.cardLink.createMany).not.toHaveBeenCalled(); - }); - - it('updates links atomically when all supplied linkIds are owned', async () => { - mockPrisma.card.findFirst.mockResolvedValue(mockCard); - mockPrisma.platformLink.findMany.mockResolvedValue([{ id: OWNED_LINK_ID }]); - mockPrisma.cardLink.deleteMany.mockResolvedValue({ count: 0 }); - mockPrisma.cardLink.createMany.mockResolvedValue({ count: 1 }); - mockPrisma.card.findUnique.mockResolvedValue({ ...mockCard, cardLinks: [] }); - - const app = await buildApp(); - const res = await app.inject({ - method: 'PUT', - url: `/api/cards/${CARD_ID}`, - payload: { linkIds: [OWNED_LINK_ID] }, - }); - - expect(res.statusCode).toBe(200); - expect(mockPrisma.platformLink.findMany).toHaveBeenCalledWith({ - where: { id: { in: [OWNED_LINK_ID] }, userId: USER_ID }, - select: { id: true }, - }); - // Both operations must run inside the transaction, not as bare queries - expect(mockPrisma.$transaction).toHaveBeenCalledOnce(); - expect(mockPrisma.cardLink.deleteMany).toHaveBeenCalledWith({ where: { cardId: CARD_ID } }); - expect(mockPrisma.cardLink.createMany).toHaveBeenCalled(); - }); - - it('returns 404 when the card does not belong to the user', async () => { - mockPrisma.card.findFirst.mockResolvedValue(null); - - const app = await buildApp(); - const res = await app.inject({ - method: 'PUT', - url: `/api/cards/${CARD_ID}`, - payload: { linkIds: [OWNED_LINK_ID] }, - }); - - expect(res.statusCode).toBe(404); - expect(mockPrisma.platformLink.findMany).not.toHaveBeenCalled(); - }); - - it('returns 500 when the ownership query throws and no mutation occurs', async () => { - mockPrisma.card.findFirst.mockResolvedValue(mockCard); - mockPrisma.platformLink.findMany.mockRejectedValue(new Error('DB timeout')); - - const app = await buildApp(); - const res = await app.inject({ - method: 'PUT', - url: `/api/cards/${CARD_ID}`, - payload: { linkIds: [OWNED_LINK_ID] }, - }); - - expect(res.statusCode).toBe(500); - expect(mockPrisma.$transaction).not.toHaveBeenCalled(); - expect(mockPrisma.cardLink.deleteMany).not.toHaveBeenCalled(); - }); - - it('returns 500 and preserves existing links when the transaction fails mid-flight', async () => { - // Ownership check passes; deleteMany succeeds; createMany fails. - // The transaction rolls back, so the card retains its original links. - mockPrisma.card.findFirst.mockResolvedValue(mockCard); - mockPrisma.platformLink.findMany.mockResolvedValue([{ id: OWNED_LINK_ID }]); - mockPrisma.cardLink.deleteMany.mockResolvedValue({ count: 1 }); - mockPrisma.cardLink.createMany.mockRejectedValue(new Error('FK constraint')); - - const app = await buildApp(); - const res = await app.inject({ - method: 'PUT', - url: `/api/cards/${CARD_ID}`, - payload: { linkIds: [OWNED_LINK_ID] }, - }); - - expect(res.statusCode).toBe(500); - // Both were attempted inside the transaction (the DB rolls them back together) - expect(mockPrisma.cardLink.deleteMany).toHaveBeenCalled(); - expect(mockPrisma.cardLink.createMany).toHaveBeenCalled(); - // The final read must not have been called -- we short-circuited on error - expect(mockPrisma.card.findUnique).not.toHaveBeenCalled(); - }); - - it('returns 500 when card.findFirst throws', async () => { - mockPrisma.card.findFirst.mockRejectedValue(new Error('Connection refused')); - - const app = await buildApp(); - const res = await app.inject({ - method: 'PUT', - url: `/api/cards/${CARD_ID}`, - payload: { linkIds: [OWNED_LINK_ID] }, - }); - - expect(res.statusCode).toBe(500); - }); -}); - -// ───────────────────────────────────────────────────────────────────────────── -// DELETE /api/cards/:id -// ───────────────────────────────────────────────────────────────────────────── - -describe('DELETE /api/cards/:id', () => { - beforeEach(() => { - vi.clearAllMocks(); - wireTransaction(); - }); - - it('returns 204 on successful deletion of a non-default card', async () => { - mockPrisma.card.findFirst.mockResolvedValue({ ...mockCard, isDefault: false }); - mockPrisma.card.count.mockResolvedValue(2); - mockPrisma.card.delete.mockResolvedValue(mockCard); - - const app = await buildApp(); - const res = await app.inject({ method: 'DELETE', url: `/api/cards/${CARD_ID}` }); - - expect(res.statusCode).toBe(204); - expect(mockPrisma.card.delete).toHaveBeenCalledWith({ where: { id: CARD_ID } }); - // No reassignment needed for a non-default card - expect(mockPrisma.card.update).not.toHaveBeenCalled(); - }); - - it('returns 204 and reassigns default when deleting the current default card', async () => { - const otherCard = { id: 'card-other', isDefault: false, userId: USER_ID }; - // First findFirst: card being deleted. Second findFirst: oldest remaining. - mockPrisma.card.findFirst - .mockResolvedValueOnce({ ...mockCard, isDefault: true }) - .mockResolvedValueOnce(otherCard); - mockPrisma.card.count.mockResolvedValue(2); - mockPrisma.card.update.mockResolvedValue({ ...otherCard, isDefault: true }); - mockPrisma.card.delete.mockResolvedValue(mockCard); - - const app = await buildApp(); - const res = await app.inject({ method: 'DELETE', url: `/api/cards/${CARD_ID}` }); - - expect(res.statusCode).toBe(204); - expect(mockPrisma.card.update).toHaveBeenCalledWith({ - where: { id: otherCard.id }, - data: { isDefault: true }, - }); - expect(mockPrisma.card.delete).toHaveBeenCalledWith({ where: { id: CARD_ID } }); - }); - - it('returns 404 when the card is not owned by the user', async () => { - mockPrisma.card.findFirst.mockResolvedValue(null); - - const app = await buildApp(); - const res = await app.inject({ method: 'DELETE', url: `/api/cards/${CARD_ID}` }); - - expect(res.statusCode).toBe(404); - expect(mockPrisma.card.delete).not.toHaveBeenCalled(); - }); - - it('returns 400 when attempting to delete the last remaining card', async () => { - mockPrisma.card.findFirst.mockResolvedValue(mockCard); - mockPrisma.card.count.mockResolvedValue(1); - - const app = await buildApp(); - const res = await app.inject({ method: 'DELETE', url: `/api/cards/${CARD_ID}` }); - - expect(res.statusCode).toBe(400); - expect(res.json().error).toBe('Cannot delete the last remaining card. A user must have at least one card.'); - expect(mockPrisma.card.delete).not.toHaveBeenCalled(); - }); - - it('returns 500 when card.delete throws', async () => { - mockPrisma.card.findFirst.mockResolvedValue({ ...mockCard, isDefault: false }); - mockPrisma.card.count.mockResolvedValue(2); - mockPrisma.card.delete.mockRejectedValue(new Error('Deadlock detected')); - - const app = await buildApp(); - const res = await app.inject({ method: 'DELETE', url: `/api/cards/${CARD_ID}` }); - - expect(res.statusCode).toBe(500); - }); -}); - -// ───────────────────────────────────────────────────────────────────────────── -// PUT /api/cards/:id/default -// ───────────────────────────────────────────────────────────────────────────── - -describe('PUT /api/cards/:id/default', () => { - beforeEach(() => { - vi.clearAllMocks(); - wireTransaction(); - }); - - it('returns 200 and sets the card as default', async () => { - mockPrisma.card.findFirst.mockResolvedValue(mockCard); - mockPrisma.card.updateMany.mockResolvedValue({ count: 2 }); - mockPrisma.card.update.mockResolvedValue({ ...mockCard, isDefault: true }); - - const app = await buildApp(); - const res = await app.inject({ method: 'PUT', url: `/api/cards/${CARD_ID}/default` }); - - expect(res.statusCode).toBe(200); - expect(res.json().message).toBe('Default card updated'); - expect(mockPrisma.$transaction).toHaveBeenCalledOnce(); - // Clear-all and set-one must both run inside the transaction - expect(mockPrisma.card.updateMany).toHaveBeenCalledWith({ - where: { userId: USER_ID }, - data: { isDefault: false }, - }); - expect(mockPrisma.card.update).toHaveBeenCalledWith({ - where: { id: CARD_ID }, - data: { isDefault: true }, - }); - }); - - it('returns 404 when the card is not owned by the user', async () => { - mockPrisma.card.findFirst.mockResolvedValue(null); - - const app = await buildApp(); - const res = await app.inject({ method: 'PUT', url: `/api/cards/${CARD_ID}/default` }); - - expect(res.statusCode).toBe(404); - expect(mockPrisma.$transaction).not.toHaveBeenCalled(); - }); - - it('returns 500 and rolls back when the transaction fails mid-flight', async () => { - // updateMany clears all defaults; then update fails => transaction aborts, - // the user retains a consistent default card rather than having none. - mockPrisma.card.findFirst.mockResolvedValue(mockCard); - mockPrisma.card.updateMany.mockResolvedValue({ count: 2 }); - mockPrisma.card.update.mockRejectedValue(new Error('DB write failure')); - - const app = await buildApp(); - const res = await app.inject({ method: 'PUT', url: `/api/cards/${CARD_ID}/default` }); - - expect(res.statusCode).toBe(500); - expect(mockPrisma.card.updateMany).toHaveBeenCalled(); - expect(mockPrisma.card.update).toHaveBeenCalled(); - }); -}); diff --git a/apps/backend/src/__tests__/event.test.ts b/apps/backend/src/__tests__/event.test.ts deleted file mode 100644 index 44806af1..00000000 --- a/apps/backend/src/__tests__/event.test.ts +++ /dev/null @@ -1,686 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import Fastify, { FastifyInstance } from 'fastify'; -import { PrismaClient } from '@prisma/client'; -import { eventRoutes } from '../routes/event'; - -// ─── Shared mock data ──────────────────────────────────────────────────────── - -const MOCK_USER_ID = 'user-uuid-001'; -const MOCK_OTHER_USER_ID = 'user-uuid-002'; - -const MOCK_EVENT = { - id: 'event-uuid-001', - name: 'DevCard Conf 2025', - slug: 'devcard-conf-2025', - description: 'Annual DevCard conference', - location: 'San Francisco, CA', - organizerId: MOCK_USER_ID, - startDate: new Date('2025-09-01T09:00:00Z'), - endDate: new Date('2025-09-02T18:00:00Z'), - isPublic: true, - createdAt: new Date('2025-01-01T00:00:00Z'), -}; - -const MOCK_USER_PROFILE = { - id: MOCK_USER_ID, - username: 'johndoe', - displayName: 'John Doe', - bio: 'Software engineer', - pronouns: 'he/him', - company: 'Acme Corp', - avatarUrl: 'https://example.com/avatar.png', - accentColor: '#6366f1', -}; - -const MOCK_OTHER_USER_PROFILE = { - id: MOCK_OTHER_USER_ID, - username: 'janedoe', - displayName: 'Jane Doe', - bio: null, - pronouns: null, - company: null, - avatarUrl: null, - accentColor: '#6366f1', -}; - -// ─── Prisma mock ───────────────────────────────────────────────────────────── - -const prismaMock = { - event: { - create: vi.fn(), - findUnique: vi.fn(), - }, - eventAttendee: { - create: vi.fn(), - delete: vi.fn(), - }, -}; - -// ─── App factory ───────────────────────────────────────────────────────────── -// -// Builds a minimal Fastify instance that wires up: -// • app.prisma – the Prisma mock above -// • request.jwtVerify() – overridden per-test via `mockJwtVerify` -// -// This mirrors the real app setup without touching a real DB or real JWT keys. - -let mockJwtVerify = vi.fn(); - -async function buildApp(): Promise { - const app = Fastify({ logger: false }); - - // Decorate prisma so routes can use app.prisma.* - app.decorate('prisma', prismaMock as unknown as PrismaClient); - - // Decorate jwtVerify on the request prototype so request.jwtVerify() resolves - // to whatever the current test wants. - app.decorateRequest('jwtVerify', function () { - return mockJwtVerify(); - }); - - // Register with the same prefix used in production (app.ts) so that - // tests exercise routes at their real paths — /api/events, /api/events/:slug, etc. - await app.register(eventRoutes, { prefix: '/api/events' }); - await app.ready(); - return app; -} - -// ─── Helpers ───────────────────────────────────────────────────────────────── - -/** Returns a valid JWT-authenticated inject payload */ -function authHeader(): Record { - return { Authorization: 'Bearer mock-token' }; -} - -/** Injects a POST /api/events request */ -async function createEvent( - app: FastifyInstance, - body: Record, - authenticated = true, -) { - return app.inject({ - method: 'POST', - url: '/api/events', - headers: authenticated ? authHeader() : {}, - payload: body, - }); -} - -// ─── Test suite ────────────────────────────────────────────────────────────── - -describe('Events API', () => { - let app: FastifyInstance; - - beforeEach(async () => { - vi.clearAllMocks(); - // Default: authenticated as MOCK_USER_ID - mockJwtVerify.mockResolvedValue({ id: MOCK_USER_ID }); - app = await buildApp(); - }); - - afterEach(async () => { - await app.close(); - }); - - // ── POST /api/events ─────────────────────────────────────────────────────── - - describe('POST /api/events — create event', () => { - const validBody = { - name: 'DevCard Conf 2025', - description: 'Annual DevCard conference', - location: 'San Francisco, CA', - startDate: '2025-09-01T09:00:00Z', - endDate: '2025-09-02T18:00:00Z', - isPublic: true, - }; - - it('201 — creates event and returns it for authenticated organizer', async () => { - prismaMock.event.findUnique.mockResolvedValue(null); // slug is free - prismaMock.event.create.mockResolvedValue(MOCK_EVENT); - - const res = await createEvent(app, validBody); - - expect(res.statusCode).toBe(201); - const body = res.json(); - expect(body.slug).toBe('devcard-conf-2025'); - expect(body.organizerId).toBe(MOCK_USER_ID); - expect(body.location).toBe('San Francisco, CA'); - - // Prisma was called with correct fields - expect(prismaMock.event.create).toHaveBeenCalledOnce(); - const callArg = prismaMock.event.create.mock.calls[0][0].data; - expect(callArg.name).toBe('DevCard Conf 2025'); - expect(callArg.organizerId).toBe(MOCK_USER_ID); - expect(callArg.location).toBe('San Francisco, CA'); - }); - - it('401 — rejects unauthenticated request', async () => { - mockJwtVerify.mockRejectedValue(new Error('Unauthorized')); - - const res = await createEvent(app, validBody, false); - - expect(res.statusCode).toBe(401); - expect(res.json()).toMatchObject({ error: 'Unauthorized' }); - }); - - it('400 — rejects missing required fields (no dates, no location)', async () => { - const res = await createEvent(app, { name: 'Hello World' }); // missing dates + location - expect(res.statusCode).toBe(400); - }); - - it('400 — rejects missing location', async () => { - const { location: _omit, ...bodyWithoutLocation } = validBody; - const res = await createEvent(app, bodyWithoutLocation); - expect(res.statusCode).toBe(400); - }); - - it('400 — rejects location shorter than 2 characters', async () => { - const res = await createEvent(app, { ...validBody, location: 'A' }); - expect(res.statusCode).toBe(400); - }); - - it('400 — rejects location longer than 100 characters', async () => { - const res = await createEvent(app, { ...validBody, location: 'A'.repeat(101) }); - expect(res.statusCode).toBe(400); - }); - - it('400 — rejects event name shorter than 3 characters', async () => { - const res = await createEvent(app, { ...validBody, name: 'Hi' }); - expect(res.statusCode).toBe(400); - }); - - it('400 — rejects event name longer than 100 characters', async () => { - const longName = 'A'.repeat(101); - const res = await createEvent(app, { ...validBody, name: longName }); - expect(res.statusCode).toBe(400); - }); - - it('400 — rejects invalid date format', async () => { - const res = await createEvent(app, { - ...validBody, - startDate: 'not-a-date', - }); - expect(res.statusCode).toBe(400); - }); - - it('201 — generates a unique slug when the first candidate is taken', async () => { - // First findUnique returns a conflict, second returns null (slug free) - prismaMock.event.findUnique - .mockResolvedValueOnce(MOCK_EVENT) // slug taken - .mockResolvedValueOnce(null); // randomised slug free - - prismaMock.event.create.mockResolvedValue({ - ...MOCK_EVENT, - slug: 'devcard-conf-2025-ab12', - }); - - const res = await createEvent(app, validBody); - - expect(res.statusCode).toBe(201); - // create was eventually called with a slug different from the base one - const createdSlug: string = prismaMock.event.create.mock.calls[0][0].data.slug; - expect(createdSlug).toMatch(/^devcard-conf-2025-[a-z0-9]+$/); - }); - - it('201 — isPublic defaults to true when omitted', async () => { - prismaMock.event.findUnique.mockResolvedValue(null); - prismaMock.event.create.mockResolvedValue(MOCK_EVENT); - - const { isPublic: _omit, ...bodyWithoutIsPublic } = validBody; - const res = await createEvent(app, bodyWithoutIsPublic); - - expect(res.statusCode).toBe(201); - const callData = prismaMock.event.create.mock.calls[0][0].data; - expect(callData.isPublic).toBe(true); - }); - - it('500 — returns 500 when database write fails', async () => { - prismaMock.event.findUnique.mockResolvedValue(null); - prismaMock.event.create.mockRejectedValue(new Error('DB error')); - - const res = await createEvent(app, validBody); - - expect(res.statusCode).toBe(500); - expect(res.json()).toMatchObject({ error: 'Failed to create event' }); - }); - }); - - // ── GET /api/events/:slug ────────────────────────────────────────────────── - - describe('GET /api/events/:slug — event details', () => { - it('200 — returns event info with attendee count', async () => { - prismaMock.event.findUnique.mockResolvedValue({ - ...MOCK_EVENT, - _count: { attendees: 42 }, - }); - - const res = await app.inject({ - method: 'GET', - url: '/api/events/devcard-conf-2025', - }); - - expect(res.statusCode).toBe(200); - const body = res.json(); - expect(body.slug).toBe('devcard-conf-2025'); - expect(body.attendeesCount).toBe(42); - expect(body.location).toBe('San Francisco, CA'); - // organizerId is exposed (public info) - expect(body.organizerId).toBe(MOCK_USER_ID); - }); - - it('404 — returns 404 for unknown slug', async () => { - prismaMock.event.findUnique.mockResolvedValue(null); - - const res = await app.inject({ - method: 'GET', - url: '/api/events/ghost-event', - }); - - expect(res.statusCode).toBe(404); - expect(res.json()).toMatchObject({ error: 'Event not found' }); - }); - - it('200 — works without authentication (public endpoint)', async () => { - // Even if JWT would fail, this route should not call jwtVerify - mockJwtVerify.mockRejectedValue(new Error('Should not be called')); - prismaMock.event.findUnique.mockResolvedValue({ - ...MOCK_EVENT, - _count: { attendees: 0 }, - }); - - const res = await app.inject({ - method: 'GET', - url: '/api/events/devcard-conf-2025', - // No Authorization header - }); - - expect(res.statusCode).toBe(200); - expect(mockJwtVerify).not.toHaveBeenCalled(); - }); - }); - - // ── POST /api/events/:slug/join ──────────────────────────────────────────── - - describe('POST /api/events/:slug/join — join event', () => { - it('201 — authenticated user joins an existing event', async () => { - prismaMock.event.findUnique.mockResolvedValue(MOCK_EVENT); - prismaMock.eventAttendee.create.mockResolvedValue({ - id: 'attendee-uuid-001', - userId: MOCK_OTHER_USER_ID, - eventId: MOCK_EVENT.id, - joinedAt: new Date(), - }); - - mockJwtVerify.mockResolvedValue({ id: MOCK_OTHER_USER_ID }); - - const res = await app.inject({ - method: 'POST', - url: '/api/events/devcard-conf-2025/join', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(201); - expect(res.json()).toMatchObject({ message: 'User joined successfully' }); - - const callData = prismaMock.eventAttendee.create.mock.calls[0][0].data; - expect(callData.eventId).toBe(MOCK_EVENT.id); - expect(callData.userId).toBe(MOCK_OTHER_USER_ID); - }); - - it('401 — rejects unauthenticated request', async () => { - mockJwtVerify.mockRejectedValue(new Error('Unauthorized')); - - const res = await app.inject({ - method: 'POST', - url: '/api/events/devcard-conf-2025/join', - }); - - expect(res.statusCode).toBe(401); - expect(res.json()).toMatchObject({ error: 'Unauthorized' }); - }); - - it('404 — returns 404 when event does not exist', async () => { - prismaMock.event.findUnique.mockResolvedValue(null); - - const res = await app.inject({ - method: 'POST', - url: '/api/events/ghost-event/join', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(404); - expect(res.json()).toMatchObject({ error: 'Event not found' }); - }); - - it('409 — returns 409 when user already joined the event', async () => { - prismaMock.event.findUnique.mockResolvedValue(MOCK_EVENT); - // Prisma unique constraint error - const uniqueError = Object.assign(new Error('Unique constraint'), { - code: 'P2002', - }); - prismaMock.eventAttendee.create.mockRejectedValue(uniqueError); - - const res = await app.inject({ - method: 'POST', - url: '/api/events/devcard-conf-2025/join', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(409); - expect(res.json()).toMatchObject({ error: 'Already joined' }); - }); - - it('500 — returns 500 on unexpected database error', async () => { - prismaMock.event.findUnique.mockResolvedValue(MOCK_EVENT); - prismaMock.eventAttendee.create.mockRejectedValue(new Error('DB error')); - - const res = await app.inject({ - method: 'POST', - url: '/api/events/devcard-conf-2025/join', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(500); - expect(res.json()).toMatchObject({ error: 'Failed to join' }); - }); - }); - - // ── DELETE /api/events/:slug/leave ──────────────────────────────────────── - - describe('DELETE /api/events/:slug/leave — leave event', () => { - it('204 — authenticated user leaves an event they joined', async () => { - prismaMock.event.findUnique.mockResolvedValue(MOCK_EVENT); - prismaMock.eventAttendee.delete.mockResolvedValue({}); - - mockJwtVerify.mockResolvedValue({ id: MOCK_OTHER_USER_ID }); - - const res = await app.inject({ - method: 'DELETE', - url: '/api/events/devcard-conf-2025/leave', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(204); - - // Verify the compound unique key used in the delete - const deleteArg = prismaMock.eventAttendee.delete.mock.calls[0][0].where; - expect(deleteArg).toMatchObject({ - userId_eventId: { - userId: MOCK_OTHER_USER_ID, - eventId: MOCK_EVENT.id, - }, - }); - }); - - it('401 — rejects unauthenticated request', async () => { - mockJwtVerify.mockRejectedValue(new Error('Unauthorized')); - - const res = await app.inject({ - method: 'DELETE', - url: '/api/events/devcard-conf-2025/leave', - }); - - expect(res.statusCode).toBe(401); - expect(res.json()).toMatchObject({ error: 'Unauthorized' }); - }); - - it('404 — returns 404 when event does not exist', async () => { - prismaMock.event.findUnique.mockResolvedValue(null); - - const res = await app.inject({ - method: 'DELETE', - url: '/api/events/ghost-event/leave', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(404); - expect(res.json()).toMatchObject({ error: 'Event not found' }); - }); - - it('404 — returns 404 when user was never an attendee (P2025)', async () => { - prismaMock.event.findUnique.mockResolvedValue(MOCK_EVENT); - // Prisma record-not-found error - const notFoundError = Object.assign(new Error('Record not found'), { - code: 'P2025', - }); - prismaMock.eventAttendee.delete.mockRejectedValue(notFoundError); - - const res = await app.inject({ - method: 'DELETE', - url: '/api/events/devcard-conf-2025/leave', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(404); - expect(res.json()).toMatchObject({ error: 'User not found' }); - }); - - it('500 — returns 500 on unexpected database error', async () => { - prismaMock.event.findUnique.mockResolvedValue(MOCK_EVENT); - prismaMock.eventAttendee.delete.mockRejectedValue(new Error('DB error')); - - const res = await app.inject({ - method: 'DELETE', - url: '/api/events/devcard-conf-2025/leave', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(500); - expect(res.json()).toMatchObject({ error: 'Failed to leave' }); - }); - }); - - // ── GET /api/events/:slug/attendees ─────────────────────────────────────── - - describe('GET /api/events/:slug/attendees — paginated attendee list', () => { - /** Builds a raw EventAttendee row as Prisma returns it (with nested user) */ - function makeAttendeeRow( - profile: typeof MOCK_USER_PROFILE | typeof MOCK_OTHER_USER_PROFILE, - ) { - return { - id: `attendee-${profile.id}`, - userId: profile.id, - eventId: MOCK_EVENT.id, - joinedAt: new Date(), - user: { ...profile }, - }; - } - - it('200 — returns paginated attendees with default page/limit', async () => { - const attendeeRows = [ - makeAttendeeRow(MOCK_USER_PROFILE), - makeAttendeeRow(MOCK_OTHER_USER_PROFILE), - ]; - - prismaMock.event.findUnique.mockResolvedValue({ - ...MOCK_EVENT, - attendees: attendeeRows, - }); - - const res = await app.inject({ - method: 'GET', - url: '/api/events/devcard-conf-2025/attendees', - }); - - expect(res.statusCode).toBe(200); - const body = res.json(); - - expect(body.attendees).toHaveLength(2); - expect(body.attendees[0]).toMatchObject({ - id: MOCK_USER_ID, - username: 'johndoe', - displayName: 'John Doe', - }); - - expect(body.pagination).toMatchObject({ - page: 1, - limit: 10, - total: 2, - }); - }); - - it('200 — respects custom page and limit query params', async () => { - prismaMock.event.findUnique.mockResolvedValue({ - ...MOCK_EVENT, - attendees: [makeAttendeeRow(MOCK_OTHER_USER_PROFILE)], - }); - - const res = await app.inject({ - method: 'GET', - url: '/api/events/devcard-conf-2025/attendees?page=2&limit=5', - }); - - expect(res.statusCode).toBe(200); - const body = res.json(); - expect(body.pagination.page).toBe(2); - expect(body.pagination.limit).toBe(5); - - // Verify skip/take were passed correctly to Prisma - const includeArg = prismaMock.event.findUnique.mock.calls[0][0].include; - expect(includeArg.attendees.skip).toBe(5); // (page-1) * limit = 1 * 5 - expect(includeArg.attendees.take).toBe(5); - }); - - it('200 — caps limit at 50 even if higher value is requested', async () => { - prismaMock.event.findUnique.mockResolvedValue({ - ...MOCK_EVENT, - attendees: [], - }); - - const res = await app.inject({ - method: 'GET', - url: '/api/events/devcard-conf-2025/attendees?limit=200', - }); - - expect(res.statusCode).toBe(200); - const includeArg = prismaMock.event.findUnique.mock.calls[0][0].include; - expect(includeArg.attendees.take).toBe(50); - }); - - it('200 — treats page < 1 as page 1', async () => { - prismaMock.event.findUnique.mockResolvedValue({ - ...MOCK_EVENT, - attendees: [], - }); - - const res = await app.inject({ - method: 'GET', - url: '/api/events/devcard-conf-2025/attendees?page=0', - }); - - expect(res.statusCode).toBe(200); - const includeArg = prismaMock.event.findUnique.mock.calls[0][0].include; - expect(includeArg.attendees.skip).toBe(0); // page forced to 1 → skip = 0 - }); - - it('200 — returns empty attendees list for event with no attendees', async () => { - prismaMock.event.findUnique.mockResolvedValue({ - ...MOCK_EVENT, - attendees: [], - }); - - const res = await app.inject({ - method: 'GET', - url: '/api/events/devcard-conf-2025/attendees', - }); - - expect(res.statusCode).toBe(200); - const body = res.json(); - expect(body.attendees).toHaveLength(0); - expect(body.pagination.total).toBe(0); - }); - - it('200 — public profiles do not leak sensitive fields', async () => { - prismaMock.event.findUnique.mockResolvedValue({ - ...MOCK_EVENT, - attendees: [makeAttendeeRow(MOCK_USER_PROFILE)], - }); - - const res = await app.inject({ - method: 'GET', - url: '/api/events/devcard-conf-2025/attendees', - }); - - const attendee = res.json().attendees[0]; - - // These fields MUST be present - expect(attendee).toHaveProperty('id'); - expect(attendee).toHaveProperty('username'); - expect(attendee).toHaveProperty('displayName'); - expect(attendee).toHaveProperty('accentColor'); - - // These fields MUST NOT be present - expect(attendee).not.toHaveProperty('email'); - expect(attendee).not.toHaveProperty('provider'); - expect(attendee).not.toHaveProperty('providerId'); - expect(attendee).not.toHaveProperty('role'); - }); - - it('404 — returns 404 for unknown event slug', async () => { - prismaMock.event.findUnique.mockResolvedValue(null); - - const res = await app.inject({ - method: 'GET', - url: '/api/events/ghost-event/attendees', - }); - - expect(res.statusCode).toBe(404); - expect(res.json()).toMatchObject({ error: 'Event not found' }); - }); - - it('200 — attendees are ordered by joinedAt desc (latest first)', async () => { - prismaMock.event.findUnique.mockResolvedValue({ - ...MOCK_EVENT, - attendees: [], - }); - - await app.inject({ - method: 'GET', - url: '/api/events/devcard-conf-2025/attendees', - }); - - const includeArg = prismaMock.event.findUnique.mock.calls[0][0].include; - expect(includeArg.attendees.orderBy).toMatchObject({ joinedAt: 'desc' }); - }); - }); - - // ── Slug generation edge cases ──────────────────────────────────────────── - - describe('Slug generation', () => { - const baseBody = { - location: 'San Francisco, CA', - startDate: '2025-09-01T09:00:00Z', - endDate: '2025-09-02T18:00:00Z', - }; - - it('converts spaces and special characters to hyphens', async () => { - prismaMock.event.findUnique.mockResolvedValue(null); - prismaMock.event.create.mockResolvedValue({ ...MOCK_EVENT, slug: 'my-awesome-event' }); - - await createEvent(app, { ...baseBody, name: 'My Awesome Event!!!' }); - - const slug: string = prismaMock.event.create.mock.calls[0][0].data.slug; - expect(slug).toBe('my-awesome-event'); - }); - - it('strips leading and trailing hyphens from slug', async () => { - prismaMock.event.findUnique.mockResolvedValue(null); - prismaMock.event.create.mockResolvedValue({ ...MOCK_EVENT, slug: 'event-name' }); - - await createEvent(app, { ...baseBody, name: '---Event Name---' }); - - const slug: string = prismaMock.event.create.mock.calls[0][0].data.slug; - expect(slug).not.toMatch(/^-|-$/); - }); - - it('collapses multiple consecutive hyphens into one', async () => { - prismaMock.event.findUnique.mockResolvedValue(null); - prismaMock.event.create.mockResolvedValue({ ...MOCK_EVENT, slug: 'event-name' }); - - await createEvent(app, { ...baseBody, name: 'Event Name' }); - - const slug: string = prismaMock.event.create.mock.calls[0][0].data.slug; - expect(slug).not.toMatch(/--/); - }); - }); -}); \ No newline at end of file diff --git a/apps/backend/src/__tests__/follow.test.ts b/apps/backend/src/__tests__/follow.test.ts index 41830018..8338f606 100644 --- a/apps/backend/src/__tests__/follow.test.ts +++ b/apps/backend/src/__tests__/follow.test.ts @@ -1,5 +1,5 @@ -import Fastify, { FastifyInstance } from 'fastify'; -import { describe, expect, it, vi, beforeAll, beforeEach, afterAll } from 'vitest'; +import Fastify from 'fastify'; +import { describe, expect, it, vi } from 'vitest'; import { followRoutes } from '../routes/follow.js'; @@ -7,57 +7,32 @@ vi.mock('../utils/encryption.js', () => ({ decrypt: vi.fn(() => 'fake-access-token'), })); -// ── Shared mock data ────────────────────────────────────────────────────────── - -const MOCK_USER_ID = 'user-uuid-001'; - -const MOCK_OAUTH_TOKEN = { - id: 'token-1', - userId: MOCK_USER_ID, - platform: 'unknown', - accessToken: 'encrypted-token', -}; - -// ── App factory ─────────────────────────────────────────────────────────────── - -function buildApp(overrides: { - oAuthToken?: Record; - followLog?: Record; -} = {}): FastifyInstance { - const app = Fastify({ logger: false }); - - app.decorate('prisma', { - oAuthToken: { - findUnique: vi.fn(), - ...overrides.oAuthToken, - }, - followLog: { - create: vi.fn(), - deleteMany: vi.fn(), - ...overrides.followLog, - }, - } as any); - - app.decorate('authenticate', async (request: any) => { - request.user = { id: MOCK_USER_ID }; - }); +describe('POST /api/follow/:platform/:targetUsername', () => { + it('returns 400 when API follow is not supported for the platform', async () => { + const app = Fastify({ logger: false }); - return app; -} + const findUnique = vi.fn().mockResolvedValue({ + id: 'token-1', + userId: 'user-1', + platform: 'unknown', + accessToken: 'encrypted-token', + }); -async function makeApp(overrides?: Parameters[0]): Promise { - const app = buildApp(overrides); - await app.register(followRoutes, { prefix: '/api/follow' }); - await app.ready(); - return app; -} + app.decorate('prisma', { + oAuthToken: { + findUnique, + }, + followLog: { + create: vi.fn(), + }, + }as any); -// ───────────────────────────────────────────────────────────────────────────── + app.decorate('authenticate', async (request: any) => { + request.user = { id: 'user-1' }; + }); -describe('POST /api/follow/:platform/:targetUsername — API follow', () => { - it('returns 400 when API follow is not supported for the platform', async () => { - const findUnique = vi.fn().mockResolvedValue(MOCK_OAUTH_TOKEN); - const app = await makeApp({ oAuthToken: { findUnique } }); + await app.register(followRoutes, { prefix: '/api/follow' }); + await app.ready(); const response = await app.inject({ method: 'POST', @@ -71,7 +46,7 @@ describe('POST /api/follow/:platform/:targetUsername — API follow', () => { expect(findUnique).toHaveBeenCalledWith({ where: { userId_platform: { - userId: MOCK_USER_ID, + userId: 'user-1', platform: 'unknown', }, }, @@ -79,250 +54,4 @@ describe('POST /api/follow/:platform/:targetUsername — API follow', () => { await app.close(); }); - - it('returns webview strategy and url for webview-strategy platforms (e.g. linkedin)', async () => { - const app = await makeApp(); - - const response = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser', - }); - - const body = response.json(); - - expect(response.statusCode).toBe(200); - expect(body.strategy).toBe('webview'); - expect(body.url).toContain('linkedin.com/in/testuser'); - - await app.close(); - }); -}); - -// ───────────────────────────────────────────────────────────────────────────── - -describe('POST /api/follow/:platform/:targetUsername/log — follow log validation', () => { - let app: FastifyInstance; - let createLog: ReturnType; - - // One app instance shared across all log tests; mock reset between each test. - beforeAll(async () => { - createLog = vi.fn(); - app = await makeApp({ followLog: { create: createLog } }); - }); - - afterAll(async () => { - await app.close(); - }); - - beforeEach(() => { - createLog.mockReset(); - createLog.mockResolvedValue({ id: 'log-uuid-001' }); - }); - - // ── Valid payloads ──────────────────────────────────────────────────────── - - it('200 — accepts status: success, layer: foreground', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: { status: 'success', layer: 'foreground' }, - }); - - expect(res.statusCode).toBe(200); - expect(res.json()).toMatchObject({ status: 'success', logId: 'log-uuid-001' }); - expect(createLog).toHaveBeenCalledOnce(); - expect(createLog.mock.calls[0][0].data.status).toBe('success'); - }); - - it('200 — accepts status: failed', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: { status: 'failed', layer: 'foreground' }, - }); - - expect(res.statusCode).toBe(200); - expect(createLog).toHaveBeenCalledOnce(); - expect(createLog.mock.calls[0][0].data.status).toBe('failed'); - }); - - it('200 — accepts status: pending, layer: background', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: { status: 'pending', layer: 'background' }, - }); - - expect(res.statusCode).toBe(200); - expect(createLog).toHaveBeenCalledOnce(); - expect(createLog.mock.calls[0][0].data.layer).toBe('background'); - }); - - // ── Invalid status values — analytics integrity ─────────────────────────── - - it('400 — rejects invalid status "error" (old unvalidated internal value)', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: { status: 'error', layer: 'foreground' }, - }); - - expect(res.statusCode).toBe(400); - expect(res.json()).toMatchObject({ error: 'Invalid follow log payload' }); - // DB must NOT be written — this is the analytics integrity guarantee - expect(createLog).not.toHaveBeenCalled(); - }); - - it('400 — rejects arbitrary status string injection', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: { status: '"; DROP TABLE follow_logs; --', layer: 'foreground' }, - }); - - expect(res.statusCode).toBe(400); - expect(createLog).not.toHaveBeenCalled(); - }); - - // ── Invalid layer values — analytics integrity ──────────────────────────── - - // 'webview' was the old unvalidated default — it is now explicitly rejected. - // Any existing caller sending layer: 'webview' must migrate to 'foreground' - // (in-app WebView session) or 'background' (passive deep-link strategy). - it('400 — rejects legacy layer "webview" (old unvalidated default)', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: { status: 'success', layer: 'webview' }, - }); - - expect(res.statusCode).toBe(400); - expect(res.json()).toMatchObject({ error: 'Invalid follow log payload' }); - expect(createLog).not.toHaveBeenCalled(); - }); - - it('400 — rejects invalid layer "api"', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: { status: 'success', layer: 'api' }, - }); - - expect(res.statusCode).toBe(400); - expect(createLog).not.toHaveBeenCalled(); - }); - - // ── Malformed / missing payloads ────────────────────────────────────────── - - it('400 — rejects missing status field', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: { layer: 'foreground' }, - }); - - expect(res.statusCode).toBe(400); - expect(createLog).not.toHaveBeenCalled(); - }); - - it('400 — rejects missing layer field', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: { status: 'success' }, - }); - - expect(res.statusCode).toBe(400); - expect(createLog).not.toHaveBeenCalled(); - }); - - it('400 — rejects empty body', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: {}, - }); - - expect(res.statusCode).toBe(400); - expect(createLog).not.toHaveBeenCalled(); - }); - - // ── Correct data persisted to DB ────────────────────────────────────────── - - it('persists exactly the validated platform, targetUsername, status, and layer', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/twitter/janedoe/log', - payload: { status: 'pending', layer: 'background' }, - }); - - expect(res.statusCode).toBe(200); - expect(createLog).toHaveBeenCalledOnce(); - - const written = createLog.mock.calls[0][0].data; - expect(written).toMatchObject({ - followerId: MOCK_USER_ID, - targetUsername: 'janedoe', - platform: 'twitter', - status: 'pending', - layer: 'background', - }); - }); - - // ── Response does not leak validation internals ─────────────────────────── - - it('400 response only exposes { error } — no schema internals or stack traces', async () => { - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: { status: 'bad', layer: 'bad' }, - }); - - expect(res.statusCode).toBe(400); - const body = res.json(); - expect(body).not.toHaveProperty('issues'); - expect(body).not.toHaveProperty('stack'); - expect(Object.keys(body)).toEqual(['error']); - }); - - // ── DB failure after valid payload ──────────────────────────────────────── - - it('500 — returns 500 when DB write fails after successful validation', async () => { - createLog.mockRejectedValueOnce(new Error('DB connection lost')); - - const res = await app.inject({ - method: 'POST', - url: '/api/follow/linkedin/testuser/log', - payload: { status: 'success', layer: 'foreground' }, - }); - - expect(res.statusCode).toBe(500); - expect(res.json()).toMatchObject({ error: 'Failed to log follow event' }); - }); -}); - -// ───────────────────────────────────────────────────────────────────────────── - -describe('DELETE /api/follow/:platform/:targetUsername/log — clear follow log', () => { - it('clears follow log entries for the authenticated user', async () => { - const deleteMany = vi.fn().mockResolvedValue({ count: 1 }); - const app = await makeApp({ followLog: { deleteMany } }); - - const response = await app.inject({ - method: 'DELETE', - url: '/api/follow/linkedin/testuser/log', - }); - - expect(response.statusCode).toBe(200); - expect(response.json()).toMatchObject({ status: 'cleared' }); - expect(deleteMany).toHaveBeenCalledWith({ - where: { - followerId: MOCK_USER_ID, - platform: 'linkedin', - targetUsername: 'testuser', - }, - }); - - await app.close(); - }); -}); +}); \ No newline at end of file diff --git a/apps/backend/src/__tests__/oauth-scope.test.ts b/apps/backend/src/__tests__/oauth-scope.test.ts deleted file mode 100644 index 0985dfa7..00000000 --- a/apps/backend/src/__tests__/oauth-scope.test.ts +++ /dev/null @@ -1,392 +0,0 @@ -/** - * Regression tests for OAuth token scope isolation. - * - * Prior to the fix, the authentication flow (scope: read:user user:email, - * platform key: 'github') and the GitHub connect/follow flow (scope: - * user:follow, platform key: 'github') both wrote to the same OAuthToken - * record. Whichever executed last silently overwrote the other's access - * token, causing follow capability to disappear after re-authentication. - * - * The fix uses a dedicated platform key ('github_follow') for the connect - * flow so the two records are independent and can never overwrite each other. - */ - -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import Fastify from 'fastify'; -import { connectRoutes } from '../routes/connect.js'; -import { followRoutes } from '../routes/follow.js'; -import type { PrismaClient } from '@prisma/client'; - -// ── Mocks ───────────────────────────────────────────────────────────────────── - -vi.mock('../utils/encryption.js', () => ({ - encrypt: vi.fn((v: string) => `enc:${v}`), - decrypt: vi.fn((v: string) => v.replace(/^enc:/, '')), -})); - -const mockFetch = vi.fn(); -beforeEach(() => { - vi.clearAllMocks(); - (globalThis as any).fetch = mockFetch; - - process.env.PUBLIC_APP_URL = 'http://localhost:5173'; - process.env.BACKEND_URL = 'http://localhost:3000'; - process.env.GITHUB_CLIENT_ID = 'test-client-id'; -}); - -const USER_ID = 'user-scope-test'; - -// ── Connect-route test harness ──────────────────────────────────────────────── - -function makeConnectState(userId: string): string { - return Buffer.from(JSON.stringify({ userId, nonce: 'test-nonce' })).toString('base64'); -} - -function buildConnectApp(mockPrisma: Partial) { - const app = Fastify({ logger: false }); - app.decorate('prisma', mockPrisma as PrismaClient); - app.decorate('authenticate', async (req: any) => { req.user = { id: USER_ID }; }); - app.register(connectRoutes, { prefix: '/api/connect' }); - return app.ready().then(() => app); -} - -// ── Follow-route test harness ───────────────────────────────────────────────── - -function buildFollowApp(mockPrisma: Partial) { - const app = Fastify({ logger: false }); - app.decorate('prisma', mockPrisma as PrismaClient); - app.decorate('authenticate', async (req: any) => { req.user = { id: USER_ID }; }); - app.register(followRoutes, { prefix: '/api/follow' }); - return app.ready().then(() => app); -} - -// ───────────────────────────────────────────────────────────────────────────── -// 1. Connect flow — platform key -// ───────────────────────────────────────────────────────────────────────────── - -describe('GitHub connect flow — token stored under github_follow', () => { - it('writes the follow token to platform=github_follow, not github', async () => { - const upsert = vi.fn().mockResolvedValue({}); - const app = await buildConnectApp({ oAuthToken: { upsert } as any }); - - mockFetch.mockResolvedValue({ - json: async () => ({ access_token: 'follow-token', scope: 'user:follow' }), - }); - - await app.inject({ - method: 'GET', - url: `/api/connect/github/callback?code=code123&state=${makeConnectState(USER_ID)}`, - }); - - expect(upsert).toHaveBeenCalledOnce(); - const call = upsert.mock.calls[0][0]; - - // Key must be github_follow - expect(call.where.userId_platform.platform).toBe('github_follow'); - expect(call.create.platform).toBe('github_follow'); - expect(call.update).not.toHaveProperty('platform'); // update never changes the key - }); - - it('stores the scope returned by GitHub in the follow token record', async () => { - const upsert = vi.fn().mockResolvedValue({}); - const app = await buildConnectApp({ oAuthToken: { upsert } as any }); - - mockFetch.mockResolvedValue({ - json: async () => ({ access_token: 'follow-token', scope: 'user:follow' }), - }); - - await app.inject({ - method: 'GET', - url: `/api/connect/github/callback?code=code123&state=${makeConnectState(USER_ID)}`, - }); - - const { create } = upsert.mock.calls[0][0]; - expect(create.scopes).toBe('user:follow'); - }); - - it('falls back to user:follow scope when GitHub omits the scope field', async () => { - const upsert = vi.fn().mockResolvedValue({}); - const app = await buildConnectApp({ oAuthToken: { upsert } as any }); - - mockFetch.mockResolvedValue({ - json: async () => ({ access_token: 'follow-token' }), // no scope field - }); - - await app.inject({ - method: 'GET', - url: `/api/connect/github/callback?code=code123&state=${makeConnectState(USER_ID)}`, - }); - - const { create } = upsert.mock.calls[0][0]; - expect(create.scopes).toBe('user:follow'); - }); - - it('does NOT touch the github (auth) token record during the connect flow', async () => { - const upsert = vi.fn().mockResolvedValue({}); - const app = await buildConnectApp({ oAuthToken: { upsert } as any }); - - mockFetch.mockResolvedValue({ - json: async () => ({ access_token: 'follow-token', scope: 'user:follow' }), - }); - - await app.inject({ - method: 'GET', - url: `/api/connect/github/callback?code=code123&state=${makeConnectState(USER_ID)}`, - }); - - // Exactly one upsert — the github_follow record; never 'github' - expect(upsert).toHaveBeenCalledTimes(1); - const key = upsert.mock.calls[0][0].where.userId_platform.platform; - expect(key).not.toBe('github'); - }); -}); - -// ───────────────────────────────────────────────────────────────────────────── -// 2. Follow route — uses github_follow token -// ───────────────────────────────────────────────────────────────────────────── - -describe('GitHub follow route — looks up github_follow token', () => { - it('resolves the token from platform=github_follow', async () => { - const findUnique = vi.fn().mockResolvedValue({ - id: 'tok-1', - accessToken: 'enc:follow-token', - }); - - mockFetch.mockResolvedValue({ status: 204 }); - - const app = await buildFollowApp({ - oAuthToken: { findUnique } as any, - followLog: { create: vi.fn().mockReturnValue({ catch: vi.fn() }) } as any, - }); - - const res = await app.inject({ - method: 'POST', - url: '/api/follow/github/targetuser', - }); - - expect(res.statusCode).toBe(200); - expect(findUnique).toHaveBeenCalledWith({ - where: { userId_platform: { userId: USER_ID, platform: 'github_follow' } }, - }); - }); - - it('returns 400 with requiresAuth when github_follow token is absent', async () => { - const findUnique = vi.fn().mockResolvedValue(null); - - const app = await buildFollowApp({ - oAuthToken: { findUnique } as any, - followLog: { create: vi.fn() } as any, - }); - - const res = await app.inject({ - method: 'POST', - url: '/api/follow/github/targetuser', - }); - - expect(res.statusCode).toBe(400); - expect(res.json().requiresAuth).toBe(true); - - // The lookup must be for github_follow — not the auth token - expect(findUnique.mock.calls[0][0].where.userId_platform.platform).toBe('github_follow'); - }); - - it('does NOT fall back to the github (auth) token if github_follow is missing', async () => { - // Only the github_follow lookup should be attempted; never a fallback to 'github' - const findUnique = vi.fn().mockResolvedValue(null); - - const app = await buildFollowApp({ - oAuthToken: { findUnique } as any, - followLog: { create: vi.fn() } as any, - }); - - await app.inject({ method: 'POST', url: '/api/follow/github/targetuser' }); - - // Exactly one DB call, and it is for github_follow - expect(findUnique).toHaveBeenCalledTimes(1); - expect(findUnique.mock.calls[0][0].where.userId_platform.platform).toBe('github_follow'); - }); - - it('non-GitHub platforms still use their own name as the token key', async () => { - const findUnique = vi.fn().mockResolvedValue({ - id: 'tok-2', - accessToken: 'enc:some-token', - }); - - const app = await buildFollowApp({ - oAuthToken: { findUnique } as any, - followLog: { create: vi.fn() } as any, - }); - - // 'twitter' is not GitHub — it should not be mapped to 'twitter_follow' - await app.inject({ method: 'POST', url: '/api/follow/twitter/targetuser' }); - - expect(findUnique.mock.calls[0][0].where.userId_platform.platform).toBe('twitter'); - }); -}); - -// ───────────────────────────────────────────────────────────────────────────── -// 3. Scope-overwrite lifecycle — the full story -// ───────────────────────────────────────────────────────────────────────────── - -describe('Scope-overwrite lifecycle — isolation between auth and connect tokens', () => { - it('connect flow and auth flow target independent platform keys (no shared record)', async () => { - // Simulate: user logs in → connect → log in again. - // The auth upsert writes 'github'; the connect upsert writes 'github_follow'. - // They never share a key, so neither can overwrite the other. - - const upsertCalls: string[] = []; - const upsert = vi.fn().mockImplementation(async (args: any) => { - upsertCalls.push(args.where.userId_platform.platform); - return {}; - }); - - // ── Simulate the connect callback ────────────────────────────────────── - const connectApp = await buildConnectApp({ oAuthToken: { upsert } as any }); - - mockFetch.mockResolvedValue({ - json: async () => ({ access_token: 'follow-token', scope: 'user:follow' }), - }); - - await connectApp.inject({ - method: 'GET', - url: `/api/connect/github/callback?code=code1&state=${makeConnectState(USER_ID)}`, - }); - - // Connect writes github_follow - expect(upsertCalls).toContain('github_follow'); - expect(upsertCalls).not.toContain('github'); - }); - - it('repeated connect cycles only ever touch github_follow', async () => { - const upsert = vi.fn().mockResolvedValue({}); - const app = await buildConnectApp({ oAuthToken: { upsert } as any }); - - mockFetch.mockResolvedValue({ - json: async () => ({ access_token: 'follow-token', scope: 'user:follow' }), - }); - - // Three consecutive reconnect attempts - for (let i = 0; i < 3; i++) { - await app.inject({ - method: 'GET', - url: `/api/connect/github/callback?code=code${i}&state=${makeConnectState(USER_ID)}`, - }); - } - - expect(upsert).toHaveBeenCalledTimes(3); - for (const call of upsert.mock.calls) { - expect(call[0].where.userId_platform.platform).toBe('github_follow'); - } - }); - - it('follow route succeeds immediately after a connect cycle', async () => { - const ENCRYPTED_FOLLOW_TOKEN = 'enc:follow-token-v1'; - - const findUnique = vi.fn().mockResolvedValue({ - id: 'tok-follow', - accessToken: ENCRYPTED_FOLLOW_TOKEN, - }); - - mockFetch.mockResolvedValue({ status: 204 }); // GitHub follow API - - const app = await buildFollowApp({ - oAuthToken: { findUnique } as any, - followLog: { create: vi.fn().mockReturnValue({ catch: vi.fn() }) } as any, - }); - - const res = await app.inject({ - method: 'POST', - url: '/api/follow/github/somedev', - }); - - expect(res.statusCode).toBe(200); - // Confirm we went to the GitHub API with the decrypted token - expect(mockFetch).toHaveBeenCalledWith( - expect.stringContaining('api.github.com/user/following/somedev'), - expect.objectContaining({ - headers: expect.objectContaining({ - Authorization: 'Bearer follow-token-v1', - }), - }), - ); - }); - - it('follow route still works after a simulated re-login cycle', async () => { - // Re-login would call auth.ts → upsert to 'github'. - // The github_follow record is untouched. Follow should still resolve. - - const FOLLOW_TOKEN = { id: 'tok-follow', accessToken: 'enc:follow-token' }; - const findUnique = vi.fn().mockResolvedValue(FOLLOW_TOKEN); - - mockFetch.mockResolvedValue({ status: 204 }); - - const app = await buildFollowApp({ - oAuthToken: { findUnique } as any, - followLog: { create: vi.fn().mockReturnValue({ catch: vi.fn() }) } as any, - }); - - const res = await app.inject({ - method: 'POST', - url: '/api/follow/github/postlogindev', - }); - - expect(res.statusCode).toBe(200); - // Lookup must target github_follow, not the re-written auth token - expect(findUnique.mock.calls[0][0].where.userId_platform.platform).toBe('github_follow'); - }); -}); - -// ───────────────────────────────────────────────────────────────────────────── -// 4. Encrypted token persistence -// ───────────────────────────────────────────────────────────────────────────── - -describe('Encrypted token persistence', () => { - it('connect flow stores an encrypted token, not the raw access token', async () => { - const RAW_TOKEN = 'ghs_raw_follow_token_abc123'; - const upsert = vi.fn().mockResolvedValue({}); - - const app = await buildConnectApp({ oAuthToken: { upsert } as any }); - - mockFetch.mockResolvedValue({ - json: async () => ({ access_token: RAW_TOKEN, scope: 'user:follow' }), - }); - - await app.inject({ - method: 'GET', - url: `/api/connect/github/callback?code=code&state=${makeConnectState(USER_ID)}`, - }); - - const { create } = upsert.mock.calls[0][0]; - // encrypt mock prefixes with 'enc:' — raw token must not appear verbatim - expect(create.accessToken).toBe(`enc:${RAW_TOKEN}`); - expect(create.accessToken).not.toBe(RAW_TOKEN); - }); - - it('follow route decrypts the stored token before calling GitHub API', async () => { - const STORED = 'enc:decrypted-follow-token'; - - const findUnique = vi.fn().mockResolvedValue({ - id: 'tok-1', - accessToken: STORED, - }); - - mockFetch.mockResolvedValue({ status: 204 }); - - const app = await buildFollowApp({ - oAuthToken: { findUnique } as any, - followLog: { create: vi.fn().mockReturnValue({ catch: vi.fn() }) } as any, - }); - - await app.inject({ method: 'POST', url: '/api/follow/github/dev' }); - - // decrypt mock strips 'enc:' prefix - expect(mockFetch).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - headers: expect.objectContaining({ - Authorization: 'Bearer decrypted-follow-token', - }), - }), - ); - }); -}); diff --git a/apps/backend/src/__tests__/profiles.test.ts b/apps/backend/src/__tests__/profiles.test.ts index 07d10f98..ef1aad65 100644 --- a/apps/backend/src/__tests__/profiles.test.ts +++ b/apps/backend/src/__tests__/profiles.test.ts @@ -1,7 +1,6 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import Fastify from 'fastify'; import { profileRoutes } from '../routes/profiles.js'; -import type { PrismaClient } from '@prisma/client'; const mockUser = { id: 'user-123', @@ -20,17 +19,17 @@ const mockUser = { providerId: 'gh-123', }; -const mockPrisma: Pick = { +const mockPrisma = { user: { findUnique: vi.fn(), findFirst: vi.fn(), update: vi.fn(), - } as unknown as PrismaClient['user'], + }, }; async function buildApp() { const app = Fastify(); - app.decorate('prisma', mockPrisma as unknown as PrismaClient); + app.decorate('prisma', mockPrisma); app.decorate('authenticate', async (request: any) => { request.user = { id: 'user-123' }; }); @@ -90,7 +89,7 @@ describe('PUT /api/profiles/me', () => { expect(res.json().error).toBe('Validation failed'); }); - it('should return 409 if username is already taken (pre-check)', async () => { + it('should return 409 if username is already taken', async () => { mockPrisma.user.findFirst.mockResolvedValue({ id: 'other-user' }); const app = await buildApp(); const res = await app.inject({ @@ -101,50 +100,4 @@ describe('PUT /api/profiles/me', () => { expect(res.statusCode).toBe(409); expect(res.json().error).toBe('Username already taken'); }); - - it('should return 409 when a concurrent request wins the unique constraint race (P2002)', async () => { - // Both requests pass the findFirst check; the DB unique constraint fires on - // the losing write — Prisma raises P2002. - mockPrisma.user.findFirst.mockResolvedValue(null); - const p2002 = Object.assign(new Error('Unique constraint failed'), { code: 'P2002' }); - mockPrisma.user.update.mockRejectedValue(p2002); - - const app = await buildApp(); - const res = await app.inject({ - method: 'PUT', - url: '/api/profiles/me', - payload: { username: 'raced-username' }, - }); - - expect(res.statusCode).toBe(409); - expect(res.json().error).toBe('Username already taken'); - }); - - it('should return 500 for unexpected database errors during update', async () => { - mockPrisma.user.findFirst.mockResolvedValue(null); - mockPrisma.user.update.mockRejectedValue(new Error('Connection refused')); - - const app = await buildApp(); - const res = await app.inject({ - method: 'PUT', - url: '/api/profiles/me', - payload: { username: 'anyuser' }, - }); - - expect(res.statusCode).toBe(500); - expect(res.json().error).toBe('Internal server error'); - }); - - it('should not call findFirst when no username is provided in the update', async () => { - mockPrisma.user.update.mockResolvedValue({ ...mockUser, displayName: 'New Name' }); - const app = await buildApp(); - const res = await app.inject({ - method: 'PUT', - url: '/api/profiles/me', - payload: { displayName: 'New Name' }, - }); - - expect(res.statusCode).toBe(200); - expect(mockPrisma.user.findFirst).not.toHaveBeenCalled(); - }); }); \ No newline at end of file diff --git a/apps/backend/src/__tests__/public.test.ts b/apps/backend/src/__tests__/public.test.ts deleted file mode 100644 index a767b25d..00000000 --- a/apps/backend/src/__tests__/public.test.ts +++ /dev/null @@ -1,466 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import Fastify from 'fastify'; -import jwt from '@fastify/jwt'; -import { publicRoutes } from '../routes/public.js'; -import type { PrismaClient } from '@prisma/client'; - -// ── Mock QR utilities ───────────────────────────────────────────────────────── -// Prevents real QR rasterisation (and any native canvas/image deps) from running -// during unit tests. The stubs return minimal valid values that satisfy the -// Content-Type assertions below. -vi.mock('../utils/qr.js', () => ({ - generateQRBuffer: vi.fn().mockResolvedValue(Buffer.from('fake-png')), - generateQRSvg: vi.fn().mockResolvedValue('fake'), -})); - -import { generateQRBuffer, generateQRSvg } from '../utils/qr.js'; - -const mockUser = { - id: 'user-123', - username: 'testuser', - displayName: 'Test User', - bio: null, - pronouns: null, - role: null, - company: null, - avatarUrl: null, - accentColor: '#ffffff', - platformLinks: [], -}; - -const mockPrisma = { - user: { - findUnique: vi.fn(), - }, - platformLink: {} as any, - cardView: { - create: vi.fn().mockReturnValue({ catch: vi.fn() }), - }, - followLog: { - findMany: vi.fn().mockResolvedValue([]), - }, - card: {} as any, -}; - -// ── Redis mock ──────────────────────────────────────────────────────────────── -// Simulates ioredis behaviour: get returns null (MISS) by default. -const mockRedis = { - get: vi.fn().mockResolvedValue(null), - set: vi.fn().mockResolvedValue('OK'), - del: vi.fn().mockResolvedValue(1), -}; - -async function buildApp() { - const app = Fastify(); - // Register JWT so app.jwt.sign() is available for the qr-session route. - // @fastify/jwt also adds request.jwtVerify(), which throws when no valid - // Authorization header is present — matching the soft-auth pattern in the routes. - await app.register(jwt, { secret: 'test-secret-for-unit-tests-only' }); - app.decorate('prisma', mockPrisma as unknown as PrismaClient); - // Decorate with the Redis mock so cache branches execute in tests. - app.decorate('redis', mockRedis as any); - app.register(publicRoutes, { prefix: '/api/public' }); - await app.ready(); - return app; -} - -// ─── QR size validation ─────────────────────────────────────────────────────── - -describe('GET /api/public/:username/qr — size validation', () => { - beforeEach(() => { - vi.clearAllMocks(); - // Re-attach default mock behaviour cleared by clearAllMocks - (generateQRBuffer as ReturnType).mockResolvedValue(Buffer.from('fake-png')); - (generateQRSvg as ReturnType).mockResolvedValue('fake'); - mockRedis.get.mockResolvedValue(null); - mockRedis.set.mockResolvedValue('OK'); - }); - - // ── Reject before DB touch ───────────────────────────────────────────────── - - it('rejects size=0 with 400 before any DB query', async () => { - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr?size=0', - }); - expect(res.statusCode).toBe(400); - expect(res.json().error).toMatch(/integer between/i); - expect(mockPrisma.user.findUnique).not.toHaveBeenCalled(); - }); - - it('rejects size=-1 with 400 before any DB query', async () => { - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr?size=-1', - }); - expect(res.statusCode).toBe(400); - expect(mockPrisma.user.findUnique).not.toHaveBeenCalled(); - }); - - it('rejects size=50000 (above upper bound) with 400', async () => { - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr?size=50000', - }); - expect(res.statusCode).toBe(400); - expect(res.json().error).toMatch(/integer between/i); - expect(mockPrisma.user.findUnique).not.toHaveBeenCalled(); - }); - - it('rejects size=2049 (one above upper bound) with 400', async () => { - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr?size=2049', - }); - expect(res.statusCode).toBe(400); - expect(mockPrisma.user.findUnique).not.toHaveBeenCalled(); - }); - - it('rejects non-numeric size (abc) with 400', async () => { - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr?size=abc', - }); - expect(res.statusCode).toBe(400); - expect(mockPrisma.user.findUnique).not.toHaveBeenCalled(); - }); - - it('rejects floating-point size (400.5) with 400', async () => { - // parseInt('400.5') === 400, which IS in range — this passes. - // Documenting the boundary: fractional strings are truncated, not rejected. - // A string like '0.5' parseInt → 0, which is out of range. - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr?size=0.5', - }); - expect(res.statusCode).toBe(400); - expect(mockPrisma.user.findUnique).not.toHaveBeenCalled(); - }); - - // ── Accept valid sizes ───────────────────────────────────────────────────── - - it('accepts size=1 (lower bound) and returns PNG', async () => { - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr?size=1', - }); - expect(res.statusCode).toBe(200); - expect(res.headers['content-type']).toMatch(/image\/png/); - expect(generateQRBuffer).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ width: 1 }), - ); - }); - - it('accepts size=2048 (upper bound) and returns PNG', async () => { - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr?size=2048', - }); - expect(res.statusCode).toBe(200); - expect(res.headers['content-type']).toMatch(/image\/png/); - expect(generateQRBuffer).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ width: 2048 }), - ); - }); - - it('defaults to size=400 when no size param is provided', async () => { - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr', - }); - expect(res.statusCode).toBe(200); - expect(generateQRBuffer).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ width: 400 }), - ); - }); - - // ── Format selection ─────────────────────────────────────────────────────── - - it('returns SVG when format=svg is requested', async () => { - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr?format=svg&size=200', - }); - expect(res.statusCode).toBe(200); - expect(res.headers['content-type']).toMatch(/image\/svg\+xml/); - expect(generateQRSvg).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ width: 200 }), - ); - }); - - // ── User not found ───────────────────────────────────────────────────────── - - it('returns 404 for an unknown username (valid size)', async () => { - mockPrisma.user.findUnique.mockResolvedValue(null); - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/nobody/qr?size=400', - }); - expect(res.statusCode).toBe(404); - expect(res.json().error).toBe('User not found'); - }); - - // ── QR generation error ──────────────────────────────────────────────────── - - it('returns 500 when QR generation throws', async () => { - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - (generateQRBuffer as ReturnType).mockRejectedValueOnce( - new Error('canvas error'), - ); - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr?size=400', - }); - expect(res.statusCode).toBe(500); - expect(res.json().error).toBe('QR code generation failed'); - }); -}); - -// ─── Redis cache HIT / MISS behaviour ──────────────────────────────────────── - -describe('GET /api/public/:username — Redis cache', () => { - beforeEach(() => { - vi.clearAllMocks(); - mockRedis.get.mockResolvedValue(null); - mockRedis.set.mockResolvedValue('OK'); - mockPrisma.followLog.findMany.mockResolvedValue([]); - mockPrisma.cardView.create.mockReturnValue({ catch: vi.fn() }); - }); - - it('returns X-Cache: MISS and queries DB on first request', async () => { - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - const app = await buildApp(); - - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser', - }); - - expect(res.statusCode).toBe(200); - expect(res.headers['x-cache']).toBe('MISS'); - expect(res.headers['cache-control']).toBe('public, max-age=300, stale-while-revalidate=60'); - // DB was queried since Redis returned null - expect(mockPrisma.user.findUnique).toHaveBeenCalledOnce(); - // Profile should be written to Redis after the DB fetch - expect(mockRedis.set).toHaveBeenCalledWith( - 'profile:testuser', - expect.any(String), - 'EX', - 300, - ); - }); - - it('returns X-Cache: HIT and skips DB on cached request', async () => { - // Simulate a warm cache entry - const cached = JSON.stringify({ - _userId: 'user-123', - username: 'testuser', - displayName: 'Test User', - bio: null, - pronouns: null, - role: null, - company: null, - avatarUrl: null, - accentColor: '#ffffff', - links: [], - }); - mockRedis.get.mockResolvedValue(cached); - - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser', - }); - - expect(res.statusCode).toBe(200); - expect(res.headers['x-cache']).toBe('HIT'); - // DB must NOT be queried when cache is warm - expect(mockPrisma.user.findUnique).not.toHaveBeenCalled(); - }); - - it('response body on cache HIT matches the cached profile', async () => { - const cached = JSON.stringify({ - _userId: 'user-123', - username: 'testuser', - displayName: 'Test User', - bio: 'A bio', - pronouns: null, - role: 'Engineer', - company: null, - avatarUrl: null, - accentColor: '#123456', - links: [], - }); - mockRedis.get.mockResolvedValue(cached); - - const app = await buildApp(); - const res = await app.inject({ method: 'GET', url: '/api/public/testuser' }); - const body = res.json(); - - expect(body.username).toBe('testuser'); - expect(body.accentColor).toBe('#123456'); - // Internal _userId field must not leak into the HTTP response - expect(body._userId).toBeUndefined(); - }); - - it('falls through to DB when Redis.get throws', async () => { - mockRedis.get.mockRejectedValue(new Error('Redis down')); - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - - const app = await buildApp(); - const res = await app.inject({ method: 'GET', url: '/api/public/testuser' }); - - expect(res.statusCode).toBe(200); - // DB was reached despite the Redis failure - expect(mockPrisma.user.findUnique).toHaveBeenCalledOnce(); - }); - - it('returns 404 when user does not exist (cache MISS)', async () => { - mockPrisma.user.findUnique.mockResolvedValue(null); - - const app = await buildApp(); - const res = await app.inject({ method: 'GET', url: '/api/public/nobody' }); - - expect(res.statusCode).toBe(404); - expect(res.json().error).toBe('User not found'); - }); -}); - -// ─── QR session endpoint ────────────────────────────────────────────────────── - -describe('GET /api/public/:username/qr-session', () => { - beforeEach(() => { - vi.clearAllMocks(); - mockRedis.get.mockResolvedValue(null); - mockRedis.set.mockResolvedValue('OK'); - }); - - it('returns 404 when the user does not exist', async () => { - mockPrisma.user.findUnique.mockResolvedValue(null); - - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/nobody/qr-session', - }); - - expect(res.statusCode).toBe(404); - expect(res.json().error).toBe('User not found'); - }); - - it('returns a JWT token with correct shape on DB fetch (cache MISS)', async () => { - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr-session', - }); - - expect(res.statusCode).toBe(200); - const body = res.json(); - expect(typeof body.token).toBe('string'); - expect(body.tokenType).toBe('JWT'); - expect(body.expiresIn).toBe(600); - expect(typeof body.expiresAt).toBe('string'); - // expiresAt must be a valid ISO 8601 date string - expect(new Date(body.expiresAt).getTime()).toBeGreaterThan(Date.now()); - }); - - it('token payload encodes the public profile snapshot', async () => { - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr-session', - }); - - const { token } = res.json(); - // Decode without verifying so we can inspect the payload in the test - const decoded = JSON.parse( - Buffer.from(token.split('.')[1], 'base64url').toString(), - ); - expect(decoded.sub).toBe('testuser'); - expect(decoded.profile.username).toBe('testuser'); - expect(decoded.profile.displayName).toBe('Test User'); - }); - - it('serves snapshot from Redis cache without querying DB', async () => { - const cached = JSON.stringify({ - _userId: 'user-123', - username: 'testuser', - displayName: 'Cached User', - bio: null, - pronouns: null, - role: null, - company: null, - avatarUrl: null, - accentColor: '#ffffff', - links: [], - }); - mockRedis.get.mockResolvedValue(cached); - - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr-session', - }); - - expect(res.statusCode).toBe(200); - // DB must not be reached when the cache is warm - expect(mockPrisma.user.findUnique).not.toHaveBeenCalled(); - - const { token } = res.json(); - const decoded = JSON.parse( - Buffer.from(token.split('.')[1], 'base64url').toString(), - ); - expect(decoded.profile.displayName).toBe('Cached User'); - }); - - it('includes Cache-Control header in qr-session response', async () => { - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - - const app = await buildApp(); - const res = await app.inject({ - method: 'GET', - url: '/api/public/testuser/qr-session', - }); - - expect(res.headers['cache-control']).toBe('public, max-age=300, stale-while-revalidate=60'); - }); - - it('caches the profile in Redis when served from DB', async () => { - mockPrisma.user.findUnique.mockResolvedValue(mockUser); - - const app = await buildApp(); - await app.inject({ method: 'GET', url: '/api/public/testuser/qr-session' }); - - expect(mockRedis.set).toHaveBeenCalledWith( - 'profile:testuser', - expect.any(String), - 'EX', - 300, - ); - }); -}); diff --git a/apps/backend/src/__tests__/team.test.ts b/apps/backend/src/__tests__/team.test.ts deleted file mode 100644 index 350298a1..00000000 --- a/apps/backend/src/__tests__/team.test.ts +++ /dev/null @@ -1,776 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import Fastify, { FastifyInstance } from 'fastify'; -import { PrismaClient, TeamRole } from '@prisma/client'; -import { teamRoutes } from '../routes/team'; - -// ─── Shared mock data ───────────────────────────────────────────────────────── - -const MOCK_OWNER_ID = 'user-uuid-001'; -const MOCK_MEMBER_ID = 'user-uuid-002'; -const MOCK_OUTSIDER_ID = 'user-uuid-003'; - -const MOCK_OWNER = { - id: MOCK_OWNER_ID, - username: 'johndoe', - displayName: 'John Doe', - bio: 'Team owner', - pronouns: 'he/him', - role: 'Software Engineer', - company: 'Acme Corp', - avatarUrl: 'https://example.com/john.png', - accentColor: '#6366f1', -}; - -const MOCK_MEMBER_USER = { - id: MOCK_MEMBER_ID, - username: 'janedoe', - displayName: 'Jane Doe', - bio: null, - pronouns: null, - role: 'Designer', - company: null, - avatarUrl: null, - accentColor: '#f43f5e', -}; - -const MOCK_PLATFORM_LINKS = [ - { id: 'link-uuid-001', platform: 'github', username: 'johndoe', url: 'https://github.com/johndoe', displayOrder: 0 }, - { id: 'link-uuid-002', platform: 'twitter', username: 'johndoe_', url: 'https://twitter.com/johndoe_', displayOrder: 1 }, -]; - -const MOCK_TEAM = { - id: 'team-uuid-001', - name: 'DevCard Core', - slug: 'devcard-core', - description: 'Building the future of developer cards', - avatarUrl: 'https://example.com/team.png', - ownerId: MOCK_OWNER_ID, - createdAt: new Date('2024-01-01T00:00:00Z'), - updatedAt: new Date('2024-06-01T00:00:00Z'), -}; - -const MOCK_TEAM_WITH_MEMBERS = { - ...MOCK_TEAM, - members: [ - { - id: 'tm-uuid-001', - teamId: MOCK_TEAM.id, - userId: MOCK_OWNER_ID, - role: TeamRole.OWNER, - joinedAt: new Date('2024-01-01T00:00:00Z'), - user: { ...MOCK_OWNER, platformLinks: MOCK_PLATFORM_LINKS }, - }, - { - id: 'tm-uuid-002', - teamId: MOCK_TEAM.id, - userId: MOCK_MEMBER_ID, - role: TeamRole.MEMBER, - joinedAt: new Date('2024-02-01T00:00:00Z'), - user: { ...MOCK_MEMBER_USER, platformLinks: [] }, - }, - ], -}; - -// ─── Prisma mock ────────────────────────────────────────────────────────────── - -const prismaMock = { - team: { - create: vi.fn(), - findUnique: vi.fn(), - update: vi.fn(), - delete: vi.fn(), - }, - teamMember: { - create: vi.fn(), - delete: vi.fn(), - }, - user: { - findUnique: vi.fn(), - }, - $transaction: vi.fn(), -}; - -// ─── App factory ────────────────────────────────────────────────────────────── - -let mockJwtVerify = vi.fn(); - -async function buildApp(): Promise { - const app = Fastify({ logger: false }); - - app.decorate('prisma', prismaMock as unknown as PrismaClient); - - app.decorateRequest('jwtVerify', function () { - return mockJwtVerify(); - }); - - await app.register(teamRoutes); - await app.ready(); - return app; -} - -// ─── Helpers ───────────────────────────────────────────────────────────────── - -function authHeader(): Record { - return { Authorization: 'Bearer mock-token' }; -} - -async function createTeam( - app: FastifyInstance, - body: Record, - authenticated = true, -) { - return app.inject({ - method: 'POST', - url: '/', - headers: authenticated ? authHeader() : {}, - payload: body, - }); -} - -// ─── Test suite ─────────────────────────────────────────────────────────────── - -describe('Teams API', () => { - let app: FastifyInstance; - - beforeEach(async () => { - vi.clearAllMocks(); - mockJwtVerify.mockResolvedValue({ id: MOCK_OWNER_ID }); - app = await buildApp(); - }); - - afterEach(async () => { - await app.close(); - }); - - // ── POST / — create team ────────────────────────────────────────────────── - - describe('POST / — create team', () => { - const validBody = { - name: 'DevCard Core', - description: 'Building the future of developer cards', - avatarUrl: 'https://example.com/team.png', - }; - - it('201 — creates team and auto-adds owner as OWNER member', async () => { - prismaMock.team.findUnique.mockResolvedValue(null); - prismaMock.$transaction.mockImplementation(async (cb: any) => { - return cb({ - team: { create: vi.fn().mockResolvedValue(MOCK_TEAM) }, - teamMember: { create: vi.fn().mockResolvedValue({}) }, - }); - }); - - const res = await createTeam(app, validBody); - - expect(res.statusCode).toBe(201); - const body = res.json(); - expect(body.name).toBe('DevCard Core'); - expect(body.ownerId).toBe(MOCK_OWNER_ID); - expect(body.slug).toBe('devcard-core'); - }); - - it('401 — rejects unauthenticated request', async () => { - mockJwtVerify.mockRejectedValue(new Error('Unauthorized')); - - const res = await createTeam(app, validBody, false); - - expect(res.statusCode).toBe(401); - expect(res.json()).toMatchObject({ error: 'Unauthorized' }); - }); - - it('400 — rejects name shorter than 3 characters', async () => { - const res = await createTeam(app, { ...validBody, name: 'AB' }); - expect(res.statusCode).toBe(400); - }); - - it('400 — rejects name longer than 100 characters', async () => { - const res = await createTeam(app, { ...validBody, name: 'A'.repeat(101) }); - expect(res.statusCode).toBe(400); - }); - - it('400 — rejects invalid avatarUrl', async () => { - const res = await createTeam(app, { ...validBody, avatarUrl: 'not-a-url' }); - expect(res.statusCode).toBe(400); - }); - - it('400 — rejects missing name', async () => { - const { name: _omit, ...bodyWithoutName } = validBody; - const res = await createTeam(app, bodyWithoutName); - expect(res.statusCode).toBe(400); - }); - - it('201 — creates team without optional fields', async () => { - prismaMock.team.findUnique.mockResolvedValue(null); - prismaMock.$transaction.mockImplementation(async (cb: any) => { - return cb({ - team: { create: vi.fn().mockResolvedValue({ ...MOCK_TEAM, description: null, avatarUrl: null }) }, - teamMember: { create: vi.fn().mockResolvedValue({}) }, - }); - }); - - const res = await createTeam(app, { name: 'DevCard Core' }); - expect(res.statusCode).toBe(201); - }); - - it('500 — returns 500 on database failure', async () => { - prismaMock.team.findUnique.mockResolvedValue(null); - prismaMock.$transaction.mockRejectedValue(new Error('DB error')); - - const res = await createTeam(app, validBody); - expect(res.statusCode).toBe(500); - expect(res.json()).toMatchObject({ error: 'Failed to create team' }); - }); - }); - - // ── GET /:slug — public team profile ───────────────────────────────────── - - describe('GET /:slug — public team profile', () => { - it('200 — returns team with members in PublicProfile shape', async () => { - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM_WITH_MEMBERS); - - const res = await app.inject({ method: 'GET', url: '/devcard-core' }); - - expect(res.statusCode).toBe(200); - const body = res.json(); - - expect(body.slug).toBe('devcard-core'); - expect(body.ownerId).toBe(MOCK_OWNER_ID); - expect(body.members).toHaveLength(2); - }); - - it('200 — each member has PublicProfile fields and links array', async () => { - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM_WITH_MEMBERS); - - const res = await app.inject({ method: 'GET', url: '/devcard-core' }); - const owner = res.json().members[0]; - - expect(owner).toHaveProperty('username', 'johndoe'); - expect(owner).toHaveProperty('displayName', 'John Doe'); - expect(owner).toHaveProperty('accentColor'); - expect(owner).toHaveProperty('links'); - expect(owner.links).toHaveLength(2); - expect(owner.links[0]).toMatchObject({ - platform: 'github', - username: 'johndoe', - url: 'https://github.com/johndoe', - displayOrder: 0, - }); - }); - - it('200 — member has teamRole and joinedAt fields', async () => { - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM_WITH_MEMBERS); - - const res = await app.inject({ method: 'GET', url: '/devcard-core' }); - const owner = res.json().members[0]; - - expect(owner).toHaveProperty('teamRole', 'OWNER'); - expect(owner).toHaveProperty('joinedAt'); - }); - - it('200 — does not leak sensitive user fields on members', async () => { - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM_WITH_MEMBERS); - - const res = await app.inject({ method: 'GET', url: '/devcard-core' }); - const member = res.json().members[0]; - - expect(member).not.toHaveProperty('email'); - expect(member).not.toHaveProperty('provider'); - expect(member).not.toHaveProperty('providerId'); - }); - - it('200 — works without authentication (public endpoint)', async () => { - mockJwtVerify.mockRejectedValue(new Error('Should not be called')); - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM_WITH_MEMBERS); - - const res = await app.inject({ method: 'GET', url: '/devcard-core' }); - - expect(res.statusCode).toBe(200); - expect(mockJwtVerify).not.toHaveBeenCalled(); - }); - - it('404 — returns 404 for unknown slug', async () => { - prismaMock.team.findUnique.mockResolvedValue(null); - - const res = await app.inject({ method: 'GET', url: '/ghost-team' }); - - expect(res.statusCode).toBe(404); - expect(res.json()).toMatchObject({ error: 'Team not found' }); - }); - - it('200 — returns empty members array for a team with no members', async () => { - prismaMock.team.findUnique.mockResolvedValue({ ...MOCK_TEAM, members: [] }); - - const res = await app.inject({ method: 'GET', url: '/devcard-core' }); - - expect(res.statusCode).toBe(200); - expect(res.json().members).toHaveLength(0); - }); - }); - - // ── POST /:slug/members — invite member ─────────────────────────────────── - - describe('POST /:slug/members — invite member (owner only)', () => { - const teamWithOwnerOnly = { - ...MOCK_TEAM, - owner: MOCK_OWNER, - members: [ - { - id: 'tm-uuid-001', - teamId: MOCK_TEAM.id, - userId: MOCK_OWNER_ID, - role: TeamRole.OWNER, - joinedAt: new Date(), - user: MOCK_OWNER, - }, - ], - }; - - it('201 — owner can invite a new member by username', async () => { - prismaMock.team.findUnique.mockResolvedValue(teamWithOwnerOnly); - prismaMock.user.findUnique.mockResolvedValue(MOCK_MEMBER_USER); - prismaMock.teamMember.create.mockResolvedValue({}); - - const res = await app.inject({ - method: 'POST', - url: '/devcard-core/members', - headers: authHeader(), - payload: { username: 'janedoe' }, - }); - - expect(res.statusCode).toBe(201); - expect(prismaMock.teamMember.create).toHaveBeenCalledOnce(); - - const callData = prismaMock.teamMember.create.mock.calls[0][0].data; - expect(callData.userId).toBe(MOCK_MEMBER_ID); - expect(callData.role).toBe(TeamRole.MEMBER); - }); - - it('401 — rejects unauthenticated request', async () => { - mockJwtVerify.mockRejectedValue(new Error('Unauthorized')); - - const res = await app.inject({ - method: 'POST', - url: '/devcard-core/members', - payload: { username: 'janedoe' }, - }); - - expect(res.statusCode).toBe(401); - }); - - it('403 — non-owner cannot invite members', async () => { - mockJwtVerify.mockResolvedValue({ id: MOCK_MEMBER_ID }); - prismaMock.team.findUnique.mockResolvedValue(teamWithOwnerOnly); - - const res = await app.inject({ - method: 'POST', - url: '/devcard-core/members', - headers: authHeader(), - payload: { username: 'someoneelse' }, - }); - - expect(res.statusCode).toBe(403); - expect(prismaMock.teamMember.create).not.toHaveBeenCalled(); - }); - - it('409 — cannot invite a user who is already a member', async () => { - prismaMock.team.findUnique.mockResolvedValue({ - ...teamWithOwnerOnly, - members: [ - ...teamWithOwnerOnly.members, - { - id: 'tm-uuid-002', - teamId: MOCK_TEAM.id, - userId: MOCK_MEMBER_ID, - role: TeamRole.MEMBER, - joinedAt: new Date(), - user: MOCK_MEMBER_USER, - }, - ], - }); - - const res = await app.inject({ - method: 'POST', - url: '/devcard-core/members', - headers: authHeader(), - payload: { username: 'janedoe' }, - }); - - expect(res.statusCode).toBe(409); - expect(prismaMock.teamMember.create).not.toHaveBeenCalled(); - }); - - it('409 — cannot invite the owner (they are already a member)', async () => { - prismaMock.team.findUnique.mockResolvedValue(teamWithOwnerOnly); - - const res = await app.inject({ - method: 'POST', - url: '/devcard-core/members', - headers: authHeader(), - payload: { username: 'johndoe' }, - }); - - expect(res.statusCode).toBe(409); - }); - - it('404 — returns 404 when invited username does not exist', async () => { - prismaMock.team.findUnique.mockResolvedValue(teamWithOwnerOnly); - prismaMock.user.findUnique.mockResolvedValue(null); - - const res = await app.inject({ - method: 'POST', - url: '/devcard-core/members', - headers: authHeader(), - payload: { username: 'ghostuser' }, - }); - - expect(res.statusCode).toBe(404); - }); - - it('404 — returns 404 when team does not exist', async () => { - prismaMock.team.findUnique.mockResolvedValue(null); - - const res = await app.inject({ - method: 'POST', - url: '/ghost-team/members', - headers: authHeader(), - payload: { username: 'janedoe' }, - }); - - expect(res.statusCode).toBe(404); - }); - - it('400 — rejects empty username', async () => { - const res = await app.inject({ - method: 'POST', - url: '/devcard-core/members', - headers: authHeader(), - payload: { username: '' }, - }); - - expect(res.statusCode).toBe(400); - }); - }); - - // ── DELETE /:slug/members/:userId — remove member ───────────────────────── - - describe('DELETE /:slug/members/:userId — remove member', () => { - const teamWithBothMembers = { - ...MOCK_TEAM, - members: [ - { - id: 'tm-uuid-001', - teamId: MOCK_TEAM.id, - userId: MOCK_OWNER_ID, - role: TeamRole.OWNER, - joinedAt: new Date(), - user: MOCK_OWNER, - }, - { - id: 'tm-uuid-002', - teamId: MOCK_TEAM.id, - userId: MOCK_MEMBER_ID, - role: TeamRole.MEMBER, - joinedAt: new Date(), - user: MOCK_MEMBER_USER, - }, - ], - }; - - it('200 — owner can remove a member', async () => { - prismaMock.team.findUnique.mockResolvedValue(teamWithBothMembers); - prismaMock.teamMember.delete.mockResolvedValue({}); - - const res = await app.inject({ - method: 'DELETE', - url: `/devcard-core/members/${MOCK_MEMBER_ID}`, - headers: authHeader(), - }); - - expect(res.statusCode).toBe(200); - const deleteArg = prismaMock.teamMember.delete.mock.calls[0][0].where; - expect(deleteArg).toMatchObject({ - userId_teamId: { - teamId: MOCK_TEAM.id, - userId: MOCK_MEMBER_ID, - }, - }); - }); - - it('200 — member can self-remove (leave team)', async () => { - mockJwtVerify.mockResolvedValue({ id: MOCK_MEMBER_ID }); - prismaMock.team.findUnique.mockResolvedValue(teamWithBothMembers); - prismaMock.teamMember.delete.mockResolvedValue({}); - - const res = await app.inject({ - method: 'DELETE', - url: `/devcard-core/members/${MOCK_MEMBER_ID}`, - headers: authHeader(), - }); - - expect(res.statusCode).toBe(200); - }); - - it('403 — owner cannot leave their own team', async () => { - prismaMock.team.findUnique.mockResolvedValue(teamWithBothMembers); - - const res = await app.inject({ - method: 'DELETE', - url: `/devcard-core/members/${MOCK_OWNER_ID}`, - headers: authHeader(), - }); - - expect(res.statusCode).toBe(403); - expect(prismaMock.teamMember.delete).not.toHaveBeenCalled(); - }); - - it('403 — outsider cannot remove another member', async () => { - mockJwtVerify.mockResolvedValue({ id: MOCK_OUTSIDER_ID }); - prismaMock.team.findUnique.mockResolvedValue(teamWithBothMembers); - - const res = await app.inject({ - method: 'DELETE', - url: `/devcard-core/members/${MOCK_MEMBER_ID}`, - headers: authHeader(), - }); - - expect(res.statusCode).toBe(403); - expect(prismaMock.teamMember.delete).not.toHaveBeenCalled(); - }); - - it('401 — rejects unauthenticated request', async () => { - mockJwtVerify.mockRejectedValue(new Error('Unauthorized')); - - const res = await app.inject({ - method: 'DELETE', - url: `/devcard-core/members/${MOCK_MEMBER_ID}`, - }); - - expect(res.statusCode).toBe(401); - }); - - it('404 — returns 404 when team does not exist', async () => { - prismaMock.team.findUnique.mockResolvedValue(null); - - const res = await app.inject({ - method: 'DELETE', - url: `/ghost-team/members/${MOCK_MEMBER_ID}`, - headers: authHeader(), - }); - - expect(res.statusCode).toBe(404); - }); - - it('404 — returns 404 when userId is not a team member', async () => { - prismaMock.team.findUnique.mockResolvedValue(teamWithBothMembers); - - const res = await app.inject({ - method: 'DELETE', - url: `/devcard-core/members/${MOCK_OUTSIDER_ID}`, - headers: authHeader(), - }); - - expect(res.statusCode).toBe(404); - }); - }); - - // ── PATCH /:slug — update team ──────────────────────────────────────────── - - describe('PATCH /:slug — update team (owner only)', () => { - it('200 — owner can update name, description, avatarUrl', async () => { - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM); - prismaMock.team.update.mockResolvedValue({ ...MOCK_TEAM, name: 'New Name' }); - - const res = await app.inject({ - method: 'PATCH', - url: '/devcard-core', - headers: authHeader(), - payload: { name: 'New Name' }, - }); - - expect(res.statusCode).toBe(200); - expect(res.json().name).toBe('New Name'); - }); - - it('403 — non-owner cannot update team', async () => { - mockJwtVerify.mockResolvedValue({ id: MOCK_MEMBER_ID }); - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM); - - const res = await app.inject({ - method: 'PATCH', - url: '/devcard-core', - headers: authHeader(), - payload: { name: 'Hijacked Name' }, - }); - - expect(res.statusCode).toBe(403); - expect(prismaMock.team.update).not.toHaveBeenCalled(); - }); - - it('401 — rejects unauthenticated request', async () => { - mockJwtVerify.mockRejectedValue(new Error('Unauthorized')); - - const res = await app.inject({ - method: 'PATCH', - url: '/devcard-core', - payload: { name: 'New Name' }, - }); - - expect(res.statusCode).toBe(401); - }); - - it('400 — rejects empty body (at least one field required)', async () => { - const res = await app.inject({ - method: 'PATCH', - url: '/devcard-core', - headers: authHeader(), - payload: {}, - }); - - expect(res.statusCode).toBe(400); - }); - - it('400 — rejects invalid avatarUrl', async () => { - const res = await app.inject({ - method: 'PATCH', - url: '/devcard-core', - headers: authHeader(), - payload: { avatarUrl: 'not-a-url' }, - }); - - expect(res.statusCode).toBe(400); - }); - - it('404 — returns 404 for unknown slug', async () => { - prismaMock.team.findUnique.mockResolvedValue(null); - - const res = await app.inject({ - method: 'PATCH', - url: '/ghost-team', - headers: authHeader(), - payload: { name: 'New Name' }, - }); - - expect(res.statusCode).toBe(404); - }); - }); - - // ── DELETE /:slug — delete team ─────────────────────────────────────────── - - describe('DELETE /:slug — delete team (owner only)', () => { - it('200 — owner can delete team', async () => { - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM); - prismaMock.team.delete.mockResolvedValue({}); - - const res = await app.inject({ - method: 'DELETE', - url: '/devcard-core', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(200); - expect(prismaMock.team.delete).toHaveBeenCalledOnce(); - }); - - it('403 — non-owner cannot delete team', async () => { - mockJwtVerify.mockResolvedValue({ id: MOCK_MEMBER_ID }); - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM); - - const res = await app.inject({ - method: 'DELETE', - url: '/devcard-core', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(403); - expect(prismaMock.team.delete).not.toHaveBeenCalled(); - }); - - it('401 — rejects unauthenticated request', async () => { - mockJwtVerify.mockRejectedValue(new Error('Unauthorized')); - - const res = await app.inject({ - method: 'DELETE', - url: '/devcard-core', - }); - - expect(res.statusCode).toBe(401); - }); - - it('404 — returns 404 for unknown slug', async () => { - prismaMock.team.findUnique.mockResolvedValue(null); - - const res = await app.inject({ - method: 'DELETE', - url: '/ghost-team', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(404); - }); - - it('500 — returns 500 on database failure', async () => { - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM); - prismaMock.team.delete.mockRejectedValue(new Error('DB error')); - - const res = await app.inject({ - method: 'DELETE', - url: '/devcard-core', - headers: authHeader(), - }); - - expect(res.statusCode).toBe(500); - }); - }); - - // ── GET /:slug/qr — QR code ─────────────────────────────────────────────── - - describe('GET /:slug/qr — QR code', () => { - it('200 — returns PNG image for valid slug', async () => { - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM); - - const res = await app.inject({ - method: 'GET', - url: '/devcard-core/qr', - }); - - expect(res.statusCode).toBe(200); - expect(res.headers['content-type']).toMatch('image/png'); - }); - - it('200 — encodes correct devcard.dev URL in QR', async () => { - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM); - - const res = await app.inject({ - method: 'GET', - url: '/devcard-core/qr', - }); - - expect(res.statusCode).toBe(200); - expect(res.rawPayload.length).toBeGreaterThan(0); - }); - - it('200 — works without authentication (public endpoint)', async () => { - mockJwtVerify.mockRejectedValue(new Error('Should not be called')); - prismaMock.team.findUnique.mockResolvedValue(MOCK_TEAM); - - const res = await app.inject({ - method: 'GET', - url: '/devcard-core/qr', - }); - - expect(res.statusCode).toBe(200); - expect(mockJwtVerify).not.toHaveBeenCalled(); - }); - - it('404 — returns 404 for unknown slug', async () => { - prismaMock.team.findUnique.mockResolvedValue(null); - - const res = await app.inject({ - method: 'GET', - url: '/ghost-team/qr', - }); - - expect(res.statusCode).toBe(404); - }); - }); -}); \ No newline at end of file diff --git a/apps/backend/src/__tests__/validateEnv.test.ts b/apps/backend/src/__tests__/validateEnv.test.ts deleted file mode 100644 index eb0574bd..00000000 --- a/apps/backend/src/__tests__/validateEnv.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { describe, it, expect, vi, afterEach } from 'vitest'; -import { validateEnv } from '../utils/validateEnv.js'; - -// ── helpers ────────────────────────────────────────────────────────────────── - -/** - * Replaces process.exit with a throwing stub for the duration of the test so - * that a failing validateEnv() call does not terminate the test process. - * Returns the spy so callers can assert the exit code. - */ -function stubExit() { - return vi.spyOn(process, 'exit').mockImplementation((code?: number | string) => { - throw new Error(`process.exit(${code})`); - }) as unknown as ReturnType; -} - -// ── test suite ──────────────────────────────────────────────────────────────── - -describe('validateEnv', () => { - afterEach(() => { - vi.restoreAllMocks(); - vi.unstubAllEnvs(); - }); - - // ─── JWT_SECRET ────────────────────────────────────────────────────────── - - it('exits with code 1 when JWT_SECRET is absent', () => { - vi.stubEnv('JWT_SECRET', undefined as unknown as string); - vi.stubEnv('ENCRYPTION_KEY', 'a-valid-encryption-key'); - const exit = stubExit(); - - expect(() => validateEnv()).toThrow('process.exit(1)'); - expect(exit).toHaveBeenCalledWith(1); - }); - - it('exits with code 1 when JWT_SECRET is an empty string', () => { - vi.stubEnv('JWT_SECRET', ''); - vi.stubEnv('ENCRYPTION_KEY', 'a-valid-encryption-key'); - stubExit(); - - expect(() => validateEnv()).toThrow('process.exit(1)'); - }); - - it('exits with code 1 when JWT_SECRET is the known insecure default in production', () => { - vi.stubEnv('JWT_SECRET', 'dev-secret-change-me'); - vi.stubEnv('ENCRYPTION_KEY', 'a-valid-encryption-key'); - vi.stubEnv('NODE_ENV', 'production'); - stubExit(); - - expect(() => validateEnv()).toThrow('process.exit(1)'); - }); - - it('allows the known insecure default in non-production (development)', () => { - // The known-insecure check is production-only so local development still - // works with the default value without requiring a full secrets setup. - vi.stubEnv('JWT_SECRET', 'dev-secret-change-me'); - vi.stubEnv('ENCRYPTION_KEY', 'a-valid-encryption-key'); - vi.stubEnv('NODE_ENV', 'development'); - - // Must not throw / call process.exit - expect(() => validateEnv()).not.toThrow(); - }); - - it('allows the known insecure default when NODE_ENV is not set', () => { - vi.stubEnv('JWT_SECRET', 'dev-secret-change-me'); - vi.stubEnv('ENCRYPTION_KEY', 'a-valid-encryption-key'); - vi.stubEnv('NODE_ENV', undefined as unknown as string); - - expect(() => validateEnv()).not.toThrow(); - }); - - // ─── ENCRYPTION_KEY ────────────────────────────────────────────────────── - - it('exits with code 1 when ENCRYPTION_KEY is absent', () => { - vi.stubEnv('JWT_SECRET', 'a-valid-jwt-secret-that-is-sufficiently-long'); - vi.stubEnv('ENCRYPTION_KEY', undefined as unknown as string); - stubExit(); - - expect(() => validateEnv()).toThrow('process.exit(1)'); - }); - - it('exits with code 1 when ENCRYPTION_KEY is an empty string', () => { - vi.stubEnv('JWT_SECRET', 'a-valid-jwt-secret-that-is-sufficiently-long'); - vi.stubEnv('ENCRYPTION_KEY', ''); - stubExit(); - - expect(() => validateEnv()).toThrow('process.exit(1)'); - }); - - // ─── Multiple failures ──────────────────────────────────────────────────── - - it('reports both missing secrets in a single exit call', () => { - vi.stubEnv('JWT_SECRET', undefined as unknown as string); - vi.stubEnv('ENCRYPTION_KEY', undefined as unknown as string); - const exit = stubExit(); - - expect(() => validateEnv()).toThrow('process.exit(1)'); - // A single exit — not one per error — so operators fix everything in one deploy. - expect(exit).toHaveBeenCalledTimes(1); - expect(exit).toHaveBeenCalledWith(1); - }); - - // ─── Happy path ────────────────────────────────────────────────────────── - - it('passes when both secrets are valid in development', () => { - vi.stubEnv('JWT_SECRET', 'a-valid-jwt-secret-that-is-sufficiently-long'); - vi.stubEnv('ENCRYPTION_KEY', 'a-valid-32-char-encryption-key!!'); - vi.stubEnv('NODE_ENV', 'development'); - - expect(() => validateEnv()).not.toThrow(); - }); - - it('passes when both secrets are valid in production', () => { - vi.stubEnv('JWT_SECRET', 'a-long-random-production-jwt-secret-with-enough-entropy'); - vi.stubEnv('ENCRYPTION_KEY', 'a-64-char-hex-encryption-key-for-aes-256-gcm-0000000000000000'); - vi.stubEnv('NODE_ENV', 'production'); - - expect(() => validateEnv()).not.toThrow(); - }); - - // ─── No secret leakage ─────────────────────────────────────────────────── - - it('does not log the value of JWT_SECRET when reporting errors', () => { - const secretValue = 'super-secret-value-that-must-not-appear-in-logs'; - vi.stubEnv('JWT_SECRET', undefined as unknown as string); - vi.stubEnv('ENCRYPTION_KEY', 'a-valid-encryption-key'); - stubExit(); - - const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - - expect(() => validateEnv()).toThrow('process.exit(1)'); - - const allOutput = errSpy.mock.calls.flat().join(' '); - expect(allOutput).not.toContain(secretValue); - }); -}); diff --git a/apps/backend/src/app.ts b/apps/backend/src/app.ts index 06b87205..8e8cf381 100644 --- a/apps/backend/src/app.ts +++ b/apps/backend/src/app.ts @@ -1,37 +1,26 @@ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -import cookie from '@fastify/cookie'; +import Fastify from 'fastify'; import cors from '@fastify/cors'; import helmet from '@fastify/helmet'; import jwt from '@fastify/jwt'; +import cookie from '@fastify/cookie'; import multipart from '@fastify/multipart'; -import rateLimit from '@fastify/rate-limit'; import fastifyStatic from '@fastify/static'; -import Fastify, {type FastifyInstance} from 'fastify'; +import path from 'path'; +import { fileURLToPath } from 'url'; import { prismaPlugin } from './plugins/prisma.js'; import { redisPlugin } from './plugins/redis.js'; -import { analyticsRoutes } from './routes/analytics.js'; import { authRoutes } from './routes/auth.js'; -import { cardRoutes } from './routes/cards.js'; -import { connectRoutes } from './routes/connect.js'; -import { eventRoutes } from './routes/event.js'; -import { followRoutes } from './routes/follow.js'; -import { nfcRoutes } from './routes/nfc.js'; import { profileRoutes } from './routes/profiles.js'; +import { cardRoutes } from './routes/cards.js'; import { publicRoutes } from './routes/public.js'; -import { validateEnv } from './utils/validateEnv.js'; -import { teamRoutes } from './routes/team.js'; +import { followRoutes } from './routes/follow.js'; +import { connectRoutes } from './routes/connect.js'; +import { analyticsRoutes } from './routes/analytics.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -export async function buildApp():Promise { - // Validate all required secrets before registering any plugin. - // If validation fails the process exits here — no partially-initialised - // auth state can exist because Fastify is not yet instantiated. - validateEnv(); - +export async function buildApp() { const app = Fastify({ logger: { level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', @@ -66,34 +55,28 @@ export async function buildApp():Promise { }); await app.register(jwt, { - // validateEnv() above guarantees JWT_SECRET is present and safe. - secret: process.env.JWT_SECRET!, + secret: process.env.JWT_SECRET || 'dev-secret-change-me', }); await app.register(cookie); await app.register(multipart, { limits: { fileSize: 5 * 1024 * 1024 } }); // 5MB - await app.register(rateLimit, { - max: 100, - timeWindow: '1 minute', - }); -// Files must be served through authenticated route handlers -// with ownership validation. + // Static file serving for uploads + await app.register(fastifyStatic, { + root: path.join(__dirname, '..', 'uploads'), + prefix: '/uploads/', + decorateReply: false, + }); // ─── Database & Cache Plugins ─── - if (process.env.NODE_ENV !== 'test') { - await app.register(prismaPlugin); //change -} - if (process.env.NODE_ENV !== 'test') { + await app.register(prismaPlugin); await app.register(redisPlugin); -} + // ─── Auth Decorator ─── app.decorate('authenticate', async function (request: any, reply: any) { try { - // Ensure the verified payload is assigned to `request.user` like the original plugin. - const payload = await request.jwtVerify(); - if (payload) request.user = payload; - } catch (error) { + await request.jwtVerify(); + } catch (err) { reply.status(401).send({ error: 'Unauthorized' }); } }); @@ -102,38 +85,17 @@ export async function buildApp():Promise { await app.register(authRoutes, { prefix: '/auth' }); await app.register(profileRoutes, { prefix: '/api/profiles' }); await app.register(cardRoutes, { prefix: '/api/cards' }); - // Public routes: standardise on `/api/u` (remove duplicate `/api/public`). await app.register(publicRoutes, { prefix: '/api/u' }); await app.register(followRoutes, { prefix: '/api/follow' }); await app.register(connectRoutes, { prefix: '/api/connect' }); await app.register(analyticsRoutes, { prefix: '/api/analytics' }); - await app.register(nfcRoutes, { prefix: '/api/nfc' }); - await app.register(eventRoutes, {prefix: '/api/events'}) - await app.register(teamRoutes, {prefix: '/api/teams'}) - // ─── Health Check ─── -type HealthResponse = { - status: 'ok'; -}; - -app.get('/health', async (): Promise => { - return { status: 'ok' }; -}); + app.get('/health', async () => ({ + status: 'ok', + timestamp: new Date().toISOString(), + service: 'devcard-api', + })); - // Centralized error handler: log and return a consistent 500 shape for unhandled errors. - app.setErrorHandler((error, request, reply) => { - app.log.error({ err: error }, 'Unhandled error'); - // Also print to console to aid test diagnostics when logger is disabled. - // This helps surface stack traces in CI/test runs. - // eslint-disable-next-line no-console - console.error(error); - // If headers were already sent, fall back to default behaviour. - if (reply.sent) { - return; - } - // Keep response shape consistent across the API. - reply.status(500).send({ error: 'Internal server error' }); - }); return app; } diff --git a/apps/backend/src/env.ts b/apps/backend/src/env.ts index 7d841d9c..5902853d 100644 --- a/apps/backend/src/env.ts +++ b/apps/backend/src/env.ts @@ -8,9 +8,8 @@ const envPath = path.resolve(__dirname, '../../../.env'); const result = dotenv.config({ path: envPath }); if (result.error) { - // Keep failing fast but avoid leaking via console in production code paths. - // This file runs before the Fastify logger is available; throw so the process exits. - throw result.error; + console.error('❌ Failed to load .env from:', envPath); + console.error(result.error); } else { - // .env loaded successfully + console.log('✅ Loaded .env from:', envPath); } diff --git a/apps/backend/src/plugins/prisma.ts b/apps/backend/src/plugins/prisma.ts index f6ebede8..98e7f798 100644 --- a/apps/backend/src/plugins/prisma.ts +++ b/apps/backend/src/plugins/prisma.ts @@ -1,14 +1,10 @@ import fp from 'fastify-plugin'; import { PrismaClient } from '@prisma/client'; -import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; +import type { FastifyInstance } from 'fastify'; declare module 'fastify' { interface FastifyInstance { prisma: PrismaClient; - authenticate( - request: FastifyRequest, - reply: FastifyReply - ): Promise; } } diff --git a/apps/backend/src/plugins/redis.ts b/apps/backend/src/plugins/redis.ts index 864b112f..c7b6f94d 100644 --- a/apps/backend/src/plugins/redis.ts +++ b/apps/backend/src/plugins/redis.ts @@ -17,7 +17,7 @@ export const redisPlugin = fp(async (app: FastifyInstance) => { try { await redis.connect(); app.log.info('🔴 Redis connected'); - } catch (error) { + } catch (err) { app.log.warn('⚠️ Redis connection failed — running without cache'); } diff --git a/apps/backend/src/routes/analytics.ts b/apps/backend/src/routes/analytics.ts index a975424f..e9a75bb9 100644 --- a/apps/backend/src/routes/analytics.ts +++ b/apps/backend/src/routes/analytics.ts @@ -1,162 +1,101 @@ -import type { - FastifyInstance, - FastifyRequest, - FastifyReply, -} from 'fastify'; - -export async function analyticsRoutes( - app: FastifyInstance -): Promise { - - app.get( - '/overview', - { - // eslint-disable-next-line @typescript-eslint/unbound-method - preHandler: [async (request, reply) => { const server = request.server as any; if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } }], - }, - async ( - request: FastifyRequest, - _reply: FastifyReply - ) => { - const userId = (request.user as any).id; - const username = (request.user as any).username; - - const today = new Date(); - today.setHours(0, 0, 0, 0); - - const [totalViews, viewsToday, totalFollows, recentViews] = await Promise.all([ - // Total views of this user's cards/profile - app.prisma.cardView.count({ - where: { ownerId: userId }, - }), - - // Views today - app.prisma.cardView.count({ - where: { - ownerId: userId, - createdAt: { gte: today }, +import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; + +export async function analyticsRoutes(app: FastifyInstance) { + + app.get('/overview', { + preHandler: [app.authenticate], + }, async (request: FastifyRequest, reply: FastifyReply) => { + const userId = (request.user as any).id; + + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const [totalViews, viewsToday, totalFollows, recentViews] = await Promise.all([ + // Total views of this user's cards/profile + app.prisma.cardView.count({ + where: { ownerId: userId }, + }), + // Views today + app.prisma.cardView.count({ + where: { ownerId: userId, createdAt: { gte: today } }, + }), + // Follows performed BY this user + app.prisma.followLog.count({ + where: { followerId: userId, status: 'success' }, + }), + // Recent views (last 5) + app.prisma.cardView.findMany({ + where: { ownerId: userId }, + orderBy: { createdAt: 'desc' }, + take: 5, + include: { + viewer: { + select: { displayName: true, avatarUrl: true }, }, - }), - - // Follows performed BY this user - app.prisma.followLog.count({ - where: { - targetUsername: username, - status: 'success', + card: { + select: { title: true }, }, - }), - - // Recent views (last 5) - app.prisma.cardView.findMany({ - where: { ownerId: userId }, - orderBy: { createdAt: 'desc' }, - take: 5, - include: { - viewer: { - select: { - displayName: true, - avatarUrl: true, - }, - }, - card: { - select: { - title: true, - }, - }, - }, - }), - ]); - - // Count unique viewers - // In raw SQL this is `SELECT COUNT(DISTINCT viewer_id) FROM card_views WHERE owner_id = ?` - // Prisma group-by as workaround: - const uniqueViewersQuery = - await app.prisma.cardView.groupBy({ - by: ['viewerId', 'viewerIp'], - where: { ownerId: userId }, - }); - - const uniqueViewers = uniqueViewersQuery.length; - - return { - totalViews, - viewsToday, - totalFollows, - uniqueViewers, - recentViews, - }; - } - ); - - app.get<{ - Querystring: { - page?: string; - cardId?: string; + }, + }), + ]); + + // Count unique viewers + // In raw SQL this is `SELECT COUNT(DISTINCT viewer_id) FROM card_views WHERE owner_id = ?` + // Prisma group-by as workaround: + const uniqueViewersQuery = await app.prisma.cardView.groupBy({ + by: ['viewerId', 'viewerIp'], + where: { ownerId: userId }, + }); + const uniqueViewers = uniqueViewersQuery.length; + + return { + totalViews, + viewsToday, + totalFollows, + uniqueViewers, + recentViews, }; - }>( - '/views', - { - // eslint-disable-next-line @typescript-eslint/unbound-method - preHandler: [async (request, reply) => { const server = request.server as any; if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } }], - }, - async ( - request: FastifyRequest<{ - Querystring: { - page?: string; - cardId?: string; - }; - }>, - _reply: FastifyReply - ) => { - const userId = (request.user as any).id; - const page = parseInt(request.query.page || '1', 10); - const limit = 20; - const skip = (page - 1) * limit; - - const whereClause: any = { ownerId: userId }; - - if (request.query.cardId) { - whereClause.cardId = request.query.cardId; - } - - const [total, views] = await Promise.all([ - app.prisma.cardView.count({ - where: whereClause, - }), + }); + + app.get('/views', { + preHandler: [app.authenticate], + }, async (request: FastifyRequest<{ Querystring: { page?: string, cardId?: string } }>, reply: FastifyReply) => { + const userId = (request.user as any).id; + const page = parseInt(request.query.page || '1', 10); + const limit = 20; + const skip = (page - 1) * limit; + + const whereClause: any = { ownerId: userId }; + if (request.query.cardId) { + whereClause.cardId = request.query.cardId; + } - app.prisma.cardView.findMany({ - where: whereClause, - orderBy: { createdAt: 'desc' }, - skip, - take: limit, - include: { - viewer: { - select: { - id: true, - username: true, - displayName: true, - avatarUrl: true, - }, - }, - card: { - select: { - id: true, - title: true, - }, - }, + const [total, views] = await Promise.all([ + app.prisma.cardView.count({ where: whereClause }), + app.prisma.cardView.findMany({ + where: whereClause, + orderBy: { createdAt: 'desc' }, + skip, + take: limit, + include: { + viewer: { + select: { id: true, username: true, displayName: true, avatarUrl: true }, + }, + card: { + select: { id: true, title: true }, }, - }), - ]); - - return { - data: views, - meta: { - total, - page, - limit, - totalPages: Math.ceil(total / limit), }, - }; - } - ); -} \ No newline at end of file + }), + ]); + + return { + data: views, + meta: { + total, + page, + limit, + totalPages: Math.ceil(total / limit), + }, + }; + }); +} diff --git a/apps/backend/src/routes/auth.ts b/apps/backend/src/routes/auth.ts index c14949e1..e12f10af 100644 --- a/apps/backend/src/routes/auth.ts +++ b/apps/backend/src/routes/auth.ts @@ -1,6 +1,4 @@ import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; -import { encrypt } from '../utils/encryption.js'; -import { buildOAuthState, getMobileRedirectUri } from '../services/authService.js'; const GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize'; const GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token'; @@ -15,32 +13,12 @@ interface OAuthCallbackQuery { } export async function authRoutes(app: FastifyInstance) { - // Developer login bypass (development only) - if (process.env.NODE_ENV !== 'production') { - app.post('/dev-login', async (request: FastifyRequest, reply: FastifyReply) => { - const user = await app.prisma.user.findUnique({ where: { username: 'devcard-demo' } }); - if (!user) { - return reply.status(404).send({ error: 'Demo user not seeded' }); - } - const token = app.jwt.sign({ id: user.id, username: user.username }, { expiresIn: '30d' }); - return { token }; - }); - } + // ─── GitHub OAuth ─── - // GitHub OAuth start app.get('/github', async (request: FastifyRequest, reply: FastifyReply) => { const redirectUri = `${process.env.BACKEND_URL}/auth/github/callback`; const clientState = (request.query as any).state || ''; - const mobileRedirectUri = (request.query as any).mobile_redirect_uri || ''; - const state = buildOAuthState(clientState, mobileRedirectUri); - - reply.setCookie('oauth_state', state, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'lax', - path: '/', - maxAge: 10 * 60, - }); + const state = clientState ? `${clientState}_${generateState()}` : generateState(); const params = new URLSearchParams({ client_id: (process.env.GITHUB_CLIENT_ID || '').trim(), @@ -48,29 +26,26 @@ export async function authRoutes(app: FastifyInstance) { scope: 'read:user user:email', state, }); - const authUrl = `${GITHUB_AUTH_URL}?${params}`; - app.log.debug({ provider: 'github' }, 'OAuth redirect initiated'); + console.log('--- GITHUB OAUTH REDIRECT ---'); + console.log('URL:', authUrl); return reply.redirect(authUrl); }); - // GitHub OAuth callback app.get('/github/callback', async (request: FastifyRequest<{ Querystring: OAuthCallbackQuery }>, reply: FastifyReply) => { - const { code, state } = request.query; - const storedState = request.cookies?.oauth_state; - if (!state || !storedState || state !== storedState) { - return reply.status(400).send({ error: 'Invalid or missing OAuth state — possible CSRF attack' }); - } - reply.clearCookie('oauth_state', { path: '/' }); - + const { code } = request.query; if (!code) { return reply.status(400).send({ error: 'Missing authorization code' }); } try { + // Exchange code for token const tokenRes = await fetch(GITHUB_TOKEN_URL, { method: 'POST', - headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, body: JSON.stringify({ client_id: (process.env.GITHUB_CLIENT_ID || '').trim(), client_secret: (process.env.GITHUB_CLIENT_SECRET || '').trim(), @@ -78,16 +53,20 @@ export async function authRoutes(app: FastifyInstance) { redirect_uri: `${process.env.BACKEND_URL}/auth/github/callback`, }), }); - const tokenData = (await tokenRes.json()) as any; + if (tokenData.error) { - app.log.error({ tokenData }, 'GitHub token error'); + app.log.error('GitHub token error:', tokenData); return reply.status(400).send({ error: 'Failed to authenticate with GitHub' }); } - const userRes = await fetch(GITHUB_USER_URL, { headers: { Authorization: `Bearer ${tokenData.access_token}` } }); + // Fetch user profile + const userRes = await fetch(GITHUB_USER_URL, { + headers: { Authorization: `Bearer ${tokenData.access_token}` }, + }); const githubUser = (await userRes.json()) as any; + // Fetch email if not public let email = githubUser.email; if (!email) { const emailsRes = await fetch('https://api.github.com/user/emails', { @@ -98,8 +77,14 @@ export async function authRoutes(app: FastifyInstance) { email = primary?.email || emails[0]?.email; } + // Upsert user const user = await app.prisma.user.upsert({ - where: { provider_providerId: { provider: 'github', providerId: String(githubUser.id) } }, + where: { + provider_providerId: { + provider: 'github', + providerId: String(githubUser.id), + }, + }, update: { email: email || `${githubUser.login}@github.local`, displayName: githubUser.name || githubUser.login, @@ -117,53 +102,49 @@ export async function authRoutes(app: FastifyInstance) { }, }); - try { - const encryptedToken = encrypt(tokenData.access_token); - await app.prisma.oAuthToken.upsert({ - where: { userId_platform: { userId: user.id, platform: 'github' } }, - update: { accessToken: encryptedToken, scopes: 'read:user user:email' }, - create: { userId: user.id, platform: 'github', accessToken: encryptedToken, scopes: 'read:user user:email' }, - }); - } catch (err) { - app.log.error({ err, userId: user.id }, 'Failed to persist GitHub OAuth token — authentication proceeds'); - } + // Save the authentication token for 'user:email read:user' so we have a basic platform connection + const encryptedToken = (app as any).encryption ? (app as any).encryption.encrypt(tokenData.access_token) : tokenData.access_token; + + await app.prisma.oAuthToken.upsert({ + where: { userId_platform: { userId: user.id, platform: 'github' } }, + update: { accessToken: encryptedToken, scopes: 'read:user user:email' }, + create: { userId: user.id, platform: 'github', accessToken: encryptedToken, scopes: 'read:user user:email' }, + }); - const token = app.jwt.sign({ id: user.id, username: user.username }, { expiresIn: '30d' }); + // Generate JWT + const token = app.jwt.sign( + { id: user.id, username: user.username }, + { expiresIn: '30d' } + ); + // For mobile app: redirect with token as query param + const mobileRedirect = process.env.MOBILE_REDIRECT_URI; if (request.query.state?.startsWith('mobile_')) { - const mobileRedirect = getMobileRedirectUri(request.query.state) || process.env.MOBILE_REDIRECT_URI; - return reply.redirect(`${mobileRedirect}#token=${token}`); + return reply.redirect(`${mobileRedirect}?token=${token}`); } + // For web: set cookie and redirect reply.setCookie('token', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', - maxAge: 30 * 24 * 60 * 60, + maxAge: 30 * 24 * 60 * 60, // 30 days }); return reply.redirect(`${process.env.PUBLIC_APP_URL}/dashboard`); - } catch (error) { - app.log.error({ error }, 'GitHub auth error'); + } catch (err) { + app.log.error('GitHub auth error:', err); return reply.status(500).send({ error: 'Authentication failed' }); } }); - // Google OAuth start + // ─── Google OAuth ─── + app.get('/google', async (request: FastifyRequest, reply: FastifyReply) => { const redirectUri = `${process.env.BACKEND_URL}/auth/google/callback`; const clientState = (request.query as any).state || ''; - const mobileRedirectUri = (request.query as any).mobile_redirect_uri || ''; - const state = buildOAuthState(clientState, mobileRedirectUri); - - reply.setCookie('oauth_state', state, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'lax', - path: '/', - maxAge: 10 * 60, - }); + const state = clientState ? `${clientState}_${generateState()}` : generateState(); const params = new URLSearchParams({ client_id: (process.env.GOOGLE_CLIENT_ID || '').trim(), @@ -173,22 +154,14 @@ export async function authRoutes(app: FastifyInstance) { state, access_type: 'offline', }); - const authUrl = `${GOOGLE_AUTH_URL}?${params}`; - app.log.debug({ provider: 'google' }, 'OAuth redirect initiated'); + console.log('--- GOOGLE OAUTH REDIRECT ---'); + console.log('URL:', authUrl); return reply.redirect(authUrl); }); - // Google callback app.get('/google/callback', async (request: FastifyRequest<{ Querystring: OAuthCallbackQuery }>, reply: FastifyReply) => { - const { code, state } = request.query; - - const storedState = request.cookies?.oauth_state; - if (!state || !storedState || state !== storedState) { - return reply.status(400).send({ error: 'Invalid or missing OAuth state — possible CSRF attack' }); - } - reply.clearCookie('oauth_state', { path: '/' }); - + const { code } = request.query; if (!code) { return reply.status(400).send({ error: 'Missing authorization code' }); } @@ -205,21 +178,33 @@ export async function authRoutes(app: FastifyInstance) { grant_type: 'authorization_code', }), }); - const tokenData = (await tokenRes.json()) as any; + if (tokenData.error) { - app.log.error({ tokenData }, 'Google token error'); + app.log.error('Google token error:', tokenData); return reply.status(400).send({ error: 'Failed to authenticate with Google' }); } - const userRes = await fetch(GOOGLE_USER_URL, { headers: { Authorization: `Bearer ${tokenData.access_token}` } }); + const userRes = await fetch(GOOGLE_USER_URL, { + headers: { Authorization: `Bearer ${tokenData.access_token}` }, + }); const googleUser = (await userRes.json()) as any; + // Generate username from email const baseUsername = googleUser.email.split('@')[0].replace(/[^a-zA-Z0-9_-]/g, ''); const user = await app.prisma.user.upsert({ - where: { provider_providerId: { provider: 'google', providerId: googleUser.id } }, - update: { email: googleUser.email, displayName: googleUser.name || baseUsername, avatarUrl: googleUser.picture }, + where: { + provider_providerId: { + provider: 'google', + providerId: googleUser.id, + }, + }, + update: { + email: googleUser.email, + displayName: googleUser.name || baseUsername, + avatarUrl: googleUser.picture, + }, create: { email: googleUser.email, username: `${baseUsername}_${Date.now().toString(36)}`, @@ -230,11 +215,14 @@ export async function authRoutes(app: FastifyInstance) { }, }); - const token = app.jwt.sign({ id: user.id, username: user.username }, { expiresIn: '30d' }); + const token = app.jwt.sign( + { id: user.id, username: user.username }, + { expiresIn: '30d' } + ); if (request.query.state?.startsWith('mobile_')) { - const mobileRedirect = getMobileRedirectUri(request.query.state) || process.env.MOBILE_REDIRECT_URI; - return reply.redirect(`${mobileRedirect}#token=${token}`); + const mobileRedirect = process.env.MOBILE_REDIRECT_URI; + return reply.redirect(`${mobileRedirect}?token=${token}`); } reply.setCookie('token', token, { @@ -246,19 +234,17 @@ export async function authRoutes(app: FastifyInstance) { }); return reply.redirect(`${process.env.PUBLIC_APP_URL}/dashboard`); - } catch (error) { - app.log.error({ error }, 'Google auth error'); + } catch (err) { + app.log.error('Google auth error:', err); return reply.status(500).send({ error: 'Authentication failed' }); } }); - // Current user - app.get('/me', { preHandler: [async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } - if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } - try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }] }, async (request: FastifyRequest, reply: FastifyReply) => { + // ─── Current User ─── + + app.get('/me', { + preHandler: [app.authenticate], + }, async (request: FastifyRequest, reply: FastifyReply) => { const userId = (request.user as any).id; const user = await app.prisma.user.findUnique({ where: { id: userId }, @@ -274,7 +260,9 @@ export async function authRoutes(app: FastifyInstance) { avatarUrl: true, accentColor: true, createdAt: true, - oauthTokens: { select: { platform: true, scopes: true, createdAt: true } }, + oauthTokens: { + select: { platform: true, scopes: true, createdAt: true }, + }, }, }); @@ -283,11 +271,21 @@ export async function authRoutes(app: FastifyInstance) { } const { oauthTokens, ...userData } = user; - return { ...userData, connectedPlatforms: oauthTokens }; + + return { + ...userData, + connectedPlatforms: oauthTokens, + }; }); + // ─── Logout ─── + app.post('/logout', async (request: FastifyRequest, reply: FastifyReply) => { reply.clearCookie('token', { path: '/' }); return { message: 'Logged out' }; }); } + +function generateState(): string { + return Math.random().toString(36).substring(2, 15); +} diff --git a/apps/backend/src/routes/cards.ts b/apps/backend/src/routes/cards.ts index 32fe835c..f1af7b00 100644 --- a/apps/backend/src/routes/cards.ts +++ b/apps/backend/src/routes/cards.ts @@ -1,138 +1,178 @@ -import { handleDbError } from '../utils/error.util.js'; -import { createCardSchema, updateCardSchema } from '../utils/validators.js'; -import * as cardService from '../services/cardService' - -import type { Card } from '@devcard/shared'; -import type { Prisma } from '@prisma/client'; import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; +import { createCardSchema, updateCardSchema } from '../utils/validators.js'; - -interface CreateCardBody { - title: string; - linkIds: string[]; -} - -interface UpdateCardBody { - title?: string; - linkIds?: string[]; -} - -interface CardParams { - id: string; -} - -interface PlatformLink { - id: string; - userId: string; - platform: string; - username: string; - url: string; - displayOrder: number; - createdAt: Date; -} - -interface CardLinkWithPlatform { - id: string; - cardId: string; - platformLinkId: string; - displayOrder: number; - platformLink: PlatformLink; -} - -interface CardWithLinks { - id: string; - userId: string; - title: string; - isDefault: boolean; - createdAt: Date; - updatedAt: Date; - cardLinks: CardLinkWithPlatform[]; -} - -export async function cardRoutes(app: FastifyInstance): Promise { - app.addHook('preHandler', async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } - if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } - try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }); +export async function cardRoutes(app: FastifyInstance) { + app.addHook('preHandler', app.authenticate); // ─── List Cards ─── - app.get('/', async (request: FastifyRequest, reply: FastifyReply): Promise => { - const userId = (request.user as { id: string }).id; - try { - return await cardService.listCards(app, userId) - } catch (error) { - return handleDbError(error, request, reply) - } + app.get('/', async (request: FastifyRequest, reply: FastifyReply) => { + const userId = (request.user as any).id; + + const cards = await app.prisma.card.findMany({ + where: { userId }, + include: { + cardLinks: { + include: { platformLink: true }, + orderBy: { displayOrder: 'asc' }, + }, + }, + orderBy: { createdAt: 'asc' }, + }); + + return cards.map((card) => ({ + id: card.id, + title: card.title, + isDefault: card.isDefault, + links: card.cardLinks.map((cl) => cl.platformLink), + })); }); // ─── Create Card ─── - app.post('/', async (request: FastifyRequest<{ Body: CreateCardBody }>, reply: FastifyReply): Promise => { - const userId = (request.user as { id: string }).id; + app.post('/', async (request: FastifyRequest, reply: FastifyReply) => { + const userId = (request.user as any).id; const parsed = createCardSchema.safeParse(request.body); if (!parsed.success) { return reply.status(400).send({ error: 'Validation failed', details: parsed.error.flatten() }); } - try { - const card = await cardService.createCard(app, userId, parsed.data) - return reply.status(201).send(card) - } catch (error: any) { - if (error?.code === 'OWNERSHIP') return reply.status(403).send({ error: 'One or more links do not belong to your account' }) - return handleDbError(error, request, reply) - } + // Check if user's first card → make it default + const cardCount = await app.prisma.card.count({ where: { userId } }); + + const card = await app.prisma.card.create({ + data: { + userId, + title: parsed.data.title, + isDefault: cardCount === 0, + cardLinks: { + create: parsed.data.linkIds.map((linkId, index) => ({ + platformLinkId: linkId, + displayOrder: index, + })), + }, + }, + include: { + cardLinks: { + include: { platformLink: true }, + orderBy: { displayOrder: 'asc' }, + }, + }, + }); + + return reply.status(201).send({ + id: card.id, + title: card.title, + isDefault: card.isDefault, + links: card.cardLinks.map((cl) => cl.platformLink), + }); }); // ─── Update Card ─── - app.put('/:id', async (request: FastifyRequest<{ Params: CardParams; Body: UpdateCardBody }>, reply: FastifyReply): Promise => { - const userId = (request.user as { id: string }).id; + app.put('/:id', async (request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => { + const userId = (request.user as any).id; const { id } = request.params; - try { - const parsed = updateCardSchema.safeParse(request.body) - if (!parsed.success) return reply.status(400).send({ error: 'Validation failed', details: parsed.error.flatten() }) - const updated = await cardService.updateCard(app, userId, id, parsed.data) - if (!updated) return reply.status(404).send({ error: 'Card not found' }) - return updated - } catch (error: any) { - if (error?.code === 'OWNERSHIP') return reply.status(403).send({ error: 'One or more links do not belong to your account' }) - return handleDbError(error, request, reply) + const existing = await app.prisma.card.findFirst({ + where: { id, userId }, + }); + + if (!existing) { + return reply.status(404).send({ error: 'Card not found' }); } + + const parsed = updateCardSchema.safeParse(request.body); + if (!parsed.success) { + return reply.status(400).send({ error: 'Validation failed', details: parsed.error.flatten() }); + } + + // Update card title + if (parsed.data.title) { + await app.prisma.card.update({ + where: { id }, + data: { title: parsed.data.title }, + }); + } + + // Update card links if provided + if (parsed.data.linkIds) { + // Remove existing links + await app.prisma.cardLink.deleteMany({ where: { cardId: id } }); + // Add new links + await app.prisma.cardLink.createMany({ + data: parsed.data.linkIds.map((linkId, index) => ({ + cardId: id, + platformLinkId: linkId, + displayOrder: index, + })), + }); + } + + // Fetch updated card + const updated = await app.prisma.card.findUnique({ + where: { id }, + include: { + cardLinks: { + include: { platformLink: true }, + orderBy: { displayOrder: 'asc' }, + }, + }, + }); + + return { + id: updated!.id, + title: updated!.title, + isDefault: updated!.isDefault, + links: updated!.cardLinks.map((cl) => cl.platformLink), + }; }); // ─── Delete Card ─── - app.delete('/:id', async (request: FastifyRequest<{ Params: CardParams }>, reply: FastifyReply): Promise => { - const userId = (request.user as { id: string }).id; + app.delete('/:id', async (request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => { + const userId = (request.user as any).id; const { id } = request.params; - try { - const res = await cardService.deleteCard(app, userId, id) - if (res && (res as any).code === 'NOT_FOUND') return reply.status(404).send({ error: 'Card not found' }) - if (res && (res as any).code === 'LAST_CARD') return reply.status(400).send({ error: 'Cannot delete the last remaining card. A user must have at least one card.' }) - return reply.status(204).send() - } catch (error) { - return handleDbError(error, request, reply) + const existing = await app.prisma.card.findFirst({ + where: { id, userId }, + }); + + if (!existing) { + return reply.status(404).send({ error: 'Card not found' }); } + + await app.prisma.card.delete({ where: { id } }); + return reply.status(204).send(); }); // ─── Set Default Card ─── - app.put('/:id/default', async (request: FastifyRequest<{ Params: CardParams }>, reply: FastifyReply): Promise => { - const userId = (request.user as { id: string }).id; + app.put('/:id/default', async (request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => { + const userId = (request.user as any).id; const { id } = request.params; - try { - const resp = await cardService.setDefaultCard(app, userId, id) - if (!resp) return reply.status(404).send({ error: 'Card not found' }) - return resp - } catch (error) { - return handleDbError(error, request, reply) + const existing = await app.prisma.card.findFirst({ + where: { id, userId }, + }); + + if (!existing) { + return reply.status(404).send({ error: 'Card not found' }); } + + // Unset all other defaults + await app.prisma.card.updateMany({ + where: { userId }, + data: { isDefault: false }, + }); + + // Set this one + await app.prisma.card.update({ + where: { id }, + data: { isDefault: true }, + }); + + return { message: 'Default card updated' }; }); } diff --git a/apps/backend/src/routes/connect.ts b/apps/backend/src/routes/connect.ts index bb04194d..952e8453 100644 --- a/apps/backend/src/routes/connect.ts +++ b/apps/backend/src/routes/connect.ts @@ -1,17 +1,8 @@ import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; -import { randomBytes } from 'crypto'; -import { encrypt } from '../utils/encryption.js'; const GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize'; const GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token'; -// Follow-capable tokens are stored under a dedicated platform key so that -// the authentication flow (read:user user:email scope, key = 'github') and -// the connect flow (user:follow scope, key = 'github_follow') never share -// the same OAuthToken record. Whichever flow runs last can no longer -// silently overwrite the other's access token. -const GITHUB_FOLLOW_PLATFORM = 'github_follow'; - interface OAuthCallbackQuery { code: string; state?: string; @@ -26,12 +17,7 @@ export async function connectRoutes(app: FastifyInstance) { // ─── Status ─── app.get('/status', { - preHandler: [async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } - if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } - try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }], + preHandler: [app.authenticate], }, async (request: FastifyRequest, reply: FastifyReply) => { const userId = (request.user as any).id; @@ -46,32 +32,20 @@ export async function connectRoutes(app: FastifyInstance) { // ─── GitHub Connect ─── app.get('/github', { - preHandler: [async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } - if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } - try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }], + preHandler: [app.authenticate], }, async (request: FastifyRequest, reply: FastifyReply) => { - const userId = (request.user as any).id; - const nonce = generateState(); - - // Store nonce in Redis with 10-minute TTL. - // The callback verifies this to prevent CSRF attacks. - await app.redis.set( - `oauth:nonce:${nonce}`, - userId, - 'EX', - 600 - ); - - const state = JSON.stringify({ userId, nonce }); + // Generate a secure state token linking back to this user session + // In a real app, store this in Redis to cross-check in callback + const state = JSON.stringify({ + userId: (request.user as any).id, + nonce: generateState(), + }); const redirectUri = `${process.env.BACKEND_URL}/api/connect/github/callback`; const params = new URLSearchParams({ client_id: process.env.GITHUB_CLIENT_ID || '', redirect_uri: redirectUri, - scope: 'user:follow', + scope: 'user:follow', // ONLY asking for follow scope to avoid full profile access state: Buffer.from(state).toString('base64'), }); @@ -87,25 +61,17 @@ export async function connectRoutes(app: FastifyInstance) { try { // Decode state to find which user requested the connect - const decodedState = parseOAuthState(state); + const decodedState = parseGoogleState(state); if (!decodedState) { return reply.redirect(`${process.env.PUBLIC_APP_URL}/settings?error=connect_failed`); } + const userId = decodedState.userId; - // Verify nonce was issued by this server -- prevents CSRF - const storedUserId = app.redis ? await app.redis.get(`oauth:nonce:${decodedState.nonce}`) : null; - - if (app.redis && (!storedUserId || storedUserId !== decodedState.userId)) { - app.log.warn({ nonce: decodedState.nonce }, 'OAuth CSRF check failed: nonce mismatch'); + if (!userId) { return reply.redirect(`${process.env.PUBLIC_APP_URL}/settings?error=invalid_state`); } - // Consume the nonce -- one-time use only (if redis configured) - if (app.redis) await app.redis.del(`oauth:nonce:${decodedState.nonce}`); - - const userId = decodedState.userId; - // Exchange code for token const tokenRes = await fetch(GITHUB_TOKEN_URL, { method: 'POST', @@ -128,16 +94,14 @@ export async function connectRoutes(app: FastifyInstance) { return reply.redirect(`${process.env.PUBLIC_APP_URL}/settings?error=connect_failed`); } - // Encrypt and store the token under the dedicated follow-scope key so - // that a subsequent login (which writes to 'github') cannot overwrite - // this follow-capable credential. - const encryptedToken = encrypt(tokenData.access_token); + // Encrypt and store the token + const encryptedToken = app.encryption.encrypt(tokenData.access_token); await app.prisma.oAuthToken.upsert({ where: { userId_platform: { userId, - platform: GITHUB_FOLLOW_PLATFORM, + platform: 'github', }, }, update: { @@ -146,7 +110,7 @@ export async function connectRoutes(app: FastifyInstance) { }, create: { userId, - platform: GITHUB_FOLLOW_PLATFORM, + platform: 'github', accessToken: encryptedToken, scopes: tokenData.scope || 'user:follow', }, @@ -160,9 +124,8 @@ export async function connectRoutes(app: FastifyInstance) { return reply.redirect(`${process.env.PUBLIC_APP_URL}/settings?connected=github`); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - app.log.error({ error, message }, 'GitHub connect error'); + } catch (err) { + app.log.error('GitHub connect error:', err); return reply.redirect(`${process.env.PUBLIC_APP_URL}/settings?error=server_error`); } }); @@ -171,21 +134,11 @@ export async function connectRoutes(app: FastifyInstance) { // ─── Disconnect ─── app.delete('/:platform', { - preHandler: [async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } - if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } - try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }], + preHandler: [app.authenticate], }, async (request: FastifyRequest<{ Params: { platform: string } }>, reply: FastifyReply) => { const userId = (request.user as any).id; const { platform } = request.params; - const SUPPORTED_PLATFORMS = ['github', 'google', 'twitter', 'linkedin']; - if (!SUPPORTED_PLATFORMS.includes(platform)) { - return reply.status(400).send({ error: `Unsupported platform: ${platform}` }); - } - try { await app.prisma.oAuthToken.delete({ where: { @@ -196,13 +149,13 @@ export async function connectRoutes(app: FastifyInstance) { }, }); return { success: true }; - } catch (error) { + } catch (err) { return reply.status(404).send({ error: 'Connection not found' }); } }); } -function parseOAuthState(state: string): ParsedOAuthState | null { +function parseGoogleState(state: string): ParsedOAuthState | null { try { const decoded = JSON.parse(Buffer.from(state, 'base64').toString('utf-8')); @@ -217,5 +170,5 @@ function parseOAuthState(state: string): ParsedOAuthState | null { } function generateState(): string { - return randomBytes(32).toString('hex'); + return Math.random().toString(36).substring(2, 15); } diff --git a/apps/backend/src/routes/event.ts b/apps/backend/src/routes/event.ts deleted file mode 100644 index 4d4ee2d9..00000000 --- a/apps/backend/src/routes/event.ts +++ /dev/null @@ -1,285 +0,0 @@ -import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; -import { createEventSchema, joinEventSchema} from '../validations/event.validation'; - -import {generateUniqueSlug} from '../utils/slug' - - -type EventDetails = { - id: string; - name: string; - slug: string; - location: string; - description: string | null; - organizerUsername: string; - organizerDisplayName: string; - startDate: Date; - endDate: Date; - createdAt: Date; - attendeesCount: number -} - -type AttendeePublicProfile = { - id: string; - username: string; - displayName: string; - bio: string | null; - pronouns: string | null; - company: string | null; - avatarUrl: string | null; - accentColor: string; -} - - -type PaginatedAttendeesResponse = { - attendees: AttendeePublicProfile[]; - pagination: { - page: number; - limit: number; - total: number; - }; -} - -type EventWithAttendees = { - _count: { - attendees: number; - }; - attendees: { - user: { - id: string; - username: string; - displayName: string; - bio: string | null; - pronouns: string | null; - company: string | null; - avatarUrl: string | null; - accentColor: string; - }; - }[]; -} - -export async function eventRoutes(app:FastifyInstance) { - app.post('/', { preHandler: [async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } - if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } - try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }] }, async (request: FastifyRequest<{ - Body: { - name: string, - description?: string, - startDate: string, - location: string, - endDate: string, - isPublic?: boolean - }}>, reply: FastifyReply) => { - const userId = (request.user as any).id; - const parsed = createEventSchema.safeParse(request.body); - if(!parsed.success){ - return reply.status(400).send({error: 'Bad request'}) - } - - const {name, description, startDate, endDate, isPublic ,location} = parsed.data - - let finalSlug = await generateUniqueSlug(name, async(slug) => { - const existing = await app.prisma.event.findUnique({where: {slug : slug}}) - - return !!existing - }) - - const startDateObj = new Date(startDate); - const endDateObj = new Date(endDate); - - try { - const newEvent = await app.prisma.event.create({ - data: { - name, - description, - slug: finalSlug, - location: location, - startDate: startDateObj, - endDate: endDateObj, - isPublic: isPublic ?? true, - organizerId: userId - } - }) - - return reply.status(201).send(newEvent); - } catch (error) { - app.log.error('Failed to create event'); - return reply.status(500).send({error: 'Failed to create event'}) - } - - }) - - //Returns event details and attendees count - app.get('/:slug', async(request: FastifyRequest<{Params: {slug: string}}>, reply: FastifyReply) => { - const paramsSlug = request.params.slug; - const details = await app.prisma.event.findUnique({ - where: { - slug: paramsSlug, - }, - include: { - _count: { - select: { - attendees: true - } - }, - organizer: { - select: { - username: true, - displayName: true - } - } - } - }) - if(!details){ - return reply.status(404).send({error: 'Event not found'}) - } - - const response: EventDetails = { - id: details.id, - name: details.name, - slug: details.slug, - description: details.description, - location: details.location, - organizerUsername: details.organizer.username, - organizerDisplayName: details.organizer.displayName, - startDate: details.startDate, - endDate: details.endDate, - createdAt: details.createdAt, - attendeesCount: details._count.attendees - } - - return response; - }) - - app.post('/:slug/join', { preHandler: [async (request, reply) => { const server = request.server as any; if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } }] }, async(request: FastifyRequest<{Params: {slug: string}}>, reply: FastifyReply) => { - const userId = (request.user as any).id; - const paramsSlug = request.params.slug; - - const event = await app.prisma.event.findUnique({ - where: { - slug: paramsSlug - } - }) - - if(!event){ - return reply.status(404).send({error: 'Event not found'}) - } - - try { - await app.prisma.eventAttendee.create({ - data: { - eventId: event.id, - userId: userId, - joinedAt: new Date() - } - }) - - return reply.status(201).send({message: 'User joined successfully'}) - } catch (error:any) { - if(error.code === "P2002" ){ - return reply.status(409).send({error: 'Already joined'}) - } - app.log.error((error as Error).message); - return reply.status(500).send({error: 'Failed to join'}) - } - - }) - - app.delete('/:slug/leave', { preHandler: [async (request, reply) => { const server = request.server as any; if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } }] }, async(request: FastifyRequest<{Params: {slug: string}}>, reply: FastifyReply) => { - const userId = (request.user as any).id; - const paramsSlug = request.params.slug; - - const event = await app.prisma.event.findUnique({ - where: { - slug: paramsSlug - } - }) - - if(!event){ - return reply.status(404).send({error: 'Event not found'}) - } - - try { - await app.prisma.eventAttendee.delete({ - where: { - userId_eventId: { - userId: userId, - eventId: event.id - } - } - }) - return reply.status(204).send({message: 'User left'}) - } catch (error:any) { - if(error.code === 'P2025'){ - return reply.status(404).send({error: 'User not found'}) - } - app.log.error((error as Error).message) - return reply.status(500).send({error: 'Failed to leave'}) - } - }) - - app.get('/:slug/attendees', async(request: FastifyRequest<{Params: {slug: string}, Querystring: {page?:string; limit?: string}}>, reply: FastifyReply) => { - const paramsSlug = request.params.slug; - const page = Math.max(1, Number(request.query.page) || 1); - const limit = Math.min(50, Number(request.query.limit) || 10); - const skip = (page - 1) * limit - const event = await app.prisma.event.findUnique({ - where: { - slug: paramsSlug - }, - include: { - _count: { - select: { attendees: true } - }, - attendees : { - include: { - user: { - select: { - id: true, - username: true, - displayName:true, - bio: true, - pronouns: true, - company: true, - avatarUrl: true, - accentColor: true - } - } - }, - skip, - take: limit, - orderBy: {joinedAt: 'desc'} - } - }, - })as EventWithAttendees | null; - - if(!event){ - return reply.status(404).send({error: 'Event not found'}) - } - - - const attendees = event.attendees.map((attendee: EventWithAttendees['attendees'][number]) => ({ - id: attendee.user.id, - username: attendee.user.username, - displayName: attendee.user.displayName, - bio: attendee.user.bio, - pronouns: attendee.user.pronouns, - company: attendee.user.company, - avatarUrl: attendee.user.avatarUrl, - accentColor: attendee.user.accentColor, - })); - - const response: PaginatedAttendeesResponse = { - attendees, - pagination: { - page, - limit, - total : event._count.attendees, - } - } - - return response; - }) -} \ No newline at end of file diff --git a/apps/backend/src/routes/follow.ts b/apps/backend/src/routes/follow.ts index a152fc55..aabc85b6 100644 --- a/apps/backend/src/routes/follow.ts +++ b/apps/backend/src/routes/follow.ts @@ -1,16 +1,8 @@ import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { decrypt } from '../utils/encryption.js'; -import { getErrorMessage } from '../utils/error.util.js'; -import { getPlatform, getProfileUrl, getWebViewUrl } from '@devcard/shared'; -import { followLogSchema } from '../validations/follow.validation.js'; export async function followRoutes(app: FastifyInstance) { - app.addHook('preHandler', async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } - if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } - try { const payload = await request.jwtVerify(); if (payload) (request as any).user = payload; } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }); + app.addHook('preHandler', app.authenticate); // ─── Follow via API (Layer 1) ─── // Currently supports: GitHub @@ -22,29 +14,13 @@ export async function followRoutes(app: FastifyInstance) { const userId = (request.user as any).id; const { platform, targetUsername } = request.params; - // GitHub follow tokens are stored under 'github_follow' to prevent the - // authentication flow (which writes to 'github') from silently overwriting - // the follow-capable credential. All other platforms use their plain name. - const tokenPlatform = platform === 'github' ? 'github_follow' : platform; - - // Get stored OAuth token for this platform (do this up-front so tests - // that inspect DB calls see the lookup regardless of follow strategy). + // Get stored OAuth token for this platform const oauthToken = await app.prisma.oAuthToken.findUnique({ where: { - userId_platform: { userId, platform: tokenPlatform }, + userId_platform: { userId, platform }, }, }); - // Use WebView follow strategy if configured for the platform (e.g. LinkedIn, Twitter/X) - const platformDef = getPlatform(platform); - if (platformDef?.followStrategy === 'webview') { - const url = getWebViewUrl(platform, targetUsername) || getProfileUrl(platform, targetUsername); - return reply.send({ - strategy: 'webview', - url, - }); - } - if (!oauthToken) { return reply.status(400).send({ error: `Not connected to ${platform}. Please connect your ${platform} account first.`, @@ -57,12 +33,9 @@ export async function followRoutes(app: FastifyInstance) { try { let result; - let succeeded = false; - switch (platform) { case 'github': result = await followGitHub(accessToken, targetUsername, reply); - succeeded = result.success === true; break; default: return reply.status(400).send({ @@ -70,8 +43,8 @@ export async function followRoutes(app: FastifyInstance) { }); } - // Log only genuine successes — not based on reply.statusCode default - if (succeeded) { + // If follow succeeded (or was handled by the function without throwing), log it + if (reply.statusCode === 200 || reply.statusCode === 204) { app.prisma.followLog.create({ data: { followerId: userId, @@ -80,12 +53,12 @@ export async function followRoutes(app: FastifyInstance) { status: 'success', layer: 'api', }, - }).catch((err: unknown) => app.log.error(`Failed to log follow: ${getErrorMessage(err)}`)); + }).catch(err => app.log.error('Failed to log follow:', err)); } - return result.response; - } catch (err: unknown) { - app.log.error(`Follow error for ${platform}: ${getErrorMessage(err)}`); + return result; + } catch (err: any) { + app.log.error(`Follow error for ${platform}:`, err); app.prisma.followLog.create({ data: { @@ -95,72 +68,11 @@ export async function followRoutes(app: FastifyInstance) { status: 'error', layer: 'api', }, - }).catch((e: unknown) => app.log.error(`Failed to log follow error: ${getErrorMessage(e)}`)); + }).catch(e => app.log.error('Failed to log follow error:', e)); - return reply.status(500).send({ - error: 'Follow action failed', - message: getErrorMessage(err), - }); + return reply.status(500).send({ error: 'Follow action failed', message: err.message }); } }); - - // Log follow/connect event for Layer 2/3/4 strategies (WebView, deep-link, etc.) - // - // status and layer are analytics-impacting fields: they drive totalFollows counters - // and the follower-state dashboard. Both are validated against a strict allowlist - // before any database write — arbitrary client values are rejected with 400. - app.post('/:platform/:targetUsername/log', async ( - request: FastifyRequest<{ - Params: { platform: string; targetUsername: string }; - Body: { status?: string; layer?: string }; - }>, - reply: FastifyReply - ) => { - const userId = (request.user as any).id; - const { platform, targetUsername } = request.params; - - const parsed = followLogSchema.safeParse(request.body); - if (!parsed.success) { - return reply.status(400).send({ error: 'Invalid follow log payload' }); - } - - const { status, layer } = parsed.data; - - try { - const log = await app.prisma.followLog.create({ - data: { - followerId: userId, - targetUsername, - platform, - status, - layer, - }, - }); - return reply.send({ status: 'success', logId: log.id }); - } catch (error: any) { - app.log.error('Failed to log follow:', error); - return reply.status(500).send({ error: 'Failed to log follow event' }); - } - }); - - // ─── Clear follow log (reset Done state) ─── - app.delete('/:platform/:targetUsername/log', async ( - request: FastifyRequest<{ Params: { platform: string; targetUsername: string } }>, - reply: FastifyReply - ) => { - const userId = (request.user as any).id; - const { platform, targetUsername } = request.params; - - await app.prisma.followLog.deleteMany({ - where: { - followerId: userId, - platform, - targetUsername, - }, - }); - - return reply.send({ status: 'cleared' }); - }); } // ─── GitHub Follow (Layer 1) ─── @@ -169,7 +81,7 @@ async function followGitHub( accessToken: string, targetUsername: string, reply: FastifyReply -): Promise<{ success: boolean; response: FastifyReply }> { +) { const response = await fetch(`https://api.github.com/user/following/${targetUsername}`, { method: 'PUT', headers: { @@ -180,42 +92,30 @@ async function followGitHub( }); if (response.status === 204) { - return { - success: true, - response: reply.send({ - status: 'success', - platform: 'github', - targetUsername, - message: `Now following ${targetUsername} on GitHub`, - }), - }; + return reply.send({ + status: 'success', + platform: 'github', + targetUsername, + message: `Now following ${targetUsername} on GitHub`, + }); } if (response.status === 401 || response.status === 403) { - return { - success: false, - response: reply.status(401).send({ - error: 'GitHub token expired or insufficient permissions', - requiresAuth: true, - }), - }; + return reply.status(401).send({ + error: 'GitHub token expired or insufficient permissions', + requiresAuth: true, + }); } if (response.status === 404) { - return { - success: false, - response: reply.status(404).send({ - error: `GitHub user '${targetUsername}' not found`, - }), - }; + return reply.status(404).send({ + error: `GitHub user '${targetUsername}' not found`, + }); } const errorBody = await response.text(); - return { - success: false, - response: reply.status(response.status).send({ - error: 'GitHub follow failed', - details: errorBody, - }), - }; -} \ No newline at end of file + return reply.status(response.status).send({ + error: 'GitHub follow failed', + details: errorBody, + }); +} diff --git a/apps/backend/src/routes/nfc.ts b/apps/backend/src/routes/nfc.ts deleted file mode 100644 index 5cf13f0c..00000000 --- a/apps/backend/src/routes/nfc.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; -import { z } from 'zod'; - -type NfcPayloadResponse = { - type: 'URI'; - payload: string; -}; - -const nfcQuerySchema = z.object({ - card: z.string().uuid('Invalid card ID format').optional(), -}); - -export async function nfcRoutes(app: FastifyInstance) { - app.addHook('preHandler', async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { - await server.authenticate(request, reply); - return; - } - if (typeof (app as any).authenticate === 'function') { - await (app as any).authenticate(request, reply); - return; - } - try { - await request.jwtVerify(); - } catch (e) { - reply.status(401).send({ error: 'Unauthorized' }); - } - }); - - // GET /api/nfc/payload — returns NDEF URI payload for user's default DevCard URL - // GET /api/nfc/payload?card= — returns payload for a specific card - app.get( - '/payload', - async ( - request: FastifyRequest<{ Querystring: { card?: string } }>, - reply: FastifyReply - ) => { - const userId = (request.user as any).id; - - // Validate query params with Zod - const parseResult = nfcQuerySchema.safeParse(request.query); - if (!parseResult.success) { - return reply.status(400).send({ - error: 'Invalid query parameters', - details: parseResult.error.flatten(), - }); - } - - const { card: cardId } = parseResult.data; - - let username: string; - - // Fetch username - try { - const user = await app.prisma.user.findUnique({ - where: { id: userId }, - select: { username: true }, - }); - - if (!user) { - return reply.status(404).send({ - error: 'User not found', - }); - } - - username = user.username; - } catch (error) { - request.log.error( - { error }, - 'Failed to fetch user for NFC payload' - ); - return reply.status(500).send({ - error: 'Failed to fetch user profile', - }); - } - - // If a specific card is requested, verify ownership - if (cardId) { - try { - const card = await app.prisma.card.findUnique({ - where: { id: cardId }, - select: { userId: true }, - }); - - if (!card || card.userId !== userId) { - return reply.status(404).send({ - error: 'Card not found', - }); - } - } catch (error) { - request.log.error( - { error }, - 'Failed to fetch card for NFC payload' - ); - return reply.status(500).send({ - error: 'Failed to fetch card', - }); - } - } - -const safeUsername = encodeURIComponent(username); -const payloadUrl = `${process.env.PUBLIC_APP_URL}/${safeUsername}${ - cardId ? `?card=${encodeURIComponent(cardId)}` : '' -}`; - const response: NfcPayloadResponse = { - type: 'URI', - payload: payloadUrl, - }; - - return reply.send(response); - } - ); -} \ No newline at end of file diff --git a/apps/backend/src/routes/profiles.ts b/apps/backend/src/routes/profiles.ts index 81026c74..99aacb8e 100644 --- a/apps/backend/src/routes/profiles.ts +++ b/apps/backend/src/routes/profiles.ts @@ -1,52 +1,43 @@ import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { getProfileUrl } from '@devcard/shared'; -import { updateProfileSchema, createLinkSchema, reorderLinksSchema } from '../utils/validators.js'; -import { getErrorMessage } from '../utils/error.util.js'; -import * as profileService from '../services/profileService' - -// ── Response types ──────────────────────────────────────────────────────────── -// Declared explicitly so the API contract is visible without tracing through -// Prisma's generic return types. Follows the convention in public.ts. - -type ProfileUpdateResponse = { - id: string; - email: string; - username: string; - displayName: string; - bio: string | null; - pronouns: string | null; - role: string | null; - company: string | null; - avatarUrl: string | null; - accentColor: string; -}; +import { + updateProfileSchema, + createLinkSchema, + reorderLinksSchema, +} from '../utils/validators.js'; export async function profileRoutes(app: FastifyInstance) { // All profile routes require auth - app.addHook('preHandler', async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { - await server.authenticate(request, reply); - return; - } - if (typeof (app as any).authenticate === 'function') { - await (app as any).authenticate(request, reply); - return; - } - try { - await request.jwtVerify(); - } catch (e) { - reply.status(401).send({ error: 'Unauthorized' }); - } - }); + app.addHook('preHandler', app.authenticate); // ─── Get Own Profile ─── app.get('/me', async (request: FastifyRequest, reply: FastifyReply) => { const userId = (request.user as any).id; - const user = await profileService.getOwnProfile(app, userId) - if (!user) return reply.status(404).send({ error: 'User not found' }) - return user + + const user = await app.prisma.user.findUnique({ + where: { id: userId }, + include: { + platformLinks: { + orderBy: { displayOrder: 'asc' }, + }, + cards: { + where: { isDefault: true }, + select: { id: true }, + take: 1, + }, + }, + }); + + if (!user) { + return reply.status(404).send({ error: 'User not found' }); + } + + const { provider, providerId, ...profileData } = user; + return { + ...profileData, + defaultCardId: user.cards[0]?.id || null, + }; }); // ─── Update Profile ─── @@ -59,11 +50,9 @@ export async function profileRoutes(app: FastifyInstance) { return reply.status(400).send({ error: 'Validation failed', details: parsed.error.flatten() }); } - // Fast-path uniqueness check. This read-before-write eliminates the common - // case (clearly taken username) without touching the write path, but it - // cannot prevent the race window between two concurrent requests that both - // pass this check simultaneously. The unique constraint on the DB is the - // authoritative guard — P2002 below is the definitive conflict signal. + // Check username uniqueness if changing + // Note: For production, consider adding a timestamp/version field to handle + // race conditions where two users might try to claim the same username simultaneously. if (parsed.data.username) { const existing = await app.prisma.user.findFirst({ where: { @@ -76,14 +65,24 @@ export async function profileRoutes(app: FastifyInstance) { } } - try { - const response = await profileService.updateProfile(app, userId, parsed.data) - return response - } catch (err: any) { - if (err?.code === 'P2002') return reply.status(409).send({ error: 'Username already taken' }) - app.log.error({ err }, 'DB error in PUT /profiles/me') - return reply.status(500).send({ error: 'Internal server error' }) - } + const updated = await app.prisma.user.update({ + where: { id: userId }, + data: parsed.data, + select: { + id: true, + email: true, + username: true, + displayName: true, + bio: true, + pronouns: true, + role: true, + company: true, + avatarUrl: true, + accentColor: true, + }, + }); + + return updated; }); // ─── Add Platform Link ─── @@ -96,13 +95,26 @@ export async function profileRoutes(app: FastifyInstance) { return reply.status(400).send({ error: 'Validation failed', details: parsed.error.flatten() }); } - try { - const link = await profileService.createPlatformLink(app, userId, parsed.data) - return reply.status(201).send(link) - } catch (err: any) { - app.log.error({ err }, 'Failed to create platform link') - return reply.status(500).send({ error: 'Internal server error' }) - } + // Auto-generate URL from platform registry if not provided + const url = parsed.data.url || getProfileUrl(parsed.data.platform, parsed.data.username); + + // Get next display order + const maxOrder = await app.prisma.platformLink.aggregate({ + where: { userId }, + _max: { displayOrder: true }, + }); + + const link = await app.prisma.platformLink.create({ + data: { + userId, + platform: parsed.data.platform, + username: parsed.data.username, + url, + displayOrder: (maxOrder._max.displayOrder ?? -1) + 1, + }, + }); + + return reply.status(201).send(link); }); // ─── Update Platform Link ─── @@ -111,16 +123,31 @@ export async function profileRoutes(app: FastifyInstance) { const userId = (request.user as any).id; const { id } = request.params; - const parsedReq = createLinkSchema.safeParse(request.body) - if (!parsedReq.success) return reply.status(400).send({ error: 'Validation failed', details: parsedReq.error.flatten() }) - try { - const updated = await profileService.updatePlatformLink(app, userId, id, parsedReq.data) - if (!updated) return reply.status(404).send({ error: 'Link not found' }) - return updated - } catch (err: any) { - app.log.error({ err }, 'Failed to update platform link') - return reply.status(500).send({ error: 'Internal server error' }) + const existing = await app.prisma.platformLink.findFirst({ + where: { id, userId }, + }); + + if (!existing) { + return reply.status(404).send({ error: 'Link not found' }); + } + + const parsed = createLinkSchema.safeParse(request.body); + if (!parsed.success) { + return reply.status(400).send({ error: 'Validation failed', details: parsed.error.flatten() }); } + + const url = parsed.data.url || getProfileUrl(parsed.data.platform, parsed.data.username); + + const updated = await app.prisma.platformLink.update({ + where: { id }, + data: { + platform: parsed.data.platform, + username: parsed.data.username, + url, + }, + }); + + return updated; }); // ─── Delete Platform Link ─── @@ -129,28 +156,37 @@ export async function profileRoutes(app: FastifyInstance) { const userId = (request.user as any).id; const { id } = request.params; - try { - const deleted = await profileService.deletePlatformLink(app, userId, id) - if (!deleted) return reply.status(404).send({ error: 'Link not found' }) - return reply.status(204).send() - } catch (err: any) { - app.log.error({ err }, 'Failed to delete platform link') - return reply.status(500).send({ error: 'Internal server error' }) + const existing = await app.prisma.platformLink.findFirst({ + where: { id, userId }, + }); + + if (!existing) { + return reply.status(404).send({ error: 'Link not found' }); } + + await app.prisma.platformLink.delete({ where: { id } }); + return reply.status(204).send(); }); // ─── Reorder Links ─── app.put('/me/links/reorder', async (request: FastifyRequest, reply: FastifyReply) => { const userId = (request.user as any).id; - const parsedReq = reorderLinksSchema.safeParse(request.body) - if (!parsedReq.success) return reply.status(400).send({ error: 'Validation failed', details: parsedReq.error.flatten() }) - try { - const resp = await profileService.reorderLinks(app, userId, parsedReq.data.links) - return resp - } catch (err: any) { - app.log.error({ err }, 'Failed to reorder links') - return reply.status(500).send({ error: 'Internal server error' }) + const parsed = reorderLinksSchema.safeParse(request.body); + + if (!parsed.success) { + return reply.status(400).send({ error: 'Validation failed', details: parsed.error.flatten() }); } + + await app.prisma.$transaction( + parsed.data.links.map((link) => + app.prisma.platformLink.updateMany({ + where: { id: link.id, userId }, + data: { displayOrder: link.displayOrder }, + }) + ) + ); + + return { message: 'Links reordered' }; }); } diff --git a/apps/backend/src/routes/public.ts b/apps/backend/src/routes/public.ts index 27f544d8..f60e6133 100644 --- a/apps/backend/src/routes/public.ts +++ b/apps/backend/src/routes/public.ts @@ -1,134 +1,135 @@ -import type { FastifyContextConfig, FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; +import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { generateQRBuffer, generateQRSvg } from '../utils/qr.js'; -import type { PlatformLink } from '@devcard/shared'; -import { getErrorMessage } from '../utils/error.util.js'; -import * as publicService from '../services/publicService' - - -// ── QR size bounds ──────────────────────────────────────────────────────────── -// Enforced before any DB query or image allocation. Values outside this range -// are rejected with 400 so a single unauthenticated request cannot trigger an -// unbounded memory allocation in the QR rasteriser. -const MIN_QR_SIZE = 1; -const MAX_QR_SIZE = 2048; - -// ── Cache constants ─────────────────────────────────────────────────────────── -// Public profile cache TTL matches the Cache-Control max-age (5 minutes). -// The QR session JWT TTL is 10 minutes so an offline scan remains valid well -// beyond the HTTP cache window. -const PROFILE_CACHE_TTL = 300; // seconds (5 minutes) -const CACHE_CONTROL_HEADER = 'public, max-age=300, stale-while-revalidate=60'; type PublicProfileLink = { id: string; platform: string; - username: string; - url: string; - displayOrder: number; - followed?: boolean; + username: string; + url: string; + displayOrder: number; } -type UsernamePublicProfileResponse = { - username: string; +type UsernamePublicProfileResponse = { + username: string; displayName: string; - bio: string | null; - pronouns: string | null; - role: string | null; + bio: string | null; + pronouns: string | null; + role: string | null; company: string | null; - avatarUrl: string | null; + avatarUrl: string | null; accentColor: string; links: PublicProfileLink[] -} +} type PublicProfileCardLink = { id: string; platform: string; - username: string; - url: string; - followed?: boolean; + username: string; + url: string; } type CardPublicProfileResponse = { - id: string; - title: string; + id: string; + title: string; owner: { - username: string; - displayName: string; + username: string; + displayName: string; bio: string | null; avatarUrl: string | null; - accentColor: string; - }; + accentColor: string; + }; links: PublicProfileCardLink[] } type UsernameCardPublicProfileResponse = { - title: string; + title: string; owner: { - username: string; + username: string; displayName: string; - bio: string | null; - pronouns: string | null; - role: string | null; + bio: string | null; + pronouns: string | null; + role: string | null; company: string | null; - avatarUrl: string | null; + avatarUrl: string | null; accentColor: string; - }; + }; links: PublicProfileCardLink[] } -// Represents a CardLink record with the joined PlatformLink relation -interface CardLinkWithPlatform { - id: string; - displayOrder: number; - platformLink: PlatformLink; -} - -// ── Internal Redis cache shape ──────────────────────────────────────────────── -// Extends the public response with the owner's DB id so that background view -// tracking can still fire on cache-HIT requests without an extra DB read. -type CachedProfileEntry = UsernamePublicProfileResponse & { _userId: string }; export async function publicRoutes(app: FastifyInstance) { - // ─── Public Profile ─────────────────────────────────────────────────────── // ─── Public Profile ─── - /** - * GET /api/u/:username + /** + * GET /api/public/:username * Returns the public profile information for a user. - */ - app.get('/:username', { - config: { - rateLimit: { - max: 100, - timeWindow: '1 minute', - }, - }, - }, async (request: FastifyRequest<{ Params: { username: string } }>, reply: FastifyReply) => { + */ + app.get('/:username', async (request: FastifyRequest<{ Params: { username: string } }>, reply: FastifyReply) => { const { username } = request.params; - const cacheKey = `profile:${username}`; - // Try to extract viewer from Authorization header (soft auth). - let viewerId: string | null = null + const user = await app.prisma.user.findUnique({ + where: { username }, + include: { + platformLinks: { + orderBy: { displayOrder: 'asc' }, + }, + }, + }); + + if (!user) { + return reply.status(404).send({ error: 'User not found' }); + } + + // Try to extract viewer from Authorization header (soft auth) + let viewerId = null; try { if (request.headers.authorization) { - const decoded = (await request.jwtVerify()) as { id?: string } - viewerId = decoded?.id ?? null + const decoded = await request.jwtVerify() as any; + if (decoded?.id !== user.id) { + viewerId = decoded.id; // Only log if they aren't the owner + } } else { - viewerId = null + viewerId = null; // Unauthenticated viewer } - } catch { - // ignored + } catch (e) { + // Ignored if invalid token } - try { - const result = await publicService.getPublicProfile(app, username, viewerId, request) - if (!result) return reply.status(404).send({ error: 'User not found' }) - reply.header('X-Cache', result.cached ? 'HIT' : 'MISS').header('Cache-Control', CACHE_CONTROL_HEADER) - return result.data - } catch (err: any) { - app.log.error({ err }, 'Failed to fetch public profile') - return reply.status(500).send({ error: 'Internal server error' }) + // Don't track if the owner is viewing their own profile + if (viewerId !== user.id) { + // Background view tracking + app.prisma.cardView.create({ + data: { + ownerId: user.id, + cardId: null, // this is a profile view, not a card view + viewerId, + viewerIp: request.ip || null, + viewerAgent: request.headers['user-agent'] || null, + source: (request.query as any)?.source || 'link', + }, + }).catch(err => app.log.error('Failed to log view:', err)); } + + const response: UsernamePublicProfileResponse = { + username: user.username, + displayName: user.displayName, + bio: user.bio, + pronouns: user.pronouns, + role: user.role, + company: user.company, + avatarUrl: user.avatarUrl, + accentColor: user.accentColor, + links: user.platformLinks.map((link) => ({ + id: link.id, + platform: link.platform, + username: link.username, + url: link.url, + displayOrder: link.displayOrder, + })), + } + + return response; + }); /** @@ -138,121 +139,133 @@ export async function publicRoutes(app: FastifyInstance) { */ // ─── Shared Card View (Direct) ─── - app.get('/card/:cardId', { - config: { - rateLimit: { - max: 100, - timeWindow: '1 minute' - } - } as FastifyContextConfig - }, async (request: FastifyRequest<{ Params: { cardId: string } }>, reply: FastifyReply) => { + app.get('/card/:cardId', async (request: FastifyRequest<{ Params: { cardId: string } }>, reply: FastifyReply) => { const { cardId } = request.params; - try { - const card = await publicService.getCardById(app, cardId) - if (!card) return reply.status(404).send({ error: 'Card not found' }) - const response = { id: card.id, title: card.title, owner: { username: card.user.username, displayName: card.user.displayName, bio: card.user.bio, avatarUrl: card.user.avatarUrl, accentColor: card.user.accentColor }, links: card.cardLinks.map((cl: any) => ({ id: cl.platformLink.id, platform: cl.platformLink.platform, username: cl.platformLink.username, url: cl.platformLink.url })) } - return response - } catch (err: any) { - app.log.error({ err }, 'Failed to fetch shared card') - return reply.status(500).send({ error: 'Internal server error' }) + const card = await app.prisma.card.findUnique({ + where: { id: cardId }, + include: { + user: true, + cardLinks: { + include: { platformLink: true }, + orderBy: { displayOrder: 'asc' }, + }, + }, + }); + + if (!card) { + return reply.status(404).send({ error: 'Card not found' }); } + + const response: CardPublicProfileResponse = { + id: card.id, + title: card.title, + owner: { + username: card.user.username, + displayName: card.user.displayName, + bio: card.user.bio, + avatarUrl: card.user.avatarUrl, + accentColor: card.user.accentColor, + }, + links: card.cardLinks.map((cl) => ({ + id: cl.platformLink.id, + platform: cl.platformLink.platform, + username: cl.platformLink.username, + url: cl.platformLink.url, + })), + } + + return response; + }); - // ─── Public Card View ───────────────────────────────────────────────────── // ─── Public Card View ─── /** - * GET /api/u/:username/card/:cardId + * GET /api/public/:username/card/:cardId * Returns full owner profile + specific card data. * Used when viewing a card through username + cardId (e.g. QR code scans). - */ - app.get('/:username/card/:cardId', { - config: { - rateLimit: { - max: 100, - timeWindow: '1 minute', - }, - }, - }, async (request: FastifyRequest<{ Params: { username: string; cardId: string } }>, reply: FastifyReply) => { + */ + app.get('/:username/card/:cardId', async (request: FastifyRequest<{ Params: { username: string; cardId: string } }>, reply: FastifyReply) => { const { username, cardId } = request.params; - let viewerId: string | null = null + const user = await app.prisma.user.findUnique({ + where: { username }, + }); + + if (!user) { + return reply.status(404).send({ error: 'User not found' }); + } + + const card = await app.prisma.card.findFirst({ + where: { id: cardId, userId: user.id }, + include: { + cardLinks: { + include: { platformLink: true }, + orderBy: { displayOrder: 'asc' }, + }, + }, + }); + + if (!card) { + return reply.status(404).send({ error: 'Card not found' }); + } + + let viewerId = null; try { if (request.headers.authorization) { - const decoded = (await request.jwtVerify()) as { id?: string } - viewerId = decoded?.id ?? null + const decoded = await request.jwtVerify() as any; + if (decoded?.id !== user.id) { + viewerId = decoded.id; + } } - } catch { - // ignored - } + } catch (e) {} - try { - const result = await publicService.getUserCard(app, username, cardId, viewerId, request) - if (result.notFound) return reply.status(404).send({ error: 'User or card not found' }) - return result.data - } catch (err: any) { - app.log.error({ err }, 'Failed to fetch user card') - return reply.status(500).send({ error: 'Internal server error' }) + if (viewerId !== user.id) { + app.prisma.cardView.create({ + data: { + ownerId: user.id, + cardId: card.id, + viewerId, + viewerIp: request.ip || null, + viewerAgent: request.headers['user-agent'] || null, + source: (request.query as any)?.source || 'qr', + }, + }).catch(err => app.log.error('Failed to log card view:', err)); } - }); - // ─── QR Session ────────────────────────────────────────────────────────── - // Returns a short-lived signed JWT encoding the public profile snapshot. - // Intended for native apps to generate QR codes that remain scannable when - // the device has no live network connectivity (offline QR mode, spec §5.9). - app.get('/:username/qr-session', { - config: { - rateLimit: { - max: 30, - timeWindow: '1 minute' - } - } as FastifyContextConfig - }, async (request: FastifyRequest<{ Params: { username: string } }>, reply: FastifyReply) => { - const { username } = request.params; - const cacheKey = `profile:${username}`; - try { - const result = await publicService.getPublicProfile(app, username, null, request) - if (!result) return reply.status(404).send({ error: 'User not found' }) - const snapshot = result.data - const expiresIn = 600 - const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString() - const token = app.jwt.sign({ profile: snapshot, sub: username }, { expiresIn: '10m' }) - reply.header('Cache-Control', CACHE_CONTROL_HEADER) - return { token, tokenType: 'JWT', expiresIn, expiresAt } - } catch (err: any) { - app.log.error({ err }, 'Failed to create qr-session') - return reply.status(500).send({ error: 'Internal server error' }) + const response: UsernameCardPublicProfileResponse = { + title: card.title, + owner: { + username: user.username, + displayName: user.displayName, + bio: user.bio, + pronouns: user.pronouns, + role: user.role, + company: user.company, + avatarUrl: user.avatarUrl, + accentColor: user.accentColor, + }, + links: card.cardLinks.map((cl) => ({ + id: cl.platformLink.id, + platform: cl.platformLink.platform, + username: cl.platformLink.username, + url: cl.platformLink.url, + displayOrder: cl.displayOrder, + })), } + return response; }); - // ─── QR Code Generation ─────────────────────────────────────────────────── + // ─── QR Code Generation ─── - app.get('/:username/qr', { - config: { - rateLimit: { - max: 50, // Lower limit for QR generation as it's more resource intensive - timeWindow: '1 minute' - } - } as FastifyContextConfig - }, async (request: FastifyRequest<{ + app.get('/:username/qr', async (request: FastifyRequest<{ Params: { username: string }; Querystring: { format?: string; size?: string }; }>, reply: FastifyReply) => { const { username } = request.params; const format = (request.query as any).format || 'png'; - - // Parse and validate size before touching the DB or allocating any buffers. - // parseInt safely handles non-numeric strings (returns NaN) and ignores any - // trailing fractional part, so '400.9' → 400 which is within bounds. - const rawSize = (request.query as any).size; - const size = rawSize !== undefined ? parseInt(rawSize, 10) : 400; - - if (!Number.isInteger(size) || size < MIN_QR_SIZE || size > MAX_QR_SIZE) { - return reply.status(400).send({ - error: `QR size must be an integer between ${MIN_QR_SIZE} and ${MAX_QR_SIZE}`, - }); - } + const size = parseInt((request.query as any).size || '400', 10); // Verify user exists const user = await app.prisma.user.findUnique({ @@ -265,16 +278,18 @@ export async function publicRoutes(app: FastifyInstance) { const profileUrl = `${process.env.PUBLIC_APP_URL}/u/${username}`; - try { - if (format === 'svg') { - const svg = await generateQRSvg(profileUrl, { width: size }) - return reply.header('Content-Type', 'image/svg+xml').header('Content-Disposition', `inline; filename="devcard-${username}.svg"`).send(svg) - } - const png = await generateQRBuffer(profileUrl, { width: size }) - return reply.header('Content-Type', 'image/png').header('Content-Disposition', `inline; filename="devcard-${username}.png"`).send(png) - } catch (error) { - app.log.error({ error, username, size, format }, 'QR generation failed') - return reply.status(500).send({ error: 'QR code generation failed' }) + if (format === 'svg') { + const svg = await generateQRSvg(profileUrl, { width: size }); + return reply + .header('Content-Type', 'image/svg+xml') + .header('Content-Disposition', `inline; filename="devcard-${username}.svg"`) + .send(svg); } + + const png = await generateQRBuffer(profileUrl, { width: size }); + return reply + .header('Content-Type', 'image/png') + .header('Content-Disposition', `inline; filename="devcard-${username}.png"`) + .send(png); }); } diff --git a/apps/backend/src/routes/team.ts b/apps/backend/src/routes/team.ts deleted file mode 100644 index af177e52..00000000 --- a/apps/backend/src/routes/team.ts +++ /dev/null @@ -1,389 +0,0 @@ -import {Prisma, TeamRole } from '@prisma/client'; -import QRCode from 'qrcode' - -import {generateUniqueSlug} from '../utils/slug' -import { createTeamScehma,inviteMembers,updateTeam } from '../validations/team.validation'; - -import type {PlatformLink, PublicProfile} from '@devcard/shared' -import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; - -type TeamMember = PublicProfile & { - teamRole: TeamRole - joinedAt: Date; -} - -type TeamProfile = { - id: string; - name: string; - slug: string; - description: string | null; - ownerId: string; - avatarUrl: string | null; - createdAt: Date; - updatedAt: Date | null; - members: TeamMember[]; -} - -export async function teamRoutes(app:FastifyInstance){ - app.post('/', { preHandler: [async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } - if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } - try { const payload = await request.jwtVerify(); if (payload) (request as any).user = payload; } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }] }, async(request:FastifyRequest<{ - Body: {name: string, description? : string, avatarUrl?: string } - }>, reply: FastifyReply) => { - const userId = (request.user as any).id; - const parsed = createTeamScehma.safeParse(request.body); - if(!parsed.success){ - return reply.status(400).send({error: 'Bad request'}) - }; - const {name , description , avatarUrl} = parsed.data; - - const finalSlug = await generateUniqueSlug(name, async(slug) => { - const existing = await app.prisma.team.findUnique({where: {slug }}) - - return !!existing - }) - - try { - const team = await app.prisma.$transaction(async (tx) => { - const team = await tx.team.create({ - data: { - name, - slug: finalSlug, - description, - avatarUrl, - ownerId: userId, - } - }) - - await tx.teamMember.create({ - data: { - teamId : team.id, - userId, - role: TeamRole.OWNER, - joinedAt: new Date(), - } - }) - return team - }) - return reply.status(201).send(team) - - }catch (error) { - if (error instanceof Prisma.PrismaClientKnownRequestError) { - switch (error.code) { - case 'P2002': - return reply.status(409).send({ - error: 'Team slug already exists' - }); - - case 'P2003': - return reply.status(400).send({ - error: 'Invalid organizer' - }); - } - } - app.log.error('Failed to create a team'); - return reply.status(500).send({ - error: 'Failed to create team' - }); - } - }) - - app.get('/:slug', async(request: FastifyRequest<{Params: {slug: string}}>, reply: FastifyReply) => { - const paramsSlug = request.params.slug; - - try { - const details = await app.prisma.team.findUnique( - { - where: {slug: paramsSlug}, - include: { - members: { - include: { - user: { - include: { - platformLinks: true - } - } - } - } - } - } - ) - - if(!details){ - return reply.status(404).send({error: 'Team not found'}) - } - - const members = details.members.map((tm): TeamMember => ({ - username: tm.user.username, - displayName: tm.user.displayName, - bio: tm.user.bio, - pronouns: tm.user.pronouns, - role: tm.user.role, - company: tm.user.company, - avatarUrl: tm.user.avatarUrl, - accentColor: tm.user.accentColor, - links: tm.user.platformLinks.map((pl: PlatformLink) => ({ - id: pl.id, - platform: pl.platform, - username: pl.username, - url: pl.url, - displayOrder: pl.displayOrder, - })), - teamRole: tm.role, - joinedAt: tm.joinedAt, - - })) - - const response: TeamProfile = { - id: details.id, - name: details.name, - slug: details.slug, - description: details.description, - avatarUrl: details.avatarUrl, - ownerId: details.ownerId, - createdAt: details?.createdAt, - updatedAt: details.updatedAt, - members - } - - return response; - } catch (error) { - app.log.error(error); - return reply.status(500).send('Database query failed') - } - - }) - - app.post('/:slug/members', { preHandler: [async (request, reply) => { - const server = request.server as any; - if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } - if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } - try { const payload = await request.jwtVerify(); if (payload) (request as any).user = payload; } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } - }] }, async(request: FastifyRequest<{Params: {slug:string}, Body:{username:string}}>, reply: FastifyReply) => { - const paramsSlug = request.params.slug; - const userId = (request.user as any).id; - const parsed = inviteMembers.safeParse(request.body); - if(!parsed.success){ - return reply.status(400).send({error: 'Bad request'}) - }; - const {username} = parsed.data; - try { - const teamDetails = await app.prisma.team.findUnique( - {where: {slug: paramsSlug }, - include:{ - owner: true, - members: { - include: { - user: true - } - } - } - } - ) - if(!teamDetails){ - return reply.status(404).send('Team not found'); - } - //Check request user is owner - if(teamDetails?.ownerId !== userId){ - return reply.status(403).send('Forbidden') - } - - const alreadyMember = teamDetails.members.find((u) => u.user.username === username) - - //Check invited username is not a member and owner; - if(alreadyMember || teamDetails.owner.username === username){ - return reply.status(409).send('Conflict') - } - - const invitedUserDetails = await app.prisma.user.findUnique(( - {where: { - username - }})) - - if(!invitedUserDetails){ - return reply.status(404).send('User not found') - } - - await app.prisma.teamMember.create({ - data: { - teamId: teamDetails.id, - userId: invitedUserDetails.id, - role: TeamRole.MEMBER, - joinedAt: new Date() - } - }) - - return reply.status(201).send('User invited') - - } catch (error) { - app.log.error(error); - return reply.status(500).send('Database query failed') - } - }) - - app.delete('/:slug/members/:userId', { preHandler: [async (request, reply) => { const server = request.server as any; if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } }] }, async(request: FastifyRequest<{Params: {slug: string, userId: string}}>, reply: FastifyReply) => { - const paramsSlug = request.params.slug - const paramsUserId = request.params.userId - const userID = (request.user as any).id; - const teamDetails = await app.prisma.team.findUnique( - {where: {slug: paramsSlug}, - include: { - members: { - include:{ - user: true - } - } - } - }) - - if(!teamDetails){ - return reply.status(404).send({error: 'Team not found'}) - } - - const isMember = teamDetails.members.find((m) => paramsUserId === m.user.id) - - if(!isMember){ - return reply.status(404).send({ - error: 'Member not found', - }); - } - - const isOwner = teamDetails.ownerId === userID; - const isSelfRemove = paramsUserId === userID; - - if (!isOwner && !isSelfRemove) { - return reply.status(403).send({ - error: 'Forbidden', - }); - } - - //TODO: Assign owner role to next person - if(paramsUserId === teamDetails.ownerId){ - return reply.status(403).send({ - error: 'Owner cannot leave team', - }); - } - - if(isOwner || isSelfRemove){ - try { - await app.prisma.teamMember.delete({ - where: { - userId_teamId: { - teamId: teamDetails.id, - userId: paramsUserId - } - } - }) - reply.status(200).send('Member removed') - } catch (error) { - app.log.error(error); - - return reply.status(500).send('DB query failed') - } - } - }) - - app.patch('/:slug',{ preHandler: [async (request, reply) => { const server = request.server as any; if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } }] }, async(request: FastifyRequest<{Params: {slug: string},Body: {description?:string, name?:string, avatarUrl?:string}}>, reply: FastifyReply) => { - const userId = (request.user as any).id; - const paramsSlug = request.params.slug; - const parsed = updateTeam.safeParse(request.body); - if(!parsed.success){ - return reply.status(400).send({error: 'Bad request'}) - }; - - const {name, description,avatarUrl} = parsed.data; - - - const teamDetails = await app.prisma.team.findUnique({where:{slug: paramsSlug}}) - - if(!teamDetails){ - return reply.status(404).send('Team not found'); - } - - if(teamDetails.ownerId !== userId){ - return reply.status(403).send({ - error: 'Forbidden', - }); - } - - try { - const updatedTeam = await app.prisma.team.update({ - where: { - slug: paramsSlug - }, - data: { - name, - description, - avatarUrl, - } - }) - return reply.status(200).send(updatedTeam) - } catch (error) { - app.log.error(error); - return reply.status(500).send('DB query failed') - } - - }) - - app.delete('/:slug',{ preHandler: [async (request, reply) => { const server = request.server as any; if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } }] }, async(request:FastifyRequest<{Params:{slug: string}}>, reply:FastifyReply) => { - const userId = (request.user as any).id; - const paramsSlug = request.params.slug; - - - const teamDetails = await app.prisma.team.findUnique({ - where:{ - slug: paramsSlug - } - }) - - if(!teamDetails){ - return reply.status(404).send('Team not found'); - } - - if(teamDetails.ownerId !== userId){ - return reply.status(403).send({ - error: 'Forbidden', - }); - } - - try { - await app.prisma.team.delete({ - where: { - slug: paramsSlug, - } - }) - - return reply.status(200).send('Team deleted') - } catch (error) { - app.log.error(error) - - return reply.status(500).send('DB query failed') - } - }) - - app.get('/:slug/qr',async(request:FastifyRequest<{Params:{slug:string}}>, reply: FastifyReply) => { - const paramsSlug = request.params.slug; - try { - const teamDetails = await app.prisma.team.findUnique({ - where: { - slug: paramsSlug - } - }) - - if(!teamDetails){ - return reply.status(404).send('Team not found'); - } - - const url = `https://devcard.dev/team/${teamDetails.slug}` - const qrImage = await QRCode.toBuffer(url) - return reply.type('image/png').send(qrImage) - } catch (error) { - app.log.error(error); - return reply.status(500).send("QR generation failed") - } - - }) -} \ No newline at end of file diff --git a/apps/backend/src/server.ts b/apps/backend/src/server.ts index d494cf94..aea785d9 100644 --- a/apps/backend/src/server.ts +++ b/apps/backend/src/server.ts @@ -10,8 +10,8 @@ async function start() { try { await app.listen({ port: PORT, host: HOST }); app.log.info(`🚀 DevCard API running at http://${HOST}:${PORT}`); - } catch (error) { - app.log.error(error); + } catch (err) { + app.log.error(err); process.exit(1); } } diff --git a/apps/backend/src/services/authService.ts b/apps/backend/src/services/authService.ts deleted file mode 100644 index 9af718c5..00000000 --- a/apps/backend/src/services/authService.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { randomBytes } from 'crypto'; - -export function generateState(): string { - return randomBytes(32).toString('hex'); -} - -export function buildOAuthState(clientState: string, mobileRedirectUri: string): string { - if (!clientState) { - return generateState(); - } - - if (clientState.startsWith('mobile_') && mobileRedirectUri) { - const encodedRedirect = Buffer.from(mobileRedirectUri, 'utf8').toString('base64url'); - return `${clientState}.${encodedRedirect}.${generateState()}`; - } - - return `${clientState}.${generateState()}`; -} - -export function getMobileRedirectUri(state?: string): string | null { - if (!state?.startsWith('mobile_')) { - return null; - } - - const encodedRedirect = state.split('.')[1]; - if (!encodedRedirect) { - return null; - } - - try { - return Buffer.from(encodedRedirect, 'base64url').toString('utf8'); - } catch { - return null; - } -} diff --git a/apps/backend/src/services/cardService.ts b/apps/backend/src/services/cardService.ts deleted file mode 100644 index a9721783..00000000 --- a/apps/backend/src/services/cardService.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { FastifyInstance } from 'fastify' -import type { Prisma } from '@prisma/client' - -export async function listCards(app: FastifyInstance, userId: string) { - const cards = await app.prisma.card.findMany({ - where: { userId }, - take: 50, - include: { cardLinks: { include: { platformLink: true }, orderBy: { displayOrder: 'asc' } } }, - orderBy: { createdAt: 'asc' }, - }) - - return cards.map((card: any) => ({ id: card.id, title: card.title, isDefault: card.isDefault, links: card.cardLinks.map((cl: any) => cl.platformLink) })) -} - -export async function createCard(app: FastifyInstance, userId: string, body: { title: string; linkIds: string[] }) { - if (body.linkIds.length > 0) { - const ownedLinks = await app.prisma.platformLink.findMany({ where: { id: { in: body.linkIds }, userId }, select: { id: true } }) - if (ownedLinks.length !== body.linkIds.length) throw Object.assign(new Error('Link ownership mismatch'), { code: 'OWNERSHIP' }) - } - - const cardCount = await app.prisma.card.count({ where: { userId } }) - - const card = await app.prisma.card.create({ - data: { - userId, - title: body.title, - isDefault: cardCount === 0, - cardLinks: { create: body.linkIds.map((linkId, index) => ({ platformLinkId: linkId, displayOrder: index })) }, - }, - include: { cardLinks: { include: { platformLink: true }, orderBy: { displayOrder: 'asc' } } }, - }) - - return { id: card.id, title: card.title, isDefault: card.isDefault, links: card.cardLinks.map((cl: any) => cl.platformLink) } -} - -export async function updateCard(app: FastifyInstance, userId: string, id: string, body: { title?: string; linkIds?: string[] }) { - const existing = await app.prisma.card.findFirst({ where: { id, userId } }) - if (!existing) return null - - if (body.title) { - await app.prisma.card.update({ where: { id }, data: { title: body.title } }) - } - - if (body.linkIds) { - if (body.linkIds.length > 0) { - const ownedLinks = await app.prisma.platformLink.findMany({ where: { id: { in: body.linkIds }, userId }, select: { id: true } }) - if (ownedLinks.length !== body.linkIds.length) throw Object.assign(new Error('Link ownership mismatch'), { code: 'OWNERSHIP' }) - } - - const linkIds = body.linkIds - await app.prisma.$transaction(async (tx: Prisma.TransactionClient) => { - await tx.cardLink.deleteMany({ where: { cardId: id } }) - if (linkIds.length > 0) { - await tx.cardLink.createMany({ data: linkIds.map((linkId, index) => ({ cardId: id, platformLinkId: linkId, displayOrder: index })) }) - } - }) - } - - const updated = await app.prisma.card.findUnique({ where: { id }, include: { cardLinks: { include: { platformLink: true }, orderBy: { displayOrder: 'asc' } } } }) - return { id: updated!.id, title: updated!.title, isDefault: updated!.isDefault, links: updated!.cardLinks.map((cl: any) => cl.platformLink) } -} - -export async function deleteCard(app: FastifyInstance, userId: string, id: string) { - return await app.prisma.$transaction(async (tx: Prisma.TransactionClient) => { - const existing = await tx.card.findFirst({ where: { id, userId } }) - if (!existing) return Object.assign(new Error('NotFound'), { code: 'NOT_FOUND' }) - - const userCardCount = await tx.card.count({ where: { userId } }) - if (userCardCount <= 1) return Object.assign(new Error('Cannot delete last card'), { code: 'LAST_CARD' }) - - if (existing.isDefault) { - const oldestRemainingCard = await tx.card.findFirst({ where: { userId, id: { not: id } }, orderBy: { createdAt: 'asc' } }) - if (oldestRemainingCard) { - await tx.card.update({ where: { id: oldestRemainingCard.id }, data: { isDefault: true } }) - } - } - - await tx.card.delete({ where: { id } }) - return null - }) -} - -export async function setDefaultCard(app: FastifyInstance, userId: string, id: string) { - const existing = await app.prisma.card.findFirst({ where: { id, userId } }) - if (!existing) return null - - await app.prisma.$transaction(async (tx: Prisma.TransactionClient) => { - await tx.card.updateMany({ where: { userId }, data: { isDefault: false } }) - await tx.card.update({ where: { id }, data: { isDefault: true } }) - }) - - return { message: 'Default card updated' } -} diff --git a/apps/backend/src/services/profileService.ts b/apps/backend/src/services/profileService.ts deleted file mode 100644 index dc97b2a4..00000000 --- a/apps/backend/src/services/profileService.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { FastifyInstance } from 'fastify' -import { getProfileUrl } from '@devcard/shared' -import type { PlatformLink } from '@devcard/shared' -import { getErrorMessage } from '../utils/error.util.js' - -export async function getOwnProfile(app: FastifyInstance, userId: string) { - const user = await app.prisma.user.findUnique({ - where: { id: userId }, - include: { - platformLinks: { orderBy: { displayOrder: 'asc' } }, - cards: { where: { isDefault: true }, select: { id: true }, take: 1 }, - }, - }) - - if (!user) return null - - const { provider, providerId, ...profileData } = user as any - return { ...profileData, defaultCardId: user.cards[0]?.id || null } -} - -export async function updateProfile(app: FastifyInstance, userId: string, data: any) { - // Fast-path uniqueness check - if (data.username) { - const existing = await app.prisma.user.findFirst({ - where: { username: data.username, NOT: { id: userId } }, - }) - if (existing) throw Object.assign(new Error('Username taken'), { code: 'P2002' }) - } - - const currentUser = await app.prisma.user.findUnique({ where: { id: userId }, select: { username: true } }) - - try { - const response = await app.prisma.user.update({ where: { id: userId }, data, select: { - id: true, email: true, username: true, displayName: true, bio: true, pronouns: true, role: true, company: true, avatarUrl: true, accentColor: true - } }) - - if (app.redis && currentUser) { - app.redis.del(`profile:${currentUser.username}`).catch((err: unknown) => - app.log.warn(`Failed to invalidate profile cache: ${getErrorMessage(err)}`) - ) - } - - return response - } catch (err: any) { - if (err?.code === 'P2002') throw err - app.log.error({ err }, 'DB error in updateProfile') - throw err - } -} - -export async function createPlatformLink(app: FastifyInstance, userId: string, linkData: any) { - const url = linkData.url || getProfileUrl(linkData.platform, linkData.username) - const maxOrder = await app.prisma.platformLink.aggregate({ where: { userId }, _max: { displayOrder: true } }) - return app.prisma.platformLink.create({ data: { userId, platform: linkData.platform, username: linkData.username, url, displayOrder: (maxOrder._max.displayOrder ?? -1) + 1 } }) -} - -export async function updatePlatformLink(app: FastifyInstance, userId: string, id: string, linkData: any) { - const existing = await app.prisma.platformLink.findFirst({ where: { id, userId } }) - if (!existing) return null - const url = linkData.url || getProfileUrl(linkData.platform, linkData.username) - return app.prisma.platformLink.update({ where: { id }, data: { platform: linkData.platform, username: linkData.username, url } }) -} - -export async function deletePlatformLink(app: FastifyInstance, userId: string, id: string) { - const existing = await app.prisma.platformLink.findFirst({ where: { id, userId } }) - if (!existing) return false - await app.prisma.platformLink.delete({ where: { id } }) - return true -} - -export async function reorderLinks(app: FastifyInstance, userId: string, links: Array<{ id: string; displayOrder: number }>) { - await app.prisma.$transaction(links.map((link) => app.prisma.platformLink.updateMany({ where: { id: link.id, userId }, data: { displayOrder: link.displayOrder } }))) - return { message: 'Links reordered' } -} diff --git a/apps/backend/src/services/publicService.ts b/apps/backend/src/services/publicService.ts deleted file mode 100644 index 758ab78f..00000000 --- a/apps/backend/src/services/publicService.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { FastifyInstance } from 'fastify' -import { getErrorMessage } from '../utils/error.util.js' - -const PROFILE_CACHE_TTL = 300 -const CACHE_CONTROL_HEADER = 'public, max-age=300, stale-while-revalidate=60' - -export async function getPublicProfile(app: FastifyInstance, username: string, viewerId: string | null, request: any) { - const cacheKey = `profile:${username}` - - if (app.redis) { - try { - const cached = await app.redis.get(cacheKey) - if (cached) { - const { _userId, ...profileData } = JSON.parse(cached) - if (viewerId && viewerId !== _userId) { - app.prisma.cardView.create({ data: { ownerId: _userId, cardId: null, viewerId, viewerIp: request.ip || null, viewerAgent: request.headers['user-agent'] || null, source: request.query?.source || 'link' } }).catch((err: unknown) => app.log.error(`Failed to log view: ${getErrorMessage(err)}`)) - } - return { cached: true, data: profileData, cacheKey } - } - } catch (err) { - app.log.warn(`Redis cache read failed for ${cacheKey}: ${getErrorMessage(err)}`) - } - } - - const user = await app.prisma.user.findUnique({ where: { username }, include: { platformLinks: { orderBy: { displayOrder: 'asc' } } } }) - if (!user) return null - - if (viewerId && viewerId !== user.id) { - app.prisma.cardView.create({ data: { ownerId: user.id, cardId: null, viewerId, viewerIp: request.ip || null, viewerAgent: request.headers['user-agent'] || null, source: request.query?.source || 'link' } }).catch((error: unknown) => app.log.error(`Failed to log view: ${getErrorMessage(error)}`)) - } - - let followedLinkIds: string[] = [] - if (viewerId && user.platformLinks.length > 0) { - const successfulFollows = await app.prisma.followLog.findMany({ where: { followerId: viewerId, status: 'success', OR: user.platformLinks.map((link: any) => ({ platform: link.platform, targetUsername: link.username })) }, select: { platform: true, targetUsername: true } }) - followedLinkIds = user.platformLinks.filter((link: any) => successfulFollows.some((f: any) => f.platform === link.platform && f.targetUsername.toLowerCase() === link.username.toLowerCase())).map((l: any) => l.id) - } - - const baseLinks = user.platformLinks.map((link: any) => ({ id: link.id, platform: link.platform, username: link.username, url: link.url, displayOrder: link.displayOrder, followed: false })) - - if (app.redis) { - const entry = { _userId: user.id, username: user.username, displayName: user.displayName, bio: user.bio, pronouns: user.pronouns, role: user.role, company: user.company, avatarUrl: user.avatarUrl, accentColor: user.accentColor, links: baseLinks } - app.redis.set(cacheKey, JSON.stringify(entry), 'EX', PROFILE_CACHE_TTL).catch((err: unknown) => app.log.warn(`Redis cache write failed for ${cacheKey}: ${getErrorMessage(err)}`)) - } - - const response = { username: user.username, displayName: user.displayName, bio: user.bio, pronouns: user.pronouns, role: user.role, company: user.company, avatarUrl: user.avatarUrl, accentColor: user.accentColor, links: baseLinks.map((link) => ({ ...link, followed: followedLinkIds.includes(link.id) })) } - - return { cached: false, data: response, cacheKey } -} - -export async function getCardById(app: FastifyInstance, cardId: string) { - const card = await app.prisma.card.findUnique({ where: { id: cardId }, include: { user: true, cardLinks: { include: { platformLink: true }, orderBy: { displayOrder: 'asc' } } } }) - return card -} - -export async function getUserCard(app: FastifyInstance, username: string, cardId: string, viewerId: string | null, request: any) { - const user = await app.prisma.user.findUnique({ where: { username } }) - if (!user) return { notFound: true } - const card = await app.prisma.card.findFirst({ where: { id: cardId, userId: user.id }, include: { cardLinks: { include: { platformLink: true }, orderBy: { displayOrder: 'asc' } } } }) - if (!card) return { notFound: true } - - if (viewerId && viewerId !== user.id) { - app.prisma.cardView.create({ data: { ownerId: user.id, cardId: card.id, viewerId, viewerIp: request.ip || null, viewerAgent: request.headers['user-agent'] || null, source: request.query?.source || 'qr' } }).catch((error: unknown) => app.log.error(`Failed to log view: ${getErrorMessage(error)}`)) - } - - const response = { title: card.title, owner: { username: user.username, displayName: user.displayName, bio: user.bio, pronouns: user.pronouns, role: user.role, company: user.company, avatarUrl: user.avatarUrl, accentColor: user.accentColor }, links: card.cardLinks.map((cl: any) => ({ id: cl.platformLink.id, platform: cl.platformLink.platform, username: cl.platformLink.username, url: cl.platformLink.url, displayOrder: cl.displayOrder })) } - return { notFound: false, data: response } -} diff --git a/apps/backend/src/types/fastify.d.ts b/apps/backend/src/types/fastify.d.ts deleted file mode 100644 index 8e7aee95..00000000 --- a/apps/backend/src/types/fastify.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import '@fastify/cookie'; -import { FastifyRequest } from 'fastify'; - -declare module 'fastify' { - interface FastifyRequest { - cookies: Record; - } -} diff --git a/apps/backend/src/utils/error.util.ts b/apps/backend/src/utils/error.util.ts deleted file mode 100644 index fef1b98b..00000000 --- a/apps/backend/src/utils/error.util.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { FastifyReply, FastifyRequest } from 'fastify'; -import { Prisma } from '@prisma/client'; - -export function getErrorMessage(err: unknown): string { - return err instanceof Error ? err.message : String(err); -} - -export function handleDbError(error: unknown, request: FastifyRequest, reply: FastifyReply) { - request.log.error(error); - - if (error instanceof Prisma.PrismaClientKnownRequestError) { - // P2002: Unique constraint failed - if (error.code === 'P2002') { - return reply.status(409).send({ error: 'Conflict: Record already exists or violates unique constraint' }); - } - // P2025: Record to update not found - if (error.code === 'P2025') { - return reply.status(404).send({ error: 'Not Found: Record does not exist' }); - } - // P2003: Foreign key constraint failed - if (error.code === 'P2003') { - return reply.status(400).send({ error: 'Constraint failed: Related record not found or invalid' }); - } - return reply.status(400).send({ error: `Database error: ${error.message}` }); - } - - if (error instanceof Prisma.PrismaClientValidationError) { - return reply.status(400).send({ error: 'Database validation failed' }); - } - - return reply.status(500).send({ error: 'Internal Server Error' }); -} \ No newline at end of file diff --git a/apps/backend/src/utils/slug.ts b/apps/backend/src/utils/slug.ts deleted file mode 100644 index 24b772f3..00000000 --- a/apps/backend/src/utils/slug.ts +++ /dev/null @@ -1,19 +0,0 @@ -export function createSlug(name:string){ - return name.toLowerCase().trim().replace(/\s+/g, '-').replace(/[^a-z0-9-]+/g, '').replace(/-+/g, '-').replace(/^-+|-+$/g, '') -} - -export async function generateUniqueSlug(name: string, - slugExists: (slug: string) => Promise -){ - const cleanSlug = createSlug(name) - let finalSlug = cleanSlug; - while(true){ - const exists = await slugExists(finalSlug) - - if(!exists) break; - - const randomSuffix = Math.random().toString(36).substring(2,6); - finalSlug = `${cleanSlug}-${randomSuffix}` - } - return finalSlug; -} diff --git a/apps/backend/src/utils/validateEnv.ts b/apps/backend/src/utils/validateEnv.ts deleted file mode 100644 index cd361fc8..00000000 --- a/apps/backend/src/utils/validateEnv.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Startup environment validation. - * - * Validates all required secrets before the application registers any plugins. - * Missing or insecure values cause an immediate, deterministic process exit so - * the server never reaches a partially-initialised auth state. - * - * Call this at the very top of buildApp(), before any Fastify plugin registration. - */ - -/** - * Secrets that are committed to the public repository and must not be used in - * production. Any match triggers an immediate startup failure. - */ -const KNOWN_INSECURE_DEFAULTS: ReadonlySet = new Set([ - 'dev-secret-change-me', -]); - -/** - * Validates that all required secrets are present and safe. - * Exits the process with code 1 on any violation, logging all failures at once - * so operators can fix everything in a single deploy cycle. - * - * Secrets are never logged — only their presence and safety are reported. - */ -export function validateEnv(): void { - const errors: string[] = []; - const isProduction = process.env.NODE_ENV === 'production'; - - // ── JWT_SECRET ────────────────────────────────────────────────────────────── - const jwtSecret = process.env.JWT_SECRET; - - if (!jwtSecret) { - errors.push( - 'JWT_SECRET is not set. Generate a secure value with:\n' + - ' node -e "console.log(require(\'crypto\').randomBytes(64).toString(\'hex\'))"', - ); - } else if (isProduction && KNOWN_INSECURE_DEFAULTS.has(jwtSecret)) { - errors.push( - 'JWT_SECRET is set to a known insecure default and cannot be used in production.\n' + - ' Generate a secure value with:\n' + - ' node -e "console.log(require(\'crypto\').randomBytes(64).toString(\'hex\'))"', - ); - } - - // ── ENCRYPTION_KEY ────────────────────────────────────────────────────────── - // getEncryptionKey() in utils/encryption.ts already throws at call-time when - // this is missing, but catching it at startup is safer — the error surfaces - // before any request is served rather than mid-flight on the first encrypt/ - // decrypt call. - const encryptionKey = process.env.ENCRYPTION_KEY; - - if (!encryptionKey) { - errors.push( - 'ENCRYPTION_KEY is not set. Generate a secure value with:\n' + - ' node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"', - ); - } - - // ── Fail fast ─────────────────────────────────────────────────────────────── - if (errors.length === 0) { - return; - } - - console.error(''); - console.error('╔══════════════════════════════════════════════════════════╗'); - console.error('║ STARTUP FAILED — missing or insecure required secrets ║'); - console.error('╚══════════════════════════════════════════════════════════╝'); - console.error(''); - for (const msg of errors) { - console.error(` ✖ ${msg}`); - console.error(''); - } - - process.exit(1); -} diff --git a/apps/backend/src/utils/validators.ts b/apps/backend/src/utils/validators.ts index bd41bef2..80d2caa1 100644 --- a/apps/backend/src/utils/validators.ts +++ b/apps/backend/src/utils/validators.ts @@ -1,5 +1,4 @@ import { z } from 'zod'; -import { getPlatform } from '@devcard/shared'; export const updateProfileSchema = z.object({ displayName: z.string().min(1).max(100).optional(), @@ -23,17 +22,6 @@ export const createLinkSchema = z.object({ platform: z.string().min(1), username: z.string().min(1).max(200), url: z.string().url().optional(), -}).superRefine((data, ctx) => { - const platformDef = getPlatform(data.platform); - if (platformDef?.validationRegex) { - if (!platformDef.validationRegex.test(data.username)) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: `Invalid format for ${platformDef.name} handle`, - path: ['username'], - }); - } - } }); export const reorderLinksSchema = z.object({ diff --git a/apps/backend/src/validations/event.validation.ts b/apps/backend/src/validations/event.validation.ts deleted file mode 100644 index 0fc4044f..00000000 --- a/apps/backend/src/validations/event.validation.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {z} from 'zod' - -export const createEventSchema = z.object({ - name: z.string().min(3, 'Event name must be at least 3 characters long').max(100,'Event name cannot be longer than 100 characters'), - description: z.string().min(1).optional(), - location: z.string().min(2, 'Location should be atleast 2 characters long').max(100,'Location cannot be longer than 100 characters'), - startDate: z.string().pipe(z.coerce.date()), - endDate: z.string().pipe(z.coerce.date()), - isPublic: z.boolean().default(true) -}) - -export const joinEventSchema = z.object({}) \ No newline at end of file diff --git a/apps/backend/src/validations/follow.validation.ts b/apps/backend/src/validations/follow.validation.ts deleted file mode 100644 index 319f1de1..00000000 --- a/apps/backend/src/validations/follow.validation.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { z } from 'zod'; - -/** - * Strict allowlist schema for analytics-impacting follow log fields. - * - * Both `status` and `layer` feed directly into analytics counters and the - * follower-state dashboard. Only the values enumerated below may be - * persisted — all other values are rejected before any database write. - * - * status: - * 'success' — the follow action completed and was accepted by the platform - * 'failed' — the action completed but was rejected (e.g. rate-limit, block) - * 'pending' — the action was initiated; outcome not yet confirmed by client - * - * layer (hybrid follow engine interaction surface): - * 'foreground' — user interacted directly with an in-app WebView session - * 'background' — follow triggered through a passive deep-link / redirect strategy - */ -export const followLogSchema = z.object({ - status: z.enum(['success', 'failed', 'pending'], { - errorMap: () => ({ - message: "status must be one of: 'success', 'failed', 'pending'", - }), - }), - layer: z.enum(['foreground', 'background'], { - errorMap: () => ({ - message: "layer must be one of: 'foreground', 'background'", - }), - }), -}); - -export type FollowLogBody = z.infer; diff --git a/apps/backend/src/validations/team.validation.ts b/apps/backend/src/validations/team.validation.ts deleted file mode 100644 index 153333c0..00000000 --- a/apps/backend/src/validations/team.validation.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {z} from 'zod'; - -export const createTeamScehma = z.object({ - name: z.string().min(3, 'Event name must be at least 3 characters long').max(100,'Event name cannot be longer than 100 characters'), - description: z.string().min(1).optional(), - avatarUrl : z.string().url().optional(), -}) - - -export const inviteMembers = z.object({ - username: z.string().min(1,'Username must be atleast 1 character') -}) - -export const updateTeam = z.object({ - name: z.string().min(1, 'Name must be at least 1 character').optional(), - description: z.string().min(1,'Description must be at least 1 character').optional(), - avatarUrl: z.string().url('Invalid avatar URL').optional(), -}).refine( - (data) => - data.name !== undefined || - data.description !== undefined || - data.avatarUrl !== undefined, - { - message: 'At least one field is required', - } -) \ No newline at end of file diff --git a/apps/mobile/App.tsx b/apps/mobile/App.tsx index d577bd7a..811892f4 100644 --- a/apps/mobile/App.tsx +++ b/apps/mobile/App.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { NavigationContainer, LinkingOptions } from '@react-navigation/native'; +import { NavigationContainer } from '@react-navigation/native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'; @@ -7,44 +7,21 @@ import { AuthProvider, useAuth } from './src/context/AuthContext'; import { ThemeProvider } from './src/context/ThemeContext'; import AuthStack from './src/navigation/AuthStack'; import MainTabs from './src/navigation/MainTabs'; -import SplashScreen from './src/screens/SplashScreen'; -import { DEEP_LINK_SCHEME } from './src/config'; import { Linking, StyleSheet } from 'react-native'; -// ── Deep Link Configuration ─────────────────────────────────────────────────── - -const linking: LinkingOptions<{}> = { - prefixes: [`${DEEP_LINK_SCHEME}://`], - config: { - screens: { - MainTabs: { - screens: { - Home: 'home', - Scan: 'scan', - }, - }, - DevCardView: 'u/:username', - }, - }, -}; - -// ── App Content ─────────────────────────────────────────────────────────────── - function AppContent() { const { isAuthenticated, isLoading, login } = useAuth(); React.useEffect(() => { const handleDeepLink = (event: { url: string }) => { - try { - const url = new URL(event.url); - const hashParams = new URLSearchParams(url.hash.replace(/^#/, '')); - const token = url.searchParams.get('token') || hashParams.get('token'); - if (token) { - login(token); - } - } catch (error) { - console.error('Deep link parse error:', error); + console.log('--- DEEP LINK RECEIVED ---'); + console.log('URL:', event.url); + const url = new URL(event.url); + const token = url.searchParams.get('token'); + if (token) { + console.log('Token found, logging in...'); + login(token); } }; @@ -60,18 +37,16 @@ function AppContent() { }, [login]); if (isLoading) { - return ; + return null; // Splash screen could go here } return ( - + {isAuthenticated ? : } ); } -// ── Root ─────────────────────────────────────────────────────────────────────── - export default function App() { return ( diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 2917e473..20c74dff 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -1,5 +1,4 @@ { "name": "DevCard", - "displayName": "DevCard", - "scheme": "devcard" + "displayName": "DevCard" } diff --git a/apps/mobile/babel.config.js b/apps/mobile/babel.config.js index 8ba8eb65..02c7d135 100644 --- a/apps/mobile/babel.config.js +++ b/apps/mobile/babel.config.js @@ -1,4 +1,4 @@ module.exports = { presets: ['module:@react-native/babel-preset'], - plugins: ['react-native-worklets/plugin'], + plugins: ['react-native-reanimated/plugin'], }; diff --git a/apps/mobile/index.js b/apps/mobile/index.js index d5ce57df..fd5fb918 100644 --- a/apps/mobile/index.js +++ b/apps/mobile/index.js @@ -1,3 +1,7 @@ +/** + * @format + */ + import 'react-native-gesture-handler'; import { AppRegistry } from 'react-native'; import App from './App'; diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js index 0d21ee3a..ca00dd01 100644 --- a/apps/mobile/metro.config.js +++ b/apps/mobile/metro.config.js @@ -1,4 +1,4 @@ -const { getDefaultConfig } = require('@react-native/metro-config'); +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); const path = require('path'); // Monorepo root @@ -6,47 +6,21 @@ const projectRoot = __dirname; const monorepoRoot = path.resolve(projectRoot, '../..'); /** - * Metro configuration for React Native monorepo + * Metro configuration for monorepo + * https://reactnative.dev/docs/metro + * + * @type {import('@react-native/metro-config').MetroConfig} */ -module.exports = (async () => { - const config = await getDefaultConfig(projectRoot); +const config = { + watchFolders: [monorepoRoot], + resolver: { + nodeModulesPaths: [ + path.resolve(projectRoot, 'node_modules'), + path.resolve(monorepoRoot, 'node_modules'), + ], + // Ensure shared package is resolved + disableHierarchicalLookup: false, + }, +}; - config.watchFolders = [monorepoRoot]; - config.resolver = config.resolver || {}; - config.resolver.nodeModulesPaths = [ - path.resolve(projectRoot, 'node_modules'), - path.resolve(monorepoRoot, 'node_modules'), - ]; - config.resolver.disableHierarchicalLookup = false; - - const pinnedModules = { - react: path.resolve(projectRoot, 'node_modules/react'), - 'react-native': path.resolve(projectRoot, 'node_modules/react-native'), - 'react-native-reanimated': path.resolve( - projectRoot, - 'node_modules/react-native-reanimated' - ), - 'react-native-worklets': path.resolve( - projectRoot, - 'node_modules/react-native-worklets' - ), - 'react-native-gesture-handler': path.resolve( - projectRoot, - 'node_modules/react-native-gesture-handler' - ), - }; - - config.resolver.extraNodeModules = pinnedModules; - config.resolver.resolveRequest = (context, moduleName, platform) => { - for (const [name, modulePath] of Object.entries(pinnedModules)) { - if (moduleName === name || moduleName.startsWith(`${name}/`)) { - const target = path.join(modulePath, moduleName.slice(name.length)); - return context.resolveRequest(context, target, platform); - } - } - - return context.resolveRequest(context, moduleName, platform); - }; - - return config; -})(); +module.exports = mergeConfig(getDefaultConfig(__dirname), config); diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 4cae19e2..92fcba44 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -2,7 +2,6 @@ "name": "@devcard/mobile", "version": "0.0.1", "private": true, - "main": "index.js", "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", @@ -19,20 +18,17 @@ "@react-navigation/native": "^7.0.0", "@react-navigation/native-stack": "^7.0.0", "react": "19.2.3", - "react-dom": "^19.2.3", + "react-dom": "^19.2.4", "react-native": "0.84.1", - "react-native-camera-kit": "^14.0.0", - "react-native-gesture-handler": "^2.28.0", + "react-native-gesture-handler": "^2.20.2", "react-native-qrcode-svg": "^6.3.0", - "react-native-reanimated": "^3.16.7", + "react-native-reanimated": "^3.15.0", "react-native-safe-area-context": "^5.5.2", "react-native-screens": "^4.0.0", "react-native-svg": "^15.0.0", "react-native-vector-icons": "^10.0.0", - "react-native-view-shot": "^5.1.0", "react-native-web": "^0.21.2", - "react-native-webview": "^13.0.0", - "react-native-worklets": "0.5.1" + "react-native-webview": "^13.0.0" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/apps/mobile/src/components/Avatar.tsx b/apps/mobile/src/components/Avatar.tsx deleted file mode 100644 index 8a0ee0c8..00000000 --- a/apps/mobile/src/components/Avatar.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { View, Text, Image, ViewStyle, ImageStyle, StyleSheet } from 'react-native'; -import { COLORS } from '../theme/tokens'; - -type Props = { - uri?: string | null; - name?: string; - size?: number; - style?: ViewStyle | ImageStyle; -}; - -export const Avatar: React.FC = ({ uri, name = 'D', size = 56, style }) => { - const initials = name.charAt(0).toUpperCase(); - const imageStyle = [{ width: size, height: size, borderRadius: size / 2 } as ImageStyle, style as ImageStyle]; - const placeholderStyle = [{ width: size, height: size, borderRadius: size / 2, backgroundColor: COLORS.primary }, style as ViewStyle]; - - return uri ? ( - - ) : ( - - {initials} - - ); -}; - -export default Avatar; - -const styles = StyleSheet.create({ - placeholder: { - alignItems: 'center', - justifyContent: 'center', - }, - placeholderText: { - color: COLORS.white, - fontWeight: '800', - }, -}); diff --git a/apps/mobile/src/components/CardPickerSheet.tsx b/apps/mobile/src/components/CardPickerSheet.tsx index 7cbb12d3..44af9d93 100644 --- a/apps/mobile/src/components/CardPickerSheet.tsx +++ b/apps/mobile/src/components/CardPickerSheet.tsx @@ -8,7 +8,6 @@ import { BottomSheetScrollView, } from '@gorhom/bottom-sheet'; import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS } from '../theme/tokens'; -import { EmptyState } from './EmptyState'; type Props = { cards: Card[]; @@ -51,10 +50,7 @@ const CardPickerSheet = React.forwardRef( {cards.length === 0 ? ( - + No cards yet ) : ( cards.map(card => { @@ -148,10 +144,12 @@ const styles = StyleSheet.create({ textAlign: 'center', }, noCards: { - backgroundColor: COLORS.bgCard, - borderRadius: BORDER_RADIUS.md, - borderWidth: 1, - borderColor: COLORS.border, + alignItems: 'center', + paddingVertical: SPACING.lg, + }, + noCardsText: { + fontSize: FONT_SIZE.sm, + color: COLORS.textMuted, }, cardRow: { flexDirection: 'row', diff --git a/apps/mobile/src/components/ColorPicker.tsx b/apps/mobile/src/components/ColorPicker.tsx deleted file mode 100644 index 83eecd8f..00000000 --- a/apps/mobile/src/components/ColorPicker.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import { View, TouchableOpacity, StyleSheet } from 'react-native'; -import { COLORS, SPACING, BORDER_RADIUS } from '../theme/tokens'; - -// ── Predefined Accent Color Palette ─────────────────────────────────────────── -// 8 curated colors that work well as card accent on the dark DevCard theme. - -export const ACCENT_COLORS = [ - '#6366F1', // Indigo (default) - '#8B5CF6', // Violet - '#EC4899', // Pink - '#EF4444', // Red - '#F59E0B', // Amber - '#22C55E', // Green - '#06B6D4', // Cyan - '#3B82F6', // Blue -] as const; - -export type AccentColor = (typeof ACCENT_COLORS)[number]; - -interface ColorPickerProps { - selected: string; - onSelect: (color: string) => void; -} - -export default function ColorPicker({ selected, onSelect }: ColorPickerProps) { - return ( - - {ACCENT_COLORS.map((color) => { - const isActive = selected === color; - return ( - onSelect(color)} - activeOpacity={0.7} - accessibilityLabel={`Select accent color ${color}`} - accessibilityRole="radio" - accessibilityState={{ selected: isActive }} - /> - ); - })} - - ); -} - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - flexWrap: 'wrap', - gap: SPACING.sm, - justifyContent: 'center', - }, - swatch: { - width: 40, - height: 40, - borderRadius: BORDER_RADIUS.full, - borderWidth: 2, - borderColor: COLORS.transparent, - }, - swatchActive: { - borderColor: COLORS.white, - transform: [{ scale: 1.15 }], - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.4, - shadowRadius: 4, - elevation: 6, - }, -}); diff --git a/apps/mobile/src/components/EmptyState.tsx b/apps/mobile/src/components/EmptyState.tsx deleted file mode 100644 index 2ad886db..00000000 --- a/apps/mobile/src/components/EmptyState.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import { COLORS, SPACING, FONT_SIZE } from '../theme/tokens'; - -interface EmptyStateProps { - emoji?: string; - title: string; - description?: string; -} - -export const EmptyState: React.FC = ({ emoji, title, description }) => ( - - {emoji ? {emoji} : null} - {title} - {description ? {description} : null} - -); - -const styles = StyleSheet.create({ - container: { - alignItems: 'center', - paddingVertical: SPACING.xxl, - paddingHorizontal: SPACING.lg, - }, - emoji: { - fontSize: 48, - marginBottom: SPACING.md, - }, - title: { - fontSize: FONT_SIZE.lg, - fontWeight: '700', - color: COLORS.textPrimary, - textAlign: 'center', - }, - description: { - marginTop: SPACING.xs, - fontSize: FONT_SIZE.sm, - color: COLORS.textMuted, - textAlign: 'center', - lineHeight: 20, - }, -}); diff --git a/apps/mobile/src/components/LoadingPlaceholder.tsx b/apps/mobile/src/components/LoadingPlaceholder.tsx deleted file mode 100644 index 22f5b211..00000000 --- a/apps/mobile/src/components/LoadingPlaceholder.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { View, StyleSheet } from 'react-native'; -import { Skeleton } from './Skeleton'; -import { SPACING, BORDER_RADIUS, COLORS } from '../theme/tokens'; - -interface LoadingPlaceholderProps { - rows?: number; -} - -export const LoadingPlaceholder: React.FC = ({ rows = 3 }) => ( - - {Array.from({ length: rows }).map((_, index) => ( - - - - - - - - ))} - -); - -const styles = StyleSheet.create({ - container: { - padding: SPACING.lg, - }, - item: { - flexDirection: 'row', - alignItems: 'center', - gap: SPACING.md, - marginBottom: SPACING.md, - backgroundColor: COLORS.bgCard, - borderRadius: BORDER_RADIUS.lg, - padding: SPACING.md, - }, - textColumn: { - flex: 1, - justifyContent: 'center', - }, - secondLine: { - marginTop: SPACING.xs, - }, -}); diff --git a/apps/mobile/src/components/Skeleton.tsx b/apps/mobile/src/components/Skeleton.tsx index 4c65e855..23f52d27 100644 --- a/apps/mobile/src/components/Skeleton.tsx +++ b/apps/mobile/src/components/Skeleton.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useRef } from 'react'; -import { Animated, StyleSheet, ViewStyle, DimensionValue } from 'react-native'; +import { View, Animated, StyleSheet, ViewStyle } from 'react-native'; import { COLORS } from '../theme/tokens'; interface SkeletonProps { - width?: DimensionValue; - height?: DimensionValue; + width?: number | string; + height?: number | string; borderRadius?: number; style?: ViewStyle; } diff --git a/apps/mobile/src/config.ts b/apps/mobile/src/config.ts index 3ef038e2..7d3e7dda 100644 --- a/apps/mobile/src/config.ts +++ b/apps/mobile/src/config.ts @@ -1,22 +1,12 @@ -import { Platform } from 'react-native'; +// DevCard API Configuration -// ── DevCard API Configuration ───────────────────────────────────────────────── -// Environment-aware URLs with no Expo dependency. On Android emulators the -// loopback address is 10.0.2.2; on iOS simulators localhost works directly. - -const ANDROID_LOCALHOST = '10.0.2.2'; -const IOS_LOCALHOST = 'localhost'; -const DEV_HOST = Platform.OS === 'android' ? ANDROID_LOCALHOST : IOS_LOCALHOST; - -export const API_BASE_URL: string = __DEV__ - ? `http://${DEV_HOST}:3000` +// For Android emulator, use localhost with 'adb reverse tcp:3000 tcp:3000' +export const API_BASE_URL = __DEV__ + ? 'http://localhost:3000' : 'https://api.devcard.dev'; -export const APP_URL: string = __DEV__ +export const APP_URL = __DEV__ ? 'http://localhost:5173' : 'https://devcard.dev'; -// Deep link scheme — must match android/app/build.gradle and ios/Info.plist -export const DEEP_LINK_SCHEME = 'devcard'; - -export const OAUTH_REDIRECT_URI = `${DEEP_LINK_SCHEME}://oauth/callback`; +export const OAUTH_REDIRECT_URI = 'devcard://oauth/callback'; diff --git a/apps/mobile/src/context/AuthContext.tsx b/apps/mobile/src/context/AuthContext.tsx index 343e103c..77559e4c 100644 --- a/apps/mobile/src/context/AuthContext.tsx +++ b/apps/mobile/src/context/AuthContext.tsx @@ -1,13 +1,5 @@ -import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { get } from '../services/api'; - -// ── Storage Keys ────────────────────────────────────────────────────────────── - -const TOKEN_KEY = 'devcard.auth.token'; -const FIRST_LAUNCH_KEY = 'devcard.firstLaunch'; - -// ── Types ───────────────────────────────────────────────────────────────────── +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { API_BASE_URL } from '../config'; interface User { id: string; @@ -28,99 +20,59 @@ interface AuthContextType { token: string | null; isAuthenticated: boolean; isLoading: boolean; - isFirstLaunch: boolean; login: (token: string) => Promise; - logout: () => Promise; + logout: () => void; refreshUser: () => Promise; } -// ── Context ─────────────────────────────────────────────────────────────────── - const AuthContext = createContext(undefined); export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); const [token, setToken] = useState(null); const [isLoading, setIsLoading] = useState(true); - const [isFirstLaunch, setIsFirstLaunch] = useState(false); - - // ── Hydrate token from AsyncStorage on mount ── useEffect(() => { - const hydrate = async () => { - try { - const [storedToken, launchFlag] = await Promise.all([ - AsyncStorage.getItem(TOKEN_KEY), - AsyncStorage.getItem(FIRST_LAUNCH_KEY), - ]); - - if (launchFlag === null) { - setIsFirstLaunch(true); - await AsyncStorage.setItem(FIRST_LAUNCH_KEY, 'false'); - } - - if (storedToken) { - setToken(storedToken); - // Validate token by fetching profile - const userData = await get('/api/profiles/me', storedToken).catch(() => null); - if (userData) { - setUser(userData); - } else { - // Token expired or invalid — clear it - await AsyncStorage.removeItem(TOKEN_KEY); - setToken(null); - } - } - } catch (error) { - console.error('Auth hydration failed:', error); - } finally { - setIsLoading(false); - } - }; - - hydrate(); + // TODO: Load token from secure storage on app start + setIsLoading(false); }, []); - // ── Login ── - - const login = useCallback(async (newToken: string) => { + const login = async (newToken: string) => { setToken(newToken); + // TODO: Save token to secure storage try { - await AsyncStorage.setItem(TOKEN_KEY, newToken); - const userData = await get('/api/profiles/me', newToken).catch(() => null); - if (userData) { + const res = await fetch(`${API_BASE_URL}/api/profiles/me`, { + headers: { Authorization: `Bearer ${newToken}` }, + }); + if (res.ok) { + const userData = await res.json(); setUser(userData); } - } catch (error) { - console.error('Failed to persist token or fetch user:', error); + } catch (err) { + console.error('Failed to fetch user:', err); } - }, []); - - // ── Logout ── + }; - const logout = useCallback(async () => { + const logout = () => { setToken(null); setUser(null); - try { - await AsyncStorage.removeItem(TOKEN_KEY); - } catch (error) { - console.error('Failed to clear stored token:', error); - } - }, []); - - // ── Refresh User ── + // TODO: Clear token from secure storage + }; - const refreshUser = useCallback(async () => { + const refreshUser = async () => { if (!token) return; try { - const userData = await get('/api/profiles/me', token).catch(() => null); - if (userData) { + const res = await fetch(`${API_BASE_URL}/api/profiles/me`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (res.ok) { + const userData = await res.json(); setUser(userData); } - } catch (error) { - console.error('Failed to refresh user:', error); + } catch (err) { + console.error('Failed to refresh user:', err); } - }, [token]); + }; return ( { - data: T | null; - loading: boolean; - error: string | null; - refetch: () => Promise; -} - -interface UseApiQueryOptions { - /** Skip the initial fetch (useful for conditional queries) */ - skip?: boolean; -} - -export function useApiQuery( - path: string, - options: UseApiQueryOptions = {}, -): UseApiQueryResult { - const { token, logout } = useAuth(); - const [data, setData] = useState(null); - const [loading, setLoading] = useState(!options.skip); - const [error, setError] = useState(null); - - const fetchData = useCallback(async () => { - if (!token) { - setLoading(false); - return; - } - setLoading(true); - setError(null); - try { - const result = await apiRequest(path, { - method: 'GET', - token, - onUnauthorized: logout, - }); - setData(result); - } catch (err: unknown) { - const message = err instanceof Error ? err.message : 'Request failed'; - setError(message); - console.error(`useApiQuery(${path}):`, message); - } finally { - setLoading(false); - } - }, [path, token, logout]); - - useEffect(() => { - if (!options.skip) { - fetchData(); - } - }, [fetchData, options.skip]); - - return { data, loading, error, refetch: fetchData }; -} diff --git a/apps/mobile/src/hooks/useContacts.ts b/apps/mobile/src/hooks/useContacts.ts deleted file mode 100644 index 99b59b2a..00000000 --- a/apps/mobile/src/hooks/useContacts.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { useState, useEffect, useCallback } from 'react'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import type { SavedContact } from '../types'; - -// ── Storage Key ─────────────────────────────────────────────────────────────── - -const CONTACTS_KEY = 'devcard.contacts'; - -// ── Hook ────────────────────────────────────────────────────────────────────── - -interface UseContactsResult { - contacts: SavedContact[]; - loading: boolean; - saveContact: (contact: Omit) => Promise; - removeContact: (username: string) => Promise; - isContactSaved: (username: string) => boolean; - refetch: () => Promise; -} - -export function useContacts(): UseContactsResult { - const [contacts, setContacts] = useState([]); - const [loading, setLoading] = useState(true); - - const loadContacts = useCallback(async () => { - try { - const raw = await AsyncStorage.getItem(CONTACTS_KEY); - if (raw) { - const parsed: SavedContact[] = JSON.parse(raw); - // Sort by most recently saved first - parsed.sort((a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime()); - setContacts(parsed); - } else { - setContacts([]); - } - } catch (error) { - console.error('Failed to load contacts:', error); - setContacts([]); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - loadContacts(); - }, [loadContacts]); - - const persistContacts = async (updated: SavedContact[]) => { - try { - await AsyncStorage.setItem(CONTACTS_KEY, JSON.stringify(updated)); - setContacts(updated); - } catch (error) { - console.error('Failed to persist contacts:', error); - } - }; - - const saveContact = useCallback( - async (contact: Omit) => { - const existing = contacts.filter((c) => c.username !== contact.username); - const newContact: SavedContact = { - ...contact, - savedAt: new Date().toISOString(), - }; - const updated = [newContact, ...existing]; - await persistContacts(updated); - }, - [contacts], - ); - - const removeContact = useCallback( - async (username: string) => { - const updated = contacts.filter((c) => c.username !== username); - await persistContacts(updated); - }, - [contacts], - ); - - const isContactSaved = useCallback( - (username: string) => contacts.some((c) => c.username === username), - [contacts], - ); - - return { - contacts, - loading, - saveContact, - removeContact, - isContactSaved, - refetch: loadContacts, - }; -} diff --git a/apps/mobile/src/navigation/MainTabs.tsx b/apps/mobile/src/navigation/MainTabs.tsx index 74cb88af..11e4e9a4 100644 --- a/apps/mobile/src/navigation/MainTabs.tsx +++ b/apps/mobile/src/navigation/MainTabs.tsx @@ -11,49 +11,26 @@ import SettingsScreen from '../screens/SettingsScreen'; import ScanScreen from '../screens/ScanScreen'; import DevCardViewScreen from '../screens/DevCardViewScreen'; import WebViewScreen from '../screens/WebViewScreen'; -import ContactsScreen from '../screens/ContactsScreen'; -import EventsScreen from '../screens/EventsScreen'; -import EventDetailScreen from '../screens/EventDetailScreen'; -import TeamsScreen from '../screens/TeamsScreen'; -import TeamDetailScreen from '../screens/TeamDetailScreen'; -import NfcScreen from '../screens/NfcScreen'; -import { ConnectPlatformsScreen } from '../screens/ConnectPlatformsScreen'; -import { ViewsScreen } from '../screens/ViewsScreen'; +import ConnectPlatformsScreen from '../screens/ConnectPlatformsScreen'; +import ViewsScreen from '../screens/ViewsScreen'; // ─── Types ─── export type MainTabsParamList = { Home: undefined; - Contacts: undefined; + Links: undefined; Scan: undefined; Cards: undefined; Settings: undefined; }; -// Standalone type for WebViewConnect route params — exported for reuse in -// WebViewScreen, DevCardViewScreen, or any future screen that navigates here. -export type WebViewConnectParams = { - platform: string; - url: string; - platformName: string; - username?: string; - linkId?: string; - cardOwnerUsername: string; -}; - export type RootStackParamList = { MainTabs: undefined; - DevCardView: { username: string; followSuccessLinkId?: string }; - WebViewConnect: WebViewConnectParams; + DevCardView: { username: string }; + WebViewConnect: { platform: string; profileUrl: string; displayName: string }; ConnectPlatforms: undefined; Views: undefined; - Links: undefined; - Events: undefined; - EventDetail: { slug: string; name: string }; - Teams: undefined; - TeamDetail: { slug: string; name: string }; - Nfc: undefined; }; // ─── Tab Bar Icon ─── @@ -61,7 +38,7 @@ export type RootStackParamList = { function TabIcon({ name, focused }: { name: string; focused: boolean }) { const icons: Record = { Home: '🏠', - Contacts: '📇', + Links: '🔗', Scan: '📷', Cards: '💳', Settings: '⚙️', @@ -75,14 +52,6 @@ function TabIcon({ name, focused }: { name: string; focused: boolean }) { ); } -function ScanButton() { - return ( - - 📷 - - ); -} - // ─── Tab Navigator ─── const Tab = createBottomTabNavigator(); @@ -101,13 +70,17 @@ function TabNavigator() { ), })}> - + , + tabBarIcon: ({ focused }) => ( + + 📷 + + ), }} /> @@ -144,12 +117,6 @@ export default function MainTabs() { component={ViewsScreen} options={{ title: 'Card Views Analytics', headerShown: true, headerStyle: { backgroundColor: COLORS.bgPrimary }, headerTintColor: COLORS.textPrimary }} /> - - - - - - ); } diff --git a/apps/mobile/src/screens/CardsScreen.tsx b/apps/mobile/src/screens/CardsScreen.tsx index 6953ffd2..023ceb42 100644 --- a/apps/mobile/src/screens/CardsScreen.tsx +++ b/apps/mobile/src/screens/CardsScreen.tsx @@ -16,9 +16,7 @@ import { useFocusEffect } from '@react-navigation/native'; import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; import { useAuth } from '../context/AuthContext'; import { PLATFORMS } from '@devcard/shared'; -import { get, post, del, put } from '../services/api'; -import { EmptyState } from '../components/EmptyState'; -import { Skeleton } from '../components/Skeleton'; +import { API_BASE_URL } from '../config'; interface PlatformLink { id: string; @@ -41,22 +39,26 @@ export default function CardsScreen() { const [newTitle, setNewTitle] = useState(''); const [selectedLinkIds, setSelectedLinkIds] = useState([]); const [refreshing, setRefreshing] = useState(false); - const [loading, setLoading] = useState(true); - const fetchData = useCallback(async (showLoading = true) => { - if (showLoading) setLoading(true); + const fetchData = useCallback(async () => { try { - const [cardsData, profileData] = await Promise.all([ - get('/api/cards', token).catch(() => []), - get('/api/profiles/me', token).catch(() => null), + const [cardsRes, profileRes] = await Promise.all([ + fetch(`${API_BASE_URL}/api/cards`, { + headers: { Authorization: `Bearer ${token}` }, + }), + fetch(`${API_BASE_URL}/api/profiles/me`, { + headers: { Authorization: `Bearer ${token}` }, + }), ]); - setCards(cardsData || []); - setAllLinks(profileData?.platformLinks || []); - } catch (error) { - console.error('Failed to fetch:', error); + if (cardsRes.ok) setCards(await cardsRes.json()); + if (profileRes.ok) { + const data = await profileRes.json(); + setAllLinks(data.platformLinks || []); + } + } catch (err) { + console.error('Failed to fetch:', err); } finally { setRefreshing(false); - if (showLoading) setLoading(false); } }, [token]); @@ -68,7 +70,7 @@ export default function CardsScreen() { const onRefresh = () => { setRefreshing(true); - fetchData(false); + fetchData(); }; const createCard = async () => { @@ -77,12 +79,21 @@ export default function CardsScreen() { return; } try { - await post('/api/cards', { title: newTitle.trim(), linkIds: selectedLinkIds }, token); - setShowCreate(false); - setNewTitle(''); - setSelectedLinkIds([]); - fetchData(); - } catch { + const res = await fetch(`${API_BASE_URL}/api/cards`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ title: newTitle.trim(), linkIds: selectedLinkIds }), + }); + if (res.ok) { + setShowCreate(false); + setNewTitle(''); + setSelectedLinkIds([]); + fetchData(); + } + } catch (err) { Alert.alert('Error', 'Failed to create card'); } }; @@ -94,11 +105,10 @@ export default function CardsScreen() { text: 'Delete', style: 'destructive', onPress: async () => { - try { - await del(`/api/cards/${id}`, undefined, token); - } catch { - // ignore - } + await fetch(`${API_BASE_URL}/api/cards/${id}`, { + method: 'DELETE', + headers: { Authorization: `Bearer ${token}` }, + }); fetchData(); }, }, @@ -106,11 +116,10 @@ export default function CardsScreen() { }; const setDefault = async (id: string) => { - try { - await put(`/api/cards/${id}/default`, undefined, token); - } catch { - // ignore - } + await fetch(`${API_BASE_URL}/api/cards/${id}/default`, { + method: 'PUT', + headers: { Authorization: `Bearer ${token}` }, + }); fetchData(); }; @@ -122,29 +131,6 @@ export default function CardsScreen() { ); }; - if (loading) { - return ( - - - - - - - - {[1, 2].map((item) => ( - - - - - - - - ))} - - - ); - } - return ( @@ -225,11 +211,11 @@ export default function CardsScreen() { )} ListEmptyComponent={ - + + 💳 + No cards yet + Create context cards for different situations + } /> @@ -297,19 +283,6 @@ const styles = StyleSheet.create({ emptyEmoji: { fontSize: 48, marginBottom: SPACING.md }, emptyText: { fontSize: FONT_SIZE.lg, fontWeight: '600', color: COLORS.textPrimary }, emptySubtext: { fontSize: FONT_SIZE.sm, color: COLORS.textMuted, marginTop: SPACING.xs }, - loadingList: { paddingHorizontal: SPACING.lg }, - loadingCard: { - borderRadius: BORDER_RADIUS.lg, - backgroundColor: COLORS.bgCard, - padding: SPACING.md, - marginBottom: SPACING.lg, - }, - loadingActionRow: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - marginTop: SPACING.md, - }, modalOverlay: { flex: 1, backgroundColor: COLORS.overlay, justifyContent: 'flex-end' }, modalContent: { backgroundColor: COLORS.bgSecondary, borderTopLeftRadius: BORDER_RADIUS.xl, diff --git a/apps/mobile/src/screens/ConnectPlatformsScreen.tsx b/apps/mobile/src/screens/ConnectPlatformsScreen.tsx index 2e59ed11..8b359ca7 100644 --- a/apps/mobile/src/screens/ConnectPlatformsScreen.tsx +++ b/apps/mobile/src/screens/ConnectPlatformsScreen.tsx @@ -1,12 +1,10 @@ import React, { useState, useEffect, useCallback } from 'react'; -import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert, Linking } from 'react-native'; +import { View, Text, StyleSheet, ScrollView, ActivityIndicator, TouchableOpacity, Alert, Linking } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS } from '../theme/tokens'; import { useAuth } from '../context/AuthContext'; import { API_BASE_URL } from '../config'; -import { get, del } from '../services/api'; -import { LoadingPlaceholder } from '../components/LoadingPlaceholder'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../navigation/MainTabs'; @@ -29,10 +27,15 @@ export const ConnectPlatformsScreen: React.FC = ({ navigation: _navigatio return; } try { - const data = await get('/api/connect/status', token).catch(() => null); - setConnectedPlatforms(data?.connectedPlatforms || []); - } catch (error) { - console.error('Failed to fetch connected platforms', error); + const response = await fetch(`${API_BASE_URL}/api/connect/status`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (response.ok) { + const data = await response.json(); + setConnectedPlatforms(data.connectedPlatforms || []); + } + } catch (err) { + console.error('Failed to fetch connected platforms', err); } finally { setLoading(false); } @@ -75,8 +78,15 @@ export const ConnectPlatformsScreen: React.FC = ({ navigation: _navigatio onPress: async () => { try { if (!token) return; - await del(`/api/connect/${platform}`, undefined, token); - fetchConnections(); + const response = await fetch(`${API_BASE_URL}/api/connect/${platform}`, { + method: 'DELETE', + headers: { Authorization: `Bearer ${token}` }, + }); + if (response.ok) { + fetchConnections(); + } else { + Alert.alert('Error', 'Failed to disconnect'); + } } catch { Alert.alert('Error', 'Failed to disconnect'); } @@ -126,9 +136,9 @@ export const ConnectPlatformsScreen: React.FC = ({ navigation: _navigatio if (loading) { return ( - - - + + + ); } diff --git a/apps/mobile/src/screens/ContactsScreen.tsx b/apps/mobile/src/screens/ContactsScreen.tsx deleted file mode 100644 index a657592a..00000000 --- a/apps/mobile/src/screens/ContactsScreen.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import React, { useCallback } from 'react'; -import { - View, - Text, - StyleSheet, - FlatList, - TouchableOpacity, - Alert, - StatusBar, -} from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import { useFocusEffect } from '@react-navigation/native'; -import Avatar from '../components/Avatar'; -import { EmptyState } from '../components/EmptyState'; -import { LoadingPlaceholder } from '../components/LoadingPlaceholder'; -import { useContacts } from '../hooks/useContacts'; -import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS } from '../theme/tokens'; -import type { SavedContact } from '../types'; -import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import type { RootStackParamList } from '../navigation/MainTabs'; - -type Props = { - navigation: NativeStackNavigationProp; -}; - -export default function ContactsScreen({ navigation }: Props) { - const { contacts, loading, removeContact, refetch } = useContacts(); - - useFocusEffect( - useCallback(() => { - refetch(); - }, [refetch]), - ); - - const handlePress = (contact: SavedContact) => { - navigation.navigate('DevCardView', { username: contact.username }); - }; - - const handleRemove = (contact: SavedContact) => { - Alert.alert( - 'Remove Contact', - `Remove ${contact.displayName} from saved contacts?`, - [ - { text: 'Cancel', style: 'cancel' }, - { - text: 'Remove', - style: 'destructive', - onPress: () => removeContact(contact.username), - }, - ], - ); - }; - - const formatDate = (dateString: string) => { - const d = new Date(dateString); - return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); - }; - - if (loading) { - return ( - - - - - ); - } - - return ( - - - - - Saved Contacts - {contacts.length} - - - item.username} - contentContainerStyle={styles.list} - renderItem={({ item }) => ( - handlePress(item)} - onLongPress={() => handleRemove(item)} - activeOpacity={0.7}> - - - - {item.displayName} - - {item.role || item.company ? ( - - {[item.role, item.company].filter(Boolean).join(' · ')} - - ) : null} - {item.metAt ? ( - - Met at {item.metAt} - - ) : null} - - - - {formatDate(item.savedAt)} - - - )} - ListEmptyComponent={ - - } - /> - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: COLORS.bgPrimary }, - header: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - padding: SPACING.lg, - paddingBottom: SPACING.md, - }, - title: { fontSize: FONT_SIZE.xl, fontWeight: '800', color: COLORS.textPrimary }, - count: { - fontSize: FONT_SIZE.sm, - fontWeight: '700', - color: COLORS.textMuted, - backgroundColor: COLORS.bgElevated, - borderRadius: BORDER_RADIUS.full, - paddingHorizontal: SPACING.sm, - paddingVertical: 2, - overflow: 'hidden', - }, - list: { padding: SPACING.lg, gap: SPACING.sm, paddingTop: 0 }, - contactItem: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: COLORS.bgCard, - borderRadius: BORDER_RADIUS.md, - padding: SPACING.md, - borderWidth: 1, - borderColor: COLORS.border, - }, - avatar: { - width: 48, - height: 48, - borderRadius: 24, - marginRight: SPACING.md, - }, - info: { flex: 1 }, - name: { fontSize: FONT_SIZE.md, fontWeight: '600', color: COLORS.textPrimary }, - detail: { fontSize: FONT_SIZE.sm, color: COLORS.textSecondary, marginTop: 2 }, - metAt: { fontSize: FONT_SIZE.xs, color: COLORS.primary, marginTop: 2 }, - meta: { alignItems: 'flex-end', gap: 4 }, - accentDot: { width: 10, height: 10, borderRadius: 5 }, - date: { fontSize: FONT_SIZE.xs, color: COLORS.textMuted }, -}); diff --git a/apps/mobile/src/screens/DevCardViewScreen.tsx b/apps/mobile/src/screens/DevCardViewScreen.tsx index 7d6de992..46cf9519 100644 --- a/apps/mobile/src/screens/DevCardViewScreen.tsx +++ b/apps/mobile/src/screens/DevCardViewScreen.tsx @@ -1,10 +1,11 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, + Image, Linking, Clipboard, StatusBar, @@ -14,12 +15,9 @@ import { import { SafeAreaView } from 'react-native-safe-area-context'; import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; import { Skeleton } from '../components/Skeleton'; -import { EmptyState } from '../components/EmptyState'; -import Avatar from '../components/Avatar'; import { PLATFORMS, getProfileUrl, getWebViewUrl } from '@devcard/shared'; -import { get, post, del } from '../services/api'; +import { API_BASE_URL } from '../config'; import { useAuth } from '../context/AuthContext'; -import { useContacts } from '../hooks/useContacts'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import type { RouteProp } from '@react-navigation/native'; import type { RootStackParamList } from '../navigation/MainTabs'; @@ -52,106 +50,29 @@ interface ProfileData { type FollowState = Record; -// ─── Platform Emoji Icon Map ─── -const PLATFORM_EMOJI: Record = { - github: '🐙', - linkedin: 'in', - twitter: '𝕏', - gitlab: '🦊', - devfolio: '🏗️', - npm: '📦', - devto: '👩‍💻', - hashnode: '📝', - medium: 'M', - leetcode: '🏆', - hackerrank: '⚔️', - stackoverflow: '💬', - discord: '🎮', - telegram: '✈️', - email: '✉️', - portfolio: '🌐', - custom: '🔗', -}; - -// ─── Brand-colored action buttons ─── -const PLATFORM_BTN_COLOR: Record = { - github: '#238636', - linkedin: '#0A66C2', - twitter: '#1D9BF0', - gitlab: '#FC6D26', - devfolio: '#3770FF', - npm: '#CB3837', - devto: '#3B49DF', - leetcode: '#FFA116', - hackerrank: '#00B86B', - stackoverflow: '#F58025', - discord: '#5865F2', - telegram: '#26A5E4', - email: '#EA4335', - portfolio: '#6366F1', -}; - export default function DevCardViewScreen({ navigation, route }: Props) { const { username } = route.params; const { token } = useAuth(); - const { isContactSaved, saveContact, removeContact } = useContacts(); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); const [followStates, setFollowStates] = useState({}); - const isSaved = isContactSaved(username); - - const handleSaveContact = async () => { - if (!profile) return; - if (isSaved) { - await removeContact(username); - } else { - await saveContact({ - username: profile.username, - displayName: profile.displayName, - avatarUrl: profile.avatarUrl, - accentColor: profile.accentColor || COLORS.primary, - bio: profile.bio, - role: profile.role, - company: profile.company, - metAt: 'DevCard App', - note: null, - }); - Alert.alert('Saved!', `${profile.displayName} has been added to your contacts.`); - } - }; + useEffect(() => { + fetchProfile(); + }, [username]); - const fetchProfile = useCallback(async () => { + const fetchProfile = async () => { try { - const data = await get(`/api/u/${username}`, token); - if (data) { - setProfile(data); - const initialFollowStates: FollowState = {}; - if (data.links) { - data.links.forEach((link: any) => { - if (link.followed) initialFollowStates[link.id] = 'success'; - }); - } - setFollowStates(initialFollowStates); + const res = await fetch(`${API_BASE_URL}/api/u/${username}`); + if (res.ok) { + setProfile(await res.json()); } - } catch (error) { - console.error('Failed to fetch profile:', error); + } catch (err) { + console.error('Failed to fetch profile:', err); } finally { setLoading(false); } - }, [token, username]); - - useEffect(() => { - fetchProfile(); - }, [fetchProfile]); - - const successLinkId = route.params?.followSuccessLinkId; - useEffect(() => { - if (successLinkId) { - setFollowStates(prev => ({ ...prev, [successLinkId]: 'success' })); - navigation.setParams({ followSuccessLinkId: undefined } as any); - } - }, [navigation, successLinkId]); + }; // ─── Hybrid Follow Engine ─── @@ -163,26 +84,17 @@ export default function DevCardViewScreen({ navigation, route }: Props) { switch (strategy) { case 'api': + // Layer 1: Silent API follow await handleApiFollow(link); break; case 'webview': - setFollowStates(prev => ({ ...prev, [link.id]: 'loading' })); - try { - const data = await post(`/api/follow/${link.platform}/${link.username}`, undefined, token); - setFollowStates(prev => ({ ...prev, [link.id]: 'idle' })); - if (data?.strategy === 'webview') { - handleWebViewConnect(link, data.url); - } else { - setFollowStates(prev => ({ ...prev, [link.id]: 'success' })); - } - } catch { - setFollowStates(prev => ({ ...prev, [link.id]: 'idle' })); - handleWebViewConnect(link); - } + // Layer 2: WebView connect + handleWebViewConnect(link); break; case 'copy': + // Copy to clipboard (Discord) Clipboard.setString(link.username); Alert.alert('Copied!', `${link.username} copied to clipboard`); setFollowStates(prev => ({ ...prev, [link.id]: 'success' })); @@ -190,6 +102,7 @@ export default function DevCardViewScreen({ navigation, route }: Props) { case 'link': default: + // Layer 3: Open in browser/app const url = link.url || getProfileUrl(link.platform, link.username); if (url) { Linking.openURL(url).catch(() => @@ -204,49 +117,40 @@ export default function DevCardViewScreen({ navigation, route }: Props) { const handleApiFollow = async (link: PlatformLink) => { setFollowStates(prev => ({ ...prev, [link.id]: 'loading' })); try { - await post(`/api/follow/${link.platform}/${link.username}`, undefined, token); - setFollowStates(prev => ({ ...prev, [link.id]: 'success' })); - } catch (err: any) { - const msg = (err && err.message) || ''; - if (msg.includes('requiresAuth')) { - setFollowStates(prev => ({ ...prev, [link.id]: 'idle' })); - const webViewUrl = getWebViewUrl(link.platform, link.username); - if (webViewUrl) { + const res = await fetch( + `${API_BASE_URL}/api/follow/${link.platform}/${link.username}`, + { + method: 'POST', + headers: { Authorization: `Bearer ${token}` }, + } + ); + if (res.ok) { + setFollowStates(prev => ({ ...prev, [link.id]: 'success' })); + } else { + const data = await res.json(); + if (data.requiresAuth) { + // Fall back to WebView if token missing handleWebViewConnect(link); } else { - const profileUrl = link.url || getProfileUrl(link.platform, link.username); - if (profileUrl) Linking.openURL(profileUrl).catch(() => Alert.alert('Error', `Could not open ${link.platform} profile`)); + setFollowStates(prev => ({ ...prev, [link.id]: 'error' })); } - } else { - setFollowStates(prev => ({ ...prev, [link.id]: 'error' })); } - } - }; - - // Reset a "Done" tile — clears follow log from backend and resets local state - const handleResetFollowState = async (link: PlatformLink) => { - try { - await del(`/api/follow/${link.platform}/${link.username}/log`, undefined, token); } catch { - // ignore + setFollowStates(prev => ({ ...prev, [link.id]: 'error' })); } - setFollowStates(prev => ({ ...prev, [link.id]: 'idle' })); }; // Layer 2: WebView-based connect - const handleWebViewConnect = (link: PlatformLink, resolvedUrl?: string) => { + const handleWebViewConnect = (link: PlatformLink) => { const webViewUrl = getWebViewUrl(link.platform, link.username); const profileUrl = link.url || getProfileUrl(link.platform, link.username); - const url = resolvedUrl || webViewUrl || profileUrl; + const url = webViewUrl || profileUrl; if (url) { navigation.navigate('WebViewConnect', { platform: link.platform, - url, - platformName: PLATFORMS[link.platform]?.name || link.platform, - username: link.username, - linkId: link.id, - cardOwnerUsername: username, + profileUrl: url, + displayName: PLATFORMS[link.platform]?.name || link.platform, }); } }; @@ -269,27 +173,20 @@ export default function DevCardViewScreen({ navigation, route }: Props) { } }; - const getButtonColor = (link: PlatformLink, state: string): string => { - if (state === 'success') return COLORS.success; - if (state === 'loading') return COLORS.primaryDark; - if (state === 'error') return '#DC2626'; - return PLATFORM_BTN_COLOR[link.platform] || COLORS.primary; - }; - if (loading) { return ( {/* Header Skeleton */} - + - + @@ -301,15 +198,15 @@ export default function DevCardViewScreen({ navigation, route }: Props) { {/* Tiles Skeleton */} - + {[1, 2, 3].map(i => ( - - - + + + - + ))} @@ -341,138 +238,93 @@ export default function DevCardViewScreen({ navigation, route }: Props) { - {/* Save Contact Button */} - {profile && ( - - - {isSaved ? 'Saved' : 'Save'} - - - )} - - {/* Profile Card */} - - {/* Gradient layers */} - + {/* Profile Card — PREMIUM REDESIGN */} + - - {/* Top row: brand + contactless */} + - + DevCard PRO - - PLATINUM - + 📶 - {/* Middle: avatar + name/role */} - - + + {profile.avatarUrl ? ( + + ) : ( + + + {profile.displayName.charAt(0).toUpperCase()} + + + )} - {profile.displayName} - {(profile.role || profile.company) && ( - - {profile.role}{profile.company ? ` @ ${profile.company}` : ''} - - )} + {profile.displayName} + + {profile.role}{profile.company ? ` @ ${profile.company}` : ''} + {profile.pronouns && ( {profile.pronouns} )} - {/* Bottom: bio + divider */} - {profile.bio ? ( - - - {profile.bio} + + + {profile.bio && {profile.bio}} - ) : null} + + PLATINUM + + {/* Platform Tiles Section */} - - Digital Touchpoints - - {profile.links.length} - - - - {profile.links.length === 0 ? ( - - - - ) : profile.links.map(link => { + Digital Touchpoints + {profile.links.map(link => { const platform = PLATFORMS[link.platform]; const state = followStates[link.id] || 'idle'; - const btnColor = getButtonColor(link, state); - const isDone = state === 'success'; - const tileIconDynamic = isDone - ? { backgroundColor: 'rgba(34,197,94,0.12)', borderColor: COLORS.success } - : { backgroundColor: (platform?.color || COLORS.primary) + '22', borderColor: (platform?.color || COLORS.primary) + '66' }; return ( handlePlatformAction(link)} - onLongPress={() => { - if (isDone) { - Alert.alert( - 'Reset connection?', - `This will clear the "Done" status for ${platform?.name || link.platform}.`, - [ - { text: 'Cancel', style: 'cancel' }, - { - text: 'Reset', - style: 'destructive', - onPress: () => handleResetFollowState(link), - }, - ] - ); - } - }} - activeOpacity={isDone ? 0.9 : 0.8} + activeOpacity={0.8} disabled={state === 'loading'}> - - {/* Icon */} - - {isDone ? ( - - ) : ( - - {PLATFORM_EMOJI[link.platform] || platform?.name.charAt(0) || '?'} - - )} + + + {platform?.name.charAt(0) || '?'} + - - {/* Info */} {platform?.name || link.platform} - {link.username} + {link.username} - - {/* Action Button */} - + {state === 'loading' ? ( ) : ( - {getButtonLabel(link)} + + {getButtonLabel(link)} + )} - ); })} @@ -480,7 +332,6 @@ export default function DevCardViewScreen({ navigation, route }: Props) { {/* Footer */} - Powered by DevCard ⚡ @@ -493,139 +344,159 @@ const styles = StyleSheet.create({ closeBtn: { position: 'absolute', top: 50, right: 20, zIndex: 10, width: 36, height: 36, borderRadius: 18, - backgroundColor: 'rgba(255,255,255,0.08)', - borderWidth: 1, borderColor: 'rgba(255,255,255,0.12)', - alignItems: 'center', justifyContent: 'center', + backgroundColor: COLORS.bgElevated, alignItems: 'center', justifyContent: 'center', }, closeBtnText: { color: COLORS.textSecondary, fontSize: FONT_SIZE.md }, - saveContactBtn: { - position: 'absolute', top: 50, left: 20, zIndex: 10, - paddingHorizontal: SPACING.md, paddingVertical: 8, borderRadius: 18, - backgroundColor: COLORS.primary, - alignItems: 'center', justifyContent: 'center', - }, - saveContactBtnText: { color: COLORS.white, fontSize: FONT_SIZE.sm, fontWeight: '700' }, scrollContent: { padding: SPACING.lg, paddingTop: SPACING.xxl }, premiumHeaderCard: { - backgroundColor: '#0B1120', - borderRadius: 20, - padding: SPACING.lg, + backgroundColor: '#0F172A', + borderRadius: 24, + padding: SPACING.xl, borderWidth: 1, ...SHADOWS.card, marginBottom: SPACING.xl, position: 'relative', overflow: 'hidden', - gap: SPACING.md, - }, - cardGlowTop: { - position: 'absolute', - top: -40, - left: -40, - width: 160, - height: 160, - borderRadius: 80, - backgroundColor: 'rgba(99,102,241,0.12)', + aspectRatio: 1.58, + justifyContent: 'space-between', }, cardGlass: { ...StyleSheet.absoluteFillObject, - backgroundColor: 'rgba(255,255,255,0.015)', + backgroundColor: 'rgba(255, 255, 255, 0.03)', }, cardTop: { - flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + brandRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + }, + miniChip: { + width: 30, + height: 20, + borderRadius: 4, + backgroundColor: '#94A3B8', + opacity: 0.5, + }, + brandText: { + color: 'rgba(255,255,255,0.5)', + fontSize: 10, + fontWeight: '800', + letterSpacing: 2, + }, + contactless: { + fontSize: 20, + opacity: 0.4, + }, + cardMid: { + flexDirection: 'row', + alignItems: 'center', + gap: SPACING.lg, }, - brandRow: { flexDirection: 'row', alignItems: 'center', gap: 7 }, - miniChip: { width: 28, height: 18, borderRadius: 4, opacity: 0.7 }, - brandText: { color: 'rgba(255,255,255,0.45)', fontSize: 9, fontWeight: '800', letterSpacing: 2.5 }, - cardMid: { flexDirection: 'row', alignItems: 'center', gap: SPACING.md }, - avatarRing: { - borderRadius: 38, + avatarContainer: { + ...SHADOWS.card, + shadowOpacity: 0.3, + }, + avatar: { + width: 70, + height: 70, + borderRadius: 35, borderWidth: 2, - padding: 2, + borderColor: 'rgba(255,255,255,0.1)', + }, + avatarPlaceholder: { + alignItems: 'center', + justifyContent: 'center', + }, + avatarText: { + fontSize: 32, + fontWeight: '800', + color: COLORS.white, + }, + mainInfo: { + flex: 1, }, - avatar: { width: 64, height: 64, borderRadius: 32 }, - avatarPlaceholder: { alignItems: 'center', justifyContent: 'center' }, - avatarText: { fontSize: 28, fontWeight: '800', color: COLORS.white }, - mainInfo: { flex: 1, gap: 3 }, profileName: { - fontSize: 20, fontWeight: '800', color: COLORS.white, letterSpacing: 0.2, + fontSize: 24, + fontWeight: '800', + color: COLORS.white, + letterSpacing: 0.5, }, profileRole: { - fontSize: 11, color: 'rgba(255,255,255,0.55)', fontWeight: '500', lineHeight: 15, + fontSize: 12, + color: COLORS.textSecondary, + fontWeight: '600', + marginTop: 2, }, - pronouns: { fontSize: 10, color: COLORS.textMuted, fontStyle: 'italic' }, - cardBottom: { gap: SPACING.xs }, - cardDivider: { - height: 1, backgroundColor: 'rgba(255,255,255,0.06)', marginBottom: 2, + pronouns: { + fontSize: 10, + color: COLORS.textMuted, + marginTop: 4, + fontStyle: 'italic', + }, + cardBottom: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + bioContainer: { + flex: 1, + marginRight: SPACING.md, + }, + bioText: { + fontSize: 10, + color: 'rgba(255,255,255,0.4)', + lineHeight: 14, }, - bioText: { fontSize: 10.5, color: 'rgba(255,255,255,0.38)', lineHeight: 15 }, cardBadge: { - alignSelf: 'flex-start', - paddingHorizontal: 8, paddingVertical: 3, borderRadius: 4, - borderWidth: 1, + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 4, + backgroundColor: 'rgba(255,255,255,0.05)', + borderWidth: 0.5, + borderColor: 'rgba(255,255,255,0.1)', }, - badgeText: { fontSize: 8, fontWeight: '900', letterSpacing: 1.5 }, - - // ─── Tiles ─── - tilesSection: { gap: SPACING.sm }, - tilesHeader: { - flexDirection: 'row', alignItems: 'center', - justifyContent: 'space-between', marginBottom: SPACING.xs, + badgeText: { + fontSize: 8, + fontWeight: '900', + color: 'rgba(255,255,255,0.6)', + letterSpacing: 1.5, }, + tilesSection: { gap: SPACING.sm }, tilesLabel: { - fontSize: FONT_SIZE.xs, color: COLORS.textMuted, fontWeight: '700', - textTransform: 'uppercase', letterSpacing: 1.5, + fontSize: FONT_SIZE.sm, color: COLORS.textMuted, fontWeight: '600', + textTransform: 'uppercase', letterSpacing: 1, marginBottom: SPACING.xs, }, - tilesCount: { - backgroundColor: 'rgba(255,255,255,0.08)', - borderRadius: 10, paddingHorizontal: 8, paddingVertical: 2, - borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)', - }, - tilesCountText: { fontSize: 11, fontWeight: '700', color: COLORS.textMuted }, platformTile: { flexDirection: 'row', alignItems: 'center', backgroundColor: COLORS.bgCard, borderRadius: BORDER_RADIUS.md, padding: SPACING.md, borderWidth: 1, borderColor: COLORS.border, - gap: SPACING.sm, - }, - tileDone: { - borderColor: COLORS.success + '55', - backgroundColor: 'rgba(34, 197, 94, 0.06)', }, + tileDone: { borderColor: COLORS.success, backgroundColor: 'rgba(34, 197, 94, 0.05)' }, tileIcon: { - width: 44, height: 44, borderRadius: 12, + width: 40, height: 40, borderRadius: 10, alignItems: 'center', justifyContent: 'center', }, - tileIconBorder: { borderWidth: 1 }, - tileIconText: { fontWeight: '800', fontSize: 16, letterSpacing: -0.5 }, - tileIconDoneText: { fontWeight: '800', fontSize: 18, color: COLORS.success }, - tileInfo: { flex: 1 }, + tileIconText: { color: COLORS.white, fontWeight: '700', fontSize: FONT_SIZE.md }, + tileInfo: { flex: 1, marginLeft: SPACING.md }, tilePlatform: { fontSize: FONT_SIZE.md, fontWeight: '600', color: COLORS.textPrimary }, tileUsername: { fontSize: FONT_SIZE.sm, color: COLORS.textMuted, marginTop: 1 }, tileAction: { - borderRadius: BORDER_RADIUS.sm, - paddingHorizontal: SPACING.md, paddingVertical: 7, - minWidth: 72, alignItems: 'center', justifyContent: 'center', + backgroundColor: COLORS.primary, borderRadius: BORDER_RADIUS.sm, + paddingHorizontal: SPACING.md, paddingVertical: SPACING.xs, + minWidth: 72, alignItems: 'center', }, - tileActionText: { color: COLORS.white, fontWeight: '700', fontSize: 13 }, - emptyLinksCard: { - backgroundColor: COLORS.bgCard, - borderRadius: BORDER_RADIUS.md, - borderWidth: 1, - borderColor: COLORS.border, - }, - skelMb8: { marginBottom: 8 }, - skelMb12: { marginBottom: 12 }, - skelMb6: { marginBottom: 6 }, - tileInfoMl16: { marginLeft: 16 }, - - // ─── Error / Footer ─── + tileActionDone: { backgroundColor: COLORS.success }, + tileActionLoading: { backgroundColor: COLORS.primaryDark }, + tileActionText: { color: COLORS.white, fontWeight: '700', fontSize: FONT_SIZE.sm }, + tileActionTextDone: {}, errorState: { flex: 1, alignItems: 'center', justifyContent: 'center' }, errorEmoji: { fontSize: 48, marginBottom: SPACING.md }, errorText: { fontSize: FONT_SIZE.lg, color: COLORS.textPrimary, fontWeight: '600' }, backLink: { color: COLORS.primary, fontSize: FONT_SIZE.md, marginTop: SPACING.md }, footer: { alignItems: 'center', paddingVertical: SPACING.xl }, - footerDivider: { - width: 40, height: 1, backgroundColor: 'rgba(255,255,255,0.08)', marginBottom: SPACING.md, - }, - footerText: { fontSize: FONT_SIZE.xs, color: COLORS.textMuted, letterSpacing: 0.5 }, + footerText: { fontSize: FONT_SIZE.xs, color: COLORS.textMuted }, }); diff --git a/apps/mobile/src/screens/EventDetailScreen.tsx b/apps/mobile/src/screens/EventDetailScreen.tsx deleted file mode 100644 index 3b5e2428..00000000 --- a/apps/mobile/src/screens/EventDetailScreen.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { - View, Text, StyleSheet, FlatList, TouchableOpacity, - StatusBar, Alert, -} from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; -import Avatar from '../components/Avatar'; -import { LoadingPlaceholder } from '../components/LoadingPlaceholder'; -import { EmptyState } from '../components/EmptyState'; -import { useAuth } from '../context/AuthContext'; -import { get, post, del } from '../services/api'; -import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; -import type { NativeStackScreenProps } from '@react-navigation/native-stack'; -import type { RootStackParamList } from '../navigation/MainTabs'; - -type Props = NativeStackScreenProps; - -interface Attendee { - id: string; username: string; displayName: string; - bio: string | null; avatarUrl: string | null; accentColor: string; -} - -export default function EventDetailScreen({ route, navigation }: Props) { - const { slug, name } = route.params; - const { token } = useAuth(); - const [event, setEvent] = useState(null); - const [attendees, setAttendees] = useState([]); - const [loading, setLoading] = useState(true); - const [joining, setJoining] = useState(false); - - const fetchEvent = useCallback(async () => { - setLoading(true); - try { - const [detail, atts] = await Promise.all([ - get(`/api/events/${slug}`, token), - get(`/api/events/${slug}/attendees`, token), - ]); - setEvent(detail); - setAttendees(atts?.attendees || []); - } catch { Alert.alert('Error', 'Failed to load event'); } - finally { setLoading(false); } - }, [slug, token]); - - useEffect(() => { fetchEvent(); }, [fetchEvent]); - - const handleJoin = async () => { - setJoining(true); - try { - await post(`/api/events/${slug}/join`, undefined, token); - Alert.alert('Joined!', 'You are now part of this event.'); - fetchEvent(); - } catch (err: unknown) { - const msg = err instanceof Error ? err.message : ''; - Alert.alert(msg.includes('409') ? 'Already Joined' : 'Error', - msg.includes('409') ? 'You are already part of this event.' : 'Failed to join.'); - } finally { setJoining(false); } - }; - - const handleLeave = () => { - Alert.alert('Leave Event', 'Are you sure?', [ - { text: 'Cancel', style: 'cancel' }, - { text: 'Leave', style: 'destructive', onPress: async () => { - try { await del(`/api/events/${slug}/leave`, undefined, token); fetchEvent(); } - catch { Alert.alert('Error', 'Failed to leave event'); } - }}, - ]); - }; - - const fmtDate = (s: string) => new Date(s).toLocaleDateString(undefined, { - weekday: 'short', month: 'short', day: 'numeric', - }); - - if (loading) return ( - - - - - ); - - return ( - - - item.id} - contentContainerStyle={styles.list} - ListHeaderComponent={ - - - {event?.name || name} - {event?.location && ( - - - {event.location} - - )} - - - - {event ? `${fmtDate(event.startDate)} – ${fmtDate(event.endDate)}` : ''} - - - {event?.description && ( - {event.description} - )} - - - - {joining ? 'Joining…' : 'Join Event'} - - - - Leave - - - - - Attendees ({event?.attendeesCount || attendees.length}) - - - } - renderItem={({ item }) => ( - navigation.navigate('DevCardView', { username: item.username })} - activeOpacity={0.7}> - - - {item.displayName} - @{item.username} - - - - )} - ListEmptyComponent={ - - } - /> - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: COLORS.bgPrimary }, - list: { padding: SPACING.lg }, - infoCard: { - backgroundColor: COLORS.bgCard, borderRadius: BORDER_RADIUS.lg, - padding: SPACING.lg, borderWidth: 1, borderColor: COLORS.border, - marginBottom: SPACING.lg, ...SHADOWS.card, - }, - eventName: { fontSize: FONT_SIZE.xl, fontWeight: '800', color: COLORS.textPrimary, marginBottom: SPACING.sm }, - metaRow: { flexDirection: 'row', alignItems: 'center', gap: SPACING.xs, marginBottom: 4 }, - metaText: { fontSize: FONT_SIZE.sm, color: COLORS.textSecondary }, - description: { fontSize: FONT_SIZE.sm, color: COLORS.textSecondary, marginTop: SPACING.sm, lineHeight: 20 }, - actions: { flexDirection: 'row', gap: SPACING.sm, marginTop: SPACING.lg }, - joinBtn: { - flex: 1, backgroundColor: COLORS.primary, borderRadius: BORDER_RADIUS.md, - padding: SPACING.md, alignItems: 'center', ...SHADOWS.button, - }, - joinBtnText: { color: COLORS.white, fontWeight: '700', fontSize: FONT_SIZE.md }, - leaveBtn: { - backgroundColor: COLORS.bgElevated, borderRadius: BORDER_RADIUS.md, - padding: SPACING.md, paddingHorizontal: SPACING.lg, - borderWidth: 1, borderColor: COLORS.border, - }, - leaveBtnText: { color: COLORS.error, fontWeight: '600', fontSize: FONT_SIZE.md }, - sectionTitle: { - fontSize: FONT_SIZE.lg, fontWeight: '700', color: COLORS.textPrimary, - marginBottom: SPACING.md, - }, - attendeeRow: { - flexDirection: 'row', alignItems: 'center', backgroundColor: COLORS.bgCard, - borderRadius: BORDER_RADIUS.md, padding: SPACING.md, marginBottom: SPACING.sm, - borderWidth: 1, borderColor: COLORS.border, - }, - avatar: { width: 40, height: 40, borderRadius: 20, marginRight: SPACING.md }, - attendeeInfo: { flex: 1 }, - attendeeName: { fontSize: FONT_SIZE.md, fontWeight: '600', color: COLORS.textPrimary }, - attendeeUser: { fontSize: FONT_SIZE.sm, color: COLORS.textMuted, marginTop: 1 }, -}); diff --git a/apps/mobile/src/screens/EventsScreen.tsx b/apps/mobile/src/screens/EventsScreen.tsx deleted file mode 100644 index c4dbf7bf..00000000 --- a/apps/mobile/src/screens/EventsScreen.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, { useState, useCallback } from 'react'; -import { - View, Text, StyleSheet, TextInput, TouchableOpacity, - StatusBar, Alert, -} from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; -import { EmptyState } from '../components/EmptyState'; -import { useAuth } from '../context/AuthContext'; -import { get } from '../services/api'; -import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; -import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import type { RootStackParamList } from '../navigation/MainTabs'; - -type Props = { navigation: NativeStackNavigationProp }; - -export default function EventsScreen({ navigation }: Props) { - const { token } = useAuth(); - const [slugInput, setSlugInput] = useState(''); - const [loading, setLoading] = useState(false); - - const handleLookup = async () => { - const slug = slugInput.trim().toLowerCase(); - if (!slug) { Alert.alert('Enter a slug', 'Please enter the event slug or code.'); return; } - setLoading(true); - try { - const event = await get(`/api/events/${slug}`, token); - if (event) navigation.navigate('EventDetail', { slug: event.slug, name: event.name }); - } catch { Alert.alert('Not Found', 'No event found with that code.'); } - finally { setLoading(false); setSlugInput(''); } - }; - - return ( - - - - Events - Join an event to network with attendees - - - - - - - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: COLORS.bgPrimary }, - header: { padding: SPACING.lg, paddingBottom: SPACING.sm }, - title: { fontSize: FONT_SIZE.xl, fontWeight: '800', color: COLORS.textPrimary }, - subtitle: { fontSize: FONT_SIZE.sm, color: COLORS.textSecondary, marginTop: SPACING.xs }, - joinSection: { paddingHorizontal: SPACING.lg, paddingBottom: SPACING.lg }, - inputRow: { flexDirection: 'row', gap: SPACING.sm }, - input: { - flex: 1, backgroundColor: COLORS.bgCard, borderRadius: BORDER_RADIUS.md, - padding: SPACING.md, color: COLORS.textPrimary, fontSize: FONT_SIZE.md, - borderWidth: 1, borderColor: COLORS.border, - }, - searchBtn: { - backgroundColor: COLORS.primary, borderRadius: BORDER_RADIUS.md, - width: 48, alignItems: 'center', justifyContent: 'center', ...SHADOWS.button, - }, - disabled: { opacity: 0.5 }, -}); diff --git a/apps/mobile/src/screens/HomeScreen.tsx b/apps/mobile/src/screens/HomeScreen.tsx index b4d504b2..80de203c 100644 --- a/apps/mobile/src/screens/HomeScreen.tsx +++ b/apps/mobile/src/screens/HomeScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect } from 'react'; import { View, Text, @@ -7,18 +7,15 @@ import { TouchableOpacity, Share, StatusBar, + Image, RefreshControl, - TextInput, } from 'react-native'; -import { Skeleton } from '../components/Skeleton'; -import Avatar from '../components/Avatar'; import { SafeAreaView } from 'react-native-safe-area-context'; import QRCode from 'react-native-qrcode-svg'; import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; import { useAuth } from '../context/AuthContext'; import { PLATFORMS } from '@devcard/shared'; -import { APP_URL } from '../config'; -import { get } from '../services/api'; +import { APP_URL, API_BASE_URL } from '../config'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../navigation/MainTabs'; @@ -40,37 +37,37 @@ export default function HomeScreen({ navigation }: Props) { const [analytics, setAnalytics] = useState(null); const [showQR, setShowQR] = useState(false); const [refreshing, setRefreshing] = useState(false); - const [loading, setLoading] = useState(true); - const [searchUsername, setSearchUsername] = useState(''); const profileUrl = user?.defaultCardId ? `${APP_URL}/devcard/${user.defaultCardId}` : `${APP_URL}/u/${user?.username}`; - const fetchData = useCallback(async () => { - setLoading(true); + useEffect(() => { + fetchData(); + }, []); + + const fetchData = async () => { try { - const [profileData, analyticsData] = await Promise.all([ - get('/api/profiles/me', token).catch(() => null), - get('/api/analytics/overview', token).catch(() => null), + const [profileRes, analyticsRes] = await Promise.all([ + fetch(`${API_BASE_URL}/api/profiles/me`, { + headers: { Authorization: `Bearer ${token}` }, + }), + fetch(`${API_BASE_URL}/api/analytics/overview`, { + headers: { Authorization: `Bearer ${token}` }, + }) ]); - if (profileData) { - setLinks(profileData.platformLinks || []); + if (profileRes.ok) { + const data = await profileRes.json(); + setLinks(data.platformLinks || []); } - if (analyticsData) { - setAnalytics(analyticsData); + if (analyticsRes.ok) { + setAnalytics(await analyticsRes.json()); } - } catch (error) { - console.error('Failed to fetch dashboard data:', error); - } finally { - setLoading(false); + } catch (err) { + console.error('Failed to fetch dashboard data:', err); } - }, [token]); - - useEffect(() => { - fetchData(); - }, [fetchData]); + }; const onRefresh = async () => { setRefreshing(true); @@ -84,26 +81,11 @@ export default function HomeScreen({ navigation }: Props) { message: `Check out my DevCard: ${profileUrl}`, url: profileUrl, }); - } catch (error) { - console.error('Share failed:', error); + } catch (err) { + console.error('Share failed:', err); } }; - if (loading) { - return ( - - - - - - - - - - - ); - } - return ( @@ -126,7 +108,15 @@ export default function HomeScreen({ navigation }: Props) { {/* Profile Card Preview */} - + {user?.avatarUrl ? ( + + ) : ( + + + {(user?.displayName || 'D').charAt(0).toUpperCase()} + + + )} {user?.displayName} {user?.pronouns && ( @@ -145,26 +135,20 @@ export default function HomeScreen({ navigation }: Props) { {/* Platform Links Summary */} - {links.length > 0 ? ( - <> - {links.slice(0, 4).map(link => { - const platform = PLATFORMS[link.platform]; - return ( - - - {platform?.name || link.platform} - - - ); - })} - {links.length > 4 && ( - - +{links.length - 4} - - )} - - ) : ( - No platform links added yet. Add links in the Links tab to populate your preview. + {links.slice(0, 4).map(link => { + const platform = PLATFORMS[link.platform]; + return ( + + + {platform?.name || link.platform} + + + ); + })} + {links.length > 4 && ( + + +{links.length - 4} + )} @@ -193,13 +177,13 @@ export default function HomeScreen({ navigation }: Props) { {/* Action Buttons */} - + 📤 - Share + Share Card (navigation as any).navigate('Views')} activeOpacity={0.85}> 📈 - Stats + Analytics 👁️ Preview - - (navigation as any).navigate('Links')} - activeOpacity={0.85}> - 🔗 - Links - - - - - (navigation as any).navigate('Events')} - activeOpacity={0.85}> - 🎪 - Events - - - (navigation as any).navigate('Teams')} - activeOpacity={0.85}> - 👥 - Teams - - - (navigation as any).navigate('Nfc')} - activeOpacity={0.85}> - 📳 - NFC - - - - - {/* Search / Lookup */} - - 🔍 View a DevCard - - { - const u = searchUsername.trim(); - if (u) (navigation as any).navigate('DevCardView', { username: u }); - }} - /> - { - const u = searchUsername.trim(); - if (u) (navigation as any).navigate('DevCardView', { username: u }); - }} - > - Go → - - {/* Stats */} @@ -356,13 +275,12 @@ const styles = StyleSheet.create({ qrToggle: { flexDirection: 'row', alignItems: 'center', gap: SPACING.sm }, qrToggleEmoji: { fontSize: 24 }, qrToggleText: { fontSize: FONT_SIZE.md, color: COLORS.textSecondary, fontWeight: '500' }, - actionsGrid: { flexDirection: 'row', gap: SPACING.sm, marginBottom: SPACING.sm }, + actions: { flexDirection: 'row', gap: SPACING.md, marginBottom: SPACING.lg }, actionButton: { flex: 1, backgroundColor: COLORS.bgCard, borderRadius: BORDER_RADIUS.md, - padding: SPACING.sm, - paddingVertical: SPACING.md, + padding: SPACING.md, alignItems: 'center', borderWidth: 1, borderColor: COLORS.border, @@ -381,48 +299,4 @@ const styles = StyleSheet.create({ statNumber: { fontSize: FONT_SIZE.xl, fontWeight: '800', color: COLORS.primary }, statLabel: { fontSize: FONT_SIZE.xs, color: COLORS.textMuted, marginTop: 4 }, statDivider: { width: 1, backgroundColor: COLORS.border }, - loadingRoot: { - flex: 1, - padding: SPACING.lg, - backgroundColor: COLORS.bgPrimary, - }, - loadingSpacer: { - marginTop: SPACING.sm, - }, - loadingSection: { - marginTop: SPACING.lg, - }, - emptyHint: { - color: COLORS.textMuted, - fontSize: FONT_SIZE.sm, - lineHeight: 20, - marginTop: SPACING.sm, - maxWidth: '70%', - }, - // Search - searchSection: { - marginBottom: SPACING.lg, - }, - searchLabel: { - fontSize: FONT_SIZE.sm, fontWeight: '700', color: COLORS.textSecondary, - marginBottom: SPACING.sm, letterSpacing: 0.3, - }, - searchRow: { - flexDirection: 'row', gap: SPACING.sm, - }, - searchInput: { - flex: 1, - backgroundColor: COLORS.bgCard, - borderRadius: BORDER_RADIUS.md, - paddingHorizontal: SPACING.md, paddingVertical: 12, - color: COLORS.textPrimary, fontSize: FONT_SIZE.md, - borderWidth: 1, borderColor: COLORS.border, - }, - searchBtn: { - backgroundColor: COLORS.primary, - borderRadius: BORDER_RADIUS.md, - paddingHorizontal: SPACING.lg, - justifyContent: 'center', alignItems: 'center', - }, - searchBtnText: { color: COLORS.white, fontWeight: '700', fontSize: FONT_SIZE.md }, }); diff --git a/apps/mobile/src/screens/LinksScreen.tsx b/apps/mobile/src/screens/LinksScreen.tsx index fd420275..daded55f 100644 --- a/apps/mobile/src/screens/LinksScreen.tsx +++ b/apps/mobile/src/screens/LinksScreen.tsx @@ -14,11 +14,8 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS } from '../theme/tokens'; import { useAuth } from '../context/AuthContext'; import { PLATFORMS, getAllPlatforms } from '@devcard/shared'; -import { get, post, del } from '../services/api'; -import { EmptyState } from '../components/EmptyState'; -import { LoadingPlaceholder } from '../components/LoadingPlaceholder'; +import { API_BASE_URL } from '../config'; import type { PlatformDef } from '@devcard/shared'; -import DraggableFlatList, { ScaleDecorator, RenderItemParams } from 'react-native-draggable-flatlist'; interface PlatformLink { id: string; @@ -34,17 +31,18 @@ export default function LinksScreen() { const [showAddModal, setShowAddModal] = useState(false); const [selectedPlatform, setSelectedPlatform] = useState(null); const [usernameInput, setUsernameInput] = useState(''); - const [loading, setLoading] = useState(true); const fetchLinks = useCallback(async () => { - setLoading(true); try { - const data = await get('/api/profiles/me', token).catch(() => null); - setLinks(data?.platformLinks || []); - } catch (error) { - console.error('Failed to fetch links:', error); - } finally { - setLoading(false); + const res = await fetch(`${API_BASE_URL}/api/profiles/me`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (res.ok) { + const data = await res.json(); + setLinks(data.platformLinks || []); + } + } catch (err) { + console.error('Failed to fetch links:', err); } }, [token]); @@ -55,12 +53,24 @@ export default function LinksScreen() { const addLink = async () => { if (!selectedPlatform || !usernameInput.trim()) return; try { - await post('/api/profiles/me/links', { platform: selectedPlatform.id, username: usernameInput.trim() }, token); - setShowAddModal(false); - setSelectedPlatform(null); - setUsernameInput(''); - fetchLinks(); - } catch { + const res = await fetch(`${API_BASE_URL}/api/profiles/me/links`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + platform: selectedPlatform.id, + username: usernameInput.trim(), + }), + }); + if (res.ok) { + setShowAddModal(false); + setSelectedPlatform(null); + setUsernameInput(''); + fetchLinks(); + } + } catch (err) { Alert.alert('Error', 'Failed to add link'); } }; @@ -72,71 +82,20 @@ export default function LinksScreen() { text: 'Remove', style: 'destructive', onPress: async () => { - try { - await del(`/api/profiles/me/links/${id}`, undefined, token); - fetchLinks(); - } catch { - Alert.alert('Error', 'Failed to remove link'); - } + try { + await fetch(`${API_BASE_URL}/api/profiles/me/links/${id}`, { + method: 'DELETE', + headers: { Authorization: `Bearer ${token}` }, + }); + fetchLinks(); + } catch (err) { + Alert.alert('Error', 'Failed to remove link'); + } }, }, ]); }; - const handleReorder = async (data: PlatformLink[]) => { - setLinks(data); - try { - const payload = { - links: data.map((link, index) => ({ id: link.id, displayOrder: index })), - }; - await put('/api/profiles/me/links/reorder', payload, token); - } catch { - Alert.alert('Error', 'Failed to save new order'); - fetchLinks(); // Revert on failure - } - }; - - const renderItem = ({ item, drag, isActive }: RenderItemParams) => { - const platform = PLATFORMS[item.platform]; - return ( - - - - ⋮⋮ - - - - {platform?.name || item.platform} - {item.username} - - deleteLink(item.id)} - style={styles.deleteBtn}> - - - - - ); - }; - - if (loading) { - return ( - - - - - ); - } - return ( @@ -150,18 +109,33 @@ export default function LinksScreen() { - handleReorder(data)} keyExtractor={item => item.id} contentContainerStyle={styles.list} - renderItem={renderItem} + renderItem={({ item }) => { + const platform = PLATFORMS[item.platform]; + return ( + + + + {platform?.name || item.platform} + {item.username} + + deleteLink(item.id)} + style={styles.deleteBtn}> + + + + ); + }} ListEmptyComponent={ - + + 🔗 + No links yet + Add your first platform link + } /> @@ -238,30 +212,16 @@ const styles = StyleSheet.create({ backgroundColor: COLORS.bgCard, borderRadius: BORDER_RADIUS.md, padding: SPACING.md, borderWidth: 1, borderColor: COLORS.border, }, - linkItemActive: { - backgroundColor: COLORS.bgElevated, - borderColor: COLORS.primary, - elevation: 8, - shadowColor: COLORS.black, - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.3, - shadowRadius: 8, - }, - dragHandle: { - paddingRight: SPACING.sm, - justifyContent: 'center', - }, - dragHandleText: { - color: COLORS.textMuted, - fontSize: 20, - fontWeight: 'bold', - }, platformDot: { width: 12, height: 12, borderRadius: 6, marginRight: SPACING.md }, linkInfo: { flex: 1 }, platformName: { fontSize: FONT_SIZE.md, fontWeight: '600', color: COLORS.textPrimary }, username: { fontSize: FONT_SIZE.sm, color: COLORS.textSecondary, marginTop: 2 }, deleteBtn: { padding: SPACING.sm }, deleteBtnText: { color: COLORS.error, fontSize: FONT_SIZE.md, fontWeight: '700' }, + empty: { alignItems: 'center', paddingVertical: SPACING.xxl }, + emptyEmoji: { fontSize: 48, marginBottom: SPACING.md }, + emptyText: { fontSize: FONT_SIZE.lg, fontWeight: '600', color: COLORS.textPrimary }, + emptySubtext: { fontSize: FONT_SIZE.sm, color: COLORS.textMuted, marginTop: SPACING.xs }, modalOverlay: { flex: 1, backgroundColor: COLORS.overlay, justifyContent: 'flex-end', diff --git a/apps/mobile/src/screens/NfcScreen.tsx b/apps/mobile/src/screens/NfcScreen.tsx deleted file mode 100644 index d14c317d..00000000 --- a/apps/mobile/src/screens/NfcScreen.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import React, { useState, useCallback } from 'react'; -import { - View, Text, StyleSheet, TouchableOpacity, StatusBar, Alert, -} from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; -import { useAuth } from '../context/AuthContext'; -import { get } from '../services/api'; -import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; -import type { NfcPayload } from '../types'; - -/** - * NfcScreen — NFC tag write/read UI. - * - * NOTE: Actual NFC hardware interaction requires `react-native-nfc-manager` - * which needs a dev build (not Expo Go). This screen provides the UI and - * fetches the NDEF payload from the backend. The NFC write call is stubbed - * with a TODO for native module integration. - */ -export default function NfcScreen() { - const { token } = useAuth(); - const [payload, setPayload] = useState(null); - const [loading, setLoading] = useState(false); - const [written, setWritten] = useState(false); - - const fetchPayload = useCallback(async () => { - setLoading(true); - try { - const data = await get('/api/nfc/payload', token); - setPayload(data); - } catch { - Alert.alert('Error', 'Failed to fetch NFC payload from server.'); - } finally { - setLoading(false); - } - }, [token]); - - const handleWriteTag = async () => { - if (!payload) { - await fetchPayload(); - return; - } - - // TODO: Integrate react-native-nfc-manager here - // import NfcManager, { NfcTech, Ndef } from 'react-native-nfc-manager'; - // await NfcManager.requestTechnology(NfcTech.Ndef); - // const bytes = Ndef.encodeMessage([Ndef.uriRecord(payload.payload)]); - // await NfcManager.ndefHandler.writeNdefMessage(bytes); - // await NfcManager.cancelTechnologyRequest(); - - Alert.alert( - 'NFC Not Available', - 'NFC write requires a dev build with react-native-nfc-manager. The payload URL has been prepared.', - [{ text: 'OK' }], - ); - setWritten(false); - }; - - return ( - - - - - - - - - NFC Tag Writer - - Write your DevCard URL to an NFC tag so anyone can tap to view your profile. - - - - - - Payload URL - - - {payload?.payload || 'Tap "Prepare" to generate'} - - - - - - - {loading ? 'Loading…' : 'Prepare Payload'} - - - - - - - Write to NFC Tag - - - - {written && ( - - - Tag written successfully! - - )} - - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: COLORS.bgPrimary }, - content: { flex: 1, padding: SPACING.lg, alignItems: 'center', justifyContent: 'center' }, - iconContainer: { - width: 120, height: 120, borderRadius: 60, - backgroundColor: COLORS.bgCard, alignItems: 'center', justifyContent: 'center', - borderWidth: 2, borderColor: COLORS.primary + '44', marginBottom: SPACING.lg, - }, - title: { fontSize: FONT_SIZE.xl, fontWeight: '800', color: COLORS.textPrimary }, - subtitle: { - fontSize: FONT_SIZE.sm, color: COLORS.textSecondary, textAlign: 'center', - marginTop: SPACING.xs, marginBottom: SPACING.xl, lineHeight: 20, maxWidth: 300, - }, - card: { - width: '100%', backgroundColor: COLORS.bgCard, borderRadius: BORDER_RADIUS.lg, - padding: SPACING.lg, borderWidth: 1, borderColor: COLORS.border, marginBottom: SPACING.lg, - }, - cardRow: { flexDirection: 'row', alignItems: 'center', gap: SPACING.xs, marginBottom: SPACING.sm }, - cardLabel: { fontSize: FONT_SIZE.sm, color: COLORS.textSecondary, fontWeight: '500' }, - payloadUrl: { fontSize: FONT_SIZE.sm, color: COLORS.primary, fontFamily: 'monospace' }, - prepareBtn: { - flexDirection: 'row', alignItems: 'center', gap: SPACING.sm, - backgroundColor: COLORS.bgElevated, borderRadius: BORDER_RADIUS.md, - padding: SPACING.md, paddingHorizontal: SPACING.lg, marginBottom: SPACING.md, - borderWidth: 1, borderColor: COLORS.border, - }, - prepareBtnText: { color: COLORS.textPrimary, fontWeight: '600', fontSize: FONT_SIZE.md }, - writeBtn: { - flexDirection: 'row', alignItems: 'center', gap: SPACING.sm, - backgroundColor: COLORS.primary, borderRadius: BORDER_RADIUS.md, - padding: SPACING.md, paddingHorizontal: SPACING.xl, ...SHADOWS.button, - }, - writeBtnDisabled: { backgroundColor: COLORS.bgElevated }, - writeBtnText: { color: COLORS.white, fontWeight: '700', fontSize: FONT_SIZE.md }, - writeBtnTextDisabled: { color: COLORS.textMuted }, - successBanner: { - flexDirection: 'row', alignItems: 'center', gap: SPACING.sm, - marginTop: SPACING.lg, backgroundColor: 'rgba(34,197,94,0.1)', - borderRadius: BORDER_RADIUS.md, padding: SPACING.md, - }, - successText: { color: COLORS.success, fontWeight: '600', fontSize: FONT_SIZE.sm }, -}); diff --git a/apps/mobile/src/screens/ScanScreen.tsx b/apps/mobile/src/screens/ScanScreen.tsx index 1f300351..b864cdd6 100644 --- a/apps/mobile/src/screens/ScanScreen.tsx +++ b/apps/mobile/src/screens/ScanScreen.tsx @@ -7,26 +7,19 @@ import { TextInput, StatusBar, Alert, - Share, - Platform, - PermissionsAndroid, + ActivityIndicator, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useFocusEffect } from '@react-navigation/native'; import QRCode from 'react-native-qrcode-svg'; -import ViewShot from 'react-native-view-shot'; -import { Camera } from 'react-native-camera-kit'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS } from '../theme/tokens'; -import { EmptyState } from '../components/EmptyState'; -import { Skeleton } from '../components/Skeleton'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../navigation/MainTabs'; import type { BottomSheetModal } from '@gorhom/bottom-sheet'; import type { Card } from '@devcard/shared'; import { useAuth } from '../context/AuthContext'; -import { APP_URL } from '../config'; -import { get } from '../services/api'; +import { API_BASE_URL, APP_URL } from '../config'; import CardPickerSheet from '../components/CardPickerSheet'; type Props = { @@ -46,9 +39,6 @@ export default function ScanScreen({ navigation }: Props) { const [loadingCards, setLoadingCards] = useState(false); const sheetRef = useRef(null); - const qrRef = useRef(null); - const [hasPermission, setHasPermission] = useState(false); - // Extract username from DevCard URL const parseDevCardUrl = (url: string): string | null => { const match = url.match(/\/u\/([a-zA-Z0-9_-]+)/); @@ -65,59 +55,22 @@ export default function ScanScreen({ navigation }: Props) { } }; - const requestCameraPermission = async () => { - if (Platform.OS === 'android') { - try { - const granted = await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.CAMERA, - { - title: 'Camera Permission', - message: 'DevCard needs camera access to scan QR codes.', - buttonNeutral: 'Ask Me Later', - buttonNegative: 'Cancel', - buttonPositive: 'OK', - }, - ); - setHasPermission(granted === PermissionsAndroid.RESULTS.GRANTED); - } catch (err) { - console.warn(err); - } - } else { - // iOS permissions would typically be handled via react-native-permissions - // For this demo, assume true if not Android - setHasPermission(true); - } - }; - - const handleCameraRead = (url: string) => { - const username = parseDevCardUrl(url); - if (username) { - navigation.navigate('DevCardView', { username }); - } - }; - - const handleSaveQR = async () => { - if (qrRef.current && qrRef.current.capture) { - try { - const uri = await qrRef.current.capture(); - await Share.share({ - title: 'My DevCard QR', - url: uri, - }); - } catch (err) { - Alert.alert('Error', 'Failed to save QR code'); - } - } - }; + // NOTE: Camera QR scanning requires react-native-camera-kit + // which needs native setup. For now, we provide manual entry. + // Camera integration will be added when building on device. const fetchCards = useCallback(async () => { if (!token) return; setLoadingCards(true); try { - const data = await get('/api/cards', token).catch(() => []); - setCards(data || []); - } catch (error) { - console.error('Failed to fetch cards:', error); + const res = await fetch(`${API_BASE_URL}/api/cards`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (res.ok) { + setCards(await res.json()); + } + } catch (err) { + console.error('Failed to fetch cards:', err); } finally { setLoadingCards(false); } @@ -178,8 +131,8 @@ export default function ScanScreen({ navigation }: Props) { setSelectedCardId(cardId); try { await AsyncStorage.setItem(LAST_SELECTED_CARD_KEY, cardId); - } catch (error) { - console.error('Failed to persist selected card:', error); + } catch (err) { + console.error('Failed to persist selected card:', err); } finally { sheetRef.current?.dismiss(); } @@ -227,12 +180,9 @@ export default function ScanScreen({ navigation }: Props) { - + {loadingCards ? ( - - - - + ) : qrUrl ? ( ) : ( - + Create a card to generate a QR )} - - + {!!qrUrl && ( - - Scan to open your DevCard - - Share QR Image - - + Scan to open your DevCard )} - {/* Camera Scanner */} + {/* Camera Placeholder */} - {hasPermission ? ( - handleCameraRead(event.nativeEvent.codeStringValue)} - showFrame={false} - /> - ) : ( - - 📷 - Camera Permission Required - - Grant Permission - - - )} + + 📷 + Camera QR Scanner + + Point your camera at a DevCard QR code + + {/* Corner markers */} @@ -358,22 +290,7 @@ const styles = StyleSheet.create({ minHeight: 220, }, qrHint: { textAlign: 'center', color: COLORS.textMuted, fontSize: FONT_SIZE.sm }, - saveQrBtn: { - backgroundColor: COLORS.bgElevated, - borderRadius: BORDER_RADIUS.sm, - paddingHorizontal: SPACING.md, - paddingVertical: SPACING.xs, - borderWidth: 1, - borderColor: COLORS.border, - }, - saveQrBtnText: { color: COLORS.primary, fontSize: FONT_SIZE.xs, fontWeight: '600' }, - qrFooter: { alignItems: 'center', marginTop: SPACING.sm, gap: SPACING.xs }, - qrSkeleton: { - alignItems: 'center', - }, - qrSkeletonText: { - marginTop: SPACING.md, - }, + qrPlaceholder: { color: COLORS.textMuted, fontSize: FONT_SIZE.sm }, cameraArea: { flex: 1, maxHeight: 350, backgroundColor: COLORS.bgCard, borderRadius: BORDER_RADIUS.lg, @@ -385,12 +302,6 @@ const styles = StyleSheet.create({ cameraEmoji: { fontSize: 48, marginBottom: SPACING.md }, cameraText: { fontSize: FONT_SIZE.md, fontWeight: '600', color: COLORS.textPrimary }, cameraSubtext: { fontSize: FONT_SIZE.sm, color: COLORS.textMuted, marginTop: SPACING.xs }, - reqPermBtn: { - backgroundColor: COLORS.primary, borderRadius: BORDER_RADIUS.sm, - paddingHorizontal: SPACING.md, paddingVertical: SPACING.sm, - marginTop: SPACING.md, - }, - reqPermBtnText: { color: COLORS.white, fontSize: FONT_SIZE.sm, fontWeight: '600' }, corner: { position: 'absolute', width: 30, height: 30, borderColor: COLORS.primary, borderWidth: 3, diff --git a/apps/mobile/src/screens/SettingsScreen.tsx b/apps/mobile/src/screens/SettingsScreen.tsx index 933d08d9..7d282a63 100644 --- a/apps/mobile/src/screens/SettingsScreen.tsx +++ b/apps/mobile/src/screens/SettingsScreen.tsx @@ -8,53 +8,46 @@ import { TextInput, Alert, StatusBar, + Image, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { useNavigation } from '@react-navigation/native'; import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS } from '../theme/tokens'; -import Avatar from '../components/Avatar'; -import ColorPicker from '../components/ColorPicker'; import { useAuth } from '../context/AuthContext'; -import { put } from '../services/api'; +import { API_BASE_URL } from '../config'; export default function SettingsScreen() { - const navigation = useNavigation(); const { user, token, refreshUser, logout } = useAuth(); const [displayName, setDisplayName] = useState(user?.displayName || ''); const [bio, setBio] = useState(user?.bio || ''); const [pronouns, setPronouns] = useState(user?.pronouns || ''); const [role, setRole] = useState(user?.role || ''); const [company, setCompany] = useState(user?.company || ''); - const [accentColor, setAccentColor] = useState(user?.accentColor || '#6366F1'); const [saving, setSaving] = useState(false); - const handleAvatarTap = () => { - // TODO: Integrate react-native-image-picker when building on device - // import { launchImageLibrary } from 'react-native-image-picker'; - // const result = await launchImageLibrary({ mediaType: 'photo', quality: 0.8 }); - // Upload via multipart/form-data to PUT /api/profiles/me/avatar - Alert.alert( - 'Change Avatar', - 'Avatar upload requires react-native-image-picker in a dev build. Coming soon!', - ); - }; - const handleSave = async () => { setSaving(true); try { - const payload = { - displayName: displayName.trim() || undefined, - bio: bio.trim() || null, - pronouns: pronouns.trim() || null, - role: role.trim() || null, - company: company.trim() || null, - accentColor, - }; - - await put('/api/profiles/me', payload, token); - await refreshUser(); - Alert.alert('Success', 'Profile updated!'); - } catch { + const res = await fetch(`${API_BASE_URL}/api/profiles/me`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + displayName: displayName.trim(), + bio: bio.trim() || null, + pronouns: pronouns.trim() || null, + role: role.trim() || null, + company: company.trim() || null, + }), + }); + if (res.ok) { + await refreshUser(); + Alert.alert('Success', 'Profile updated!'); + } else { + Alert.alert('Error', 'Failed to update profile'); + } + } catch (err) { Alert.alert('Error', 'Something went wrong'); } finally { setSaving(false); @@ -76,16 +69,17 @@ export default function SettingsScreen() { Profile Settings {/* Avatar */} - - - Tap to change + + {user?.avatarUrl ? ( + + ) : ( + + + {(user?.displayName || 'D').charAt(0).toUpperCase()} + + + )} @{user?.username} - - - {/* Accent Color */} - - Card Accent Color - {/* Form */} @@ -169,13 +163,11 @@ const styles = StyleSheet.create({ title: { fontSize: FONT_SIZE.xl, fontWeight: '800', color: COLORS.textPrimary, marginBottom: SPACING.lg }, avatarSection: { alignItems: 'center', marginBottom: SPACING.xl }, avatar: { width: 80, height: 80, borderRadius: 40 }, - avatarHint: { fontSize: FONT_SIZE.xs, color: COLORS.primary, marginTop: SPACING.xs, fontWeight: '500' }, avatarPlaceholder: { backgroundColor: COLORS.primary, alignItems: 'center', justifyContent: 'center', }, avatarText: { fontSize: FONT_SIZE.xxl, fontWeight: '700', color: COLORS.white }, usernameDisplay: { fontSize: FONT_SIZE.md, color: COLORS.textSecondary, marginTop: SPACING.sm }, - colorSection: { marginBottom: SPACING.lg }, form: { gap: SPACING.md, marginBottom: SPACING.lg }, field: {}, fieldLabel: { fontSize: FONT_SIZE.sm, color: COLORS.textSecondary, marginBottom: SPACING.xs, fontWeight: '500' }, diff --git a/apps/mobile/src/screens/SplashScreen.tsx b/apps/mobile/src/screens/SplashScreen.tsx deleted file mode 100644 index 2e6c4991..00000000 --- a/apps/mobile/src/screens/SplashScreen.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { View, Text, StyleSheet, Animated } from 'react-native'; -import { COLORS, FONT_SIZE, SPACING } from '../theme/tokens'; - -/** - * SplashScreen — Branded loading screen shown during auth token hydration. - * - * Uses a pulsing opacity animation on the logo to indicate loading activity - * without requiring any external dependencies. - */ -export default function SplashScreen() { - const opacity = useRef(new Animated.Value(0.4)).current; - const scale = useRef(new Animated.Value(0.9)).current; - - useEffect(() => { - Animated.loop( - Animated.sequence([ - Animated.parallel([ - Animated.timing(opacity, { - toValue: 1, - duration: 800, - useNativeDriver: true, - }), - Animated.timing(scale, { - toValue: 1.05, - duration: 800, - useNativeDriver: true, - }), - ]), - Animated.parallel([ - Animated.timing(opacity, { - toValue: 0.4, - duration: 800, - useNativeDriver: true, - }), - Animated.timing(scale, { - toValue: 0.9, - duration: 800, - useNativeDriver: true, - }), - ]), - ]), - ).start(); - }, [opacity, scale]); - - return ( - - - - - DevCard - Loading your profile… - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: COLORS.bgPrimary, - alignItems: 'center', - justifyContent: 'center', - gap: SPACING.md, - }, - logoContainer: { - width: 100, - height: 100, - borderRadius: 50, - backgroundColor: COLORS.bgCard, - alignItems: 'center', - justifyContent: 'center', - borderWidth: 2, - borderColor: COLORS.primary + '44', - }, - logo: { - fontSize: 48, - }, - title: { - fontSize: FONT_SIZE.xxl, - fontWeight: '800', - color: COLORS.textPrimary, - letterSpacing: -0.5, - }, - subtitle: { - fontSize: FONT_SIZE.sm, - color: COLORS.textMuted, - }, -}); diff --git a/apps/mobile/src/screens/TeamDetailScreen.tsx b/apps/mobile/src/screens/TeamDetailScreen.tsx deleted file mode 100644 index 9503bb72..00000000 --- a/apps/mobile/src/screens/TeamDetailScreen.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { - View, Text, StyleSheet, FlatList, TouchableOpacity, - StatusBar, Alert, -} from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; -import Avatar from '../components/Avatar'; -import { LoadingPlaceholder } from '../components/LoadingPlaceholder'; -import { EmptyState } from '../components/EmptyState'; -import { useAuth } from '../context/AuthContext'; -import { get } from '../services/api'; -import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; -import type { NativeStackScreenProps } from '@react-navigation/native-stack'; -import type { RootStackParamList } from '../navigation/MainTabs'; -import type { TeamMember } from '../types'; - -type Props = NativeStackScreenProps; - -export default function TeamDetailScreen({ route, navigation }: Props) { - const { slug, name } = route.params; - const { token } = useAuth(); - const [team, setTeam] = useState(null); - const [loading, setLoading] = useState(true); - - const fetchTeam = useCallback(async () => { - setLoading(true); - try { - const data = await get(`/api/teams/${slug}`, token); - setTeam(data); - } catch { Alert.alert('Error', 'Failed to load team'); } - finally { setLoading(false); } - }, [slug, token]); - - useEffect(() => { fetchTeam(); }, [fetchTeam]); - - const getRoleBadge = (role: string) => { - switch (role) { - case 'OWNER': return { label: 'Owner', color: COLORS.warning }; - case 'ADMIN': return { label: 'Admin', color: COLORS.info }; - default: return { label: 'Member', color: COLORS.textMuted }; - } - }; - - if (loading) return ( - - - - - ); - - const members: TeamMember[] = team?.members || []; - - return ( - - - item.username} - contentContainerStyle={styles.list} - ListHeaderComponent={ - - - {team?.name || name} - {team?.description && ( - {team.description} - )} - - {members.length} member{members.length !== 1 ? 's' : ''} - - - Members - - } - renderItem={({ item }) => { - const badge = getRoleBadge(item.teamRole); - return ( - navigation.navigate('DevCardView', { username: item.username })} - activeOpacity={0.7}> - - - {item.displayName} - {item.role && {item.role}} - - - {badge.label} - - - ); - }} - ListEmptyComponent={ - - } - /> - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: COLORS.bgPrimary }, - list: { padding: SPACING.lg }, - infoCard: { - backgroundColor: COLORS.bgCard, borderRadius: BORDER_RADIUS.lg, - padding: SPACING.lg, borderWidth: 1, borderColor: COLORS.border, - marginBottom: SPACING.lg, ...SHADOWS.card, - }, - teamName: { fontSize: FONT_SIZE.xl, fontWeight: '800', color: COLORS.textPrimary, marginBottom: SPACING.xs }, - description: { fontSize: FONT_SIZE.sm, color: COLORS.textSecondary, lineHeight: 20, marginBottom: SPACING.sm }, - memberCount: { fontSize: FONT_SIZE.sm, color: COLORS.primary, fontWeight: '600' }, - sectionTitle: { fontSize: FONT_SIZE.lg, fontWeight: '700', color: COLORS.textPrimary, marginBottom: SPACING.md }, - memberRow: { - flexDirection: 'row', alignItems: 'center', backgroundColor: COLORS.bgCard, - borderRadius: BORDER_RADIUS.md, padding: SPACING.md, marginBottom: SPACING.sm, - borderWidth: 1, borderColor: COLORS.border, - }, - avatar: { width: 40, height: 40, borderRadius: 20, marginRight: SPACING.md }, - memberInfo: { flex: 1 }, - memberName: { fontSize: FONT_SIZE.md, fontWeight: '600', color: COLORS.textPrimary }, - memberRole: { fontSize: FONT_SIZE.sm, color: COLORS.textMuted, marginTop: 1 }, - roleBadge: { - borderWidth: 1, borderRadius: BORDER_RADIUS.full, - paddingHorizontal: SPACING.sm, paddingVertical: 2, - }, - roleBadgeText: { fontSize: FONT_SIZE.xs, fontWeight: '600' }, -}); diff --git a/apps/mobile/src/screens/TeamsScreen.tsx b/apps/mobile/src/screens/TeamsScreen.tsx deleted file mode 100644 index c64e047e..00000000 --- a/apps/mobile/src/screens/TeamsScreen.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, { useState, useCallback } from 'react'; -import { - View, Text, StyleSheet, FlatList, TouchableOpacity, - TextInput, StatusBar, Alert, -} from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; -import { EmptyState } from '../components/EmptyState'; -import { useAuth } from '../context/AuthContext'; -import { get } from '../services/api'; -import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; -import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import type { RootStackParamList } from '../navigation/MainTabs'; - -type Props = { navigation: NativeStackNavigationProp }; - -export default function TeamsScreen({ navigation }: Props) { - const { token } = useAuth(); - const [slugInput, setSlugInput] = useState(''); - const [loading, setLoading] = useState(false); - - const handleLookup = async () => { - const slug = slugInput.trim().toLowerCase(); - if (!slug) { Alert.alert('Enter a slug', 'Enter the team slug.'); return; } - setLoading(true); - try { - const team = await get(`/api/teams/${slug}`, token); - if (team) navigation.navigate('TeamDetail', { slug: team.slug, name: team.name }); - } catch { Alert.alert('Not Found', 'No team found with that slug.'); } - finally { setLoading(false); setSlugInput(''); } - }; - - return ( - - - - Teams - Look up a team to view their group DevCard - - - - - - - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: COLORS.bgPrimary }, - header: { padding: SPACING.lg, paddingBottom: SPACING.sm }, - title: { fontSize: FONT_SIZE.xl, fontWeight: '800', color: COLORS.textPrimary }, - subtitle: { fontSize: FONT_SIZE.sm, color: COLORS.textSecondary, marginTop: SPACING.xs }, - joinSection: { paddingHorizontal: SPACING.lg, paddingBottom: SPACING.lg }, - inputRow: { flexDirection: 'row', gap: SPACING.sm }, - input: { - flex: 1, backgroundColor: COLORS.bgCard, borderRadius: BORDER_RADIUS.md, - padding: SPACING.md, color: COLORS.textPrimary, fontSize: FONT_SIZE.md, - borderWidth: 1, borderColor: COLORS.border, - }, - searchBtn: { - backgroundColor: COLORS.primary, borderRadius: BORDER_RADIUS.md, - width: 48, alignItems: 'center', justifyContent: 'center', ...SHADOWS.button, - }, - disabled: { opacity: 0.5 }, -}); diff --git a/apps/mobile/src/screens/ViewsScreen.tsx b/apps/mobile/src/screens/ViewsScreen.tsx index cd0654ea..24dc79ee 100644 --- a/apps/mobile/src/screens/ViewsScreen.tsx +++ b/apps/mobile/src/screens/ViewsScreen.tsx @@ -1,13 +1,10 @@ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; -import { View, Text, StyleSheet, FlatList } from 'react-native'; +import React, { useState, useEffect, useCallback } from 'react'; +import { View, Text, StyleSheet, FlatList, ActivityIndicator, Image } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS } from '../theme/tokens'; import { useAuth } from '../context/AuthContext'; -import { get } from '../services/api'; -import { EmptyState } from '../components/EmptyState'; -import Avatar from '../components/Avatar'; -import { LoadingPlaceholder } from '../components/LoadingPlaceholder'; +import { API_BASE_URL } from '../config'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../navigation/MainTabs'; @@ -17,30 +14,30 @@ export const ViewsScreen: React.FC = () => { const { token } = useAuth(); const [loading, setLoading] = useState(true); const [views, setViews] = useState([]); - const [overview, setOverview] = useState(null); - const fetchData = useCallback(async () => { + const fetchViews = useCallback(async () => { if (!token) { setLoading(false); return; } try { - const [viewsData, overviewData] = await Promise.all([ - get('/api/analytics/views', token).catch(() => null), - get('/api/analytics/overview', token).catch(() => null), - ]); - setViews(viewsData?.data || []); - setOverview(overviewData); - } catch (error) { - console.error('Failed to fetch analytics', error); + const response = await fetch(`${API_BASE_URL}/api/analytics/views`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (response.ok) { + const data = await response.json(); + setViews(data.data || []); + } + } catch (err) { + console.error('Failed to fetch views analytics', err); } finally { setLoading(false); } }, [token]); useEffect(() => { - fetchData(); - }, [fetchData]); + fetchViews(); + }, [fetchViews]); const formatDate = (dateString: string) => { const d = new Date(dateString); @@ -56,59 +53,6 @@ export const ViewsScreen: React.FC = () => { } }; - // Generate simple bar chart data for last 7 days - const chartData = useMemo(() => { - const last7Days = Array.from({ length: 7 }, (_, i) => { - const d = new Date(); - d.setDate(d.getDate() - (6 - i)); - return { date: d.toLocaleDateString('en-US', { weekday: 'short' }), count: 0 }; - }); - - views.forEach(v => { - const d = new Date(v.createdAt).toLocaleDateString('en-US', { weekday: 'short' }); - const day = last7Days.find(x => x.date === d); - if (day) day.count++; - }); - - const max = Math.max(...last7Days.map(d => d.count), 1); // prevent division by zero - return { data: last7Days, max }; - }, [views]); - - const renderHeader = () => ( - - - - {overview?.totalViews || 0} - Total Views - - - {overview?.followsCount || 0} - Connections - - - - - Views (Last 7 Days) - - {chartData.data.map((item, idx) => { - const heightPerc = (item.count / chartData.max) * 100; - return ( - - {item.count > 0 ? item.count : ''} - - - - {item.date} - - ); - })} - - - - Recent Activity - - ); - const renderItem = ({ item }: { item: any }) => { const isAnonymous = !item.viewer; @@ -120,9 +64,11 @@ export const ViewsScreen: React.FC = () => { ) : item.viewer.avatarUrl ? ( - + ) : ( - + + {item.viewer.displayName.charAt(0)} + )} @@ -146,26 +92,25 @@ export const ViewsScreen: React.FC = () => { if (loading) { return ( - - - + + + ); } return ( {views.length === 0 ? ( - + + + No Views Yet + Share your card or QR code to start collecting analytics. + ) : ( item.id} renderItem={renderItem} - ListHeaderComponent={renderHeader} contentContainerStyle={styles.listContainer} /> )} @@ -269,87 +214,4 @@ const styles = StyleSheet.create({ textAlign: 'center', marginTop: SPACING.sm, }, - headerContainer: { - paddingBottom: SPACING.lg, - }, - statsRow: { - flexDirection: 'row', - gap: SPACING.md, - marginBottom: SPACING.lg, - }, - statCard: { - flex: 1, - backgroundColor: COLORS.bgCard, - borderRadius: BORDER_RADIUS.lg, - padding: SPACING.lg, - alignItems: 'center', - borderWidth: 1, - borderColor: COLORS.borderLight, - }, - statValue: { - fontSize: 28, - fontWeight: '800', - color: COLORS.primary, - }, - statLabel: { - fontSize: FONT_SIZE.sm, - color: COLORS.textMuted, - marginTop: 4, - fontWeight: '600', - }, - chartCard: { - backgroundColor: COLORS.bgCard, - borderRadius: BORDER_RADIUS.lg, - padding: SPACING.lg, - borderWidth: 1, - borderColor: COLORS.borderLight, - marginBottom: SPACING.xl, - }, - chartTitle: { - fontSize: FONT_SIZE.md, - fontWeight: '700', - color: COLORS.textPrimary, - marginBottom: SPACING.lg, - }, - chartContainer: { - flexDirection: 'row', - alignItems: 'flex-end', - justifyContent: 'space-between', - height: 140, - paddingTop: 20, - }, - barWrapper: { - alignItems: 'center', - flex: 1, - }, - barTrack: { - width: 24, - height: 100, - backgroundColor: COLORS.bgElevated, - borderRadius: 4, - justifyContent: 'flex-end', - overflow: 'hidden', - }, - barFill: { - width: '100%', - backgroundColor: COLORS.primary, - borderRadius: 4, - }, - barLabel: { - fontSize: 10, - color: COLORS.textMuted, - marginTop: SPACING.sm, - }, - barLabelTop: { - fontSize: 10, - color: COLORS.primary, - marginBottom: 4, - fontWeight: 'bold', - }, - sectionTitle: { - fontSize: FONT_SIZE.lg, - fontWeight: '700', - color: COLORS.textPrimary, - marginBottom: SPACING.md, - }, }); diff --git a/apps/mobile/src/screens/WebViewScreen.tsx b/apps/mobile/src/screens/WebViewScreen.tsx index 844c248a..03806d8f 100644 --- a/apps/mobile/src/screens/WebViewScreen.tsx +++ b/apps/mobile/src/screens/WebViewScreen.tsx @@ -1,19 +1,14 @@ -import React, { useRef, useState, useEffect } from 'react'; +import React, { useRef } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, StatusBar, - Linking, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { WebView } from 'react-native-webview'; -import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; -import { Skeleton } from '../components/Skeleton'; -import { getDeepLinkUrl } from '@devcard/shared'; -import { post } from '../services/api'; -import { useAuth } from '../context/AuthContext'; +import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS } from '../theme/tokens'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import type { RouteProp } from '@react-navigation/native'; import type { RootStackParamList } from '../navigation/MainTabs'; @@ -28,486 +23,64 @@ type Props = { * * Opens the platform profile in an in-app WebView so the user can * tap the native Follow/Connect button without leaving DevCard. + * + * Key features: + * - sharedCookiesEnabled: shares auth cookies from system browser OAuth + * - Auto-detects when user navigates away (they tapped Connect) + * - Clean close button to dismiss */ export default function WebViewScreen({ navigation, route }: Props) { - const { - platform, - url, - platformName, - username, - linkId, - cardOwnerUsername, - } = route.params; - - const { token } = useAuth(); - const platformDisplayName = platformName || platform; + const { platform, profileUrl, displayName } = route.params; const webViewRef = useRef(null); - const [hasLoaded, setHasLoaded] = useState(false); - const [fallbackTriggered, setFallbackTriggered] = useState(false); - const [showFallbackOverlay, setShowFallbackOverlay] = useState(false); - const [successToast, setSuccessToast] = useState(null); - const [progress, setProgress] = useState(0); - - const isSuccessHandled = useRef(false); - const successTimerRef = useRef | null>(null); - // Track whether the injected JS ever detected success during this session - const successDetectedInSession = useRef(false); - - // Safety Timeout Fallback: 10 seconds - useEffect(() => { - if (hasLoaded || fallbackTriggered) return; - - const timer = setTimeout(() => { - setFallbackTriggered(true); - setShowFallbackOverlay(true); - }, 10000); - - return () => clearTimeout(timer); - }, [hasLoaded, fallbackTriggered]); - - useEffect(() => { - return () => { - if (successTimerRef.current) { - clearTimeout(successTimerRef.current); - } - }; - }, []); - - const handleOpenDeepLink = () => { - let targetUsername = username; - if (!targetUsername && url) { - const parts = url.split('/'); - const lastPart = parts[parts.length - 1] || parts[parts.length - 2]; - targetUsername = lastPart.split('?')[0]; - } - - const deepLink = targetUsername ? getDeepLinkUrl(platform, targetUsername) : null; - if (deepLink) { - Linking.canOpenURL(deepLink) - .then((supported) => { - Linking.openURL(supported ? deepLink : url); - navigation.goBack(); - }) - .catch(() => { - Linking.openURL(url); - navigation.goBack(); - }); - } else { - Linking.openURL(url); - navigation.goBack(); - } - }; - - const handleOpenBrowser = () => { - Linking.openURL(url); - navigation.goBack(); - }; - - const handleRetryWebView = () => { - setHasLoaded(false); - setFallbackTriggered(false); - setShowFallbackOverlay(false); - setProgress(0); - webViewRef.current?.reload(); - }; - - const handleSuccess = async () => { - if (isSuccessHandled.current) return; - isSuccessHandled.current = true; - successDetectedInSession.current = true; - setSuccessToast(`Connection request sent on ${platformDisplayName}`); - - // Asynchronously log follow to the backend - if (token && username) { - try { - await post(`/api/follow/${platform}/${username}/log`, { status: 'success', layer: 'webview' }, token); - } catch (error) { - console.warn('Failed to log WebView follow success:', error); - } - } - - // Auto-dismiss after 2 seconds with success param back to parent - successTimerRef.current = setTimeout(() => { - navigateBackWithSuccess(); - }, 2000); - }; - - const navigateBackWithSuccess = () => { - if (linkId) { - navigation.navigate({ - name: 'DevCardView', - params: { username: cardOwnerUsername, followSuccessLinkId: linkId }, - merge: true, - }); - } else { - navigation.goBack(); - } - }; - - // Done button: check current page state live before going back - const handleDonePress = () => { - // If success was already handled, navigate with success immediately - if (successDetectedInSession.current) { - if (successTimerRef.current) clearTimeout(successTimerRef.current); - navigateBackWithSuccess(); - return; - } - - // Inject a one-shot check script to see if LinkedIn currently shows success - const checkScript = ` - (function() { - var bodyText = document.body ? document.body.innerText.toLowerCase() : ''; - var successKeywords = ['invite sent', 'invitation sent', 'request sent', 'pending']; - var found = successKeywords.some(function(k) { return bodyText.includes(k); }); - if (!found) { - var els = document.querySelectorAll('button, a, span, [role="button"]'); - for (var i = 0; i < els.length; i++) { - var t = (els[i].textContent || '').toLowerCase(); - var lbl = (els[i].getAttribute('aria-label') || '').toLowerCase(); - if (successKeywords.some(function(k) { return t.includes(k) || lbl.includes(k); })) { - found = true; - break; - } - } - } - window.ReactNativeWebView.postMessage(JSON.stringify({ status: found ? 'done_with_success' : 'done_without_success' })); - })(); - `; - if (webViewRef.current) { - webViewRef.current.injectJavaScript(checkScript); - } else { - navigation.goBack(); - } - }; - - const handleHttpError = (syntheticEvent: any) => { - const { nativeEvent } = syntheticEvent; - console.warn('WebView HTTP error: ', nativeEvent?.statusCode, nativeEvent?.description); - }; - - const handleError = (syntheticEvent: any) => { - const { nativeEvent } = syntheticEvent; - console.warn('WebView general loading error:', nativeEvent?.description); - if (!fallbackTriggered) { - setFallbackTriggered(true); - setShowFallbackOverlay(true); - } - }; - - // JS Injection: LinkedIn-specific Connect button highlighting & event detection - // injectedJavaScriptBeforeContentLoaded runs BEFORE any page content — sets up listeners early - const injectedJSBeforeLoad = platform === 'linkedin' ? ` - (function() { - // Set up the SUCCESS_KEYWORDS and postMessage bridge as early as possible - window.__devcardSuccessKeywords = [ - 'invite sent', 'invitation sent', 'request sent', - 'connection request sent', 'pending', 'withdraw' - ]; - window.__devcardSuccessReported = false; - window.__devcardHighlighted = false; - - window.__devcardCheck = function() { - if (window.__devcardSuccessReported) return; - var kws = window.__devcardSuccessKeywords; - var bodyText = document.body ? document.body.innerText.toLowerCase() : ''; - for (var k = 0; k < kws.length; k++) { - if (bodyText.includes(kws[k])) { - window.__devcardSuccessReported = true; - try { window.ReactNativeWebView.postMessage(JSON.stringify({ status: 'success' })); } catch(error){} - return; - } - } - var els = document.querySelectorAll('button, span, a, [role="button"]'); - for (var i = 0; i < els.length; i++) { - var t = (els[i].textContent || '').toLowerCase(); - var l = (els[i].getAttribute('aria-label') || '').toLowerCase(); - for (var j = 0; j < kws.length; j++) { - if (t.includes(kws[j]) || l.includes(kws[j])) { - window.__devcardSuccessReported = true; - try { window.ReactNativeWebView.postMessage(JSON.stringify({ status: 'success' })); } catch(error){} - return; - } - } - } - }; - - // Check when page becomes visible (fires after dialogs close) - document.addEventListener('visibilitychange', function() { - if (document.visibilityState === 'visible') { - setTimeout(window.__devcardCheck, 200); - setTimeout(window.__devcardCheck, 600); - } - }); - - // Check on focus events (modal dismissal, back navigation) - window.addEventListener('focus', function() { - setTimeout(window.__devcardCheck, 300); - }); - })(); - ` : undefined; - - const injectedJS = platform === 'linkedin' ? ` - (function() { - function log(msg) { - try { - window.ReactNativeWebView.postMessage(JSON.stringify({ status: 'debug', message: msg })); - } catch(error){} - } - - log('LinkedIn JS Engine Started'); - - // Inject pulsating highlight CSS for the Connect button - var styleEl = document.createElement('style'); - styleEl.innerHTML = [ - '@keyframes pulse-highlight {', - ' 0% { box-shadow: 0 0 0 0px rgba(10,102,194,0.7); border-color: #0A66C2; }', - ' 70% { box-shadow: 0 0 0 10px rgba(10,102,194,0); border-color: #0084FF; }', - ' 100% { box-shadow: 0 0 0 0px rgba(10,102,194,0); border-color: #0A66C2; }', - '}', - '.devcard-highlight {', - ' animation: pulse-highlight 2s infinite !important;', - ' border: 3px solid #0A66C2 !important;', - ' transform: scale(1.02) !important;', - '}' - ].join(''); - if (document.head) document.head.appendChild(styleEl); - - // Reuse globals set by injectedJavaScriptBeforeContentLoaded if available - var SUCCESS_KEYWORDS = (window.__devcardSuccessKeywords) || [ - 'invite sent', 'invitation sent', 'request sent', - 'connection request sent', 'pending', 'withdraw' - ]; - var successReported = (window.__devcardSuccessReported) || false; - var highlighted = (window.__devcardHighlighted) || false; - - function reportSuccess(reason) { - if (successReported) return; - successReported = true; - if (window.__devcardSuccessReported !== undefined) window.__devcardSuccessReported = true; - try { window.ReactNativeWebView.postMessage(JSON.stringify({ status: 'success' })); } catch(error){} - log('Success: ' + reason); - } - - function checkPage() { - if (successReported) return; - - // 1. Body text scan - var bodyText = document.body ? document.body.innerText.toLowerCase() : ''; - for (var k = 0; k < SUCCESS_KEYWORDS.length; k++) { - if (bodyText.includes(SUCCESS_KEYWORDS[k])) { - reportSuccess('body:' + SUCCESS_KEYWORDS[k]); - return; - } - } - - // 2. Element scan - var allEls = document.querySelectorAll('button, a, span, [role="button"], li'); - for (var i = 0; i < allEls.length; i++) { - var el = allEls[i]; - var text = (el.textContent || '').replace(new RegExp('\\s+', 'g'), ' ').trim().toLowerCase(); - var aria = (el.getAttribute('aria-label') || '').toLowerCase(); - var combined = text + ' ' + aria; - for (var j = 0; j < SUCCESS_KEYWORDS.length; j++) { - if (combined.includes(SUCCESS_KEYWORDS[j])) { - reportSuccess('element:' + combined.substring(0, 40)); - return; - } - } - // Highlight the Connect button - if (!highlighted) { - var isConnect = (text === 'connect' || aria === 'connect' || aria.includes('connect to')) - && !text.includes('connections') && !text.includes('connected') && !el.disabled; - if (isConnect) { - el.scrollIntoView({ behavior: 'smooth', block: 'center' }); - el.classList.add('devcard-highlight'); - highlighted = true; - log('Connect button highlighted'); - } - } - } - } - - checkPage(); - - // MutationObserver — watches childList, subtree AND characterData - function startObserver() { - var obs = new MutationObserver(function(mutations) { checkPage(); }); - obs.observe(document.body, { - childList: true, subtree: true, characterData: true, attributes: true, - attributeFilter: ['aria-label', 'class', 'disabled'] - }); - log('MutationObserver active'); - } - - if (document.body) { - startObserver(); - } else { - document.addEventListener('DOMContentLoaded', startObserver); - } - - // Polling every 700ms (runs for up to 90 seconds) - var pollCount = 0; - var pollTimer = setInterval(function() { - pollCount++; - checkPage(); - if (successReported || pollCount > 128) clearInterval(pollTimer); - }, 700); - - // Also run check on popstate (LinkedIn SPA navigation) - window.addEventListener('popstate', function() { - setTimeout(checkPage, 300); - setTimeout(checkPage, 800); - }); - - log('Engine ready, polling + observer active'); - })(); - ` : undefined; - return ( - {/* Header Container */} - - - navigation.goBack()} activeOpacity={0.7}> - ✕ Close - - {platformDisplayName} - - - {/* Loading Progress Bar */} - {progress > 0 && progress < 1 && ( - - )} + {/* Header Bar */} + + navigation.goBack()}> + ✕ Close + + {displayName} + {/* Info Banner */} - You are viewing this profile in DevCard — tap Connect on {platformDisplayName} to send your request + Tap the Follow or{' '} + Connect button below to complete the action - {successToast && ( - - {successToast} - - )} - {/* WebView */} - {url ? ( - - setProgress(nativeEvent.progress)} - onLoadEnd={() => setHasLoaded(true)} - onError={handleError} - onHttpError={handleHttpError} - onMessage={(event) => { - try { - const data = JSON.parse(event.nativeEvent.data); - if (data.status === 'success') { - handleSuccess(); - } else if (data.status === 'done_with_success') { - // Done button pressed: success found on current page - handleSuccess(); - } else if (data.status === 'done_without_success') { - // Done button pressed: no success found, just go back - navigation.goBack(); - } else if (data.status === 'debug') { - console.log('[WebView JS] ' + data.message); - } - } catch {} - }} - onNavigationStateChange={(navState) => { - // Detect final invite-sent/shared subroutes (exclude early pages like send-invite) - if ( - navState.url.includes('invite-sent') || - navState.url.includes('inviteShared') || - navState.url.includes('invitation-sent') - ) { - handleSuccess(); - } - }} - renderLoading={() => ( - - - - - - Loading {platformDisplayName}... - - )} - /> - - {/* Premium Fallback Overlay for slow load / timeouts */} - {showFallbackOverlay && ( - - - - Profile loading is slow - - {platformDisplayName} is taking longer than usual to load inside the app. Would you like to open it directly in the native app? - - - - Open in {platformDisplayName} App - - - - Open in Default Browser - - - - - Retry Loading - - navigation.goBack()} - activeOpacity={0.7}> - Cancel - - - - - )} - - ) : ( - - Invalid profile URL - - )} - - {/* Done Button Footer */} + ( + + Loading {displayName}... + + )} + onNavigationStateChange={(navState) => { + // If user navigates away from the profile page, + // they likely completed the action + // We could auto-close here in the future + }} + /> + + {/* Done Button */} + onPress={() => navigation.goBack()}> Done @@ -520,142 +93,25 @@ const styles = StyleSheet.create({ header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', padding: SPACING.md, borderBottomWidth: 1, borderBottomColor: COLORS.border, - backgroundColor: COLORS.bgSecondary, }, - closeText: { color: COLORS.textSecondary, fontSize: FONT_SIZE.md, fontWeight: '600' }, + closeText: { color: COLORS.textSecondary, fontSize: FONT_SIZE.md }, headerTitle: { fontSize: FONT_SIZE.md, fontWeight: '700', color: COLORS.textPrimary }, headerSpacer: { width: 60 }, - progressBar: { - height: 3, - position: 'absolute', - bottom: 0, - left: 0, - zIndex: 10, - }, banner: { backgroundColor: COLORS.bgCard, padding: SPACING.md, borderBottomWidth: 1, borderBottomColor: COLORS.border, }, - bannerText: { fontSize: FONT_SIZE.sm, color: COLORS.textSecondary, textAlign: 'center', lineHeight: 20 }, + bannerText: { fontSize: FONT_SIZE.sm, color: COLORS.textSecondary, textAlign: 'center' }, bannerBold: { fontWeight: '700', color: COLORS.primary }, - toast: { - position: 'absolute', - top: 118, - left: SPACING.md, - right: SPACING.md, - zIndex: 20, - backgroundColor: COLORS.success, - borderRadius: BORDER_RADIUS.md, - padding: SPACING.md, - alignItems: 'center', - ...SHADOWS.button, - }, - toastText: { color: COLORS.white, fontSize: FONT_SIZE.sm, fontWeight: '700' }, - webContainer: { flex: 1, position: 'relative' }, webview: { flex: 1 }, - loading: { - ...StyleSheet.absoluteFillObject, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: COLORS.bgPrimary, - padding: SPACING.lg, - zIndex: 5, - }, - loadingBlock: { marginTop: SPACING.lg }, - loadingLine: { marginTop: SPACING.md }, - loadingText: { color: COLORS.textMuted, fontSize: FONT_SIZE.md, marginTop: SPACING.lg }, + loading: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: COLORS.bgPrimary }, + loadingText: { color: COLORS.textMuted, fontSize: FONT_SIZE.md }, footer: { padding: SPACING.md, borderTopWidth: 1, borderTopColor: COLORS.border, - backgroundColor: COLORS.bgSecondary, }, doneButton: { - backgroundColor: COLORS.bgElevated, borderRadius: BORDER_RADIUS.md, + backgroundColor: COLORS.success, borderRadius: BORDER_RADIUS.md, padding: SPACING.md, alignItems: 'center', - borderWidth: 1, - borderColor: COLORS.border, - }, - doneButtonText: { color: COLORS.textPrimary, fontWeight: '700', fontSize: FONT_SIZE.md }, - - // Custom Fallback Overlay Styling - overlayContainer: { - ...StyleSheet.absoluteFillObject, - backgroundColor: 'rgba(15, 15, 26, 0.95)', - justifyContent: 'center', - alignItems: 'center', - padding: SPACING.lg, - zIndex: 50, - }, - overlayCard: { - backgroundColor: COLORS.bgSecondary, - borderRadius: BORDER_RADIUS.lg, - padding: SPACING.xl, - width: '100%', - maxWidth: 340, - alignItems: 'center', - borderWidth: 1, - borderColor: COLORS.border, - ...SHADOWS.card, - }, - overlayIcon: { - fontSize: 48, - marginBottom: SPACING.md, - }, - overlayTitle: { - fontSize: FONT_SIZE.lg, - fontWeight: '700', - color: COLORS.textPrimary, - marginBottom: SPACING.sm, - textAlign: 'center', - }, - overlayDescription: { - fontSize: FONT_SIZE.sm, - color: COLORS.textSecondary, - textAlign: 'center', - marginBottom: SPACING.lg, - lineHeight: 20, - }, - overlayPrimaryButton: { - backgroundColor: COLORS.primary, - borderRadius: BORDER_RADIUS.md, - paddingVertical: SPACING.md, - width: '100%', - alignItems: 'center', - marginBottom: SPACING.sm, - ...SHADOWS.button, - }, - overlayPrimaryButtonText: { - color: COLORS.white, - fontWeight: '700', - fontSize: FONT_SIZE.md, - }, - overlaySecondaryButton: { - backgroundColor: COLORS.bgElevated, - borderRadius: BORDER_RADIUS.md, - paddingVertical: SPACING.md, - width: '100%', - alignItems: 'center', - marginBottom: SPACING.lg, - borderWidth: 1, - borderColor: COLORS.border, - }, - overlaySecondaryButtonText: { - color: COLORS.textPrimary, - fontWeight: '600', - fontSize: FONT_SIZE.md, - }, - overlayRowButtons: { - flexDirection: 'row', - justifyContent: 'space-between', - width: '100%', - paddingHorizontal: SPACING.sm, - }, - overlayTextButton: { - paddingVertical: SPACING.sm, - paddingHorizontal: SPACING.md, - }, - overlayTextButtonText: { - color: COLORS.textMuted, - fontSize: FONT_SIZE.sm, - fontWeight: '600', }, + doneButtonText: { color: COLORS.white, fontWeight: '700', fontSize: FONT_SIZE.md }, }); diff --git a/apps/mobile/src/services/api.ts b/apps/mobile/src/services/api.ts deleted file mode 100644 index 70daf195..00000000 --- a/apps/mobile/src/services/api.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { API_BASE_URL } from '../config'; - -type RequestOptions = { - method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; - body?: unknown; - token?: string | null; - onUnauthorized?: () => void; -}; - -export async function apiRequest( - path: string, - { method = 'GET', body, token, onUnauthorized }: RequestOptions = {} -): Promise { - const headers: Record = { - 'Content-Type': 'application/json', - ...(token ? { Authorization: `Bearer ${token}` } : {}), - }; - - const res = await fetch(`${API_BASE_URL}${path}`, { - method, - headers, - ...(body ? { body: JSON.stringify(body) } : {}), - }); - - if (res.status === 401 || res.status === 403) { - onUnauthorized?.(); - throw new Error('Unauthorized'); - } - - if (!res.ok) { - const err = await res.json().catch(() => ({})); - throw new Error((err as any)?.message ?? `Request failed: ${res.status}`); - } - - // Some endpoints may return empty responses - const text = await res.text(); - if (!text) return (null as unknown) as T; - return JSON.parse(text) as T; -} - -export const get = (path: string, token?: string | null) => apiRequest(path, { method: 'GET', token }); -export const post = (path: string, body?: unknown, token?: string | null) => apiRequest(path, { method: 'POST', body, token }); -export const put = (path: string, body?: unknown, token?: string | null) => apiRequest(path, { method: 'PUT', body, token }); -export const del = (path: string, body?: unknown, token?: string | null) => apiRequest(path, { method: 'DELETE', body, token }); - -export default { apiRequest, get, post, put, del }; diff --git a/apps/mobile/src/types/index.ts b/apps/mobile/src/types/index.ts deleted file mode 100644 index c815a5d0..00000000 --- a/apps/mobile/src/types/index.ts +++ /dev/null @@ -1,100 +0,0 @@ -// ── Centralized Mobile Type Definitions ─────────────────────────────────────── -// Re-exports shared types and defines mobile-only types to eliminate duplicate -// interface declarations across screens (was duplicated in 4+ files). - -export type { - User, - PlatformLink, - Card, - PublicProfile, - PublicCard, - FollowStatus, - FollowResult, - AuthResponse, - CardView, - AnalyticsOverview, - ConnectedPlatform, - FollowLog, - OAuthTokenInfo, - CreateLinkPayload, - UpdateProfilePayload, - CreateCardPayload, - UpdateCardPayload, - ReorderLinksPayload, -} from '@devcard/shared'; - -export type { PlatformDef, FollowStrategy } from '@devcard/shared'; - -// ── Mobile-only Types ───────────────────────────────────────────────────────── - -export interface SavedContact { - username: string; - displayName: string; - avatarUrl: string | null; - accentColor: string; - bio: string | null; - role: string | null; - company: string | null; - metAt: string | null; - note: string | null; - savedAt: string; -} - -export interface EventSummary { - id: string; - name: string; - slug: string; - location: string; - description: string | null; - startDate: string; - endDate: string; - attendeesCount: number; -} - -export interface EventDetail extends EventSummary { - organizerId: string; - createdAt: string; -} - -export interface EventAttendee { - id: string; - username: string; - displayName: string; - bio: string | null; - pronouns: string | null; - company: string | null; - avatarUrl: string | null; - accentColor: string; -} - -export interface TeamSummary { - id: string; - name: string; - slug: string; - description: string | null; - avatarUrl: string | null; - ownerId: string; - createdAt: string; - updatedAt: string | null; - members: TeamMember[]; -} - -export interface TeamMember { - username: string; - displayName: string; - bio: string | null; - pronouns: string | null; - role: string | null; - company: string | null; - avatarUrl: string | null; - accentColor: string; - teamRole: 'OWNER' | 'ADMIN' | 'MEMBER'; - joinedAt: string; -} - -export type FollowState = Record; - -export interface NfcPayload { - type: 'URI'; - payload: string; -} diff --git a/apps/web/src/app.css b/apps/web/src/app.css index 0f9e8bb0..bb09fef9 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -3,51 +3,48 @@ :root { /* Primary Palette */ --primary: #6366f1; - --primary-glow: rgba(99, 102, 241, 0.4); + --primary-glow: rgba(99, 102, 241, 0.5); --accent: #a855f7; - --accent-glow: rgba(168, 85, 247, 0.35); - + --accent-glow: rgba(168, 85, 247, 0.4); + /* Backgrounds */ --bg-primary: #ffffff; --bg-secondary: #f8fafc; - --bg-page: #eef2ff; - --bg-glass: rgba(255, 255, 255, 0.38); + --bg-glass: rgba(255, 255, 255, 0.7); --bg-card: #ffffff; - + /* Text */ --text-primary: #0f172a; --text-secondary: #475569; - --text-muted: #64748b; - + --text-muted: #94a3b8; + /* Effects */ - --border: rgba(226, 232, 240, 0.9); - --border-glass: rgba(99, 102, 241, 0.25); + --border: rgba(226, 232, 240, 0.8); + --border-glass: rgba(255, 255, 255, 0.3); --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --shadow-md: 0 6px 18px -8px rgb(0 0 0 / 0.12), 0 4px 14px -12px rgb(0 0 0 / 0.08); - --shadow-lg: 0 12px 24px -10px rgb(0 0 0 / 0.15), 0 6px 12px -14px rgb(0 0 0 / 0.08); - --shadow-nav: 0 8px 32px -8px rgba(99, 102, 241, 0.18), 0 2px 8px 0 rgba(99, 102, 241, 0.08); - - --radius: 14px; - --radius-lg: 26px; - --radius-xl: 34px; - - --theme-transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + + --radius: 12px; + --radius-lg: 20px; + --radius-xl: 32px; + + --theme-transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); } html.dark { --bg-primary: #020617; --bg-secondary: #0f172a; - --bg-page: #050b18; - --bg-glass: rgba(15, 23, 42, 0.72); + --bg-glass: rgba(15, 23, 42, 0.6); --bg-card: #0f172a; - + --text-primary: #f8fafc; --text-secondary: #cbd5e1; --text-muted: #64748b; - - --border: rgba(30, 41, 59, 0.85); - --border-glass: rgba(255, 255, 255, 0.12); - --shadow-nav: 0 4px 24px -6px rgba(0, 0, 0, 0.45), 0 1px 4px 0 rgba(0, 0, 0, 0.25); + + --border: rgba(30, 41, 59, 0.8); + --border-glass: rgba(255, 255, 255, 0.1); } * { @@ -58,11 +55,7 @@ html.dark { body { font-family: 'Inter', sans-serif; - background: - radial-gradient(ellipse at 60% -10%, rgba(99, 102, 241, 0.28) 0%, transparent 55%), - radial-gradient(ellipse at -10% 80%, rgba(168, 85, 247, 0.18) 0%, transparent 45%), - radial-gradient(ellipse at 100% 60%, rgba(99, 102, 241, 0.12) 0%, transparent 40%), - var(--bg-page); + background-color: var(--bg-primary); color: var(--text-primary); transition: var(--theme-transition); -webkit-font-smoothing: antialiased; @@ -73,7 +66,7 @@ body { h1, h2, h3, h4, h5, h6 { font-family: 'Outfit', sans-serif; font-weight: 700; - line-height: 1.15; + line-height: 1.1; } a { @@ -82,22 +75,11 @@ a { transition: var(--theme-transition); } -button, -.btn-primary, -.btn-secondary { - transition: transform 0.24s ease, box-shadow 0.24s ease, background-color 0.24s ease, border-color 0.24s ease, color 0.24s ease; -} - -button { - font: inherit; -} - .glass { background: var(--bg-glass); - backdrop-filter: blur(18px); - -webkit-backdrop-filter: blur(18px); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); border: 1px solid var(--border-glass); - box-shadow: var(--shadow-nav); } .gradient-text { @@ -108,99 +90,17 @@ button { } .btn-primary { - display: inline-flex; - align-items: center; - justify-content: center; background: linear-gradient(135deg, var(--primary), var(--accent)); color: white; - padding: 0.95rem 1.85rem; - border-radius: calc(var(--radius) * 1.2); - font-weight: 700; - box-shadow: 0 18px 35px -18px rgba(99, 102, 241, 0.9); + padding: 0.8rem 1.6rem; + border-radius: var(--radius); + font-weight: 600; + box-shadow: 0 4px 15px var(--primary-glow); border: none; cursor: pointer; } .btn-primary:hover { transform: translateY(-2px); - box-shadow: 0 22px 40px -16px rgba(99, 102, 241, 0.9); -} - -.btn-primary:focus-visible { - outline: 3px solid rgba(99, 102, 241, 0.35); - outline-offset: 3px; -} - -.btn-secondary { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.85rem 1.75rem; - border-radius: calc(var(--radius) * 1.2); - font-weight: 700; - border: 1px solid rgba(255, 255, 255, 0.14); - background: rgba(255, 255, 255, 0.08); - color: var(--text-primary); - cursor: pointer; + box-shadow: 0 6px 20px var(--primary-glow); } - -.btn-secondary:hover { - background: rgba(255, 255, 255, 0.14); - border-color: rgba(99, 102, 241, 0.45); -} - -.btn-secondary:focus-visible { - outline: 3px solid rgba(99, 102, 241, 0.18); - outline-offset: 3px; -} - -/* ---------- Custom themed scrollbar (issue #151) ---------- */ - -/* Firefox */ -html { - scrollbar-width: thin; - scrollbar-color: var(--primary) var(--bg-secondary); -} - -/* WebKit (Chromium, Safari, Edge) */ -::-webkit-scrollbar { - width: 10px; - height: 10px; -} - -::-webkit-scrollbar-track { - background: var(--bg-secondary); - border-radius: 999px; -} - -::-webkit-scrollbar-thumb { - background: linear-gradient(135deg, var(--primary), var(--accent)); - border-radius: 999px; - border: 2px solid var(--bg-secondary); - background-clip: padding-box; - transition: background 0.2s ease, box-shadow 0.2s ease; -} - -::-webkit-scrollbar-thumb:hover { - background: linear-gradient(135deg, var(--accent), var(--primary)); - box-shadow: 0 0 8px var(--primary-glow); -} - -::-webkit-scrollbar-corner { - background: var(--bg-secondary); -} - -@media (prefers-reduced-motion: reduce) { - * { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} - -/* Light mode btn-secondary fix */ -:root:not(.dark) .btn-secondary { - border-color: var(--border); - background: rgba(0, 0, 0, 0.04); -} \ No newline at end of file diff --git a/apps/web/src/app.html b/apps/web/src/app.html index 666257e4..f273cc58 100644 --- a/apps/web/src/app.html +++ b/apps/web/src/app.html @@ -3,11 +3,6 @@ - - - - - %sveltekit.head% diff --git a/apps/web/src/lib/apiClient.ts b/apps/web/src/lib/apiClient.ts deleted file mode 100644 index dbaad43f..00000000 --- a/apps/web/src/lib/apiClient.ts +++ /dev/null @@ -1,36 +0,0 @@ -const API_BASE_URL = import.meta.env.PUBLIC_API_URL ?? 'http://localhost:3000'; - -type RequestOptions = { - method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; - body?: unknown; - token?: string | null; - onUnauthorized?: () => void; -}; - -export async function apiRequest( - endpoint: string, - { method = 'GET', body, token, onUnauthorized }: RequestOptions = {} -): Promise { - const headers: Record = { - 'Content-Type': 'application/json', - ...(token ? { Authorization: `Bearer ${token}` } : {}), - }; - - const response = await fetch(`${API_BASE_URL}${endpoint}`, { - method, - headers, - ...(body ? { body: JSON.stringify(body) } : {}), - }); - - if (response.status === 401 || response.status === 403) { - onUnauthorized?.(); - throw new Error('Unauthorized'); - } - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw new Error((error as any)?.message ?? `Request failed: ${response.status}`); - } - - return response.json() as Promise; -} \ No newline at end of file diff --git a/apps/web/src/routes/+page.svelte b/apps/web/src/routes/+page.svelte index efaa65e5..512f9053 100644 --- a/apps/web/src/routes/+page.svelte +++ b/apps/web/src/routes/+page.svelte @@ -1,3 +1,92 @@ + + + + DevCard — One Tap. Every Profile. Every Platform. + + + +
+ +
+ + +
+
GSSoC'26 Edition
+

One Tap. Every Profile.
Every Platform.

+

+ The open-source standard for developer networking. Put all your profiles—GitHub, LinkedIn, LeetCode, and more—into a single, high-impact digital card. +

+ +
+ +
+
+
🔗
+

Unified Identity

+

Combine your fragmented online presence into a cohesive professional identity.

+
+
+
+

Instant Follow

+

Integrated APIs allow followers to connect with you instantly across platforms.

+
+
+
🔒
+

Private by Design

+

No tracking, no data selling. Your information stays where it belongs: with you.

+
+
+ +
+

© 2026 DevCard • Built for the Developer Community

+
+
+ \ No newline at end of file + diff --git a/apps/web/src/routes/devcard/[id]/+page.server.ts b/apps/web/src/routes/devcard/[id]/+page.server.ts index a93fbc75..adc98179 100644 --- a/apps/web/src/routes/devcard/[id]/+page.server.ts +++ b/apps/web/src/routes/devcard/[id]/+page.server.ts @@ -1,28 +1,17 @@ import { error } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; -const API_BASE = process.env.BACKEND_URL || 'http://localhost:3000'; - export const load: PageServerLoad = async ({ params, fetch }) => { const { id } = params; - try { - const res = await fetch(`${API_BASE}/api/u/card/${id}`); - - if (res.status === 404) { - throw error(404, 'Card not found'); - } + // Use internal fetch to reach the backend + // In production, this would be the actual API URL + const res = await fetch(`http://localhost:3000/api/u/card/${id}`); - if (!res.ok) { - throw error(500, 'Failed to load card'); - } - - const card = await res.json(); - return { card }; - } catch (error) { - if (error && typeof error === 'object' && 'status' in error) { - throw error; - } - throw error(500, 'Failed to connect to backend'); + if (!res.ok) { + throw error(404, 'Card not found'); } + + const card = await res.json(); + return { card }; }; diff --git a/apps/web/src/routes/devcard/[id]/+page.svelte b/apps/web/src/routes/devcard/[id]/+page.svelte index 7423f7ba..a38073fe 100644 --- a/apps/web/src/routes/devcard/[id]/+page.svelte +++ b/apps/web/src/routes/devcard/[id]/+page.svelte @@ -104,7 +104,7 @@ diff --git a/apps/web/src/routes/u/[username]/+page.svelte b/apps/web/src/routes/u/[username]/+page.svelte index 50cb4226..d75e485c 100644 --- a/apps/web/src/routes/u/[username]/+page.svelte +++ b/apps/web/src/routes/u/[username]/+page.svelte @@ -15,48 +15,9 @@ }; let mounted = $state(false); - let copyMessage = $state(''); - let copyStatus = $state<'success' | 'error'>('success'); - let copyMessageTimeout: ReturnType; - onMount(() => { mounted = true; - - return () => { - if (copyMessageTimeout) { - clearTimeout(copyMessageTimeout); - } - }; }); - - function showCopyMessage(message: string, status: 'success' | 'error') { - copyMessage = message; - copyStatus = status; - - if (copyMessageTimeout) { - clearTimeout(copyMessageTimeout); - } - - clearTimeout(copyTimeout); - - copyTimeout = setTimeout(() => { - copyMessage = ''; - }, 3000); - } - - async function copyProfileUrl() { - if (!navigator.clipboard?.writeText) { - showCopyMessage('Clipboard API unavailable. Copy the URL from your address bar.', 'error'); - return; - } - - try { - await navigator.clipboard.writeText(window.location.href); - showCopyMessage('Profile link copied.', 'success'); - } catch { - showCopyMessage('Could not copy link. Copy the URL from your address bar.', 'error'); - } - } @@ -135,17 +96,7 @@

Want a card like this?

-
- Create your DevCard ⚡ - -
- {#if copyMessage} -

- {copyMessage} -

- {/if} + Create your DevCard ⚡
{/if} @@ -159,7 +110,7 @@ bottom: 0; background: radial-gradient(circle at 50% 0%, var(--accent), transparent 50%), #020617; - opacity: 0.18; + opacity: 0.15; z-index: -1; } @@ -168,10 +119,10 @@ display: flex; flex-direction: column; align-items: center; - padding: clamp(2rem, 6vw, 5rem) 1.25rem 3rem; + padding: 4rem 1.5rem; opacity: 0; - transform: translateY(22px); - transition: opacity 0.65s ease, transform 0.65s ease; + transform: translateY(20px); + transition: all 0.8s cubic-bezier(0.2, 0.8, 0.2, 1); } .profile-container.loaded { @@ -181,67 +132,65 @@ .profile-card { width: 100%; - max-width: 540px; + max-width: 480px; border-radius: var(--radius-xl); - padding: 2.5rem 2rem; - box-shadow: 0 26px 60px -20px rgba(0, 0, 0, 0.55); + padding: 3rem 2rem; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); position: relative; overflow: hidden; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(15, 23, 42, 0.96); } .profile-header { text-align: center; - margin-bottom: 2.5rem; + margin-bottom: 3rem; } .avatar-wrapper { position: relative; - width: 120px; - height: 120px; - margin: 0 auto 1.75rem; + width: 110px; + height: 110px; + margin: 0 auto 1.5rem; } .avatar { width: 100%; height: 100%; - border-radius: 32% 68% 63% 37% / 34% 36% 64% 66%; + border-radius: 35% 65% 70% 30% / 30% 30% 70% 70%; object-fit: cover; - border: 3px solid rgba(255, 255, 255, 0.18); + border: 3px solid white; position: relative; z-index: 2; + animation: morph 8s ease-in-out infinite; } - .avatar-placeholder { - width: 100%; - height: 100%; - border-radius: 32% 68% 63% 37% / 34% 36% 64% 66%; - display: flex; - align-items: center; - justify-content: center; - font-size: 3rem; - font-weight: 800; - color: white; + @keyframes morph { + 0%, 100% { border-radius: 35% 65% 70% 30% / 30% 30% 70% 70%; } + 50% { border-radius: 65% 35% 30% 70% / 70% 70% 30% 30%; } + } + + .avatar-glow { + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + filter: blur(20px); + opacity: 0.4; + z-index: 1; + border-radius: 50%; } .display-name { - font-size: clamp(2rem, 4vw, 2.5rem); + font-size: 2.25rem; font-weight: 800; - letter-spacing: -0.5px; - margin-bottom: 0.75rem; + letter-spacing: -1px; + margin-bottom: 0.5rem; } .role-badge { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.45rem 1rem; - background: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 999px; - font-size: 0.9rem; - font-weight: 700; + display: inline-block; + padding: 0.4rem 1rem; + background: rgba(255, 255, 255, 0.1); + border-radius: 100px; + font-size: 0.85rem; + font-weight: 600; color: var(--text-secondary); margin-bottom: 1rem; } @@ -249,83 +198,72 @@ .bio { color: var(--text-secondary); font-size: 1rem; - line-height: 1.85; - max-width: 640px; + line-height: 1.6; + max-width: 320px; margin: 0 auto; } .links-grid { display: flex; flex-direction: column; - gap: 1rem; + gap: 0.75rem; } .link-tile { display: flex; align-items: center; padding: 1rem; - border-radius: calc(var(--radius) * 1.1); - border: 1px solid rgba(255, 255, 255, 0.1); - background: rgba(255, 255, 255, 0.06); - box-shadow: 0 12px 30px -18px rgba(0, 0, 0, 0.35); - transition: transform 0.25s ease, background 0.25s ease, border-color 0.25s ease; + border-radius: var(--radius-lg); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); animation: slideIn 0.5s ease-out forwards; animation-delay: var(--delay); opacity: 0; } - .link-tile:hover, - .link-tile:focus-visible { - background: rgba(255, 255, 255, 0.13); - transform: translateY(-2px); - border-color: rgba(99, 102, 241, 0.35); - } - - .link-tile:focus-visible { - outline: 3px solid rgba(99, 102, 241, 0.2); - outline-offset: 3px; - } - @keyframes slideIn { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } } + .link-tile:hover { + background: rgba(255, 255, 255, 0.15); + transform: scale(1.02) translateX(5px); + } + .tile-icon { - width: 46px; - height: 46px; - border-radius: 15px; + width: 44px; + height: 44px; + border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-weight: 800; - font-size: 1.1rem; - box-shadow: 0 8px 18px -10px rgba(0,0,0,0.4); + font-size: 1.2rem; + box-shadow: 0 4px 12px rgba(0,0,0,0.2); } .tile-content { flex: 1; - margin-left: 1.1rem; + margin-left: 1.25rem; } .platform-name { display: block; font-weight: 700; - font-size: 1rem; + font-size: 1.05rem; } .username { display: block; - font-size: 0.9rem; + font-size: 0.85rem; color: var(--text-muted); - margin-top: 0.1rem; } .arrow { - opacity: 0.45; + opacity: 0.3; font-size: 1.2rem; - transition: transform 0.25s ease, opacity 0.25s ease; + transition: all 0.3s; } .link-tile:hover .arrow { @@ -334,104 +272,48 @@ } .card-footer { - margin-top: 2.5rem; - padding-top: 1.75rem; - border-top: 1px solid rgba(255,255,255,0.08); + margin-top: 3rem; + padding-top: 2rem; + border-top: 1px solid rgba(255,255,255,0.05); display: flex; justify-content: space-between; align-items: center; color: var(--text-muted); - font-size: 0.82rem; - gap: 1rem; - flex-wrap: wrap; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; } .logo-sm { color: var(--text-secondary); font-family: 'Outfit', sans-serif; - font-weight: 700; } .get-your-own { - margin-top: 2rem; + margin-top: 3rem; text-align: center; } .get-your-own p { - margin-bottom: 0.5rem; - font-size: 0.95rem; + font-size: 0.9rem; color: var(--text-muted); + margin-bottom: 0.5rem; } - .profile-actions { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: center; - gap: 0.75rem; - } - - .get-devcard-link { - font-weight: 700; - font-size: 1.05rem; - } - - .copy-link-button { - border: 1px solid var(--border-glass); - border-radius: var(--radius); - background: rgba(255, 255, 255, 0.08); - color: var(--text-primary); - cursor: pointer; - font: inherit; + .get-your-own a { font-weight: 700; - padding: 0.65rem 1rem; - transition: all 0.2s ease; - } - - .copy-link-button:hover { - background: rgba(255, 255, 255, 0.15); - transform: translateY(-1px); - } - - .copy-link-button:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 3px; - } - - .copy-message { - min-height: 1.2rem; - margin-top: 0.75rem; - margin-bottom: 0; - font-size: 0.85rem; - } - - .copy-message.success { - color: var(--text-secondary); - } - - .copy-message.error { - color: #ef4444; + font-size: 1.1rem; } .error-glass { text-align: center; - padding: 3rem; + padding: 4rem; border-radius: var(--radius-xl); - width: min(100%, 520px); } - @media (max-width: 720px) { + @media (max-width: 480px) { .profile-card { padding: 2rem 1.5rem; } - .profile-header { margin-bottom: 2rem; } - .avatar-wrapper { width: 108px; height: 108px; margin-bottom: 1.5rem; } - .card-footer { flex-direction: column; align-items: flex-start; } - } - - @media (max-width: 520px) { - .profile-container { padding: 2rem 1rem 2.5rem; } - .display-name { font-size: 2rem; } - .link-tile { padding: 0.95rem; } - .tile-content { margin-left: 0.9rem; } - .card-footer { text-align: left; } + .display-name { font-size: 1.75rem; } } diff --git a/docker-compose.yml b/docker-compose.yml index cfa524ca..0786787a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: container_name: devcard-postgres restart: unless-stopped ports: - - '5433:5432' + - '5432:5432' environment: POSTGRES_USER: devcard POSTGRES_PASSWORD: devcard diff --git a/packages/shared/src/__tests__/cards.test.ts b/packages/shared/src/__tests__/cards.test.ts deleted file mode 100644 index 0c1a6d1e..00000000 --- a/packages/shared/src/__tests__/cards.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { validateCardPlatforms, diffCardPlatforms } from '../cards'; - -describe('validateCardPlatforms', () => { - it('passes with valid platforms', () => { - const result = validateCardPlatforms(['github', 'linkedin']); - expect(result.valid).toBe(true); - expect(result.errors).toHaveLength(0); - }); - - it('fails with empty array', () => { - const result = validateCardPlatforms([]); - expect(result.valid).toBe(false); - expect(result.errors).toContain('At least one platform is required.'); - }); - - it('fails with unknown platform', () => { - const result = validateCardPlatforms(['github', 'myspace']); - expect(result.valid).toBe(false); - expect(result.errors.some(e => e.includes('myspace'))).toBe(true); - }); - - it('fails with duplicate platforms', () => { - const result = validateCardPlatforms(['github', 'github']); - expect(result.valid).toBe(false); - expect(result.errors.some(e => e.includes('Duplicate'))).toBe(true); - }); - - it('passes with exactly 10 platforms', () => { - const platforms = ['github','linkedin','twitter','youtube','twitch', - 'discord','devto','medium','dribbble','leetcode']; - const result = validateCardPlatforms(platforms); - expect(result.valid).toBe(true); - }); - - it('fails with more than 10 platforms', () => { - const platforms = ['github','linkedin','twitter','youtube','twitch', - 'discord','devto','medium','dribbble','leetcode','npm']; - const result = validateCardPlatforms(platforms); - expect(result.valid).toBe(false); - expect(result.errors.some(e => e.includes('Maximum 10'))).toBe(true); - }); - - it('fails with all invalid platforms', () => { - const result = validateCardPlatforms(['myspace', 'bebo']); - expect(result.valid).toBe(false); - expect(result.errors.length).toBeGreaterThanOrEqual(2); - }); -}); - -describe('diffCardPlatforms', () => { - it('correctly identifies added, removed, unchanged', () => { - const diff = diffCardPlatforms(['github', 'linkedin'], ['github', 'twitter']); - expect(diff.added).toEqual(['twitter']); - expect(diff.removed).toEqual(['linkedin']); - expect(diff.unchanged).toEqual(['github']); - }); - - it('handles empty old card', () => { - const diff = diffCardPlatforms([], ['github']); - expect(diff.added).toEqual(['github']); - expect(diff.removed).toEqual([]); - expect(diff.unchanged).toEqual([]); - }); - - it('handles identical cards', () => { - const diff = diffCardPlatforms(['github'], ['github']); - expect(diff.added).toEqual([]); - expect(diff.removed).toEqual([]); - expect(diff.unchanged).toEqual(['github']); - }); -}); \ No newline at end of file diff --git a/packages/shared/src/__tests__/platforms-url.test.ts b/packages/shared/src/__tests__/platforms-url.test.ts deleted file mode 100644 index cbfac373..00000000 --- a/packages/shared/src/__tests__/platforms-url.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { getProfileUrl, getWebViewUrl, getDeepLinkUrl } from '../platforms'; - -// ─── getProfileUrl Tests ─── - -describe('getProfileUrl', () => { - it('should return the correct GitHub profile URL', () => { - expect(getProfileUrl('github', 'octocat')).toBe('https://github.com/octocat'); - }); - - it('should return the correct LinkedIn profile URL', () => { - expect(getProfileUrl('linkedin', 'john')).toBe('https://www.linkedin.com/in/john'); - }); - - it('should return the correct Twitter profile URL', () => { - expect(getProfileUrl('twitter', 'john')).toBe('https://x.com/john'); - }); - - it('should return empty string for an unknown platform', () => { - expect(getProfileUrl('nonexistent', 'user')).toBe(''); - }); -}); - -// ─── getWebViewUrl Tests ─── - -describe('getWebViewUrl', () => { - it('should return the correct LinkedIn webview URL', () => { - expect(getWebViewUrl('linkedin', 'john')).toBe('https://www.linkedin.com/in/john'); - }); - - it('should return the correct Twitter webview URL', () => { - expect(getWebViewUrl('twitter', 'john')).toBe('https://x.com/john'); - }); - - it('should return null for platforms without a webview URL (github)', () => { - expect(getWebViewUrl('github', 'octocat')).toBeNull(); - }); - - it('should return null for an unknown platform', () => { - expect(getWebViewUrl('nonexistent', 'user')).toBeNull(); - }); -}); - -// ─── getDeepLinkUrl Tests ─── - -describe('getDeepLinkUrl', () => { - it('should return the correct Twitter deep link URL', () => { - expect(getDeepLinkUrl('twitter', 'john')).toBe('twitter://user?screen_name=john'); - }); - - it('should return the correct LinkedIn deep link URL', () => { - expect(getDeepLinkUrl('linkedin', 'john')).toBe('linkedin://profile?id=john'); - }); - - it('should return null for platforms without a deep link (github)', () => { - expect(getDeepLinkUrl('github', 'octocat')).toBeNull(); - }); - - it('should return null for an unknown platform', () => { - expect(getDeepLinkUrl('nonexistent', 'user')).toBeNull(); - }); -}); diff --git a/packages/shared/src/cards.ts b/packages/shared/src/cards.ts deleted file mode 100644 index d9fa5130..00000000 --- a/packages/shared/src/cards.ts +++ /dev/null @@ -1,50 +0,0 @@ -export type CardValidationResult = { - valid: boolean; - errors: string[]; -}; - -const PLATFORMS = new Set([ - 'github', 'linkedin', 'twitter', 'instagram', 'youtube', - 'twitch', 'discord', 'devto', 'hashnode', 'medium', - 'dribbble', 'behance', 'figma', 'stackoverflow', 'leetcode', - 'codepen', 'replit', 'npm', 'producthunt', 'website', -]); - -export function validateCardPlatforms(platforms: string[]): CardValidationResult { - const errors: string[] = []; - - if (platforms.length === 0) { - errors.push('At least one platform is required.'); - } - - if (platforms.length > 10) { - errors.push(`Maximum 10 platforms allowed, got ${platforms.length}.`); - } - - const seen = new Set(); - for (const p of platforms) { - if (!PLATFORMS.has(p)) { - errors.push(`Unknown platform: "${p}".`); - } - if (seen.has(p)) { - errors.push(`Duplicate platform: "${p}".`); - } - seen.add(p); - } - - return { valid: errors.length === 0, errors }; -} - -export function diffCardPlatforms( - oldCard: string[], - newCard: string[] -): { added: string[]; removed: string[]; unchanged: string[] } { - const oldSet = new Set(oldCard); - const newSet = new Set(newCard); - - return { - added: newCard.filter(p => !oldSet.has(p)), - removed: oldCard.filter(p => !newSet.has(p)), - unchanged: oldCard.filter(p => newSet.has(p)), - }; -} \ No newline at end of file diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 409d3e76..a57e7e77 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,3 +1,2 @@ export * from './platforms'; export * from './types'; -export * from './cards'; \ No newline at end of file diff --git a/packages/shared/src/platforms.test.ts b/packages/shared/src/platforms.test.ts index 6ce07a0b..d1ff86b7 100644 --- a/packages/shared/src/platforms.test.ts +++ b/packages/shared/src/platforms.test.ts @@ -74,38 +74,3 @@ describe('getWebViewUrl / getDeepLinkUrl – stackoverflow', () => { expect(getDeepLinkUrl('stackoverflow', '1234/user')).toBeNull(); }); }); - -// ─── validationRegex Tests ─── - -describe('validationRegex logic', () => { - it('should correctly validate github usernames', () => { - const regex = PLATFORMS.github.validationRegex!; - expect(regex.test('valid-user')).toBe(true); - expect(regex.test('a')).toBe(true); - expect(regex.test('user123')).toBe(true); - // Invalid - expect(regex.test('-invalid')).toBe(false); - expect(regex.test('invalid-')).toBe(false); - expect(regex.test('in--valid')).toBe(false); - expect(regex.test('user name')).toBe(false); - }); - - it('should correctly validate linkedin usernames', () => { - const regex = PLATFORMS.linkedin.validationRegex!; - expect(regex.test('valid-user')).toBe(true); - expect(regex.test('user123')).toBe(true); - // Invalid - expect(regex.test('ab')).toBe(false); // Too short - expect(regex.test('user name')).toBe(false); - }); - - it('should correctly validate twitter usernames', () => { - const regex = PLATFORMS.twitter.validationRegex!; - expect(regex.test('valid_user')).toBe(true); - expect(regex.test('user123')).toBe(true); - // Invalid - expect(regex.test('user-name')).toBe(false); // Hyphens not allowed - expect(regex.test('this_is_a_very_long_name_indeed')).toBe(false); // Too long - expect(regex.test('user name')).toBe(false); - }); -}); diff --git a/packages/shared/src/platforms.ts b/packages/shared/src/platforms.ts index 81c81ab4..a218957c 100644 --- a/packages/shared/src/platforms.ts +++ b/packages/shared/src/platforms.ts @@ -27,8 +27,6 @@ export interface PlatformDef { usernamePlaceholder: string; /** Whether the platform uses full URL instead of username */ usesFullUrl: boolean; - /** Regex pattern to validate usernames */ - validationRegex?: RegExp; } // ─── Platform Registry ─── @@ -46,7 +44,6 @@ export const PLATFORMS: Record = { oauthScopes: ['user:follow', 'read:user'], usernamePlaceholder: 'e.g. octocat', usesFullUrl: false, - validationRegex: /^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/, }, linkedin: { id: 'linkedin', @@ -60,7 +57,6 @@ export const PLATFORMS: Record = { oauthScopes: ['r_liteprofile'], usernamePlaceholder: 'e.g. johndoe', usesFullUrl: false, - validationRegex: /^[a-zA-Z0-9-]{3,100}$/, }, twitter: { id: 'twitter', @@ -74,7 +70,6 @@ export const PLATFORMS: Record = { oauthScopes: [], usernamePlaceholder: 'e.g. elonmusk', usesFullUrl: false, - validationRegex: /^[A-Za-z0-9_]{1,15}$/, }, gitlab: { id: 'gitlab', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b08a8f46..68186049 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,27 +32,24 @@ importers: '@fastify/multipart': specifier: ^9.0.0 version: 9.4.0 - '@fastify/rate-limit': - specifier: ^10.3.0 - version: 10.3.0 '@fastify/static': specifier: ^8.0.0 version: 8.3.0 '@prisma/client': specifier: ^6.0.0 - version: 6.19.3(prisma@6.19.3(typescript@5.9.3))(typescript@5.9.3) + version: 6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3) dotenv: specifier: ^16.4.0 version: 16.6.1 fastify: specifier: ^5.0.0 - version: 5.8.5 + version: 5.8.2 fastify-plugin: specifier: ^5.0.0 version: 5.1.0 ioredis: specifier: ^5.4.0 - version: 5.11.0 + version: 5.10.0 qrcode: specifier: ^1.5.0 version: 1.5.4 @@ -62,49 +59,25 @@ importers: devDependencies: '@types/node': specifier: ^22.0.0 - version: 22.19.19 + version: 22.19.15 '@types/qrcode': specifier: ^1.5.0 version: 1.5.6 - eslint: - specifier: ^10.4.0 - version: 10.4.1(jiti@2.7.0) - eslint-import-resolver-typescript: - specifier: ^4.4.4 - version: 4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0)))(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-import-x: - specifier: ^4.16.2 - version: 4.16.2(@typescript-eslint/utils@8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-n: - specifier: ^18.0.1 - version: 18.0.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - eslint-plugin-promise: - specifier: ^7.3.0 - version: 7.3.0(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-security: - specifier: ^4.0.0 - version: 4.0.0 - eslint-plugin-unicorn: - specifier: ^64.0.0 - version: 64.0.0(eslint@10.4.1(jiti@2.7.0)) pino-pretty: specifier: ^13.1.3 version: 13.1.3 prisma: specifier: ^6.0.0 - version: 6.19.3(typescript@5.9.3) + version: 6.19.2(typescript@5.9.3) tsx: specifier: ^4.0.0 - version: 4.22.3 + version: 4.21.0 typescript: specifier: ^5.4.0 version: 5.9.3 - typescript-eslint: - specifier: ^8.59.3 - version: 8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.19.19)(terser@5.48.0) + version: 2.1.9(@types/node@22.19.15)(terser@5.46.0) apps/mobile: dependencies: @@ -113,77 +86,68 @@ importers: version: link:../../packages/shared '@gorhom/bottom-sheet': specifier: ^5.0.5 - version: 5.2.14(@types/react-native@0.70.19)(@types/react@19.2.15)(react-native-gesture-handler@2.31.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-reanimated@3.19.5(@babel/core@7.29.7)(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + version: 5.2.14(@types/react-native@0.70.19)(@types/react@19.2.14)(react-native-gesture-handler@2.31.2(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-reanimated@3.19.5(@babel/core@7.29.0)(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-native-async-storage/async-storage': specifier: ^2.1.0 - version: 2.2.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3)) + version: 2.2.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3)) '@react-native/new-app-screen': specifier: 0.84.1 - version: 0.84.1(@types/react@19.2.15)(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + version: 0.84.1(@types/react@19.2.14)(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/bottom-tabs': specifier: ^7.0.0 - version: 7.16.2(@react-navigation/native@7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-screens@4.25.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + version: 7.15.5(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/native': specifier: ^7.0.0 - version: 7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + version: 7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/native-stack': specifier: ^7.0.0 - version: 7.16.0(@react-navigation/native@7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-screens@4.25.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + version: 7.14.4(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react: specifier: 19.2.3 version: 19.2.3 react-dom: - specifier: ^19.2.3 - version: 19.2.6(react@19.2.3) + specifier: ^19.2.4 + version: 19.2.4(react@19.2.3) react-native: specifier: 0.84.1 - version: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) - react-native-camera-kit: - specifier: ^14.0.0 - version: 14.2.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + version: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) react-native-gesture-handler: - specifier: ^2.28.0 - version: 2.31.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + specifier: ^2.20.2 + version: 2.31.2(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-qrcode-svg: specifier: ^6.3.0 - version: 6.3.21(react-native-svg@15.15.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + version: 6.3.21(react-native-svg@15.15.3(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-reanimated: - specifier: ^3.16.7 - version: 3.19.5(@babel/core@7.29.7)(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + specifier: ^3.15.0 + version: 3.19.5(@babel/core@7.29.0)(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: specifier: ^5.5.2 - version: 5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + version: 5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-screens: specifier: ^4.0.0 - version: 4.25.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + version: 4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-svg: specifier: ^15.0.0 - version: 15.15.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + version: 15.15.3(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-vector-icons: specifier: ^10.0.0 version: 10.3.0 - react-native-view-shot: - specifier: ^5.1.0 - version: 5.1.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) react-native-web: specifier: ^0.21.2 - version: 0.21.2(react-dom@19.2.6(react@19.2.3))(react@19.2.3) + version: 0.21.2(react-dom@19.2.4(react@19.2.3))(react@19.2.3) react-native-webview: specifier: ^13.0.0 - version: 13.16.1(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) - react-native-worklets: - specifier: 0.5.1 - version: 0.5.1(@babel/core@7.29.7)(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + version: 13.16.1(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) devDependencies: '@babel/core': specifier: ^7.25.2 - version: 7.29.7 + version: 7.29.0 '@babel/preset-env': specifier: ^7.25.3 - version: 7.29.7(@babel/core@7.29.7) + version: 7.29.0(@babel/core@7.29.0) '@babel/runtime': specifier: ^7.25.0 - version: 7.29.7 + version: 7.28.6 '@react-native-community/cli': specifier: 20.1.0 version: 20.1.0(typescript@5.9.3) @@ -195,19 +159,19 @@ importers: version: 20.1.0 '@react-native/babel-preset': specifier: 0.84.1 - version: 0.84.1(@babel/core@7.29.7) + version: 0.84.1(@babel/core@7.29.0) '@react-native/codegen': specifier: 0.84.1 - version: 0.84.1(@babel/core@7.29.7) + version: 0.84.1(@babel/core@7.29.0) '@react-native/eslint-config': specifier: 0.84.1 - version: 0.84.1(eslint@8.57.1)(jest@29.7.0(@types/node@22.19.19))(prettier@2.8.8)(typescript@5.9.3) + version: 0.84.1(eslint@8.57.1)(jest@29.7.0(@types/node@22.19.15))(prettier@2.8.8)(typescript@5.9.3) '@react-native/gradle-plugin': specifier: 0.84.1 version: 0.84.1 '@react-native/metro-config': specifier: 0.84.1 - version: 0.84.1(@babel/core@7.29.7) + version: 0.84.1(@babel/core@7.29.0) '@react-native/typescript-config': specifier: 0.84.1 version: 0.84.1 @@ -216,7 +180,7 @@ importers: version: 29.5.14 '@types/react': specifier: ^19.2.0 - version: 19.2.15 + version: 19.2.14 '@types/react-native-vector-icons': specifier: ^6.4.18 version: 6.4.18 @@ -228,7 +192,7 @@ importers: version: 8.57.1 jest: specifier: ^29.6.3 - version: 29.7.0(@types/node@22.19.19) + version: 29.7.0(@types/node@22.19.15) prettier: specifier: 2.8.8 version: 2.8.8 @@ -247,25 +211,25 @@ importers: devDependencies: '@sveltejs/adapter-auto': specifier: ^7.0.0 - version: 7.0.1(@sveltejs/kit@2.61.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.56.0(@typescript-eslint/types@8.60.0))(typescript@5.9.3)(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0))) + version: 7.0.1(@sveltejs/kit@2.54.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.10)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))) '@sveltejs/kit': specifier: ^2.50.2 - version: 2.61.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.56.0(@typescript-eslint/types@8.60.0))(typescript@5.9.3)(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)) + version: 2.54.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.10)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 - version: 6.2.4(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)) + version: 6.2.4(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) svelte: specifier: ^5.51.0 - version: 5.56.0(@typescript-eslint/types@8.60.0) + version: 5.53.10 svelte-check: specifier: ^4.4.2 - version: 4.4.8(picomatch@4.0.4)(svelte@5.56.0(@typescript-eslint/types@8.60.0))(typescript@5.9.3) + version: 4.4.5(picomatch@4.0.3)(svelte@5.53.10)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 vite: specifier: ^7.3.1 - version: 7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0) + version: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) packages/shared: devDependencies: @@ -274,163 +238,157 @@ importers: version: 5.9.3 vitest: specifier: ^2.0.0 - version: 2.1.9(@types/node@22.19.19)(terser@5.48.0) + version: 2.1.9(@types/node@22.19.15)(terser@5.46.0) packages: - '@babel/code-frame@7.29.7': - resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.7': - resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} - '@babel/core@7.29.7': - resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} - '@babel/eslint-parser@7.29.7': - resolution: {integrity: sha512-zxt+UJTOMKvUt3yOg+D58MLuz334pHp93qifMFcjIIO+9hN6t+ufw2gi7vDPMpxvfnHRR+3VVXvIjineCcgyXw==} + '@babel/eslint-parser@7.28.6': + resolution: {integrity: sha512-QGmsKi2PBO/MHSQk+AAgA9R6OHQr+VqnniFE0eMWZcVcfBZoA2dKn2hUsl3Csg/Plt9opRUWdY7//VXsrIlEiA==} engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} peerDependencies: '@babel/core': ^7.11.0 eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 - '@babel/generator@7.29.7': - resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.29.7': - resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.29.7': - resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.29.7': - resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-create-regexp-features-plugin@7.29.7': - resolution: {integrity: sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==} + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.6.8': - resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} + '@babel/helper-define-polyfill-provider@0.6.7': + resolution: {integrity: sha512-6Fqi8MtQ/PweQ9xvux65emkLQ83uB+qAVtfHkC9UodyHMIZdxNI01HjLCLUtybElp2KY2XNE0nOgyP1E1vXw9w==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - '@babel/helper-globals@7.29.7': - resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.29.7': - resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.29.7': - resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.29.7': - resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-optimise-call-expression@7.29.7': - resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.29.7': - resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} - '@babel/helper-remap-async-to-generator@7.29.7': - resolution: {integrity: sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==} + '@babel/helper-remap-async-to-generator@7.27.1': + resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.29.7': - resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': - resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.29.7': - resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.29.7': - resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helper-wrap-function@7.29.7': - resolution: {integrity: sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==} + '@babel/helper-wrap-function@7.28.6': + resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.29.7': - resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7': - resolution: {integrity: sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7': - resolution: {integrity: sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==} + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': + resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7': - resolution: {integrity: sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==} + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': + resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7': - resolution: {integrity: sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==} + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': + resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7': - resolution: {integrity: sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==} + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': + resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7': - resolution: {integrity: sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==} + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-proposal-export-default-from@7.29.7': - resolution: {integrity: sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ==} + '@babel/plugin-proposal-export-default-from@7.27.1': + resolution: {integrity: sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -467,26 +425,26 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-export-default-from@7.29.7': - resolution: {integrity: sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw==} + '@babel/plugin-syntax-export-default-from@7.28.6': + resolution: {integrity: sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-flow@7.29.7': - resolution: {integrity: sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==} + '@babel/plugin-syntax-flow@7.28.6': + resolution: {integrity: sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-assertions@7.29.7': - resolution: {integrity: sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==} + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-attributes@7.29.7': - resolution: {integrity: sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==} + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -501,8 +459,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.29.7': - resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -549,8 +507,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.29.7': - resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -561,356 +519,356 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-arrow-functions@7.29.7': - resolution: {integrity: sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==} + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-generator-functions@7.29.7': - resolution: {integrity: sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==} + '@babel/plugin-transform-async-generator-functions@7.29.0': + resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-to-generator@7.29.7': - resolution: {integrity: sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==} + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoped-functions@7.29.7': - resolution: {integrity: sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==} + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.29.7': - resolution: {integrity: sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==} + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@7.29.7': - resolution: {integrity: sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==} + '@babel/plugin-transform-class-properties@7.28.6': + resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-static-block@7.29.7': - resolution: {integrity: sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==} + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 - '@babel/plugin-transform-classes@7.29.7': - resolution: {integrity: sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==} + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-computed-properties@7.29.7': - resolution: {integrity: sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==} + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-destructuring@7.29.7': - resolution: {integrity: sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==} + '@babel/plugin-transform-destructuring@7.28.5': + resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dotall-regex@7.29.7': - resolution: {integrity: sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==} + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-keys@7.29.7': - resolution: {integrity: sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==} + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7': - resolution: {integrity: sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==} + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-dynamic-import@7.29.7': - resolution: {integrity: sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==} + '@babel/plugin-transform-dynamic-import@7.27.1': + resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-explicit-resource-management@7.29.7': - resolution: {integrity: sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==} + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.29.7': - resolution: {integrity: sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==} + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-export-namespace-from@7.29.7': - resolution: {integrity: sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==} + '@babel/plugin-transform-export-namespace-from@7.27.1': + resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-flow-strip-types@7.29.7': - resolution: {integrity: sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==} + '@babel/plugin-transform-flow-strip-types@7.27.1': + resolution: {integrity: sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-for-of@7.29.7': - resolution: {integrity: sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==} + '@babel/plugin-transform-for-of@7.27.1': + resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-function-name@7.29.7': - resolution: {integrity: sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==} + '@babel/plugin-transform-function-name@7.27.1': + resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-json-strings@7.29.7': - resolution: {integrity: sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==} + '@babel/plugin-transform-json-strings@7.28.6': + resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-literals@7.29.7': - resolution: {integrity: sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==} + '@babel/plugin-transform-literals@7.27.1': + resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-logical-assignment-operators@7.29.7': - resolution: {integrity: sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==} + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-member-expression-literals@7.29.7': - resolution: {integrity: sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==} + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-amd@7.29.7': - resolution: {integrity: sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==} + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.29.7': - resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.29.7': - resolution: {integrity: sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==} + '@babel/plugin-transform-modules-systemjs@7.29.0': + resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-umd@7.29.7': - resolution: {integrity: sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==} + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-named-capturing-groups-regex@7.29.7': - resolution: {integrity: sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==} + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-new-target@7.29.7': - resolution: {integrity: sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==} + '@babel/plugin-transform-new-target@7.27.1': + resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.29.7': - resolution: {integrity: sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==} + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-numeric-separator@7.29.7': - resolution: {integrity: sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==} + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-rest-spread@7.29.7': - resolution: {integrity: sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==} + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-super@7.29.7': - resolution: {integrity: sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==} + '@babel/plugin-transform-object-super@7.27.1': + resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-catch-binding@7.29.7': - resolution: {integrity: sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==} + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.29.7': - resolution: {integrity: sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==} + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-parameters@7.29.7': - resolution: {integrity: sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==} + '@babel/plugin-transform-parameters@7.27.7': + resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-methods@7.29.7': - resolution: {integrity: sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==} + '@babel/plugin-transform-private-methods@7.28.6': + resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-property-in-object@7.29.7': - resolution: {integrity: sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==} + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-property-literals@7.29.7': - resolution: {integrity: sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==} + '@babel/plugin-transform-property-literals@7.27.1': + resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-display-name@7.29.7': - resolution: {integrity: sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==} + '@babel/plugin-transform-react-display-name@7.28.0': + resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.29.7': - resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.29.7': - resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx@7.29.7': - resolution: {integrity: sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==} + '@babel/plugin-transform-react-jsx@7.28.6': + resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.29.7': - resolution: {integrity: sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==} + '@babel/plugin-transform-regenerator@7.29.0': + resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regexp-modifiers@7.29.7': - resolution: {integrity: sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==} + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-reserved-words@7.29.7': - resolution: {integrity: sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==} + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-runtime@7.29.7': - resolution: {integrity: sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==} + '@babel/plugin-transform-runtime@7.29.0': + resolution: {integrity: sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-shorthand-properties@7.29.7': - resolution: {integrity: sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==} + '@babel/plugin-transform-shorthand-properties@7.27.1': + resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-spread@7.29.7': - resolution: {integrity: sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==} + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-sticky-regex@7.29.7': - resolution: {integrity: sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==} + '@babel/plugin-transform-sticky-regex@7.27.1': + resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-template-literals@7.29.7': - resolution: {integrity: sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==} + '@babel/plugin-transform-template-literals@7.27.1': + resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typeof-symbol@7.29.7': - resolution: {integrity: sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==} + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.29.7': - resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-escapes@7.29.7': - resolution: {integrity: sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==} + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-property-regex@7.29.7': - resolution: {integrity: sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==} + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-regex@7.29.7': - resolution: {integrity: sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==} + '@babel/plugin-transform-unicode-regex@7.27.1': + resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-sets-regex@7.29.7': - resolution: {integrity: sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==} + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.29.7': - resolution: {integrity: sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==} + '@babel/preset-env@7.29.0': + resolution: {integrity: sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -920,26 +878,26 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - '@babel/preset-typescript@7.29.7': - resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.29.7': - resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} - '@babel/template@7.29.7': - resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.7': - resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': @@ -949,29 +907,14 @@ packages: resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==} engines: {node: '>=0.8.0'} - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.28.0': - resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -982,14 +925,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.28.0': - resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -1000,14 +937,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.28.0': - resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -1018,14 +949,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.28.0': - resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -1036,14 +961,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.28.0': - resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -1054,14 +973,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.28.0': - resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -1072,14 +985,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.28.0': - resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -1090,14 +997,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.28.0': - resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -1108,14 +1009,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.28.0': - resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -1126,14 +1021,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.28.0': - resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -1144,14 +1033,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.28.0': - resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -1162,14 +1045,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.28.0': - resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -1180,14 +1057,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.28.0': - resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -1198,14 +1069,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.28.0': - resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -1216,14 +1081,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.28.0': - resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -1234,14 +1093,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.28.0': - resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -1252,26 +1105,14 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.28.0': - resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-arm64@0.28.0': - resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -1282,26 +1123,14 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.0': - resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-arm64@0.28.0': - resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -1312,26 +1141,14 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.28.0': - resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/openharmony-arm64@0.28.0': - resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] @@ -1342,14 +1159,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.28.0': - resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -1360,14 +1171,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.28.0': - resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -1378,14 +1183,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.28.0': - resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -1396,14 +1195,8 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.28.0': - resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1418,18 +1211,6 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.23.5': - resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/config-helpers@0.6.0': - resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/core@1.2.1': - resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1438,14 +1219,6 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@eslint/object-schema@3.0.5': - resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - '@eslint/plugin-kit@0.7.2': - resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@fastify/accept-negotiator@2.0.1': resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} @@ -1488,9 +1261,6 @@ packages: '@fastify/proxy-addr@5.1.0': resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} - '@fastify/rate-limit@10.3.0': - resolution: {integrity: sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q==} - '@fastify/send@4.1.0': resolution: {integrity: sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==} @@ -1524,18 +1294,6 @@ packages: '@hapi/topo@5.1.0': resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} - '@humanfs/core@0.19.2': - resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.8': - resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} - engines: {node: '>=18.18.0'} - - '@humanfs/types@0.15.0': - resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} - engines: {node: '>=18.18.0'} - '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -1549,12 +1307,8 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - - '@ioredis/commands@1.10.0': - resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} + '@ioredis/commands@1.5.1': + resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} '@isaacs/cliui@9.0.0': resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} @@ -1568,8 +1322,8 @@ packages: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} - '@istanbuljs/schema@0.1.6': - resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} '@jest/console@29.7.0': @@ -1665,12 +1419,6 @@ packages: resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} - '@napi-rs/wasm-runtime@1.1.4': - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} @@ -1686,17 +1434,14 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@package-json/types@0.0.12': - resolution: {integrity: sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==} - '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@prisma/client@6.19.3': - resolution: {integrity: sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==} + '@prisma/client@6.19.2': + resolution: {integrity: sha512-gR2EMvfK/aTxsuooaDA32D8v+us/8AAet+C3J1cc04SW35FPdZYgLF+iN4NDLUgAaUGTKdAB0CYenu1TAgGdMg==} engines: {node: '>=18.18'} peerDependencies: prisma: '*' @@ -1707,23 +1452,23 @@ packages: typescript: optional: true - '@prisma/config@6.19.3': - resolution: {integrity: sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==} + '@prisma/config@6.19.2': + resolution: {integrity: sha512-kadBGDl+aUswv/zZMk9Mx0C8UZs1kjao8H9/JpI4Wh4SHZaM7zkTwiKn/iFLfRg+XtOAo/Z/c6pAYhijKl0nzQ==} - '@prisma/debug@6.19.3': - resolution: {integrity: sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==} + '@prisma/debug@6.19.2': + resolution: {integrity: sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A==} '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': resolution: {integrity: sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==} - '@prisma/engines@6.19.3': - resolution: {integrity: sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==} + '@prisma/engines@6.19.2': + resolution: {integrity: sha512-TTkJ8r+uk/uqczX40wb+ODG0E0icVsMgwCTyTHXehaEfb0uo80M9g1aW1tEJrxmFHeOZFXdI2sTA1j1AgcHi4A==} - '@prisma/fetch-engine@6.19.3': - resolution: {integrity: sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==} + '@prisma/fetch-engine@6.19.2': + resolution: {integrity: sha512-h4Ff4Pho+SR1S8XerMCC12X//oY2bG3Iug/fUnudfcXEUnIeRiBdXHFdGlGOgQ3HqKgosTEhkZMvGM9tWtYC+Q==} - '@prisma/get-platform@6.19.3': - resolution: {integrity: sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==} + '@prisma/get-platform@6.19.2': + resolution: {integrity: sha512-PGLr06JUSTqIvztJtAzIxOwtWKtJm5WwOG6xpsgD37Rc84FpfUBGLKz65YpJBGtkRQGXTYEFie7pYALocC3MtA==} '@react-native-async-storage/async-storage@2.2.0': resolution: {integrity: sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==} @@ -1872,25 +1617,25 @@ packages: '@types/react': optional: true - '@react-navigation/bottom-tabs@7.16.2': - resolution: {integrity: sha512-Lbp++BGMc7SQXnyKuO/JrQJIhFH0zyB5v4kIEbnzDJLJfgubd5hoSe+QfCqy4YHfLA4phC4Xf/6Q2Ic8x7datQ==} + '@react-navigation/bottom-tabs@7.15.5': + resolution: {integrity: sha512-wQHredlCrRmShWQ1vF4HUcLdaiJ8fUgnbaeQH7BJ7MQVQh4mdzab0IOY/4QSmUyNRB350oyu1biTycyQ5FKWMQ==} peerDependencies: - '@react-navigation/native': ^7.2.5 + '@react-navigation/native': ^7.1.33 react: '>= 18.2.0' react-native: '*' react-native-safe-area-context: '>= 4.0.0' react-native-screens: '>= 4.0.0' - '@react-navigation/core@7.17.5': - resolution: {integrity: sha512-6fDCwDTWC7DJn0SDb9DJGRlipaygHIc+2elpZBJI6Crl/2Pu+Z1d6W4jMJ2gZO6iHKf+Pe5sUiQ/uwepGprZtg==} + '@react-navigation/core@7.16.1': + resolution: {integrity: sha512-xhquoyhKdqDfiL7LuupbwYnmauUGfVFGDEJO34m26k8zSN1eDjQ2stBZcHN8ILOI1PrG9885nf8ZmfaQxPS0ww==} peerDependencies: react: '>= 18.2.0' - '@react-navigation/elements@2.9.19': - resolution: {integrity: sha512-gBUvCZuUkOGw1KpLQEZIkByUz8RYPwXeoA6mZFJy9K1mxd8GdqHDMFCIoB0lfPz9rgrHj99RvtdlGZ/ZzkZv2A==} + '@react-navigation/elements@2.9.10': + resolution: {integrity: sha512-N8tuBekzTRb0pkMHFJGvmC6Q5OisSbt6gzvw7RHMnp4NDo5auVllT12sWFaTXf8mTduaLKNSrD/NZNaOqThCBg==} peerDependencies: '@react-native-masked-view/masked-view': '>= 0.2.0' - '@react-navigation/native': ^7.2.5 + '@react-navigation/native': ^7.1.33 react: '>= 18.2.0' react-native: '*' react-native-safe-area-context: '>= 4.0.0' @@ -1898,159 +1643,159 @@ packages: '@react-native-masked-view/masked-view': optional: true - '@react-navigation/native-stack@7.16.0': - resolution: {integrity: sha512-wM21rHYR2XifjDnKLrr3HeHUeGsWQZJRwPqEzy1Vp/a9k3ieiwTGpmpDItD/jtERH9qkYESwDPO6oEtrVBEpQg==} + '@react-navigation/native-stack@7.14.4': + resolution: {integrity: sha512-HFEnM5Q7JY3FmmiolD/zvgY+9sxZAyVGPZJoz7BdTvJmi1VHOdplf24YiH45mqeitlGnaOlvNT55rH4abHJ5eA==} peerDependencies: - '@react-navigation/native': ^7.2.5 + '@react-navigation/native': ^7.1.33 react: '>= 18.2.0' react-native: '*' react-native-safe-area-context: '>= 4.0.0' react-native-screens: '>= 4.0.0' - '@react-navigation/native@7.2.5': - resolution: {integrity: sha512-01AAUQiiHQAfTabq+ZyU1/ZWq+AbB/J3v0CB0UTJSON6M6cuadWNsbChzrZUdqQvHrXvg96U5i2PQLJzK3+zpg==} + '@react-navigation/native@7.1.33': + resolution: {integrity: sha512-DpFdWGcgLajKZ1TuIvDNQsblN2QaUFWpTQaB8v7WRP9Mix8H/6TFoIrZd93pbymI2hybd6UYrD+lI408eWVcfw==} peerDependencies: react: '>= 18.2.0' react-native: '*' - '@react-navigation/routers@7.5.5': - resolution: {integrity: sha512-9/hhMte12Kgu+pMnLfA4EWJ0OQmIEAMVMX06FPH2yGkEQSQ3JhhCN/GkcRikzQhtEi97VYYQA15umptBUShcOQ==} + '@react-navigation/routers@7.5.3': + resolution: {integrity: sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==} - '@rollup/rollup-android-arm-eabi@4.60.4': - resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.4': - resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.4': - resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.4': - resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.4': - resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.60.4': - resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': - resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.60.4': - resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.60.4': - resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.60.4': - resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.60.4': - resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.60.4': - resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.60.4': - resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.60.4': - resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.60.4': - resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.60.4': - resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.60.4': - resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.60.4': - resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.60.4': - resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.60.4': - resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.60.4': - resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.4': - resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.4': - resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.4': - resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.4': - resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} cpu: [x64] os: [win32] @@ -2075,8 +1820,8 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@sveltejs/acorn-typescript@1.0.10': - resolution: {integrity: sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==} + '@sveltejs/acorn-typescript@1.0.9': + resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==} peerDependencies: acorn: ^8.9.0 @@ -2085,15 +1830,15 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/kit@2.61.1': - resolution: {integrity: sha512-Ny8s1SR1TyQS2hD2Rvw0XKzU2Nw1eUF52dTb6T2bdcgz7wSC+Nyb5IwjWYlR4b2dvbbR5NJDiQwHg3rnNseghg==} + '@sveltejs/kit@2.54.0': + resolution: {integrity: sha512-WDJApQ1ipZLbaC4YjqJjwYR9y7QQgTqVwEObgNZ8Mu/eVQJqn4Qzw9a+n7mr5xnBYiAYz9UdJOOl+aqVbfGXcA==} engines: {node: '>=18.13'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.0.0 '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0 svelte: ^4.0.0 || ^5.0.0-next.0 - typescript: ^5.3.3 || ^6.0.0 + typescript: ^5.3.3 vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0 peerDependenciesMeta: '@opentelemetry/api': @@ -2116,9 +1861,6 @@ packages: svelte: ^5.0.0 vite: ^6.3.0 || ^7.0.0 - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} - '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2134,15 +1876,9 @@ packages: '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/esrecurse@4.3.1': - resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -2161,11 +1897,8 @@ packages: '@types/jest@29.5.14': resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/node@22.19.19': - resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + '@types/node@22.19.15': + resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} '@types/qrcode@1.5.6': resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} @@ -2179,8 +1912,8 @@ packages: '@types/react-test-renderer@19.1.0': resolution: {integrity: sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==} - '@types/react@19.2.15': - resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -2194,187 +1927,67 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.60.0': - resolution: {integrity: sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==} + '@typescript-eslint/eslint-plugin@8.57.0': + resolution: {integrity: sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.60.0 + '@typescript-eslint/parser': ^8.57.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.60.0': - resolution: {integrity: sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==} + '@typescript-eslint/parser@8.57.0': + resolution: {integrity: sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.60.0': - resolution: {integrity: sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==} + '@typescript-eslint/project-service@8.57.0': + resolution: {integrity: sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.1.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.60.0': - resolution: {integrity: sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==} + '@typescript-eslint/scope-manager@8.57.0': + resolution: {integrity: sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.60.0': - resolution: {integrity: sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==} + '@typescript-eslint/tsconfig-utils@8.57.0': + resolution: {integrity: sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.1.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.60.0': - resolution: {integrity: sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==} + '@typescript-eslint/type-utils@8.57.0': + resolution: {integrity: sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.60.0': - resolution: {integrity: sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==} + '@typescript-eslint/types@8.57.0': + resolution: {integrity: sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.60.0': - resolution: {integrity: sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==} + '@typescript-eslint/typescript-estree@8.57.0': + resolution: {integrity: sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.1.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.60.0': - resolution: {integrity: sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==} + '@typescript-eslint/utils@8.57.0': + resolution: {integrity: sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.60.0': - resolution: {integrity: sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==} + '@typescript-eslint/visitor-keys@8.57.0': + resolution: {integrity: sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.3.1': - resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} - - '@unrs/resolver-binding-android-arm-eabi@1.12.2': - resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} - cpu: [arm] - os: [android] - - '@unrs/resolver-binding-android-arm64@1.12.2': - resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} - cpu: [arm64] - os: [android] - - '@unrs/resolver-binding-darwin-arm64@1.12.2': - resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} - cpu: [arm64] - os: [darwin] - - '@unrs/resolver-binding-darwin-x64@1.12.2': - resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} - cpu: [x64] - os: [darwin] - - '@unrs/resolver-binding-freebsd-x64@1.12.2': - resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} - cpu: [x64] - os: [freebsd] - - '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': - resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': - resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': - resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-arm64-musl@1.12.2': - resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': - resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-loong64-musl@1.12.2': - resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': - resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': - resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': - resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': - resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-x64-gnu@1.12.2': - resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-x64-musl@1.12.2': - resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@unrs/resolver-binding-openharmony-arm64@1.12.2': - resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} - cpu: [arm64] - os: [openharmony] - - '@unrs/resolver-binding-wasm32-wasi@1.12.2': - resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': - resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} - cpu: [arm64] - os: [win32] - - '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': - resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} - cpu: [ia32] - os: [win32] - - '@unrs/resolver-binding-win32-x64-msvc@1.12.2': - resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} - cpu: [x64] - os: [win32] + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} @@ -2445,11 +2058,11 @@ packages: ajv: optional: true - ajv@6.15.0: - resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} anser@1.4.10: resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} @@ -2576,8 +2189,8 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - babel-plugin-polyfill-corejs2@0.4.17: - resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} + babel-plugin-polyfill-corejs2@0.4.16: + resolution: {integrity: sha512-xaVwwSfebXf0ooE11BJovZYKhFjIvQo7TsyVpETuIeH2JHv0k/T6Y5j22pPTvqYqmpkxdlPAJlyJ0tfOJAoMxw==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -2586,13 +2199,13 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs3@0.14.2: - resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} + babel-plugin-polyfill-corejs3@0.14.1: + resolution: {integrity: sha512-ENp89vM9Pw4kv/koBb5N2f9bDZsR0hpf3BdPMOg/pkS3pwO4dzNnQZVXtBbeyAadgm865DmQG2jMMLqmZXvuCw==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.8: - resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} + babel-plugin-polyfill-regenerator@0.6.7: + resolution: {integrity: sha512-OTYbUlSwXhNgr4g6efMZgsO8//jA61P7ZbRX3iTT53VON8l+WQS8IAUEVo4a4cWknrg2W8Cj4gQhRYNCJ8GkAA==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -2620,15 +2233,11 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - base64-arraybuffer@1.0.2: - resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} - engines: {node: '>= 0.6.0'} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.33: - resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==} + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} engines: {node: '>=6.0.0'} hasBin: true @@ -2638,26 +2247,26 @@ packages: bn.js@4.12.3: resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} - body-parser@1.20.5: - resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} + body-parser@1.20.4: + resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@1.1.15: - resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@5.0.6: - resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2670,10 +2279,6 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - builtin-modules@5.2.0: - resolution: {integrity: sha512-02yxLeyxF4dNl6SlY6/5HfRSrSdZ/sCPoxy2kZNP5dZZX8LSAD9aE2gtJIUgWrsQTiMPl3mxESyrobSwvRGisQ==} - engines: {node: '>=18.20'} - bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -2694,8 +2299,8 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.9: - resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -2714,8 +2319,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001793: - resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + caniuse-lite@1.0.30001778: + resolution: {integrity: sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==} chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} @@ -2725,9 +2330,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - change-case@5.4.4: - resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} - char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -2755,23 +2357,15 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - ci-info@4.4.0: - resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} - engines: {node: '>=8'} - citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - citty@0.2.2: - resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} + citty@0.2.1: + resolution: {integrity: sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==} cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} - clean-regexp@1.0.0: - resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} - engines: {node: '>=4'} - cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -2798,8 +2392,8 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - cluster-key-slot@1.1.1: - resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} co@4.6.0: @@ -2849,10 +2443,6 @@ packages: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} - comment-parser@1.4.7: - resolution: {integrity: sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==} - engines: {node: '>= 12.0.0'} - compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -2899,8 +2489,8 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} - core-js-compat@3.49.0: - resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-js-compat@3.48.0: + resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} cosmiconfig@9.0.1: resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} @@ -2926,9 +2516,6 @@ packages: css-in-js-utils@3.1.0: resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} - css-line-break@2.1.0: - resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} - css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} @@ -2958,8 +2545,8 @@ packages: dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - dayjs@1.11.21: - resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -3020,8 +2607,8 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - defu@6.1.7: - resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} @@ -3046,8 +2633,8 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} - devalue@5.8.1: - resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==} + devalue@5.6.4: + resolution: {integrity: sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==} diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} @@ -3091,11 +2678,11 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - effect@3.21.0: - resolution: {integrity: sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==} + effect@3.18.4: + resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} - electron-to-chromium@1.5.364: - resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==} + electron-to-chromium@1.5.313: + resolution: {integrity: sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -3119,10 +2706,6 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.22.1: - resolution: {integrity: sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==} - engines: {node: '>=10.13.0'} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -3146,8 +2729,8 @@ packages: resolution: {integrity: sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw==} engines: {node: '>= 0.8'} - es-abstract@1.24.2: - resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -3158,15 +2741,15 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-iterator-helpers@1.3.2: - resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} + es-iterator-helpers@1.3.0: + resolution: {integrity: sha512-04cg8iJFDOxWcYlu0GFFWgs7vtaEPCmr5w1nrj9V3z3axu/48HCMwK6VMp45Zh3ZB+xLP1ifbJfrq86+1ypKKQ==} engines: {node: '>= 0.4'} es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-object-atoms@1.1.2: - resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: @@ -3186,13 +2769,8 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.28.0: - resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} engines: {node: '>=18'} hasBin: true @@ -3215,46 +2793,12 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-compat-utils@0.5.1: - resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} - engines: {node: '>=12'} - peerDependencies: - eslint: '>=6.0.0' - eslint-config-prettier@8.10.2: resolution: {integrity: sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==} hasBin: true peerDependencies: eslint: '>=7.0.0' - eslint-import-context@0.1.9: - resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - peerDependencies: - unrs-resolver: ^1.0.0 - peerDependenciesMeta: - unrs-resolver: - optional: true - - eslint-import-resolver-typescript@4.4.4: - resolution: {integrity: sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==} - engines: {node: ^16.17.0 || >=18.6.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - eslint-plugin-import-x: '*' - peerDependenciesMeta: - eslint-plugin-import: - optional: true - eslint-plugin-import-x: - optional: true - - eslint-plugin-es-x@7.8.0: - resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '>=8' - eslint-plugin-eslint-comments@3.2.0: resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} engines: {node: '>=6.5.0'} @@ -3268,27 +2812,14 @@ packages: '@babel/eslint-parser': ^7.12.0 eslint: ^8.1.0 - eslint-plugin-import-x@4.16.2: - resolution: {integrity: sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/utils': ^8.56.0 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - eslint-import-resolver-node: '*' - peerDependenciesMeta: - '@typescript-eslint/utils': - optional: true - eslint-import-resolver-node: - optional: true - - eslint-plugin-jest@29.15.2: - resolution: {integrity: sha512-kEN4r9RZl1xcsb4arGq89LrcVdOUFII/JSCwtTPJyv16mDwmPrcuEQwpxqZHeINvcsd7oK5O/rhdGlxFRaZwvQ==} + eslint-plugin-jest@29.15.0: + resolution: {integrity: sha512-ZCGr7vTH2WSo2hrK5oM2RULFmMruQ7W3cX7YfwoTiPfzTGTFBMmrVIz45jZHd++cGKj/kWf02li/RhTGcANJSA==} engines: {node: ^20.12.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@typescript-eslint/eslint-plugin': ^8.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 jest: '*' - typescript: '>=4.8.4 <7.0.0' + typescript: '>=4.8.4 <6.0.0' peerDependenciesMeta: '@typescript-eslint/eslint-plugin': optional: true @@ -3297,30 +2828,11 @@ packages: typescript: optional: true - eslint-plugin-n@18.0.1: - resolution: {integrity: sha512-q3ARhk+eZRc7myR0KHx+R3/GJeOHF+Ir6PK95Pu2tEX8Sl/4BIpmmVLva2kPrjC2gCmn6WHlHm+3yeo6Rxhycw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - peerDependencies: - eslint: '>=8.57.1' - ts-declaration-location: ^1.0.6 - typescript: '>=5.0.0' - peerDependenciesMeta: - ts-declaration-location: - optional: true - typescript: - optional: true - - eslint-plugin-promise@7.3.0: - resolution: {integrity: sha512-6uGiOR0INuujr6PEQmeSSP7GbIMJ/ebEXXiEzb/nOj68LknH5Pxzb/AbZivmr6VE6TkTE8rTjRK9zhKpK6HsRA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 - - eslint-plugin-react-hooks@7.1.1: - resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} engines: {node: '>=18'} peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + 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-native-globals@0.1.2: resolution: {integrity: sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==} @@ -3336,16 +2848,6 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-plugin-security@4.0.0: - resolution: {integrity: sha512-tfuQT8K/Li1ZxhFzyD8wPIKtlzZxqBcPr9q0jFMQ77wWAbKBVEhaMPVQRTMTvCMUDhwBe5vPVqQPwAGk/ASfxQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-plugin-unicorn@64.0.0: - resolution: {integrity: sha512-rNZwalHh8i0UfPlhNwg5BTUO1CMdKNmjqe+TgzOTZnpKoi8VBgsW7u9qCHIdpxEzZ1uwrJrPF0uRb7l//K38gA==} - engines: {node: ^20.10.0 || >=21.0.0} - peerDependencies: - eslint: '>=9.38.0' - eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -3354,10 +2856,6 @@ packages: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-scope@9.1.2: - resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint-visitor-keys@2.1.0: resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} engines: {node: '>=10'} @@ -3370,16 +2868,6 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.4.1: - resolution: {integrity: sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3389,10 +2877,6 @@ packages: esm-env@1.2.2: resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} - espree@11.2.0: - resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3406,13 +2890,8 @@ packages: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} - esrap@2.2.9: - resolution: {integrity: sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==} - peerDependencies: - '@typescript-eslint/types': ^8.2.0 - peerDependenciesMeta: - '@typescript-eslint/types': - optional: true + esrap@2.2.3: + resolution: {integrity: sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -3467,8 +2946,8 @@ packages: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} - fast-copy@4.0.3: - resolution: {integrity: sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==} + fast-copy@4.0.2: + resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -3483,8 +2962,8 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-json-stringify@6.4.0: - resolution: {integrity: sha512-ibRCQ0GZKJIQ+P3Et1h0LhPgp3PMTYk0MH8O+kW3lNYsvmaQww5Nn3f1jf73Q0jR1Yz3a1CDP4/NZD3vOajWJQ==} + fast-json-stringify@6.3.0: + resolution: {integrity: sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==} fast-jwt@5.0.6: resolution: {integrity: sha512-LPE7OCGUl11q3ZgW681cEU2d0d2JZ37hhJAmetCgNyW8waVaJVZXhyFF6U2so1Iim58Yc7pfxJe2P7MNetQH2g==} @@ -3499,11 +2978,11 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.1.2: - resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fast-xml-parser@4.5.6: - resolution: {integrity: sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A==} + fast-xml-parser@4.5.4: + resolution: {integrity: sha512-jE8ugADnYOBsu1uaoayVl1tVKAMNOXyjwvv2U6udEA2ORBhDooJDWoGxTkhd4Qn4yh59JVVt/pKXtjPwx9OguQ==} hasBin: true fastfall@1.5.1: @@ -3513,8 +2992,8 @@ packages: fastify-plugin@5.1.0: resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==} - fastify@5.8.5: - resolution: {integrity: sha512-Yqptv59pQzPgQUSIm87hMqHJmdkb1+GPxdE6vW6FRyVE9G86mt7rOghitiU4JHRaTyDUk9pfeKmDeu70lAwM4Q==} + fastify@5.8.2: + resolution: {integrity: sha512-lZmt3navvZG915IE+f7/TIVamxIwmBd+OMB+O9WBzcpIwOo6F0LTh0sluoMFk5VkrKTvvrwIaoJPkir4Z+jtAg==} fastparallel@2.4.1: resolution: {integrity: sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==} @@ -3552,10 +3031,6 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -3568,14 +3043,10 @@ packages: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} engines: {node: '>= 0.8'} - find-my-way@9.6.0: - resolution: {integrity: sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==} + find-my-way@9.5.0: + resolution: {integrity: sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==} engines: {node: '>=20'} - find-up-simple@1.0.1: - resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} - engines: {node: '>=18'} - find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -3588,12 +3059,8 @@ packages: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flatted@3.4.1: + resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==} flow-enums-runtime@0.0.6: resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} @@ -3664,8 +3131,8 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.14.0: - resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + get-tsconfig@4.13.6: + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} giget@2.0.0: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} @@ -3693,21 +3160,10 @@ packages: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} - globals@15.15.0: - resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} - engines: {node: '>=18'} - - globals@17.6.0: - resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} - engines: {node: '>=18'} - globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -3741,8 +3197,8 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hasown@2.0.4: - resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} helmet@7.2.0: @@ -3761,8 +3217,8 @@ packages: hermes-estree@0.32.0: resolution: {integrity: sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==} - hermes-estree@0.35.0: - resolution: {integrity: sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==} + hermes-estree@0.33.3: + resolution: {integrity: sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==} hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} @@ -3770,8 +3226,8 @@ packages: hermes-parser@0.32.0: resolution: {integrity: sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==} - hermes-parser@0.35.0: - resolution: {integrity: sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==} + hermes-parser@0.33.3: + resolution: {integrity: sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==} hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -3779,10 +3235,6 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - html2canvas@1.4.1: - resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} - engines: {node: '>=8.0.0'} - http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -3831,10 +3283,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} - 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. @@ -3852,12 +3300,12 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - ioredis@5.11.0: - resolution: {integrity: sha512-EZBErytyVovD8f6pDfG3Kb37N6Y3lmDA9NNj+4+IP13CzzHGeX+OyeRM2Um13khRzoBSzzL+5lVnCX8V2RLeMg==} + ioredis@5.10.0: + resolution: {integrity: sha512-HVBe9OFuqs+Z6n64q09PQvP1/R4Bm+30PAyyD4wIEqssh3v9L21QjCVk4kRLucMBcDokJTcLjsGeVRlq/nH6DA==} engines: {node: '>=12.22.0'} - ipaddr.js@2.4.0: - resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} engines: {node: '>= 10'} is-array-buffer@3.0.5: @@ -3882,19 +3330,12 @@ packages: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} - is-builtin-module@5.0.0: - resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==} - engines: {node: '>=18.20'} - - 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.2: - resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} is-data-view@1.0.2: @@ -4188,8 +3629,8 @@ packages: node-notifier: optional: true - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true joi@17.13.3: @@ -4259,8 +3700,8 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - launch-editor@2.14.0: - resolution: {integrity: sha512-Pj3ZOx9dD1BClS7YcSQx0An1PCF9wz4JpvbEmKvDxQtm0jxlkk5NhW8x0SBAKA/acHBKZaqdd5FFOWlXo500JA==} + launch-editor@2.13.1: + resolution: {integrity: sha512-lPSddlAAluRKJ7/cjRFoXUFzaX7q/YKI7yPHuEvSJVqoXvFnJov1/Ud87Aa4zULIbA9Nja4mSPK8l0z/7eV2wA==} leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} @@ -4293,14 +3734,20 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} @@ -4317,8 +3764,8 @@ packages: loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - lru-cache@11.5.1: - resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -4365,61 +3812,61 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - metro-babel-transformer@0.83.7: - resolution: {integrity: sha512-sBqBkt6kNut/88bv+Ucvm4yqdPetbvAEsHzi3MAgJEifOSYYzX5Z5Kgw3TFOrwf/mHJTOBG2ONlaMHoyfP15TA==} + metro-babel-transformer@0.83.5: + resolution: {integrity: sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA==} engines: {node: '>=20.19.4'} - metro-cache-key@0.83.7: - resolution: {integrity: sha512-W1c2Nmx8MiJTJt+eWhMO08z9VKi3kZOaz99IYGdqeqDgY9j+yZjXl62rUav4Di0heZfh4/n2s722PqRL1OODeg==} + metro-cache-key@0.83.5: + resolution: {integrity: sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw==} engines: {node: '>=20.19.4'} - metro-cache@0.83.7: - resolution: {integrity: sha512-E9SRePXQ1Zvlj79VcOk57q7VC7rMHMFQ+jhmPHBiq+dJ0bJB5BL87lWZF6oh5X76Cci5tpDuQNaDwwuSCToEeg==} + metro-cache@0.83.5: + resolution: {integrity: sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng==} engines: {node: '>=20.19.4'} - metro-config@0.83.7: - resolution: {integrity: sha512-83mjWFbFOt2GeJ6pFIum5mSnc1uTsZJAtD8o4ej0s4NVsYsA7fB+pHvTfHhFrpeMONaobu2riKavkPei05Er/Q==} + metro-config@0.83.5: + resolution: {integrity: sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w==} engines: {node: '>=20.19.4'} - metro-core@0.83.7: - resolution: {integrity: sha512-6yn3w1wnltT6RQl7p7YES2l95ArC+mWrOssEiH8p5/DDrJS65/szf9LsC9JrBv8c5DdvSY3V3f0GRYg0Ox7hCg==} + metro-core@0.83.5: + resolution: {integrity: sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ==} engines: {node: '>=20.19.4'} - metro-file-map@0.83.7: - resolution: {integrity: sha512-+j0F1m+FQYVAQ6syf+mwhIPV5GoFQrkInX8bppuc50IzNsZbMrp8R5H/Sx/K2daQ3YEa9F/XwkeZT8gzJfgeCw==} + metro-file-map@0.83.5: + resolution: {integrity: sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ==} engines: {node: '>=20.19.4'} - metro-minify-terser@0.83.7: - resolution: {integrity: sha512-MfJar2IS4tBRuLb9svwb0Gu5l9BsH+pcRm8eGcEi/wy8MzZinfinh5dFLt2nWkocnulIgtGB5NkFDdbXqMXKhQ==} + metro-minify-terser@0.83.5: + resolution: {integrity: sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q==} engines: {node: '>=20.19.4'} - metro-resolver@0.83.7: - resolution: {integrity: sha512-WSJIENlMcoSsuz66IfBHOkgfp3KJt2UW2TnEHPf1b8pIG2eEXNOVmo2+03A0H17WY2XGXWgxL0CG7FAopqgB1A==} + metro-resolver@0.83.5: + resolution: {integrity: sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A==} engines: {node: '>=20.19.4'} - metro-runtime@0.83.7: - resolution: {integrity: sha512-9GKkJURaB2iyYoEExKnedzAHzxmKtSi+k0tsZUvMoU27tBZJElchYt7JH/Ai/XzYAI9lCAaV7u5HZSI8J5Z+wQ==} + metro-runtime@0.83.5: + resolution: {integrity: sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==} engines: {node: '>=20.19.4'} - metro-source-map@0.83.7: - resolution: {integrity: sha512-JgA1h7oc1a1jydBe1GhVFsUoMYo3wLPk7oRA32rjlDsq+sP2JLt9x2p2lWbNSxTm/u8NV4VRid3hvEJgcX8tKw==} + metro-source-map@0.83.5: + resolution: {integrity: sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==} engines: {node: '>=20.19.4'} - metro-symbolicate@0.83.7: - resolution: {integrity: sha512-g4suyxw20WOHWI680c+Kq4wC/NF+Hx5pRH9afrMp+sMTxqLeKcPR1Xf4wMhsjlbvx7LbIREdke6q928jEjvJWw==} + metro-symbolicate@0.83.5: + resolution: {integrity: sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==} engines: {node: '>=20.19.4'} hasBin: true - metro-transform-plugins@0.83.7: - resolution: {integrity: sha512-Ss0FpBiZDjX2kwhukMDl5sNdYK8T/06IPqxNE4H6PTlRlfs9q11cef13c/xESY/Pm4VCkp1yJUZO3kXzvMxQFA==} + metro-transform-plugins@0.83.5: + resolution: {integrity: sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw==} engines: {node: '>=20.19.4'} - metro-transform-worker@0.83.7: - resolution: {integrity: sha512-UegCo7ygB2fT64mRK2nbAjQVJ1zSwIIHy8d96jJv2nKZFDaViYBiughEdu5HM/Ceq0WN3LZrZk3zhl9aoiLYFw==} + metro-transform-worker@0.83.5: + resolution: {integrity: sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA==} engines: {node: '>=20.19.4'} - metro@0.83.7: - resolution: {integrity: sha512-SPaPEyvTsTmd0LpT7RaZciQyDw2i/JB7+iY9L5VfBo72+psescFxBqpI1TL9dnL+pmnfkU+l/J1mEEGLeF65EQ==} + metro@0.83.5: + resolution: {integrity: sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ==} engines: {node: '>=20.19.4'} hasBin: true @@ -4465,8 +3912,8 @@ packages: minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} minimatch@3.1.5: @@ -4487,8 +3934,8 @@ packages: mnemonist@0.40.0: resolution: {integrity: sha512-kdd8AFNig2AD5Rkih7EPCXhu/iMvwevQFX/uEiGhZyPZi7fHqOoF4V4kHLpCfysxXMgQ4B52kdPMCwARshKvEg==} - mnemonist@0.40.4: - resolution: {integrity: sha512-ZAv+KNavneRVzu4tUeOgzkScI3W5BGwZ3rkxIpKtzzVgfTtWQFN1CgX0U72cyvyh3iTuHL3SiSmrQxTlryEIcw==} + mnemonist@0.40.3: + resolution: {integrity: sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==} mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} @@ -4504,16 +3951,11 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + 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.3.4: - resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - hasBin: true - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -4552,9 +3994,8 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-releases@2.0.46: - resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} - engines: {node: '>=18'} + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} node-stream-zip@1.15.0: resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} @@ -4574,13 +4015,13 @@ packages: nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} - nypm@0.6.6: - resolution: {integrity: sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q==} + nypm@0.6.5: + resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==} engines: {node: '>=18'} hasBin: true - ob1@0.83.7: - resolution: {integrity: sha512-9M5kpuOLyTPogMtZiQUIxdAZxl7Dxs6tVBbJErSumsqGMuhVSoUbkfeZ3XNPpLpwBBtqY5QDUzGwggLHX3slQg==} + ob1@0.83.5: + resolution: {integrity: sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==} engines: {node: '>=20.19.4'} object-assign@4.1.1: @@ -4733,12 +4174,12 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} pino-abstract-transport@3.0.0: @@ -4763,12 +4204,8 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - pkg-types@2.3.1: - resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} - - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} @@ -4781,8 +4218,8 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -4798,8 +4235,8 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - prisma@6.19.3: - resolution: {integrity: sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==} + prisma@6.19.2: + resolution: {integrity: sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg==} engines: {node: '>=18.18'} hasBin: true peerDependencies: @@ -4842,8 +4279,8 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - qs@6.15.2: - resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} engines: {node: '>=0.6'} query-string@7.1.3: @@ -4873,10 +4310,10 @@ packages: react-devtools-core@6.1.5: resolution: {integrity: sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==} - react-dom@19.2.6: - resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: - react: ^19.2.6 + react: ^19.2.4 react-freeze@1.0.4: resolution: {integrity: sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==} @@ -4890,15 +4327,8 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-is@19.2.6: - resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} - - react-native-camera-kit@14.2.0: - resolution: {integrity: sha512-rPk/4Ux52/Kc6oIPk0x6NsrvDkeL+kd/GAUJ4xBtTlnmiWjLTgeA2Vjgg9ik03mmyf6rV+LaqaOBT7KejhuHKQ==} - engines: {node: '>=18'} - peerDependencies: - react: '*' - react-native: '*' + react-is@19.2.4: + resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} react-native-gesture-handler@2.31.2: resolution: {integrity: sha512-rw5q74i2AfS7YGYdbxQDhOU7xqgY6WRM1132/CCm3erqjblhECZDZFHIm0tteHoC9ih24wogVBVVzcTBQtZ+5A==} @@ -4926,20 +4356,20 @@ packages: react: '*' react-native: '*' - react-native-safe-area-context@5.8.0: - resolution: {integrity: sha512-t+ZsAVzY/wWzzx34vqGbo3/as9EEESJdbyZNL7Yg5EYX+toYMtMqFoDDCvqZUi35eeGVsXc6pAaEk4edMwbuCQ==} + react-native-safe-area-context@5.7.0: + resolution: {integrity: sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==} peerDependencies: react: '*' react-native: '*' - react-native-screens@4.25.2: - resolution: {integrity: sha512-1Nj1fusFd+rIMKU/qC9yGKVG+3ofh11d3OdBQKL1iVvQfKvcB8vhvTGQf2TkfxW3bamxN+hCZIXmNuU0mRkyDg==} + react-native-screens@4.24.0: + resolution: {integrity: sha512-SyoiGaDofiyGPFrUkn1oGsAzkRuX1JUvTD9YQQK3G1JGQ5VWkvHgYSsc1K9OrLsDQxN7NmV71O0sHCAh8cBetA==} peerDependencies: react: '*' - react-native: '>=0.82.0' + react-native: '*' - react-native-svg@15.15.5: - resolution: {integrity: sha512-L4go5jA+GWutdJ/JucuN20cjAbMg1HmMtAP+wZ+3JLCf6Jd0bhXQHxciRP/AQm/FlrIEZwkMcHNZP+FXAiic0w==} + react-native-svg@15.15.3: + resolution: {integrity: sha512-/k4KYwPBLGcx2f5d4FjE+vCScK7QOX14cl2lIASJ28u4slHHtIhL0SZKU7u9qmRBHxTCKPoPBtN6haT1NENJNA==} peerDependencies: react: '*' react-native: '*' @@ -4949,13 +4379,6 @@ packages: deprecated: react-native-vector-icons package has moved to a new model of per-icon-family packages. See the https://github.com/oblador/react-native-vector-icons/blob/master/MIGRATION.md on how to migrate hasBin: true - react-native-view-shot@5.1.0: - resolution: {integrity: sha512-JZgElCD82aO+hejIF/leUzI7JufL9mgJ6ChzGWIcdZ2ajpaEvvSnvIcw0qD32XWkrbId8wfSbyz/4u/ulTQzQA==} - engines: {node: '>=20', npm: '>=10'} - peerDependencies: - react: '>=18.0.0' - react-native: '>=0.76.0' - react-native-web@0.21.2: resolution: {integrity: sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==} peerDependencies: @@ -4968,13 +4391,6 @@ packages: react: '*' react-native: '*' - react-native-worklets@0.5.1: - resolution: {integrity: sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==} - peerDependencies: - '@babel/core': ^7.0.0-0 - react: '*' - react-native: '*' - react-native@0.84.1: resolution: {integrity: sha512-0PjxOyXRu3tZ8EobabxSukvhKje2HJbsZikR0U+pvS0pYZza2hXKjcSBiBdFN4h9D0S3v6a8kkrDK6WTRKMwzg==} engines: {node: '>= 20.19.4'} @@ -5011,9 +4427,6 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} - real-require@1.0.0: - resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} - redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -5036,10 +4449,6 @@ packages: regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - regexp-tree@0.1.27: - resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} - hasBin: true - regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -5051,8 +4460,8 @@ packages: regjsgen@0.8.0: resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} - regjsparser@0.13.1: - resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} + regjsparser@0.13.0: + resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} hasBin: true require-directory@2.1.1: @@ -5085,13 +4494,13 @@ packages: resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} engines: {node: '>=10'} - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} hasBin: true - resolve@2.0.0-next.7: - resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} + resolve@2.0.0-next.6: + resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} engines: {node: '>= 0.4'} hasBin: true @@ -5115,8 +4524,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.60.4: - resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -5130,8 +4539,8 @@ packages: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} - safe-array-concat@1.1.4: - resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} safe-buffer@5.2.1: @@ -5145,12 +4554,8 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} - safe-regex2@5.1.1: - resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==} - hasBin: true - - safe-regex@2.1.1: - resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==} + safe-regex2@5.0.0: + resolution: {integrity: sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==} safe-stable-stringify@2.5.0: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} @@ -5169,13 +4574,8 @@ packages: 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 - - semver@7.8.1: - resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true @@ -5197,8 +4597,8 @@ packages: set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} - set-cookie-parser@3.1.0: - resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + set-cookie-parser@3.0.1: + resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} @@ -5234,12 +4634,8 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} - shell-quote@1.8.4: - resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} - engines: {node: '>= 0.4'} - - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: @@ -5314,10 +4710,6 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - stable-hash-x@0.2.0: - resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} - engines: {node: '>=12.0.0'} - stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -5406,10 +4798,6 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - strip-indent@4.1.1: - resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} - engines: {node: '>=12'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -5436,24 +4824,20 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-check@4.4.8: - resolution: {integrity: sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w==} + svelte-check@4.4.5: + resolution: {integrity: sha512-1bSwIRCvvmSHrlK52fOlZmVtUZgil43jNL/2H18pRpa+eQjzGt6e3zayxhp1S7GajPFKNM/2PMCG+DZFHlG9fw==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' - svelte@5.56.0: - resolution: {integrity: sha512-kTXr26t1bchFp28ROrb957LtbujpBmBDibmqMGziVpUs7awBi96TGgX6SovrA8BNoEUDVRK2Fb9FkeYlGspoVg==} + svelte@5.53.10: + resolution: {integrity: sha512-UcNfWzbrjvYXYSk+U2hME25kpb87oq6/WVLeBF4khyQrb3Ob/URVlN23khal+RbdCUTMfg4qWjI9KZjCNFtYMQ==} engines: {node: '>=18'} - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} - engines: {node: '>=6'} - - terser@5.48.0: - resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} + terser@5.46.0: + resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} engines: {node: '>=10'} hasBin: true @@ -5465,14 +4849,11 @@ packages: resolution: {integrity: sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==} deprecated: no longer maintained - text-segmentation@1.0.3: - resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} - text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - thread-stream@4.2.0: - resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} engines: {node: '>=20'} throat@5.0.0: @@ -5484,12 +4865,12 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.2.3: - resolution: {integrity: sha512-g62dB+w1/OEFnPvmX0yd/HnetYITOL+1nJW7kitOycOeAvmbWC/nu0fwmmQ/kupNojqExzyC/T++pST/jRJ2mQ==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} tinypool@1.1.1: @@ -5511,9 +4892,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - toad-cache@3.7.1: - resolution: {integrity: sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==} - engines: {node: '>=20'} + toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} @@ -5530,8 +4911,8 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - ts-api-utils@2.5.0: - resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -5539,8 +4920,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.22.3: - resolution: {integrity: sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} engines: {node: '>=18.0.0'} hasBin: true @@ -5580,17 +4961,10 @@ packages: resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} engines: {node: '>= 0.4'} - typed-array-length@1.0.8: - resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript-eslint@8.60.0: - resolution: {integrity: sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -5631,9 +5005,6 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - unrs-resolver@1.12.2: - resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} - update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -5660,9 +5031,6 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - utrie@1.0.2: - resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} - v8-to-istanbul@9.3.0: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} @@ -5707,8 +5075,8 @@ packages: terser: optional: true - vite@7.3.3: - resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -5747,10 +5115,10 @@ packages: yaml: optional: true - vitefu@1.1.3: - resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + vitefu@1.1.2: + resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0 peerDependenciesMeta: vite: optional: true @@ -5816,8 +5184,8 @@ packages: which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which-typed-array@1.1.21: - resolution: {integrity: sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==} + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} engines: {node: '>= 0.4'} which@2.0.2: @@ -5849,8 +5217,8 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - ws@6.2.4: - resolution: {integrity: sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw==} + ws@6.2.3: + resolution: {integrity: sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==} peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 @@ -5860,8 +5228,8 @@ packages: utf-8-validate: optional: true - ws@7.5.11: - resolution: {integrity: sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==} + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} engines: {node: '>=8.3.0'} peerDependencies: bufferutil: ^4.0.1 @@ -5886,8 +5254,8 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@2.9.0: - resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} hasBin: true @@ -5933,25 +5301,25 @@ packages: snapshots: - '@babel/code-frame@7.29.7': + '@babel/code-frame@7.29.0': dependencies: - '@babel/helper-validator-identifier': 7.29.7 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.29.7': {} + '@babel/compat-data@7.29.0': {} - '@babel/core@7.29.7': + '@babel/core@7.29.0': dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -5961,814 +5329,805 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/eslint-parser@7.29.7(@babel/core@7.29.7)(eslint@8.57.1)': + '@babel/eslint-parser@7.28.6(@babel/core@7.29.0)(eslint@8.57.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.0 '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 eslint: 8.57.1 eslint-visitor-keys: 2.1.0 semver: 6.3.1 - '@babel/generator@7.29.7': + '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-annotate-as-pure@7.29.7': + '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.0 - '@babel/helper-compilation-targets@7.29.7': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.2 + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.29.7(@babel/core@7.29.7)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7)': + '@babel/helper-define-polyfill-provider@0.6.7(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 debug: 4.4.3 lodash.debounce: 4.0.8 - resolve: 1.22.12 + resolve: 1.22.11 transitivePeerDependencies: - supports-color - '@babel/helper-globals@7.29.7': {} + '@babel/helper-globals@7.28.0': {} - '@babel/helper-member-expression-to-functions@7.29.7': + '@babel/helper-member-expression-to-functions@7.28.5': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.29.7': + '@babel/helper-module-imports@7.28.6': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-optimise-call-expression@7.29.7': + '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.0 - '@babel/helper-plugin-utils@7.29.7': {} + '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-remap-async-to-generator@7.29.7(@babel/core@7.29.7)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-wrap-function': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-wrap-function': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-identifier@7.28.5': {} - '@babel/helper-validator-option@7.29.7': {} + '@babel/helper-validator-option@7.27.1': {} - '@babel/helper-wrap-function@7.29.7': + '@babel/helper-wrap-function@7.28.6': dependencies: - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helpers@7.29.7': + '@babel/helpers@7.28.6': dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 - '@babel/parser@7.29.7': + '@babel/parser@7.29.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.0 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-proposal-export-default-from@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.0 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-export-default-from@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-export-default-from@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-flow@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-globals': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/template': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.28.6 - '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-dotall-regex@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-keys@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-explicit-resource-management@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-for-of@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-literals@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-amd@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-new-target@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-numeric-separator@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-display-name@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/types': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-regenerator@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regexp-modifiers@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-runtime@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.7) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + babel-plugin-polyfill-corejs2: 0.4.16(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.7(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-spread@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-template-literals@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typeof-symbol@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-unicode-property-regex@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-unicode-sets-regex@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/preset-env@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/core': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7) - '@babel/plugin-syntax-import-assertions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.7) - '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-block-scoped-functions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-dotall-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-duplicate-keys': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-dynamic-import': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-explicit-resource-management': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-exponentiation-operator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-json-strings': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-member-expression-literals': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-amd': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-systemjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-umd': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-new-target': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-numeric-separator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-object-super': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-property-literals': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-regenerator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-regexp-modifiers': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-reserved-words': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-spread': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-typeof-symbol': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-escapes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-property-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-sets-regex': 7.29.7(@babel/core@7.29.7) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.7) - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) - babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.7) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) - core-js-compat: 3.49.0 + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/preset-env@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.16(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.1(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.7(@babel/core@7.29.0) + core-js-compat: 3.48.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.7)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/types': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/types': 7.29.0 esutils: 2.0.3 - '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/runtime@7.29.7': {} + '@babel/runtime@7.28.6': {} - '@babel/template@7.29.7': + '@babel/template@7.28.6': dependencies: - '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 - '@babel/traverse@7.29.7': + '@babel/traverse@7.29.0': dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.29.7': + '@babel/types@7.29.0': dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@bcoe/v8-coverage@0.2.3': {} @@ -6776,252 +6135,153 @@ snapshots: dependencies: '@types/hammerjs': 2.0.46 - '@emnapi/core@1.10.0': - dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.10.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.1': - dependencies: - tslib: 2.8.1 - optional: true - '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.27.7': - optional: true - - '@esbuild/aix-ppc64@0.28.0': + '@esbuild/aix-ppc64@0.27.3': optional: true '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.27.7': - optional: true - - '@esbuild/android-arm64@0.28.0': + '@esbuild/android-arm64@0.27.3': optional: true '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-arm@0.27.7': - optional: true - - '@esbuild/android-arm@0.28.0': + '@esbuild/android-arm@0.27.3': optional: true '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/android-x64@0.27.7': - optional: true - - '@esbuild/android-x64@0.28.0': + '@esbuild/android-x64@0.27.3': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.27.7': - optional: true - - '@esbuild/darwin-arm64@0.28.0': + '@esbuild/darwin-arm64@0.27.3': optional: true '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/darwin-x64@0.27.7': - optional: true - - '@esbuild/darwin-x64@0.28.0': + '@esbuild/darwin-x64@0.27.3': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.27.7': - optional: true - - '@esbuild/freebsd-arm64@0.28.0': + '@esbuild/freebsd-arm64@0.27.3': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.27.7': - optional: true - - '@esbuild/freebsd-x64@0.28.0': + '@esbuild/freebsd-x64@0.27.3': optional: true '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm64@0.27.7': - optional: true - - '@esbuild/linux-arm64@0.28.0': + '@esbuild/linux-arm64@0.27.3': optional: true '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.27.7': - optional: true - - '@esbuild/linux-arm@0.28.0': + '@esbuild/linux-arm@0.27.3': optional: true '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-ia32@0.27.7': - optional: true - - '@esbuild/linux-ia32@0.28.0': + '@esbuild/linux-ia32@0.27.3': optional: true '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-loong64@0.27.7': - optional: true - - '@esbuild/linux-loong64@0.28.0': + '@esbuild/linux-loong64@0.27.3': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-mips64el@0.27.7': - optional: true - - '@esbuild/linux-mips64el@0.28.0': + '@esbuild/linux-mips64el@0.27.3': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.27.7': - optional: true - - '@esbuild/linux-ppc64@0.28.0': + '@esbuild/linux-ppc64@0.27.3': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.27.7': - optional: true - - '@esbuild/linux-riscv64@0.28.0': + '@esbuild/linux-riscv64@0.27.3': optional: true '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-s390x@0.27.7': - optional: true - - '@esbuild/linux-s390x@0.28.0': + '@esbuild/linux-s390x@0.27.3': optional: true '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.27.7': - optional: true - - '@esbuild/linux-x64@0.28.0': - optional: true - - '@esbuild/netbsd-arm64@0.27.7': - optional: true - - '@esbuild/netbsd-arm64@0.28.0': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - - '@esbuild/netbsd-x64@0.27.7': - optional: true - - '@esbuild/netbsd-x64@0.28.0': + '@esbuild/linux-x64@0.27.3': optional: true - '@esbuild/openbsd-arm64@0.27.7': + '@esbuild/netbsd-arm64@0.27.3': optional: true - '@esbuild/openbsd-arm64@0.28.0': + '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.21.5': + '@esbuild/netbsd-x64@0.27.3': optional: true - '@esbuild/openbsd-x64@0.27.7': + '@esbuild/openbsd-arm64@0.27.3': optional: true - '@esbuild/openbsd-x64@0.28.0': + '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/openharmony-arm64@0.27.7': + '@esbuild/openbsd-x64@0.27.3': optional: true - '@esbuild/openharmony-arm64@0.28.0': + '@esbuild/openharmony-arm64@0.27.3': optional: true '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.27.7': - optional: true - - '@esbuild/sunos-x64@0.28.0': + '@esbuild/sunos-x64@0.27.3': optional: true '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-arm64@0.27.7': - optional: true - - '@esbuild/win32-arm64@0.28.0': + '@esbuild/win32-arm64@0.27.3': optional: true '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-ia32@0.27.7': - optional: true - - '@esbuild/win32-ia32@0.28.0': + '@esbuild/win32-ia32@0.27.3': optional: true '@esbuild/win32-x64@0.21.5': optional: true - '@esbuild/win32-x64@0.27.7': - optional: true - - '@esbuild/win32-x64@0.28.0': + '@esbuild/win32-x64@0.27.3': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1(jiti@2.7.0))': - dependencies: - eslint: 10.4.1(jiti@2.7.0) - eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': dependencies: eslint: 8.57.1 @@ -7029,25 +6289,9 @@ snapshots: '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.23.5': - dependencies: - '@eslint/object-schema': 3.0.5 - debug: 4.4.3 - minimatch: 10.2.5 - transitivePeerDependencies: - - supports-color - - '@eslint/config-helpers@0.6.0': - dependencies: - '@eslint/core': 1.2.1 - - '@eslint/core@1.2.1': - dependencies: - '@types/json-schema': 7.0.15 - '@eslint/eslintrc@2.1.4': dependencies: - ajv: 6.15.0 + ajv: 6.14.0 debug: 4.4.3 espree: 9.6.1 globals: 13.24.0 @@ -7061,20 +6305,13 @@ snapshots: '@eslint/js@8.57.1': {} - '@eslint/object-schema@3.0.5': {} - - '@eslint/plugin-kit@0.7.2': - dependencies: - '@eslint/core': 1.2.1 - levn: 0.4.1 - '@fastify/accept-negotiator@2.0.1': {} '@fastify/ajv-compiler@4.0.5': dependencies: - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - fast-uri: 3.1.2 + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + fast-uri: 3.1.0 '@fastify/busboy@3.2.0': {} @@ -7094,7 +6331,7 @@ snapshots: '@fastify/fast-json-stringify-compiler@5.0.3': dependencies: - fast-json-stringify: 6.4.0 + fast-json-stringify: 6.3.0 '@fastify/forwarded@3.0.1': {} @@ -7126,13 +6363,7 @@ snapshots: '@fastify/proxy-addr@5.1.0': dependencies: '@fastify/forwarded': 3.0.1 - ipaddr.js: 2.4.0 - - '@fastify/rate-limit@10.3.0': - dependencies: - '@lukeed/ms': 2.0.2 - fastify-plugin: 5.1.0 - toad-cache: 3.7.1 + ipaddr.js: 2.3.0 '@fastify/send@4.1.0': dependencies: @@ -7151,23 +6382,23 @@ snapshots: fastq: 1.20.1 glob: 11.1.0 - '@gorhom/bottom-sheet@5.2.14(@types/react-native@0.70.19)(@types/react@19.2.15)(react-native-gesture-handler@2.31.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-reanimated@3.19.5(@babel/core@7.29.7)(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3)': + '@gorhom/bottom-sheet@5.2.14(@types/react-native@0.70.19)(@types/react@19.2.14)(react-native-gesture-handler@2.31.2(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-reanimated@3.19.5(@babel/core@7.29.0)(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)': dependencies: - '@gorhom/portal': 1.0.14(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + '@gorhom/portal': 1.0.14(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) invariant: 2.2.4 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) - react-native-gesture-handler: 2.31.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) - react-native-reanimated: 3.19.5(@babel/core@7.29.7)(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + react-native-gesture-handler: 2.31.2(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 3.19.5(@babel/core@7.29.0)(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) optionalDependencies: - '@types/react': 19.2.15 + '@types/react': 19.2.14 '@types/react-native': 0.70.19 - '@gorhom/portal@1.0.14(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3)': + '@gorhom/portal@1.0.14(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)': dependencies: - nanoid: 3.3.12 + nanoid: 3.3.11 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) '@hapi/hoek@9.3.0': {} @@ -7175,18 +6406,6 @@ snapshots: dependencies: '@hapi/hoek': 9.3.0 - '@humanfs/core@0.19.2': - dependencies: - '@humanfs/types': 0.15.0 - - '@humanfs/node@0.16.8': - dependencies: - '@humanfs/core': 0.19.2 - '@humanfs/types': 0.15.0 - '@humanwhocodes/retry': 0.4.3 - - '@humanfs/types@0.15.0': {} - '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -7199,9 +6418,7 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} - '@humanwhocodes/retry@0.4.3': {} - - '@ioredis/commands@1.10.0': {} + '@ioredis/commands@1.5.1': {} '@isaacs/cliui@9.0.0': {} @@ -7215,12 +6432,12 @@ snapshots: js-yaml: 3.14.2 resolve-from: 5.0.0 - '@istanbuljs/schema@0.1.6': {} + '@istanbuljs/schema@0.1.3': {} '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 22.19.19 + '@types/node': 22.19.15 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -7233,14 +6450,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.19.19 + '@types/node': 22.19.15 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@22.19.19) + jest-config: 29.7.0(@types/node@22.19.15) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -7269,7 +6486,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.19.19 + '@types/node': 22.19.15 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -7287,7 +6504,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.19.19 + '@types/node': 22.19.15 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -7309,7 +6526,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 22.19.19 + '@types/node': 22.19.15 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit: 0.1.2 @@ -7356,7 +6573,7 @@ snapshots: '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 6.1.1 @@ -7379,7 +6596,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.19.19 + '@types/node': 22.19.15 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -7409,13 +6626,6 @@ snapshots: '@lukeed/ms@2.0.2': {} - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 - optional: true - '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: eslint-scope: 5.1.1 @@ -7432,51 +6642,49 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@package-json/types@0.0.12': {} - '@pinojs/redact@0.4.0': {} '@polka/url@1.0.0-next.29': {} - '@prisma/client@6.19.3(prisma@6.19.3(typescript@5.9.3))(typescript@5.9.3)': + '@prisma/client@6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3)': optionalDependencies: - prisma: 6.19.3(typescript@5.9.3) + prisma: 6.19.2(typescript@5.9.3) typescript: 5.9.3 - '@prisma/config@6.19.3': + '@prisma/config@6.19.2': dependencies: c12: 3.1.0 deepmerge-ts: 7.1.5 - effect: 3.21.0 + effect: 3.18.4 empathic: 2.0.0 transitivePeerDependencies: - magicast - '@prisma/debug@6.19.3': {} + '@prisma/debug@6.19.2': {} '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': {} - '@prisma/engines@6.19.3': + '@prisma/engines@6.19.2': dependencies: - '@prisma/debug': 6.19.3 + '@prisma/debug': 6.19.2 '@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 - '@prisma/fetch-engine': 6.19.3 - '@prisma/get-platform': 6.19.3 + '@prisma/fetch-engine': 6.19.2 + '@prisma/get-platform': 6.19.2 - '@prisma/fetch-engine@6.19.3': + '@prisma/fetch-engine@6.19.2': dependencies: - '@prisma/debug': 6.19.3 + '@prisma/debug': 6.19.2 '@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 - '@prisma/get-platform': 6.19.3 + '@prisma/get-platform': 6.19.2 - '@prisma/get-platform@6.19.3': + '@prisma/get-platform@6.19.2': dependencies: - '@prisma/debug': 6.19.3 + '@prisma/debug': 6.19.2 - '@react-native-async-storage/async-storage@2.2.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))': + '@react-native-async-storage/async-storage@2.2.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))': dependencies: merge-options: 3.0.4 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) '@react-native-community/cli-clean@20.1.0': dependencies: @@ -7489,7 +6697,7 @@ snapshots: dependencies: '@react-native-community/cli-tools': 20.1.0 fast-glob: 3.3.3 - fast-xml-parser: 4.5.6 + fast-xml-parser: 4.5.4 picocolors: 1.1.1 '@react-native-community/cli-config-apple@20.1.0': @@ -7524,9 +6732,9 @@ snapshots: node-stream-zip: 1.15.0 ora: 5.4.1 picocolors: 1.1.1 - semver: 7.8.1 + semver: 7.7.4 wcwidth: 1.0.1 - yaml: 2.9.0 + yaml: 2.8.2 transitivePeerDependencies: - typescript @@ -7543,7 +6751,7 @@ snapshots: '@react-native-community/cli-config-apple': 20.1.0 '@react-native-community/cli-tools': 20.1.0 execa: 5.1.1 - fast-xml-parser: 4.5.6 + fast-xml-parser: 4.5.4 picocolors: 1.1.1 '@react-native-community/cli-platform-ios@20.1.0': @@ -7553,7 +6761,7 @@ snapshots: '@react-native-community/cli-server-api@20.1.0': dependencies: '@react-native-community/cli-tools': 20.1.0 - body-parser: 1.20.5 + body-parser: 1.20.4 compression: 1.8.1 connect: 3.7.0 errorhandler: 1.5.2 @@ -7561,7 +6769,7 @@ snapshots: open: 6.4.0 pretty-format: 29.7.0 serve-static: 1.16.3 - ws: 6.2.4 + ws: 6.2.3 transitivePeerDependencies: - bufferutil - supports-color @@ -7573,12 +6781,12 @@ snapshots: appdirsjs: 1.2.7 execa: 5.1.1 find-up: 5.0.0 - launch-editor: 2.14.0 + launch-editor: 2.13.1 mime: 2.6.0 ora: 5.4.1 picocolors: 1.1.1 prompts: 2.4.2 - semver: 7.8.1 + semver: 7.7.4 '@react-native-community/cli-types@20.1.0': dependencies: @@ -7600,7 +6808,7 @@ snapshots: graceful-fs: 4.2.11 picocolors: 1.1.1 prompts: 2.4.2 - semver: 7.8.1 + semver: 7.7.4 transitivePeerDependencies: - bufferutil - supports-color @@ -7609,74 +6817,74 @@ snapshots: '@react-native/assets-registry@0.84.1': {} - '@react-native/babel-plugin-codegen@0.84.1(@babel/core@7.29.7)': + '@react-native/babel-plugin-codegen@0.84.1(@babel/core@7.29.0)': dependencies: - '@babel/traverse': 7.29.7 - '@react-native/codegen': 0.84.1(@babel/core@7.29.7) + '@babel/traverse': 7.29.0 + '@react-native/codegen': 0.84.1(@babel/core@7.29.0) transitivePeerDependencies: - '@babel/core' - supports-color - '@react-native/babel-preset@0.84.1(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-proposal-export-default-from': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-export-default-from': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-regenerator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) - '@react-native/babel-plugin-codegen': 0.84.1(@babel/core@7.29.7) + '@react-native/babel-preset@0.84.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-export-default-from': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@react-native/babel-plugin-codegen': 0.84.1(@babel/core@7.29.0) babel-plugin-syntax-hermes-parser: 0.32.0 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.0) react-refresh: 0.14.2 transitivePeerDependencies: - supports-color - '@react-native/codegen@0.84.1(@babel/core@7.29.7)': + '@react-native/codegen@0.84.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 hermes-parser: 0.32.0 invariant: 2.2.4 nullthrows: 1.1.1 - tinyglobby: 0.2.16 + tinyglobby: 0.2.15 yargs: 17.7.2 - '@react-native/community-cli-plugin@0.84.1(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))': + '@react-native/community-cli-plugin@0.84.1(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))': dependencies: '@react-native/dev-middleware': 0.84.1 debug: 4.4.3 invariant: 2.2.4 - metro: 0.83.7 - metro-config: 0.83.7 - metro-core: 0.83.7 - semver: 7.8.1 + metro: 0.83.5 + metro-config: 0.83.5 + metro-core: 0.83.5 + semver: 7.7.4 optionalDependencies: '@react-native-community/cli': 20.1.0(typescript@5.9.3) - '@react-native/metro-config': 0.84.1(@babel/core@7.29.7) + '@react-native/metro-config': 0.84.1(@babel/core@7.29.0) transitivePeerDependencies: - bufferutil - supports-color @@ -7705,26 +6913,26 @@ snapshots: nullthrows: 1.1.1 open: 7.4.2 serve-static: 1.16.3 - ws: 7.5.11 + ws: 7.5.10 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@react-native/eslint-config@0.84.1(eslint@8.57.1)(jest@29.7.0(@types/node@22.19.19))(prettier@2.8.8)(typescript@5.9.3)': + '@react-native/eslint-config@0.84.1(eslint@8.57.1)(jest@29.7.0(@types/node@22.19.15))(prettier@2.8.8)(typescript@5.9.3)': dependencies: - '@babel/core': 7.29.7 - '@babel/eslint-parser': 7.29.7(@babel/core@7.29.7)(eslint@8.57.1) + '@babel/core': 7.29.0 + '@babel/eslint-parser': 7.28.6(@babel/core@7.29.0)(eslint@8.57.1) '@react-native/eslint-plugin': 0.84.1 - '@typescript-eslint/eslint-plugin': 8.60.0(@typescript-eslint/parser@8.60.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/parser': 8.60.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.57.0(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 eslint-config-prettier: 8.10.2(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) - eslint-plugin-ft-flow: 2.0.3(@babel/eslint-parser@7.29.7(@babel/core@7.29.7)(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(jest@29.7.0(@types/node@22.19.19))(typescript@5.9.3) + eslint-plugin-ft-flow: 2.0.3(@babel/eslint-parser@7.28.6(@babel/core@7.29.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-jest: 29.15.0(@typescript-eslint/eslint-plugin@8.57.0(@typescript-eslint/parser@8.57.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(jest@29.7.0(@types/node@22.19.15))(typescript@5.9.3) eslint-plugin-react: 7.37.5(eslint@8.57.1) - eslint-plugin-react-hooks: 7.1.1(eslint@8.57.1) + eslint-plugin-react-hooks: 7.0.1(eslint@8.57.1) eslint-plugin-react-native: 5.0.0(eslint@8.57.1) prettier: 2.8.8 transitivePeerDependencies: @@ -7738,33 +6946,33 @@ snapshots: '@react-native/js-polyfills@0.84.1': {} - '@react-native/metro-babel-transformer@0.84.1(@babel/core@7.29.7)': + '@react-native/metro-babel-transformer@0.84.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.7 - '@react-native/babel-preset': 0.84.1(@babel/core@7.29.7) + '@babel/core': 7.29.0 + '@react-native/babel-preset': 0.84.1(@babel/core@7.29.0) hermes-parser: 0.32.0 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - '@react-native/metro-config@0.84.1(@babel/core@7.29.7)': + '@react-native/metro-config@0.84.1(@babel/core@7.29.0)': dependencies: '@react-native/js-polyfills': 0.84.1 - '@react-native/metro-babel-transformer': 0.84.1(@babel/core@7.29.7) - metro-config: 0.83.7 - metro-runtime: 0.83.7 + '@react-native/metro-babel-transformer': 0.84.1(@babel/core@7.29.0) + metro-config: 0.83.5 + metro-runtime: 0.83.5 transitivePeerDependencies: - '@babel/core' - bufferutil - supports-color - utf-8-validate - '@react-native/new-app-screen@0.84.1(@types/react@19.2.15)(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3)': + '@react-native/new-app-screen@0.84.1(@types/react@19.2.14)(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)': dependencies: react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) optionalDependencies: - '@types/react': 19.2.15 + '@types/react': 19.2.14 '@react-native/normalize-colors@0.74.89': {} @@ -7772,151 +6980,151 @@ snapshots: '@react-native/typescript-config@0.84.1': {} - '@react-native/virtualized-lists@0.84.1(@types/react@19.2.15)(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3)': + '@react-native/virtualized-lists@0.84.1(@types/react@19.2.14)(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) optionalDependencies: - '@types/react': 19.2.15 + '@types/react': 19.2.14 - '@react-navigation/bottom-tabs@7.16.2(@react-navigation/native@7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-screens@4.25.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3)': + '@react-navigation/bottom-tabs@7.15.5(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)': dependencies: - '@react-navigation/elements': 2.9.19(@react-navigation/native@7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) - '@react-navigation/native': 7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + '@react-navigation/elements': 2.9.10(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + '@react-navigation/native': 7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) - react-native-safe-area-context: 5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) - react-native-screens: 4.25.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + react-native-screens: 4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) sf-symbols-typescript: 2.2.0 transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/core@7.17.5(react@19.2.3)': + '@react-navigation/core@7.16.1(react@19.2.3)': dependencies: - '@react-navigation/routers': 7.5.5 + '@react-navigation/routers': 7.5.3 escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 - nanoid: 3.3.12 + nanoid: 3.3.11 query-string: 7.1.3 react: 19.2.3 - react-is: 19.2.6 + react-is: 19.2.4 use-latest-callback: 0.2.6(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@react-navigation/elements@2.9.19(@react-navigation/native@7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3)': + '@react-navigation/elements@2.9.10(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)': dependencies: - '@react-navigation/native': 7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + '@react-navigation/native': 7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) - react-native-safe-area-context: 5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@react-navigation/native-stack@7.16.0(@react-navigation/native@7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-screens@4.25.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3)': + '@react-navigation/native-stack@7.14.4(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)': dependencies: - '@react-navigation/elements': 2.9.19(@react-navigation/native@7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) - '@react-navigation/native': 7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + '@react-navigation/elements': 2.9.10(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + '@react-navigation/native': 7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) - react-native-safe-area-context: 5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) - react-native-screens: 4.25.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + react-native-screens: 4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) sf-symbols-typescript: 2.2.0 warn-once: 0.1.1 transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/native@7.2.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3)': + '@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)': dependencies: - '@react-navigation/core': 7.17.5(react@19.2.3) + '@react-navigation/core': 7.16.1(react@19.2.3) escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 - nanoid: 3.3.12 + nanoid: 3.3.11 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) - '@react-navigation/routers@7.5.5': + '@react-navigation/routers@7.5.3': dependencies: - nanoid: 3.3.12 + nanoid: 3.3.11 - '@rollup/rollup-android-arm-eabi@4.60.4': + '@rollup/rollup-android-arm-eabi@4.59.0': optional: true - '@rollup/rollup-android-arm64@4.60.4': + '@rollup/rollup-android-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-arm64@4.60.4': + '@rollup/rollup-darwin-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-x64@4.60.4': + '@rollup/rollup-darwin-x64@4.59.0': optional: true - '@rollup/rollup-freebsd-arm64@4.60.4': + '@rollup/rollup-freebsd-arm64@4.59.0': optional: true - '@rollup/rollup-freebsd-x64@4.60.4': + '@rollup/rollup-freebsd-x64@4.59.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.4': + '@rollup/rollup-linux-arm-musleabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.4': + '@rollup/rollup-linux-arm64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.4': + '@rollup/rollup-linux-arm64-musl@4.59.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.4': + '@rollup/rollup-linux-loong64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.4': + '@rollup/rollup-linux-loong64-musl@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.4': + '@rollup/rollup-linux-ppc64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.4': + '@rollup/rollup-linux-ppc64-musl@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.4': + '@rollup/rollup-linux-riscv64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.4': + '@rollup/rollup-linux-riscv64-musl@4.59.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.4': + '@rollup/rollup-linux-s390x-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.4': + '@rollup/rollup-linux-x64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-musl@4.60.4': + '@rollup/rollup-linux-x64-musl@4.59.0': optional: true - '@rollup/rollup-openbsd-x64@4.60.4': + '@rollup/rollup-openbsd-x64@4.59.0': optional: true - '@rollup/rollup-openharmony-arm64@4.60.4': + '@rollup/rollup-openharmony-arm64@4.59.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.4': + '@rollup/rollup-win32-arm64-msvc@4.59.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.4': + '@rollup/rollup-win32-ia32-msvc@4.59.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.60.4': + '@rollup/rollup-win32-x64-gnu@4.59.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.60.4': + '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true '@sideway/address@4.1.5': @@ -7939,88 +7147,79 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@sveltejs/acorn-typescript@1.0.10(acorn@8.16.0)': + '@sveltejs/acorn-typescript@1.0.9(acorn@8.16.0)': dependencies: acorn: 8.16.0 - '@sveltejs/adapter-auto@7.0.1(@sveltejs/kit@2.61.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.56.0(@typescript-eslint/types@8.60.0))(typescript@5.9.3)(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)))': + '@sveltejs/adapter-auto@7.0.1(@sveltejs/kit@2.54.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.10)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))': dependencies: - '@sveltejs/kit': 2.61.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.56.0(@typescript-eslint/types@8.60.0))(typescript@5.9.3)(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)) + '@sveltejs/kit': 2.54.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.10)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - '@sveltejs/kit@2.61.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.56.0(@typescript-eslint/types@8.60.0))(typescript@5.9.3)(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0))': + '@sveltejs/kit@2.54.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.10)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@standard-schema/spec': 1.1.0 - '@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)) + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 - devalue: 5.8.1 + devalue: 5.6.4 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 mrmime: 2.0.1 - set-cookie-parser: 3.1.0 + set-cookie-parser: 3.0.1 sirv: 3.0.2 - svelte: 5.56.0(@typescript-eslint/types@8.60.0) - vite: 7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0) + svelte: 5.53.10 + vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) optionalDependencies: typescript: 5.9.3 - '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) obug: 2.1.1 - svelte: 5.56.0(@typescript-eslint/types@8.60.0) - vite: 7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0) + svelte: 5.53.10 + vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)))(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.10)(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 - svelte: 5.56.0(@typescript-eslint/types@8.60.0) - vite: 7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0) - vitefu: 1.1.3(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)) - - '@tybys/wasm-util@0.10.2': - dependencies: - tslib: 2.8.1 - optional: true + svelte: 5.53.10 + vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vitefu: 1.1.2(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.0 '@types/cookie@0.6.0': {} - '@types/esrecurse@4.3.1': {} - '@types/estree@1.0.8': {} - '@types/estree@1.0.9': {} - '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 22.19.19 + '@types/node': 22.19.15 '@types/hammerjs@2.0.46': {} @@ -8039,30 +7238,28 @@ snapshots: expect: 29.7.0 pretty-format: 29.7.0 - '@types/json-schema@7.0.15': {} - - '@types/node@22.19.19': + '@types/node@22.19.15': dependencies: undici-types: 6.21.0 '@types/qrcode@1.5.6': dependencies: - '@types/node': 22.19.19 + '@types/node': 22.19.15 '@types/react-native-vector-icons@6.4.18': dependencies: - '@types/react': 19.2.15 + '@types/react': 19.2.14 '@types/react-native': 0.70.19 '@types/react-native@0.70.19': dependencies: - '@types/react': 19.2.15 + '@types/react': 19.2.14 '@types/react-test-renderer@19.1.0': dependencies: - '@types/react': 19.2.15 + '@types/react': 19.2.14 - '@types/react@19.2.15': + '@types/react@19.2.14': dependencies: csstype: 3.2.3 @@ -8076,219 +7273,98 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.57.0(@typescript-eslint/parser@8.57.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/type-utils': 8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/utils': 8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.60.0 - eslint: 10.4.1(jiti@2.7.0) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.60.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/type-utils': 8.60.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.60.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.60.0 + '@typescript-eslint/parser': 8.57.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.57.0 + '@typescript-eslint/type-utils': 8.57.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.0 eslint: 8.57.1 ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/parser@8.57.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.60.0 - debug: 4.4.3 - eslint: 10.4.1(jiti@2.7.0) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.60.0(eslint@8.57.1)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.60.0 + '@typescript-eslint/scope-manager': 8.57.0 + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.0 debug: 4.4.3 eslint: 8.57.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.60.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.57.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) - '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3) + '@typescript-eslint/types': 8.57.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.60.0': - dependencies: - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/visitor-keys': 8.60.0 - - '@typescript-eslint/tsconfig-utils@8.60.0(typescript@5.9.3)': + '@typescript-eslint/scope-manager@8.57.0': dependencies: - typescript: 5.9.3 + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/visitor-keys': 8.57.0 - '@typescript-eslint/type-utils@8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.57.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - debug: 4.4.3 - eslint: 10.4.1(jiti@2.7.0) - ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/type-utils@8.60.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.57.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.60.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.0(eslint@8.57.1)(typescript@5.9.3) debug: 4.4.3 eslint: 8.57.1 - ts-api-utils: 2.5.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.60.0': {} + '@typescript-eslint/types@8.57.0': {} - '@typescript-eslint/typescript-estree@8.60.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.60.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/visitor-keys': 8.60.0 + '@typescript-eslint/project-service': 8.57.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3) + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/visitor-keys': 8.57.0 debug: 4.4.3 - minimatch: 10.2.5 - semver: 7.8.1 - tinyglobby: 0.2.16 - ts-api-utils: 2.5.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) - eslint: 10.4.1(jiti@2.7.0) + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.60.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.57.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.57.0 + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) eslint: 8.57.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.60.0': + '@typescript-eslint/visitor-keys@8.57.0': dependencies: - '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/types': 8.57.0 eslint-visitor-keys: 5.0.1 - '@ungap/structured-clone@1.3.1': {} - - '@unrs/resolver-binding-android-arm-eabi@1.12.2': - optional: true - - '@unrs/resolver-binding-android-arm64@1.12.2': - optional: true - - '@unrs/resolver-binding-darwin-arm64@1.12.2': - optional: true - - '@unrs/resolver-binding-darwin-x64@1.12.2': - optional: true - - '@unrs/resolver-binding-freebsd-x64@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-arm64-musl@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-loong64-musl@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-x64-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-x64-musl@1.12.2': - optional: true - - '@unrs/resolver-binding-openharmony-arm64@1.12.2': - optional: true - - '@unrs/resolver-binding-wasm32-wasi@1.12.2': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - optional: true - - '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': - optional: true - - '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': - optional: true - - '@unrs/resolver-binding-win32-x64-msvc@1.12.2': - optional: true + '@ungap/structured-clone@1.3.0': {} '@vitest/expect@2.1.9': dependencies: @@ -8297,13 +7373,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.19.19)(terser@5.48.0))': + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.19.15)(terser@5.46.0))': dependencies: '@vitest/spy': 2.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 5.4.21(@types/node@22.19.19)(terser@5.48.0) + vite: 5.4.21(@types/node@22.19.15)(terser@5.46.0) '@vitest/pretty-format@2.1.9': dependencies: @@ -8356,21 +7432,21 @@ snapshots: agent-base@7.1.4: {} - ajv-formats@3.0.1(ajv@8.20.0): + ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: - ajv: 8.20.0 + ajv: 8.18.0 - ajv@6.15.0: + ajv@6.14.0: 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 - ajv@8.20.0: + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 + fast-uri: 3.1.0 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -8403,7 +7479,7 @@ snapshots: anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.2 + picomatch: 2.3.1 appdirsjs@1.2.7: {} @@ -8422,52 +7498,52 @@ snapshots: array-includes@3.1.9: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.2 + es-abstract: 1.24.1 + 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.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.2 + es-abstract: 1.24.1 es-errors: 1.3.0 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 es-shim-unscopables: 1.1.0 array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.2 + es-abstract: 1.24.1 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.2 + es-abstract: 1.24.1 es-shim-unscopables: 1.1.0 array.prototype.tosorted@1.1.4: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.2 + es-abstract: 1.24.1 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.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.2 + es-abstract: 1.24.1 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 @@ -8502,13 +7578,13 @@ snapshots: axobject-query@4.1.0: {} - babel-jest@29.7.0(@babel/core@7.29.7): + babel-jest@29.7.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.0 '@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.29.7) + babel-preset-jest: 29.6.3(@babel/core@7.29.0) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -8517,9 +7593,9 @@ snapshots: babel-plugin-istanbul@6.1.1: dependencies: - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.6 + '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 test-exclude: 6.0.0 transitivePeerDependencies: @@ -8527,40 +7603,40 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.28.0 - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): + babel-plugin-polyfill-corejs2@0.4.16(@babel/core@7.29.0): dependencies: - '@babel/compat-data': 7.29.7 - '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.7): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) - core-js-compat: 3.49.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0) + core-js-compat: 3.48.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.7): + babel-plugin-polyfill-corejs3@0.14.1(@babel/core@7.29.0): dependencies: - '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) - core-js-compat: 3.49.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0) + core-js-compat: 3.48.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7): + babel-plugin-polyfill-regenerator@0.6.7(@babel/core@7.29.0): dependencies: - '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -8568,46 +7644,44 @@ snapshots: dependencies: hermes-parser: 0.32.0 - babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.7): + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.0): dependencies: - '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - '@babel/core' - babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) - '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) - - babel-preset-jest@29.6.3(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@29.6.3(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) balanced-match@1.0.2: {} balanced-match@4.0.4: {} - base64-arraybuffer@1.0.2: {} - base64-js@1.5.1: {} - baseline-browser-mapping@2.10.33: {} + baseline-browser-mapping@2.10.0: {} bl@4.1.0: dependencies: @@ -8617,7 +7691,7 @@ snapshots: bn.js@4.12.3: {} - body-parser@1.20.5: + body-parser@1.20.4: dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -8627,7 +7701,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.15.2 + qs: 6.14.2 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -8636,12 +7710,12 @@ snapshots: boolbase@1.0.0: {} - brace-expansion@1.1.15: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@5.0.6: + brace-expansion@5.0.4: dependencies: balanced-match: 4.0.4 @@ -8649,13 +7723,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.2: + browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.10.33 - caniuse-lite: 1.0.30001793 - electron-to-chromium: 1.5.364 - node-releases: 2.0.46 - update-browserslist-db: 1.2.3(browserslist@4.28.2) + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001778 + electron-to-chromium: 1.5.313 + node-releases: 2.0.36 + update-browserslist-db: 1.2.3(browserslist@4.28.1) bser@2.1.1: dependencies: @@ -8668,23 +7742,21 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - builtin-modules@5.2.0: {} - bytes@3.1.2: {} c12@3.1.0: dependencies: chokidar: 4.0.3 confbox: 0.2.4 - defu: 6.1.7 + defu: 6.1.4 dotenv: 16.6.1 exsolve: 1.0.8 giget: 2.0.0 - jiti: 2.7.0 + jiti: 2.6.1 ohash: 2.0.11 pathe: 2.0.3 perfect-debounce: 1.0.0 - pkg-types: 2.3.1 + pkg-types: 2.3.0 rc9: 2.1.2 cac@6.7.14: {} @@ -8694,7 +7766,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.9: + call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -8712,7 +7784,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001793: {} + caniuse-lite@1.0.30001778: {} chai@5.3.3: dependencies: @@ -8727,8 +7799,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - change-case@5.4.4: {} - char-regex@1.0.2: {} check-error@2.1.3: {} @@ -8739,7 +7809,7 @@ snapshots: chrome-launcher@0.15.2: dependencies: - '@types/node': 22.19.19 + '@types/node': 22.19.15 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -8748,7 +7818,7 @@ snapshots: chromium-edge-launcher@0.2.0: dependencies: - '@types/node': 22.19.19 + '@types/node': 22.19.15 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -8761,20 +7831,14 @@ snapshots: ci-info@3.9.0: {} - ci-info@4.4.0: {} - citty@0.1.6: dependencies: consola: 3.4.2 - citty@0.2.2: {} + citty@0.2.1: {} cjs-module-lexer@1.4.3: {} - clean-regexp@1.0.0: - dependencies: - escape-string-regexp: 1.0.5 - cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -8803,7 +7867,7 @@ snapshots: clsx@2.1.1: {} - cluster-key-slot@1.1.1: {} + cluster-key-slot@1.1.2: {} co@4.6.0: {} @@ -8843,8 +7907,6 @@ snapshots: commander@9.5.0: {} - comment-parser@1.4.7: {} - compressible@2.0.18: dependencies: mime-db: 1.54.0 @@ -8897,9 +7959,9 @@ snapshots: cookie@1.1.1: {} - core-js-compat@3.49.0: + core-js-compat@3.48.0: dependencies: - browserslist: 4.28.2 + browserslist: 4.28.1 cosmiconfig@9.0.1(typescript@5.9.3): dependencies: @@ -8910,13 +7972,13 @@ snapshots: optionalDependencies: typescript: 5.9.3 - create-jest@29.7.0(@types/node@22.19.19): + create-jest@29.7.0(@types/node@22.19.15): 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@22.19.19) + jest-config: 29.7.0(@types/node@22.19.15) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -8941,10 +8003,6 @@ snapshots: dependencies: hyphenate-style-name: 1.1.0 - css-line-break@2.1.0: - dependencies: - utrie: 1.0.2 - css-select@5.2.2: dependencies: boolbase: 1.0.0 @@ -8982,7 +8040,7 @@ snapshots: dateformat@4.6.3: {} - dayjs@1.11.21: {} + dayjs@1.11.20: {} debug@2.6.9: dependencies: @@ -9022,7 +8080,7 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - defu@6.1.7: {} + defu@6.1.4: {} denque@2.1.0: {} @@ -9036,7 +8094,7 @@ snapshots: detect-newline@3.1.0: {} - devalue@5.8.1: {} + devalue@5.6.4: {} diff-sequences@29.6.3: {} @@ -9082,12 +8140,12 @@ snapshots: ee-first@1.1.1: {} - effect@3.21.0: + effect@3.18.4: dependencies: '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 - electron-to-chromium@1.5.364: {} + electron-to-chromium@1.5.313: {} emittery@0.13.1: {} @@ -9103,11 +8161,6 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.22.1: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - entities@4.5.0: {} env-paths@2.2.1: {} @@ -9127,19 +8180,19 @@ snapshots: accepts: 1.3.8 escape-html: 1.0.3 - es-abstract@1.24.2: + es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.9 + 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.2 + es-object-atoms: 1.1.1 es-set-tostringtag: 2.1.0 es-to-primitive: 1.3.0 function.prototype.name: 1.1.8 @@ -9151,7 +8204,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.4 + hasown: 2.0.2 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -9169,7 +8222,7 @@ snapshots: object.assign: 4.1.7 own-keys: 1.0.1 regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.4 + safe-array-concat: 1.1.3 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 @@ -9180,20 +8233,20 @@ snapshots: 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.8 + typed-array-length: 1.0.7 unbox-primitive: 1.1.0 - which-typed-array: 1.1.21 + which-typed-array: 1.1.20 es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-iterator-helpers@1.3.2: + es-iterator-helpers@1.3.0: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.2 + es-abstract: 1.24.1 es-errors: 1.3.0 es-set-tostringtag: 2.1.0 function-bind: 1.1.2 @@ -9206,10 +8259,11 @@ snapshots: internal-slot: 1.1.0 iterator.prototype: 1.1.5 math-intrinsics: 1.1.0 + safe-array-concat: 1.1.3 es-module-lexer@1.7.0: {} - es-object-atoms@1.1.2: + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -9218,11 +8272,11 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.4 + hasown: 2.0.2 es-shim-unscopables@1.1.0: dependencies: - hasown: 2.0.4 + hasown: 2.0.2 es-to-primitive@1.3.0: dependencies: @@ -9256,63 +8310,34 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - esbuild@0.27.7: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 - - esbuild@0.28.0: + esbuild@0.27.3: optionalDependencies: - '@esbuild/aix-ppc64': 0.28.0 - '@esbuild/android-arm': 0.28.0 - '@esbuild/android-arm64': 0.28.0 - '@esbuild/android-x64': 0.28.0 - '@esbuild/darwin-arm64': 0.28.0 - '@esbuild/darwin-x64': 0.28.0 - '@esbuild/freebsd-arm64': 0.28.0 - '@esbuild/freebsd-x64': 0.28.0 - '@esbuild/linux-arm': 0.28.0 - '@esbuild/linux-arm64': 0.28.0 - '@esbuild/linux-ia32': 0.28.0 - '@esbuild/linux-loong64': 0.28.0 - '@esbuild/linux-mips64el': 0.28.0 - '@esbuild/linux-ppc64': 0.28.0 - '@esbuild/linux-riscv64': 0.28.0 - '@esbuild/linux-s390x': 0.28.0 - '@esbuild/linux-x64': 0.28.0 - '@esbuild/netbsd-arm64': 0.28.0 - '@esbuild/netbsd-x64': 0.28.0 - '@esbuild/openbsd-arm64': 0.28.0 - '@esbuild/openbsd-x64': 0.28.0 - '@esbuild/openharmony-arm64': 0.28.0 - '@esbuild/sunos-x64': 0.28.0 - '@esbuild/win32-arm64': 0.28.0 - '@esbuild/win32-ia32': 0.28.0 - '@esbuild/win32-x64': 0.28.0 + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 escalade@3.2.0: {} @@ -9324,109 +8349,38 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.5.1(eslint@10.4.1(jiti@2.7.0)): - dependencies: - eslint: 10.4.1(jiti@2.7.0) - semver: 7.8.1 - eslint-config-prettier@8.10.2(eslint@8.57.1): dependencies: eslint: 8.57.1 - eslint-import-context@0.1.9(unrs-resolver@1.12.2): - dependencies: - get-tsconfig: 4.14.0 - stable-hash-x: 0.2.0 - optionalDependencies: - unrs-resolver: 1.12.2 - - eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0)))(eslint@10.4.1(jiti@2.7.0)): - dependencies: - debug: 4.4.3 - eslint: 10.4.1(jiti@2.7.0) - eslint-import-context: 0.1.9(unrs-resolver@1.12.2) - get-tsconfig: 4.14.0 - is-bun-module: 2.0.0 - stable-hash-x: 0.2.0 - tinyglobby: 0.2.16 - unrs-resolver: 1.12.2 - optionalDependencies: - eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0)) - transitivePeerDependencies: - - supports-color - - eslint-plugin-es-x@7.8.0(eslint@10.4.1(jiti@2.7.0)): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) - '@eslint-community/regexpp': 4.12.2 - eslint: 10.4.1(jiti@2.7.0) - eslint-compat-utils: 0.5.1(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-eslint-comments@3.2.0(eslint@8.57.1): dependencies: escape-string-regexp: 1.0.5 eslint: 8.57.1 ignore: 5.3.2 - eslint-plugin-ft-flow@2.0.3(@babel/eslint-parser@7.29.7(@babel/core@7.29.7)(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-ft-flow@2.0.3(@babel/eslint-parser@7.28.6(@babel/core@7.29.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: - '@babel/eslint-parser': 7.29.7(@babel/core@7.29.7)(eslint@8.57.1) + '@babel/eslint-parser': 7.28.6(@babel/core@7.29.0)(eslint@8.57.1) eslint: 8.57.1 - lodash: 4.18.1 + lodash: 4.17.23 string-natural-compare: 3.0.1 - eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0)): - dependencies: - '@package-json/types': 0.0.12 - '@typescript-eslint/types': 8.60.0 - comment-parser: 1.4.7 - debug: 4.4.3 - eslint: 10.4.1(jiti@2.7.0) - eslint-import-context: 0.1.9(unrs-resolver@1.12.2) - is-glob: 4.0.3 - minimatch: 10.2.5 - semver: 7.8.1 - stable-hash-x: 0.2.0 - unrs-resolver: 1.12.2 - optionalDependencies: - '@typescript-eslint/utils': 8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - transitivePeerDependencies: - - supports-color - - eslint-plugin-jest@29.15.2(@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(jest@29.7.0(@types/node@22.19.19))(typescript@5.9.3): + eslint-plugin-jest@29.15.0(@typescript-eslint/eslint-plugin@8.57.0(@typescript-eslint/parser@8.57.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(jest@29.7.0(@types/node@22.19.15))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.60.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.0(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.60.0(@typescript-eslint/parser@8.60.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) - jest: 29.7.0(@types/node@22.19.19) + '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + jest: 29.7.0(@types/node@22.19.15) typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-n@18.0.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) - enhanced-resolve: 5.22.1 - eslint: 10.4.1(jiti@2.7.0) - eslint-plugin-es-x: 7.8.0(eslint@10.4.1(jiti@2.7.0)) - get-tsconfig: 4.14.0 - globals: 15.15.0 - globrex: 0.1.2 - ignore: 5.3.2 - semver: 7.8.1 - optionalDependencies: - typescript: 5.9.3 - - eslint-plugin-promise@7.3.0(eslint@10.4.1(jiti@2.7.0)): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) - eslint: 10.4.1(jiti@2.7.0) - - eslint-plugin-react-hooks@7.1.1(eslint@8.57.1): + eslint-plugin-react-hooks@7.0.1(eslint@8.57.1): dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 eslint: 8.57.1 hermes-parser: 0.25.1 zod: 3.25.76 @@ -9448,45 +8402,21 @@ snapshots: array.prototype.flatmap: 1.3.3 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.3.2 + es-iterator-helpers: 1.3.0 eslint: 8.57.1 estraverse: 5.3.0 - hasown: 2.0.4 + hasown: 2.0.2 jsx-ast-utils: 3.3.5 minimatch: 3.1.5 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.7 + resolve: 2.0.0-next.6 semver: 6.3.1 string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-security@4.0.0: - dependencies: - safe-regex: 2.1.1 - - eslint-plugin-unicorn@64.0.0(eslint@10.4.1(jiti@2.7.0)): - dependencies: - '@babel/helper-validator-identifier': 7.29.7 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) - change-case: 5.4.4 - ci-info: 4.4.0 - clean-regexp: 1.0.0 - core-js-compat: 3.49.0 - eslint: 10.4.1(jiti@2.7.0) - find-up-simple: 1.0.1 - globals: 17.6.0 - indent-string: 5.0.0 - is-builtin-module: 5.0.0 - jsesc: 3.1.0 - pluralize: 8.0.0 - regexp-tree: 0.1.27 - regjsparser: 0.13.1 - semver: 7.8.1 - strip-indent: 4.1.1 - eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 @@ -9497,56 +8427,12 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-scope@9.1.2: - dependencies: - '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.9 - esrecurse: 4.3.0 - estraverse: 5.3.0 - eslint-visitor-keys@2.1.0: {} eslint-visitor-keys@3.4.3: {} eslint-visitor-keys@5.0.1: {} - eslint@10.4.1(jiti@2.7.0): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.5 - '@eslint/config-helpers': 0.6.0 - '@eslint/core': 1.2.1 - '@eslint/plugin-kit': 0.7.2 - '@humanfs/node': 0.16.8 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.9 - ajv: 6.15.0 - cross-spawn: 7.0.6 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - eslint-scope: 9.1.2 - eslint-visitor-keys: 5.0.1 - espree: 11.2.0 - esquery: 1.7.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 - minimatch: 10.2.5 - natural-compare: 1.4.0 - optionator: 0.9.4 - optionalDependencies: - jiti: 2.7.0 - transitivePeerDependencies: - - supports-color - eslint@8.57.1: dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) @@ -9556,8 +8442,8 @@ snapshots: '@humanwhocodes/config-array': 0.13.0 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.3.1 - ajv: 6.15.0 + '@ungap/structured-clone': 1.3.0 + ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -9592,12 +8478,6 @@ snapshots: esm-env@1.2.2: {} - espree@11.2.0: - dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 5.0.1 - espree@9.6.1: dependencies: acorn: 8.16.0 @@ -9610,11 +8490,9 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.2.9(@typescript-eslint/types@8.60.0): + esrap@2.2.3: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - optionalDependencies: - '@typescript-eslint/types': 8.60.0 esrecurse@4.3.0: dependencies: @@ -9626,7 +8504,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.9 + '@types/estree': 1.0.8 esutils@2.0.3: {} @@ -9666,7 +8544,7 @@ snapshots: dependencies: pure-rand: 6.1.0 - fast-copy@4.0.3: {} + fast-copy@4.0.2: {} fast-decode-uri-component@1.0.1: {} @@ -9682,12 +8560,12 @@ snapshots: fast-json-stable-stringify@2.1.0: {} - fast-json-stringify@6.4.0: + fast-json-stringify@6.3.0: dependencies: '@fastify/merge-json-schemas': 0.2.1 - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - fast-uri: 3.1.2 + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + fast-uri: 3.1.0 json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 @@ -9696,7 +8574,7 @@ snapshots: '@lukeed/ms': 2.0.2 asn1.js: 5.4.1 ecdsa-sig-formatter: 1.0.11 - mnemonist: 0.40.4 + mnemonist: 0.40.3 fast-levenshtein@2.0.6: {} @@ -9706,9 +8584,9 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-uri@3.1.2: {} + fast-uri@3.1.0: {} - fast-xml-parser@4.5.6: + fast-xml-parser@4.5.4: dependencies: strnum: 1.1.2 @@ -9718,7 +8596,7 @@ snapshots: fastify-plugin@5.1.0: {} - fastify@5.8.5: + fastify@5.8.2: dependencies: '@fastify/ajv-compiler': 4.0.5 '@fastify/error': 4.2.0 @@ -9726,15 +8604,15 @@ snapshots: '@fastify/proxy-addr': 5.1.0 abstract-logging: 2.0.1 avvio: 9.2.0 - fast-json-stringify: 6.4.0 - find-my-way: 9.6.0 + fast-json-stringify: 6.3.0 + find-my-way: 9.5.0 light-my-request: 6.6.0 pino: 10.3.1 process-warning: 5.0.0 rfdc: 1.4.1 secure-json-parse: 4.1.0 - semver: 7.8.1 - toad-cache: 3.7.1 + semver: 7.7.4 + toad-cache: 3.7.0 fastparallel@2.4.1: dependencies: @@ -9770,18 +8648,14 @@ snapshots: transitivePeerDependencies: - encoding - fdir@6.5.0(picomatch@4.0.4): + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: - picomatch: 4.0.4 + picomatch: 4.0.3 file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -9800,13 +8674,11 @@ snapshots: transitivePeerDependencies: - supports-color - find-my-way@9.6.0: + find-my-way@9.5.0: dependencies: fast-deep-equal: 3.1.3 fast-querystring: 1.1.2 - safe-regex2: 5.1.1 - - find-up-simple@1.0.1: {} + safe-regex2: 5.0.0 find-up@4.1.0: dependencies: @@ -9820,16 +8692,11 @@ snapshots: flat-cache@3.2.0: dependencies: - flatted: 3.4.2 + flatted: 3.4.1 keyv: 4.5.4 rimraf: 3.0.2 - flat-cache@4.0.1: - dependencies: - flatted: 3.4.2 - keyv: 4.5.4 - - flatted@3.4.2: {} + flatted@3.4.1: {} flow-enums-runtime@0.0.6: {} @@ -9859,11 +8726,11 @@ snapshots: function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 - hasown: 2.0.4 + hasown: 2.0.2 is-callable: 1.2.7 functions-have-names@1.2.3: {} @@ -9879,12 +8746,12 @@ snapshots: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.2 + 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.4 + hasown: 2.0.2 math-intrinsics: 1.1.0 get-package-type@0.1.0: {} @@ -9892,7 +8759,7 @@ snapshots: get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 get-stream@6.0.1: {} @@ -9902,7 +8769,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.14.0: + get-tsconfig@4.13.6: dependencies: resolve-pkg-maps: 1.0.0 @@ -9910,9 +8777,9 @@ snapshots: dependencies: citty: 0.1.6 consola: 3.4.2 - defu: 6.1.7 + defu: 6.1.4 node-fetch-native: 1.6.7 - nypm: 0.6.6 + nypm: 0.6.5 pathe: 2.0.3 glob-parent@5.1.2: @@ -9927,7 +8794,7 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 4.2.3 - minimatch: 10.2.5 + minimatch: 10.2.4 minipass: 7.1.3 package-json-from-dist: 1.0.1 path-scurry: 2.0.2 @@ -9945,17 +8812,11 @@ snapshots: dependencies: type-fest: 0.20.2 - globals@15.15.0: {} - - globals@17.6.0: {} - globalthis@1.0.4: dependencies: define-properties: 1.2.1 gopd: 1.2.0 - globrex@0.1.2: {} - gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -9980,7 +8841,7 @@ snapshots: dependencies: has-symbols: 1.1.0 - hasown@2.0.4: + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -9994,7 +8855,7 @@ snapshots: hermes-estree@0.32.0: {} - hermes-estree@0.35.0: {} + hermes-estree@0.33.3: {} hermes-parser@0.25.1: dependencies: @@ -10004,9 +8865,9 @@ snapshots: dependencies: hermes-estree: 0.32.0 - hermes-parser@0.35.0: + hermes-parser@0.33.3: dependencies: - hermes-estree: 0.35.0 + hermes-estree: 0.33.3 hoist-non-react-statics@3.3.2: dependencies: @@ -10014,11 +8875,6 @@ snapshots: html-escaper@2.0.2: {} - html2canvas@1.4.1: - dependencies: - css-line-break: 2.1.0 - text-segmentation: 1.0.3 - http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -10064,8 +8920,6 @@ snapshots: imurmurhash@0.1.4: {} - indent-string@5.0.0: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -10080,30 +8934,32 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.4 + hasown: 2.0.2 side-channel: 1.1.0 invariant@2.2.4: dependencies: loose-envify: 1.4.0 - ioredis@5.11.0: + ioredis@5.10.0: dependencies: - '@ioredis/commands': 1.10.0 - cluster-key-slot: 1.1.1 + '@ioredis/commands': 1.5.1 + cluster-key-slot: 1.1.2 debug: 4.4.3 denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 redis-errors: 1.2.0 redis-parser: 3.0.0 standard-as-callback: 2.1.0 transitivePeerDependencies: - supports-color - ipaddr.js@2.4.0: {} + ipaddr.js@2.3.0: {} is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -10128,19 +8984,11 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-builtin-module@5.0.0: - dependencies: - builtin-modules: 5.2.0 - - is-bun-module@2.0.0: - dependencies: - semver: 7.8.1 - is-callable@1.2.7: {} - is-core-module@2.16.2: + is-core-module@2.16.1: dependencies: - hasown: 2.0.4 + hasown: 2.0.2 is-data-view@1.0.2: dependencies: @@ -10198,14 +9046,14 @@ snapshots: is-reference@3.0.3: dependencies: - '@types/estree': 1.0.9 + '@types/estree': 1.0.8 is-regex@1.2.1: dependencies: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.4 + hasown: 2.0.2 is-set@2.0.3: {} @@ -10228,7 +9076,7 @@ snapshots: is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.21 + which-typed-array: 1.1.20 is-unicode-supported@0.1.0: {} @@ -10257,9 +9105,9 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 - '@istanbuljs/schema': 0.1.6 + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 transitivePeerDependencies: @@ -10267,11 +9115,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 - '@istanbuljs/schema': 0.1.6 + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.8.1 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -10297,7 +9145,7 @@ snapshots: iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 get-proto: 1.0.1 has-symbols: 1.1.0 @@ -10319,7 +9167,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.19.19 + '@types/node': 22.19.15 chalk: 4.1.2 co: 4.6.0 dedent: 1.7.2 @@ -10339,16 +9187,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@22.19.19): + jest-cli@29.7.0(@types/node@22.19.15): dependencies: '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.19.19) + create-jest: 29.7.0(@types/node@22.19.15) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.19.19) + jest-config: 29.7.0(@types/node@22.19.15) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -10358,12 +9206,12 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@22.19.19): + jest-config@29.7.0(@types/node@22.19.15): dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.0 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.7) + babel-jest: 29.7.0(@babel/core@7.29.0) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -10383,7 +9231,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.19.19 + '@types/node': 22.19.15 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -10412,7 +9260,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.19.19 + '@types/node': 22.19.15 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -10422,7 +9270,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 22.19.19 + '@types/node': 22.19.15 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -10448,7 +9296,7 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.29.7 + '@babel/code-frame': 7.29.0 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -10461,7 +9309,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.19.19 + '@types/node': 22.19.15 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -10485,7 +9333,7 @@ snapshots: jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) jest-util: 29.7.0 jest-validate: 29.7.0 - resolve: 1.22.12 + resolve: 1.22.11 resolve.exports: 2.0.3 slash: 3.0.0 @@ -10496,7 +9344,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.19.19 + '@types/node': 22.19.15 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -10524,7 +9372,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.19.19 + '@types/node': 22.19.15 chalk: 4.1.2 cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.3 @@ -10544,15 +9392,15 @@ snapshots: jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) - '@babel/types': 7.29.7 + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -10563,18 +9411,18 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.8.1 + semver: 7.7.4 transitivePeerDependencies: - supports-color jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.19.19 + '@types/node': 22.19.15 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 - picomatch: 2.3.2 + picomatch: 2.3.1 jest-validate@29.7.0: dependencies: @@ -10589,7 +9437,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.19.19 + '@types/node': 22.19.15 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -10598,24 +9446,24 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 22.19.19 + '@types/node': 22.19.15 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.19.19): + jest@29.7.0(@types/node@22.19.15): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.19.19) + jest-cli: 29.7.0(@types/node@22.19.15) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - jiti@2.7.0: {} + jiti@2.6.1: {} joi@17.13.3: dependencies: @@ -10677,10 +9525,10 @@ snapshots: kleur@4.1.5: {} - launch-editor@2.14.0: + launch-editor@2.13.1: dependencies: picocolors: 1.1.1 - shell-quote: 1.8.4 + shell-quote: 1.8.3 leven@3.1.0: {} @@ -10716,11 +9564,15 @@ snapshots: lodash.debounce@4.0.8: {} + lodash.defaults@4.2.0: {} + + lodash.isarguments@3.1.0: {} + lodash.merge@4.6.2: {} lodash.throttle@4.1.1: {} - lodash@4.18.1: {} + lodash@4.17.23: {} log-symbols@4.1.0: dependencies: @@ -10730,7 +9582,7 @@ snapshots: logkitty@0.7.1: dependencies: ansi-fragments: 0.2.1 - dayjs: 1.11.21 + dayjs: 1.11.20 yargs: 15.4.1 loose-envify@1.4.0: @@ -10739,7 +9591,7 @@ snapshots: loupe@3.2.1: {} - lru-cache@11.5.1: {} + lru-cache@11.2.6: {} lru-cache@5.1.1: dependencies: @@ -10751,7 +9603,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.8.1 + semver: 7.7.4 makeerror@1.0.12: dependencies: @@ -10777,51 +9629,50 @@ snapshots: merge2@1.4.1: {} - metro-babel-transformer@0.83.7: + metro-babel-transformer@0.83.5: dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.0 flow-enums-runtime: 0.0.6 - hermes-parser: 0.35.0 - metro-cache-key: 0.83.7 + hermes-parser: 0.33.3 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - metro-cache-key@0.83.7: + metro-cache-key@0.83.5: dependencies: flow-enums-runtime: 0.0.6 - metro-cache@0.83.7: + metro-cache@0.83.5: dependencies: exponential-backoff: 3.1.3 flow-enums-runtime: 0.0.6 https-proxy-agent: 7.0.6 - metro-core: 0.83.7 + metro-core: 0.83.5 transitivePeerDependencies: - supports-color - metro-config@0.83.7: + metro-config@0.83.5: dependencies: connect: 3.7.0 flow-enums-runtime: 0.0.6 jest-validate: 29.7.0 - metro: 0.83.7 - metro-cache: 0.83.7 - metro-core: 0.83.7 - metro-runtime: 0.83.7 - yaml: 2.9.0 + metro: 0.83.5 + metro-cache: 0.83.5 + metro-core: 0.83.5 + metro-runtime: 0.83.5 + yaml: 2.8.2 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - metro-core@0.83.7: + metro-core@0.83.5: dependencies: flow-enums-runtime: 0.0.6 lodash.throttle: 4.1.1 - metro-resolver: 0.83.7 + metro-resolver: 0.83.5 - metro-file-map@0.83.7: + metro-file-map@0.83.5: dependencies: debug: 4.4.3 fb-watchman: 2.0.2 @@ -10835,116 +9686,117 @@ snapshots: transitivePeerDependencies: - supports-color - metro-minify-terser@0.83.7: + metro-minify-terser@0.83.5: dependencies: flow-enums-runtime: 0.0.6 - terser: 5.48.0 + terser: 5.46.0 - metro-resolver@0.83.7: + metro-resolver@0.83.5: dependencies: flow-enums-runtime: 0.0.6 - metro-runtime@0.83.7: + metro-runtime@0.83.5: dependencies: - '@babel/runtime': 7.29.7 + '@babel/runtime': 7.28.6 flow-enums-runtime: 0.0.6 - metro-source-map@0.83.7: + metro-source-map@0.83.5: dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-symbolicate: 0.83.7 + metro-symbolicate: 0.83.5 nullthrows: 1.1.1 - ob1: 0.83.7 + ob1: 0.83.5 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - metro-symbolicate@0.83.7: + metro-symbolicate@0.83.5: dependencies: flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-source-map: 0.83.7 + metro-source-map: 0.83.5 nullthrows: 1.1.1 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - metro-transform-plugins@0.83.7: + metro-transform-plugins@0.83.5: dependencies: - '@babel/core': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 flow-enums-runtime: 0.0.6 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - metro-transform-worker@0.83.7: + metro-transform-worker@0.83.5: dependencies: - '@babel/core': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 - metro: 0.83.7 - metro-babel-transformer: 0.83.7 - metro-cache: 0.83.7 - metro-cache-key: 0.83.7 - metro-minify-terser: 0.83.7 - metro-source-map: 0.83.7 - metro-transform-plugins: 0.83.7 + metro: 0.83.5 + metro-babel-transformer: 0.83.5 + metro-cache: 0.83.5 + metro-cache-key: 0.83.5 + metro-minify-terser: 0.83.5 + metro-source-map: 0.83.5 + metro-transform-plugins: 0.83.5 nullthrows: 1.1.1 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - metro@0.83.7: + metro@0.83.5: dependencies: - '@babel/code-frame': 7.29.7 - '@babel/core': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/code-frame': 7.29.0 + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 accepts: 2.0.0 + chalk: 4.1.2 ci-info: 2.0.0 connect: 3.7.0 debug: 4.4.3 error-stack-parser: 2.1.4 flow-enums-runtime: 0.0.6 graceful-fs: 4.2.11 - hermes-parser: 0.35.0 + hermes-parser: 0.33.3 image-size: 1.2.1 invariant: 2.2.4 jest-worker: 29.7.0 jsc-safe-url: 0.2.4 lodash.throttle: 4.1.1 - metro-babel-transformer: 0.83.7 - metro-cache: 0.83.7 - metro-cache-key: 0.83.7 - metro-config: 0.83.7 - metro-core: 0.83.7 - metro-file-map: 0.83.7 - metro-resolver: 0.83.7 - metro-runtime: 0.83.7 - metro-source-map: 0.83.7 - metro-symbolicate: 0.83.7 - metro-transform-plugins: 0.83.7 - metro-transform-worker: 0.83.7 + metro-babel-transformer: 0.83.5 + metro-cache: 0.83.5 + metro-cache-key: 0.83.5 + metro-config: 0.83.5 + metro-core: 0.83.5 + metro-file-map: 0.83.5 + metro-resolver: 0.83.5 + metro-runtime: 0.83.5 + metro-source-map: 0.83.5 + metro-symbolicate: 0.83.5 + metro-transform-plugins: 0.83.5 + metro-transform-worker: 0.83.5 mime-types: 3.0.2 nullthrows: 1.1.1 serialize-error: 2.1.0 source-map: 0.5.7 throat: 5.0.0 - ws: 7.5.11 + ws: 7.5.10 yargs: 17.7.2 transitivePeerDependencies: - bufferutil @@ -10954,7 +9806,7 @@ snapshots: micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.2 + picomatch: 2.3.1 mime-db@1.52.0: {} @@ -10978,13 +9830,13 @@ snapshots: minimalistic-assert@1.0.1: {} - minimatch@10.2.5: + minimatch@10.2.4: dependencies: - brace-expansion: 5.0.6 + brace-expansion: 5.0.4 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.15 + brace-expansion: 1.1.12 minimist@1.2.8: {} @@ -10996,7 +9848,7 @@ snapshots: dependencies: obliterator: 2.0.5 - mnemonist@0.40.4: + mnemonist@0.40.3: dependencies: obliterator: 2.0.5 @@ -11008,9 +9860,7 @@ snapshots: ms@2.1.3: {} - nanoid@3.3.12: {} - - napi-postinstall@0.3.4: {} + nanoid@3.3.11: {} natural-compare@1.4.0: {} @@ -11037,7 +9887,7 @@ snapshots: node-int64@0.4.0: {} - node-releases@2.0.46: {} + node-releases@2.0.36: {} node-stream-zip@1.15.0: {} @@ -11053,13 +9903,13 @@ snapshots: nullthrows@1.1.1: {} - nypm@0.6.6: + nypm@0.6.5: dependencies: - citty: 0.2.2 + citty: 0.2.1 pathe: 2.0.3 - tinyexec: 1.2.3 + tinyexec: 1.0.2 - ob1@0.83.7: + ob1@0.83.5: dependencies: flow-enums-runtime: 0.0.6 @@ -11071,33 +9921,33 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.2 + 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.9 + call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 object.fromentries@2.0.8: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.2 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 object.values@1.2.1: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 obliterator@2.0.5: {} @@ -11187,7 +10037,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.29.7 + '@babel/code-frame': 7.29.0 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -11204,7 +10054,7 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.5.1 + lru-cache: 11.2.6 minipass: 7.1.3 pathe@1.1.2: {} @@ -11217,9 +10067,9 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.2: {} + picomatch@2.3.1: {} - picomatch@4.0.4: {} + picomatch@4.0.3: {} pino-abstract-transport@3.0.0: dependencies: @@ -11229,7 +10079,7 @@ snapshots: dependencies: colorette: 2.0.20 dateformat: 4.6.3 - fast-copy: 4.0.3 + fast-copy: 4.0.2 fast-safe-stringify: 2.1.1 help-me: 5.0.0 joycon: 3.1.1 @@ -11255,7 +10105,7 @@ snapshots: real-require: 0.2.0 safe-stable-stringify: 2.5.0 sonic-boom: 4.2.1 - thread-stream: 4.2.0 + thread-stream: 4.0.0 pirates@4.0.7: {} @@ -11263,23 +10113,21 @@ snapshots: dependencies: find-up: 4.1.0 - pkg-types@2.3.1: + pkg-types@2.3.0: dependencies: confbox: 0.2.4 exsolve: 1.0.8 pathe: 2.0.3 - pluralize@8.0.0: {} - pngjs@5.0.0: {} possible-typed-array-names@1.1.0: {} postcss-value-parser@4.2.0: {} - postcss@8.5.15: + postcss@8.5.8: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -11293,10 +10141,10 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - prisma@6.19.3(typescript@5.9.3): + prisma@6.19.2(typescript@5.9.3): dependencies: - '@prisma/config': 6.19.3 - '@prisma/engines': 6.19.3 + '@prisma/config': 6.19.2 + '@prisma/engines': 6.19.2 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -11340,7 +10188,7 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 - qs@6.15.2: + qs@6.14.2: dependencies: side-channel: 1.1.0 @@ -11370,18 +10218,18 @@ snapshots: rc9@2.1.2: dependencies: - defu: 6.1.7 + defu: 6.1.4 destr: 2.0.5 react-devtools-core@6.1.5: dependencies: - shell-quote: 1.8.4 - ws: 7.5.11 + shell-quote: 1.8.3 + ws: 7.5.10 transitivePeerDependencies: - bufferutil - utf-8-validate - react-dom@19.2.6(react@19.2.3): + react-dom@19.2.4(react@19.2.3): dependencies: react: 19.2.3 scheduler: 0.27.0 @@ -11394,89 +10242,79 @@ snapshots: react-is@18.3.1: {} - react-is@19.2.6: {} - - react-native-camera-kit@14.2.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): - dependencies: - react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) + react-is@19.2.4: {} - react-native-gesture-handler@2.31.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): + react-native-gesture-handler@2.31.2(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): dependencies: '@egjs/hammerjs': 2.0.17 '@types/react-test-renderer': 19.1.0 hoist-non-react-statics: 3.3.2 invariant: 2.2.4 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) - react-native-is-edge-to-edge@1.1.7(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): + react-native-is-edge-to-edge@1.1.7(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) - react-native-qrcode-svg@6.3.21(react-native-svg@15.15.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): + react-native-qrcode-svg@6.3.21(react-native-svg@15.15.3(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): dependencies: prop-types: 15.8.1 qrcode: 1.5.4 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) - react-native-svg: 15.15.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + react-native-svg: 15.15.3(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) text-encoding: 0.7.0 - react-native-reanimated@3.19.5(@babel/core@7.29.7)(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) - '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + react-native-reanimated@3.19.5(@babel/core@7.29.0)(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) convert-source-map: 2.0.0 invariant: 2.2.4 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) - react-native-is-edge-to-edge: 1.1.7(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + react-native-is-edge-to-edge: 1.1.7(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) transitivePeerDependencies: - supports-color - react-native-safe-area-context@5.8.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): + react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) - react-native-screens@4.25.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): + react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 react-freeze: 1.0.4(react@19.2.3) - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) warn-once: 0.1.1 - react-native-svg@15.15.5(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): + react-native-svg@15.15.3(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): dependencies: css-select: 5.2.2 css-tree: 1.1.3 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + warn-once: 0.1.1 react-native-vector-icons@10.3.0: dependencies: prop-types: 15.8.1 yargs: 16.2.0 - react-native-view-shot@5.1.0(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): - dependencies: - html2canvas: 1.4.1 - react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) - - react-native-web@0.21.2(react-dom@19.2.6(react@19.2.3))(react@19.2.3): + react-native-web@0.21.2(react-dom@19.2.4(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.29.7 + '@babel/runtime': 7.28.6 '@react-native/normalize-colors': 0.74.89 fbjs: 3.0.5 inline-style-prefixer: 7.0.1 @@ -11484,51 +10322,32 @@ snapshots: nullthrows: 1.1.1 postcss-value-parser: 4.2.0 react: 19.2.3 - react-dom: 19.2.6(react@19.2.3) + react-dom: 19.2.4(react@19.2.3) styleq: 0.1.3 transitivePeerDependencies: - encoding - react-native-webview@13.16.1(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): + react-native-webview@13.16.1(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): dependencies: escape-string-regexp: 4.0.0 invariant: 2.2.4 react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) - - react-native-worklets@0.5.1(@babel/core@7.29.7)(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) - '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) - convert-source-map: 2.0.0 - react: 19.2.3 - react-native: 0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3) - semver: 7.7.2 - transitivePeerDependencies: - - supports-color + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) - react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3): + react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3): dependencies: '@jest/create-cache-key-function': 29.7.0 '@react-native/assets-registry': 0.84.1 - '@react-native/codegen': 0.84.1(@babel/core@7.29.7) - '@react-native/community-cli-plugin': 0.84.1(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7)) + '@react-native/codegen': 0.84.1(@babel/core@7.29.0) + '@react-native/community-cli-plugin': 0.84.1(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0)) '@react-native/gradle-plugin': 0.84.1 '@react-native/js-polyfills': 0.84.1 '@react-native/normalize-colors': 0.84.1 - '@react-native/virtualized-lists': 0.84.1(@types/react@19.2.15)(react-native@0.84.1(@babel/core@7.29.7)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.3))(react@19.2.3) + '@react-native/virtualized-lists': 0.84.1(@types/react@19.2.14)(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 - babel-jest: 29.7.0(@babel/core@7.29.7) + babel-jest: 29.7.0(@babel/core@7.29.0) babel-plugin-syntax-hermes-parser: 0.32.0 base64-js: 1.5.1 commander: 12.1.0 @@ -11537,8 +10356,8 @@ snapshots: invariant: 2.2.4 jest-environment-node: 29.7.0 memoize-one: 5.2.1 - metro-runtime: 0.83.7 - metro-source-map: 0.83.7 + metro-runtime: 0.83.5 + metro-source-map: 0.83.5 nullthrows: 1.1.1 pretty-format: 29.7.0 promise: 8.3.0 @@ -11547,14 +10366,14 @@ snapshots: react-refresh: 0.14.2 regenerator-runtime: 0.13.11 scheduler: 0.27.0 - semver: 7.8.1 + semver: 7.7.4 stacktrace-parser: 0.1.11 - tinyglobby: 0.2.16 + tinyglobby: 0.2.15 whatwg-fetch: 3.6.20 - ws: 7.5.11 + ws: 7.5.10 yargs: 17.7.2 optionalDependencies: - '@types/react': 19.2.15 + '@types/react': 19.2.14 transitivePeerDependencies: - '@babel/core' - '@react-native-community/cli' @@ -11568,7 +10387,7 @@ snapshots: react-test-renderer@19.2.3(react@19.2.3): dependencies: react: 19.2.3 - react-is: 19.2.6 + react-is: 19.2.4 scheduler: 0.27.0 react@19.2.3: {} @@ -11583,8 +10402,6 @@ snapshots: real-require@0.2.0: {} - real-require@1.0.0: {} - redis-errors@1.2.0: {} redis-parser@3.0.0: @@ -11593,11 +10410,11 @@ snapshots: reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.2 + es-abstract: 1.24.1 es-errors: 1.3.0 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 @@ -11610,11 +10427,9 @@ snapshots: regenerator-runtime@0.13.11: {} - regexp-tree@0.1.27: {} - regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 @@ -11626,13 +10441,13 @@ snapshots: regenerate: 1.4.2 regenerate-unicode-properties: 10.2.2 regjsgen: 0.8.0 - regjsparser: 0.13.1 + regjsparser: 0.13.0 unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.2.1 regjsgen@0.8.0: {} - regjsparser@0.13.1: + regjsparser@0.13.0: dependencies: jsesc: 3.1.0 @@ -11654,17 +10469,16 @@ snapshots: resolve.exports@2.0.3: {} - resolve@1.22.12: + resolve@1.22.11: dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.7: + resolve@2.0.0-next.6: dependencies: es-errors: 1.3.0 - is-core-module: 2.16.2 + is-core-module: 2.16.1 node-exports-info: 1.6.0 object-keys: 1.1.1 path-parse: 1.0.7 @@ -11685,35 +10499,35 @@ snapshots: dependencies: glob: 7.2.3 - rollup@4.60.4: + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.4 - '@rollup/rollup-android-arm64': 4.60.4 - '@rollup/rollup-darwin-arm64': 4.60.4 - '@rollup/rollup-darwin-x64': 4.60.4 - '@rollup/rollup-freebsd-arm64': 4.60.4 - '@rollup/rollup-freebsd-x64': 4.60.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 - '@rollup/rollup-linux-arm-musleabihf': 4.60.4 - '@rollup/rollup-linux-arm64-gnu': 4.60.4 - '@rollup/rollup-linux-arm64-musl': 4.60.4 - '@rollup/rollup-linux-loong64-gnu': 4.60.4 - '@rollup/rollup-linux-loong64-musl': 4.60.4 - '@rollup/rollup-linux-ppc64-gnu': 4.60.4 - '@rollup/rollup-linux-ppc64-musl': 4.60.4 - '@rollup/rollup-linux-riscv64-gnu': 4.60.4 - '@rollup/rollup-linux-riscv64-musl': 4.60.4 - '@rollup/rollup-linux-s390x-gnu': 4.60.4 - '@rollup/rollup-linux-x64-gnu': 4.60.4 - '@rollup/rollup-linux-x64-musl': 4.60.4 - '@rollup/rollup-openbsd-x64': 4.60.4 - '@rollup/rollup-openharmony-arm64': 4.60.4 - '@rollup/rollup-win32-arm64-msvc': 4.60.4 - '@rollup/rollup-win32-ia32-msvc': 4.60.4 - '@rollup/rollup-win32-x64-gnu': 4.60.4 - '@rollup/rollup-win32-x64-msvc': 4.60.4 + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -11728,9 +10542,9 @@ snapshots: dependencies: mri: 1.2.0 - safe-array-concat@1.1.4: + safe-array-concat@1.1.3: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -11749,14 +10563,10 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - safe-regex2@5.1.1: + safe-regex2@5.0.0: dependencies: ret: 0.5.0 - safe-regex@2.1.1: - dependencies: - regexp-tree: 0.1.27 - safe-stable-stringify@2.5.0: {} safer-buffer@2.1.2: {} @@ -11767,9 +10577,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.2: {} - - semver@7.8.1: {} + semver@7.7.4: {} send@0.19.2: dependencies: @@ -11804,7 +10612,7 @@ snapshots: set-cookie-parser@2.7.2: {} - set-cookie-parser@3.1.0: {} + set-cookie-parser@3.0.1: {} set-function-length@1.2.2: dependencies: @@ -11826,7 +10634,7 @@ snapshots: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 setimmediate@1.0.5: {} @@ -11842,9 +10650,7 @@ snapshots: shell-quote@1.8.3: {} - shell-quote@1.8.4: {} - - side-channel-list@1.0.1: + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 @@ -11868,7 +10674,7 @@ snapshots: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.1 + side-channel-list: 1.0.0 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -11924,8 +10730,6 @@ snapshots: sprintf-js@1.0.3: {} - stable-hash-x@0.2.0: {} - stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -11976,12 +10780,12 @@ snapshots: string.prototype.matchall@4.0.12: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.2 + es-abstract: 1.24.1 es-errors: 1.3.0 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 gopd: 1.2.0 has-symbols: 1.1.0 @@ -11993,30 +10797,30 @@ snapshots: string.prototype.repeat@1.0.0: dependencies: define-properties: 1.2.1 - es-abstract: 1.24.2 + es-abstract: 1.24.1 string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.9 + 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.2 - es-object-atoms: 1.1.2 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 string_decoder@1.3.0: dependencies: @@ -12034,8 +10838,6 @@ snapshots: strip-final-newline@2.0.0: {} - strip-indent@4.1.1: {} - strip-json-comments@3.1.1: {} strip-json-comments@5.0.3: {} @@ -12054,42 +10856,38 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.4.8(picomatch@4.0.4)(svelte@5.56.0(@typescript-eslint/types@8.60.0))(typescript@5.9.3): + svelte-check@4.4.5(picomatch@4.0.3)(svelte@5.53.10)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 - fdir: 6.5.0(picomatch@4.0.4) + fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.56.0(@typescript-eslint/types@8.60.0) + svelte: 5.53.10 typescript: 5.9.3 transitivePeerDependencies: - picomatch - svelte@5.56.0(@typescript-eslint/types@8.60.0): + svelte@5.53.10: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 - '@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0) - '@types/estree': 1.0.9 + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) + '@types/estree': 1.0.8 '@types/trusted-types': 2.0.7 acorn: 8.16.0 aria-query: 5.3.1 axobject-query: 4.1.0 clsx: 2.1.1 - devalue: 5.8.1 + devalue: 5.6.4 esm-env: 1.2.2 - esrap: 2.2.9(@typescript-eslint/types@8.60.0) + esrap: 2.2.3 is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.21 zimmerframe: 1.1.4 - transitivePeerDependencies: - - '@typescript-eslint/types' - tapable@2.3.3: {} - - terser@5.48.0: + terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.16.0 @@ -12098,21 +10896,17 @@ snapshots: test-exclude@6.0.0: dependencies: - '@istanbuljs/schema': 0.1.6 + '@istanbuljs/schema': 0.1.3 glob: 7.2.3 minimatch: 3.1.5 text-encoding@0.7.0: {} - text-segmentation@1.0.3: - dependencies: - utrie: 1.0.2 - text-table@0.2.0: {} - thread-stream@4.2.0: + thread-stream@4.0.0: dependencies: - real-require: 1.0.0 + real-require: 0.2.0 throat@5.0.0: {} @@ -12120,12 +10914,12 @@ snapshots: tinyexec@0.3.2: {} - tinyexec@1.2.3: {} + tinyexec@1.0.2: {} - tinyglobby@0.2.16: + tinyglobby@0.2.15: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 tinypool@1.1.1: {} @@ -12139,7 +10933,7 @@ snapshots: dependencies: is-number: 7.0.0 - toad-cache@3.7.1: {} + toad-cache@3.7.0: {} toidentifier@1.0.1: {} @@ -12149,15 +10943,16 @@ snapshots: tree-kill@1.2.2: {} - ts-api-utils@2.5.0(typescript@5.9.3): + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 tslib@2.8.1: {} - tsx@4.22.3: + tsx@4.21.0: dependencies: - esbuild: 0.28.0 + esbuild: 0.27.3 + get-tsconfig: 4.13.6 optionalDependencies: fsevents: 2.3.3 @@ -12186,7 +10981,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -12195,33 +10990,22 @@ snapshots: typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.9 + 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.8: + typed-array-length@1.0.7: dependencies: - call-bind: 1.0.9 + 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-eslint@8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/parser': 8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.60.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - eslint: 10.4.1(jiti@2.7.0) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - typescript@5.9.3: {} ua-parser-js@1.0.41: {} @@ -12250,36 +11034,9 @@ snapshots: unpipe@1.0.0: {} - unrs-resolver@1.12.2: + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: - napi-postinstall: 0.3.4 - optionalDependencies: - '@unrs/resolver-binding-android-arm-eabi': 1.12.2 - '@unrs/resolver-binding-android-arm64': 1.12.2 - '@unrs/resolver-binding-darwin-arm64': 1.12.2 - '@unrs/resolver-binding-darwin-x64': 1.12.2 - '@unrs/resolver-binding-freebsd-x64': 1.12.2 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 - '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 - '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 - '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 - '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 - '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 - '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 - '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 - '@unrs/resolver-binding-linux-x64-musl': 1.12.2 - '@unrs/resolver-binding-openharmony-arm64': 1.12.2 - '@unrs/resolver-binding-wasm32-wasi': 1.12.2 - '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 - '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 - '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 - - update-browserslist-db@1.2.3(browserslist@4.28.2): - dependencies: - browserslist: 4.28.2 + browserslist: 4.28.1 escalade: 3.2.0 picocolors: 1.1.1 @@ -12299,10 +11056,6 @@ snapshots: utils-merge@1.0.1: {} - utrie@1.0.2: - dependencies: - base64-arraybuffer: 1.0.2 - v8-to-istanbul@9.3.0: dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -12311,13 +11064,13 @@ snapshots: vary@1.1.2: {} - vite-node@2.1.9(@types/node@22.19.19)(terser@5.48.0): + vite-node@2.1.9(@types/node@22.19.15)(terser@5.46.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 1.1.2 - vite: 5.4.21(@types/node@22.19.19)(terser@5.48.0) + vite: 5.4.21(@types/node@22.19.15)(terser@5.46.0) transitivePeerDependencies: - '@types/node' - less @@ -12329,40 +11082,40 @@ snapshots: - supports-color - terser - vite@5.4.21(@types/node@22.19.19)(terser@5.48.0): + vite@5.4.21(@types/node@22.19.15)(terser@5.46.0): dependencies: esbuild: 0.21.5 - postcss: 8.5.15 - rollup: 4.60.4 + postcss: 8.5.8 + rollup: 4.59.0 optionalDependencies: - '@types/node': 22.19.19 + '@types/node': 22.19.15 fsevents: 2.3.3 - terser: 5.48.0 + terser: 5.46.0 - vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0): + vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: - esbuild: 0.27.7 - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - postcss: 8.5.15 - rollup: 4.60.4 - tinyglobby: 0.2.16 + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.8 + rollup: 4.59.0 + tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.19.19 + '@types/node': 22.19.15 fsevents: 2.3.3 - jiti: 2.7.0 - terser: 5.48.0 - tsx: 4.22.3 - yaml: 2.9.0 + jiti: 2.6.1 + terser: 5.46.0 + tsx: 4.21.0 + yaml: 2.8.2 - vitefu@1.1.3(vite@7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0)): + vitefu@1.1.2(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): optionalDependencies: - vite: 7.3.3(@types/node@22.19.19)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.3)(yaml@2.9.0) + vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vitest@2.1.9(@types/node@22.19.19)(terser@5.48.0): + vitest@2.1.9(@types/node@22.19.15)(terser@5.46.0): dependencies: '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.19)(terser@5.48.0)) + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.15)(terser@5.46.0)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.9 '@vitest/snapshot': 2.1.9 @@ -12378,11 +11131,11 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.1.1 tinyrainbow: 1.2.0 - vite: 5.4.21(@types/node@22.19.19)(terser@5.48.0) - vite-node: 2.1.9(@types/node@22.19.19)(terser@5.48.0) + vite: 5.4.21(@types/node@22.19.15)(terser@5.46.0) + vite-node: 2.1.9(@types/node@22.19.15)(terser@5.46.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.19.19 + '@types/node': 22.19.15 transitivePeerDependencies: - less - lightningcss @@ -12437,7 +11190,7 @@ snapshots: isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.21 + which-typed-array: 1.1.20 which-collection@1.0.2: dependencies: @@ -12448,10 +11201,10 @@ snapshots: which-module@2.0.1: {} - which-typed-array@1.1.21: + which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 @@ -12488,11 +11241,11 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 - ws@6.2.4: + ws@6.2.3: dependencies: async-limiter: 1.0.1 - ws@7.5.11: {} + ws@7.5.10: {} xtend@4.0.2: {} @@ -12502,7 +11255,7 @@ snapshots: yallist@3.1.1: {} - yaml@2.9.0: {} + yaml@2.8.2: {} yargs-parser@18.1.3: dependencies: