diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..1ded96cd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +# Local dependencies and generated outputs +**/node_modules +dist +**/dist +**/android/.gradle +**/android/app/build +**/ios/build + +# Repository metadata and local development files +.git +.github +.devcontainer +.vscode +.idea +*.log +*.tmp +*.swp + +# Evidence and local environment material +artifacts +coverage +bis-evidence +.env +.env.* +!.env.example + +# Dependency-specific caches +.pnpm-store +.npm +.yarn +__pycache__ +*.pyc +.target +target diff --git a/.github/branch-protection.json b/.github/branch-protection.json index 5ebf3d86..a25251b1 100644 --- a/.github/branch-protection.json +++ b/.github/branch-protection.json @@ -4,20 +4,22 @@ "required_status_checks": { "strict": true, "contexts": [ - "Node.js CI / test", - "Go CI / test", - "Rust CI / test", - "Python CI / test", - "Security Scan / codeql-analyze", - "Docker Build / build" + "node-tests", + "gateway-race", + "archive-worker", + "mobile-security", + "CodeQL (JavaScript/TypeScript)", + "CodeQL (Go)", + "CodeQL (Python)", + "CodeQL (Rust)" ] }, - "enforce_admins": false, + "enforce_admins": true, "required_pull_request_reviews": { "dismissal_restrictions": {}, "dismiss_stale_reviews": true, "require_code_owner_reviews": true, - "required_approving_review_count": 1 + "required_approving_review_count": 2 }, "restrictions": null, "required_linear_history": true, @@ -29,11 +31,9 @@ "allow_fork_syncing": false }, "notes": [ - "Apply via: gh api repos/{owner}/{repo}/branches/main/protection -X PUT --input .github/branch-protection.json", - "Requires admin access to the repository", - "All CI checks must pass before merge", - "At least 1 code owner review required", - "Stale reviews are dismissed on new commits", - "Linear history (no merge commits) required" + "Apply only after the named checks appear on a pull request with: gh api repos/{owner}/{repo}/branches/main/protection -X PUT --input .github/branch-protection.json", + "Requires repository administration and should be reviewed as a change-control operation.", + "The protected production environment in .github/workflows/production-promotion.yml adds a separate manual approval after staging device/KMS and OBS-001 gates pass.", + "No mutable image tag, host-side image build, unreviewed migration, or unapproved production environment deployment is accepted by the promotion workflow." ] } diff --git a/.github/codeql/bis-rust-models/README.md b/.github/codeql/bis-rust-models/README.md new file mode 100644 index 00000000..ea43cb45 --- /dev/null +++ b/.github/codeql/bis-rust-models/README.md @@ -0,0 +1,45 @@ +# BIS Rust CodeQL transport-security model pack + +This model pack teaches CodeQL Rust analyses about two narrow, reviewed boundaries: +a successful `TrustedEndpoint::parse` result is a validated endpoint, and a +`BoundedGatewayPayload::into_bytes` result is a fixed-capacity HTTP body. The +endpoint parser enforces an absolute HTTPS URL, an exact configured host allow-list, +and rejects userinfo, query strings, and fragments. + +## Security scope + +The model contains exactly two narrow barriers: + +```text +::parse + -> ReturnValue.Field[core::result::Result::Ok(0)] + -> request-forgery + +::into_bytes + -> ReturnValue + -> resource-exhaustion +``` + +It does **not** model `with_path_segments` or the gateway payload encoder as a +barrier. Any future request-derived path segment or unbounded serialization path +therefore remains visible to CodeQL. Do not add a broader barrier or neutral model +without a security review and a negative test. + +## Publishing + +1. Review the model against the current canonical Rust path in the CodeQL + database and bump `version` in `qlpack.yml` for any semantic change. +2. Run the manual-only **Publish BIS CodeQL Rust Model Pack** workflow. +3. Confirm the immutable `munisp/bis-rust-models@` package exists in + the organization GitHub Container Registry. +4. Enable `.github/codeql/codeql-config.published-model-pack.yml` for the + Rust-specific CodeQL initialization, pinning the exact published version. +5. Verify the next CodeQL comparison resolves only the intended trusted-endpoint + SSRF flows and fixed-capacity payload conversion, while still reporting a + deliberately unsafe path-segment or unbounded-serialization regression. + +## Rollback + +Revert the CodeQL configuration reference to the prior known-good model-pack +version. Do not delete a published immutable package version or dismiss a +security alert merely because analysis configuration changed. diff --git a/.github/codeql/bis-rust-models/models/trusted-endpoint.yml b/.github/codeql/bis-rust-models/models/trusted-endpoint.yml new file mode 100644 index 00000000..a7ae4beb --- /dev/null +++ b/.github/codeql/bis-rust-models/models/trusted-endpoint.yml @@ -0,0 +1,14 @@ +extensions: + - addsTo: + pack: codeql/rust-all + extensible: barrierModel + data: + # TrustedEndpoint::parse rejects non-HTTPS schemes, non-allow-listed hosts, + # userinfo, query strings, and fragments before it returns Ok(endpoint). + # This barrier deliberately does not model with_path_segments so future + # user-controlled path segments remain visible to request-forgery analysis. + - ["::parse", "ReturnValue.Field[core::result::Result::Ok(0)]", "request-forgery", "manual"] + # BoundedGatewayPayload owns a fixed 16 KiB array and can only be built + # after every variable-width field and final serialized length are checked. + # Model only its conversion to an HTTP body; do not model the encoder itself. + - ["::into_bytes", "ReturnValue", "resource-exhaustion", "manual"] diff --git a/.github/codeql/bis-rust-models/qlpack.yml b/.github/codeql/bis-rust-models/qlpack.yml new file mode 100644 index 00000000..f4e0746b --- /dev/null +++ b/.github/codeql/bis-rust-models/qlpack.yml @@ -0,0 +1,7 @@ +name: munisp/bis-rust-models +version: 0.1.0 +library: true +extensionTargets: + codeql/rust-all: '>=0.0.0' +dataExtensions: + - models/**/*.yml diff --git a/.github/codeql/codeql-config.published-model-pack.yml b/.github/codeql/codeql-config.published-model-pack.yml new file mode 100644 index 00000000..07d8c69f --- /dev/null +++ b/.github/codeql/codeql-config.published-model-pack.yml @@ -0,0 +1,7 @@ +# Activate this file only after munisp/bis-rust-models@0.1.0 is published +# to the organization GitHub Container Registry and the repository workflow +# is switched to a single Rust-language CodeQL initialization or equivalent +# Rust-specific configuration. +name: BIS CodeQL Configuration +packs: + - munisp/bis-rust-models@0.1.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28e42c8d..b1389f44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,33 +2,34 @@ name: BIS Validation on: pull_request: - branches: [main] + branches: [main, hardening/production-readiness] push: - branches: [main] + branches: [main, hardening/production-readiness] permissions: contents: read jobs: - bff: - name: bff-validation - runs-on: ubuntu-latest + node-tests: + name: node-tests + runs-on: ubuntu-24.04 services: postgres: image: postgres:16-alpine env: - POSTGRES_USER: bis_user - POSTGRES_PASSWORD: bis_secure_2026 - POSTGRES_DB: bis_db + POSTGRES_USER: bis_ci + POSTGRES_PASSWORD: bis_ci_disposable_password + POSTGRES_DB: bis_ci ports: - 5432:5432 options: >- - --health-cmd "pg_isready -U bis_user -d bis_db" + --health-cmd "pg_isready -U bis_ci -d bis_ci" --health-interval 10s --health-timeout 5s - --health-retries 5 + --health-retries 10 env: - DATABASE_URL: postgresql://bis_user:bis_secure_2026@localhost:5432/bis_db + DATABASE_URL: postgresql://bis_ci:bis_ci_disposable_password@localhost:5432/bis_ci + BIS_DATABASE_URL: postgresql://bis_ci:bis_ci_disposable_password@localhost:5432/bis_ci JWT_SECRET: ci-session-secret-not-for-production KEYCLOAK_URL: https://auth.bis.invalid KEYCLOAK_REALM: bis @@ -42,11 +43,65 @@ jobs: cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Initialize disposable PostgreSQL test schema - run: pnpm drizzle-kit push --force + - name: Apply canonical PostgreSQL migrations + run: pnpm db:migrate - name: Type-check - run: pnpm check --noEmit + run: pnpm check - name: Production build - run: pnpm run build - - name: Test + run: pnpm build + - name: Node integration suite run: pnpm test + + gateway-race: + name: gateway-race + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v6 + with: + go-version-file: services/gateway/go.mod + cache-dependency-path: services/gateway/go.sum + - name: Gateway control-plane race suite + working-directory: services/gateway + run: go test -race ./... + + archive-worker: + name: archive-worker + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v6 + with: + go-version-file: services/cold-archive-writer/go.mod + cache-dependency-path: services/cold-archive-writer/go.sum + - name: Archive worker static and race checks + working-directory: services/cold-archive-writer + run: | + go vet ./... + go test -race ./... + + mobile-security: + name: mobile-security + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: | + pnpm-lock.yaml + bis-mobile/pnpm-lock.yaml + - name: Reject deprecated mobile client references + run: ./scripts/check-deprecated-mobile-references.sh + - name: Install typed AppRouter dependencies + run: pnpm install --frozen-lockfile + - name: Install authoritative native client dependencies + working-directory: bis-mobile + run: pnpm install --frozen-lockfile --ignore-workspace + - name: Validate authoritative native client + working-directory: bis-mobile + run: | + pnpm typecheck + pnpm lint diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 030794be..73c39bf0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,9 +2,9 @@ name: CodeQL Security Analysis on: push: - branches: [main] + branches: [main, hardening/production-readiness] pull_request: - branches: [main] + branches: [main, hardening/production-readiness] schedule: - cron: "17 3 * * 1" diff --git a/.github/workflows/pr-security-gates.yml b/.github/workflows/pr-security-gates.yml new file mode 100644 index 00000000..a9d8de68 --- /dev/null +++ b/.github/workflows/pr-security-gates.yml @@ -0,0 +1,229 @@ +name: Protected PR Security Gates + +on: + pull_request: + branches: [main] + types: [opened, reopened, synchronize, ready_for_review, review_requested] + pull_request_review: + types: [submitted, dismissed] + workflow_dispatch: + inputs: + pr_number: + description: "Open pull request number to inspect" + required: true + type: string + +permissions: + contents: read + pull-requests: read + checks: read + +concurrency: + group: protected-pr-security-gates-${{ github.event.pull_request.number || inputs.pr_number }} + cancel-in-progress: false + +jobs: + protected-pr-security-gates: + name: protected-pr-security-gates + runs-on: ubuntu-24.04 + # All mutations (review request, comment, publication, approval, merge, and + # branch protection changes) are deliberately outside this read-only job. + env: + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }} + REQUIRED_APPROVALS: "1" + REQUIRED_CHECKS_JSON: >- + ["node-tests","gateway-race","archive-worker","mobile-security", + "CodeQL (JavaScript/TypeScript)","CodeQL (Go)","CodeQL (Python)","Analyze rust","CodeQL"] + steps: + - name: Build a PII-minimized security gate report + id: report + uses: actions/github-script@v7 + with: + script: | + const number = Number(process.env.PR_NUMBER); + if (!Number.isInteger(number) || number < 1) { + core.setFailed("A positive pull request number is required."); + return; + } + + const { owner, repo } = context.repo; + const query = `query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + number url isDraft headRefOid baseRefName mergeable mergeStateStatus reviewDecision + } + } + }`; + const graph = await github.graphql(query, { owner, repo, number }); + const pr = graph.repository.pullRequest; + if (!pr) { + core.setFailed(`Pull request #${number} was not found.`); + return; + } + + const checkRuns = await github.paginate(github.rest.checks.listForRef, { + owner, + repo, + ref: pr.headRefOid, + per_page: 100, + }); + const latestByName = new Map(); + for (const check of checkRuns) { + const current = latestByName.get(check.name); + if (!current || new Date(check.started_at ?? 0) > new Date(current.started_at ?? 0)) { + latestByName.set(check.name, check); + } + } + + const configuredRequiredChecks = JSON.parse(process.env.REQUIRED_CHECKS_JSON ?? "[]"); + let protection = null; + try { + protection = await github.rest.repos.getBranchProtection({ + owner, + repo, + branch: pr.baseRefName, + }); + } catch { + // A read-only token may be denied branch-protection metadata. The + // classification below records this as unavailable rather than + // presuming the configured fallback matches repository policy. + } + const requiredChecks = protection?.data.required_status_checks?.contexts?.length + ? protection.data.required_status_checks.contexts + : configuredRequiredChecks; + const pendingChecks = []; + const failedChecks = []; + for (const requiredName of requiredChecks) { + const check = latestByName.get(requiredName); + if (!check || check.status !== "completed") { + pendingChecks.push(requiredName); + } else if (check.conclusion !== "success") { + failedChecks.push(`${requiredName}:${check.conclusion ?? "unknown"}`); + } + } + + const annotations = []; + const aggregateCodeQL = latestByName.get("CodeQL"); + if (aggregateCodeQL?.id) { + const result = await github.rest.checks.listAnnotations({ + owner, + repo, + check_run_id: aggregateCodeQL.id, + per_page: 100, + }); + for (const annotation of result.data) { + annotations.push({ + title: annotation.title.slice(0, 255), + path: annotation.path, + start_line: annotation.start_line, + end_line: annotation.end_line, + level: annotation.annotation_level, + message_classification: "codeql_annotation", + occurrence_count: 1, + }); + } + } + + const reasons = []; + const configuredRequiredApprovals = Number(process.env.REQUIRED_APPROVALS ?? "1"); + const requiredApprovals = protection?.data.required_pull_request_reviews?.required_approving_review_count + ?? configuredRequiredApprovals; + if (!protection) reasons.push("branch_protection_unavailable"); + if (pr.isDraft) reasons.push("draft_pull_request"); + if (pr.mergeable === "CONFLICTING") reasons.push("merge_conflict"); + if (pr.mergeStateStatus === "BEHIND") reasons.push("branch_behind"); + if (pr.reviewDecision !== "APPROVED" || requiredApprovals > 0 && pr.reviewDecision !== "APPROVED") { + reasons.push(pr.reviewDecision === "CHANGES_REQUESTED" ? "changes_requested" : "review_required"); + } + if (pendingChecks.length > 0) reasons.push("required_check_pending"); + if (failedChecks.length > 0) reasons.push("required_check_failed"); + if (aggregateCodeQL?.conclusion === "failure") reasons.push("aggregate_codeql_failure"); + + const checkItems = [...latestByName.values()].map((check) => ({ + name: check.name, + status: check.status.toUpperCase(), + conclusion: (check.conclusion ?? "NONE").toUpperCase(), + required_by_protection: requiredChecks.includes(check.name), + web_url: check.details_url, + })); + const summary = { + successful: checkItems.filter((check) => check.conclusion === "SUCCESS").length, + failed: checkItems.filter((check) => ["FAILURE", "TIMED_OUT", "CANCELLED", "ACTION_REQUIRED"].includes(check.conclusion)).length, + pending: checkItems.filter((check) => check.status !== "COMPLETED").length, + neutral: checkItems.filter((check) => ["NEUTRAL", "SKIPPED"].includes(check.conclusion)).length, + total: checkItems.length, + }; + const aggregateStatus = !aggregateCodeQL || aggregateCodeQL.status !== "completed" + ? "PENDING" + : aggregateCodeQL.conclusion === "success" + ? "SUCCESS" + : aggregateCodeQL.conclusion === "failure" + ? "FAILURE" + : "NEUTRAL"; + const report = { + schema_version: "1.0.0", + generated_at: new Date().toISOString(), + source: { + collector: "protected-pr-security-gates/1.0.0", + mode: "read_only", + snapshot_id: `pr${pr.number}-${pr.headRefOid.slice(0, 12)}`, + }, + repository: { + full_name: `${owner}/${repo}`, + default_branch: "main", + web_url: `https://github.com/${owner}/${repo}`, + }, + pull_request: { + number: pr.number, + head_sha: pr.headRefOid, + base_branch: pr.baseRefName, + merge_state: pr.mergeStateStatus ?? "UNKNOWN", + mergeable: pr.mergeable ?? "UNKNOWN", + review_decision: pr.reviewDecision ?? "NONE", + web_url: pr.url, + }, + classification: { + overall: reasons.length === 0 ? "eligible" : pendingChecks.length > 0 && failedChecks.length === 0 && pr.reviewDecision === "APPROVED" ? "pending" : "blocked", + reasons: [...new Set(reasons)], + }, + branch_protection: { + metadata_available: Boolean(protection), + required_approvals: requiredApprovals, + dismiss_stale_reviews: protection?.data.required_pull_request_reviews?.dismiss_stale_reviews ?? false, + strict_status_checks: protection?.data.required_status_checks?.strict ?? false, + required_contexts: requiredChecks, + }, + checks: { summary, items: checkItems }, + codeql: { + language_analyses: checkItems + .filter((check) => check.name.startsWith("CodeQL (") || check.name === "Analyze rust") + .map((check) => ({ language: check.name, status: check.status, conclusion: check.conclusion })), + aggregate_status: aggregateStatus, + annotations, + }, + }; + + await require("fs").promises.writeFile("pr-security-gate-report.json", `${JSON.stringify(report, null, 2)}\n`); + await core.summary + .addHeading("Protected PR Security Gates") + .addRaw(`Classification: **${report.classification.overall.toUpperCase()}**\n\n`) + .addRaw(`Reasons: ${report.classification.reasons.join(", ") || "none"}\n\n`) + .addRaw(`Required check status: ${pendingChecks.length} pending, ${failedChecks.length} failed.\n\n`) + .addRaw(`Aggregate CodeQL: ${aggregateStatus}.\n`) + .write(); + core.setOutput("classification", report.classification.overall); + core.setOutput("reasons", report.classification.reasons.join(",")); + core.setOutput("report_path", "pr-security-gate-report.json"); + + if (report.classification.overall !== "eligible") { + core.setFailed(`Protected PR security gate is ${report.classification.overall}: ${report.classification.reasons.join(", ")}`); + } + + - name: Upload read-only gate report + if: always() + uses: actions/upload-artifact@v4 + with: + name: pr-security-gate-report-${{ github.event.pull_request.number || inputs.pr_number }} + path: pr-security-gate-report.json + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/production-promotion.yml b/.github/workflows/production-promotion.yml new file mode 100644 index 00000000..e1552d7a --- /dev/null +++ b/.github/workflows/production-promotion.yml @@ -0,0 +1,362 @@ +name: BIS Production Promotion + +on: + workflow_dispatch: + inputs: + commit_sha: + description: "Full 40-character commit SHA from hardening/production-readiness to promote" + required: true + type: string + +concurrency: + group: bis-production-promotion + cancel-in-progress: false + +permissions: + contents: read + packages: write + attestations: write + id-token: write + +jobs: + validate-source: + name: validate-promotion-source + runs-on: ubuntu-24.04 + outputs: + commit_sha: ${{ steps.source.outputs.commit_sha }} + steps: + - name: Validate requested SHA shape + env: + COMMIT_SHA: ${{ inputs.commit_sha }} + run: | + set -euo pipefail + [[ "$COMMIT_SHA" =~ ^[0-9a-f]{40}$ ]] || { echo "A full lower-case 40-character commit SHA is required." >&2; exit 2; } + - name: Check out requested immutable revision + uses: actions/checkout@v7 + with: + ref: ${{ inputs.commit_sha }} + fetch-depth: 0 + - name: Require ancestry from hardening/production-readiness + id: source + env: + REQUESTED_SHA: ${{ inputs.commit_sha }} + run: | + set -euo pipefail + git fetch origin hardening/production-readiness --depth=1 + actual_sha="$(git rev-parse HEAD)" + [[ "$actual_sha" = "$REQUESTED_SHA" ]] || { echo "Checked-out revision differs from requested SHA." >&2; exit 1; } + git merge-base --is-ancestor "$actual_sha" origin/hardening/production-readiness || { + echo "Requested SHA is not reachable from hardening/production-readiness." >&2 + exit 1 + } + printf 'commit_sha=%s\n' "$actual_sha" >> "$GITHUB_OUTPUT" + + validate: + name: validate-postgresql-and-mobile + needs: validate-source + runs-on: ubuntu-24.04 + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_USER: bis_ci + POSTGRES_PASSWORD: bis_ci_disposable_password + POSTGRES_DB: bis_ci + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U bis_ci -d bis_ci" + --health-interval 10s + --health-timeout 5s + --health-retries 10 + env: + DATABASE_URL: postgresql://bis_ci:bis_ci_disposable_password@localhost:5432/bis_ci + BIS_DATABASE_URL: postgresql://bis_ci:bis_ci_disposable_password@localhost:5432/bis_ci + JWT_SECRET: ci-session-secret-not-for-production + KEYCLOAK_URL: https://auth.bis.invalid + KEYCLOAK_REALM: bis + KEYCLOAK_CLIENT_ID: bis-bff + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ needs.validate-source.outputs.commit_sha }} + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: pnpm + - name: Install root dependencies + run: pnpm install --frozen-lockfile + - name: Apply canonical PostgreSQL migrations + run: pnpm db:migrate + - name: Validate BFF and PWA + run: | + set -euo pipefail + pnpm check + pnpm test + pnpm build + - name: Validate gateway race safety + working-directory: services/gateway + run: go test -race ./... + - name: Validate archive worker race safety + working-directory: services/cold-archive-writer + run: | + go vet ./... + go test -race ./... + - name: Reject deprecated mobile client references + run: ./scripts/check-deprecated-mobile-references.sh + - name: Validate authoritative native client + working-directory: bis-mobile + run: | + pnpm install --frozen-lockfile --ignore-workspace + pnpm typecheck + pnpm lint + + publish-images: + name: publish-immutable-images + needs: [validate-source, validate] + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - image: bff + context: . + file: Dockerfile.bff + target: runner + - image: bff-migrations + context: . + file: Dockerfile.bff + target: migrations + - image: gateway + context: services/gateway + file: services/gateway/Dockerfile + target: "" + - image: cold-archive-writer + context: services/cold-archive-writer + file: services/cold-archive-writer/Dockerfile + target: "" + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ needs.validate-source.outputs.commit_sha }} + - uses: docker/setup-buildx-action@v3 + - name: Authenticate to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and publish immutable image + id: build + uses: docker/build-push-action@v6 + with: + context: ${{ matrix.context }} + file: ${{ matrix.file }} + target: ${{ matrix.target }} + push: true + provenance: mode=max + sbom: true + tags: ghcr.io/${{ github.repository }}/${{ matrix.image }}:${{ needs.validate-source.outputs.commit_sha }} + labels: | + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.revision=${{ needs.validate-source.outputs.commit_sha }} + - name: Attest image provenance + uses: actions/attest-build-provenance@v2 + with: + subject-name: ghcr.io/${{ github.repository }}/${{ matrix.image }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true + - name: Save immutable digest reference + env: + IMAGE: ${{ matrix.image }} + DIGEST: ${{ steps.build.outputs.digest }} + COMMIT_SHA: ${{ needs.validate-source.outputs.commit_sha }} + run: | + set -euo pipefail + [[ "$DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]] || { echo "Build did not return an image digest." >&2; exit 1; } + mkdir -p image-digests + printf 'ghcr.io/%s/%s@%s\n' "${GITHUB_REPOSITORY,,}" "$IMAGE" "$DIGEST" > "image-digests/$IMAGE.txt" + printf '%s\n' "$COMMIT_SHA" > image-digests/commit_sha.txt + - uses: actions/upload-artifact@v4 + with: + name: immutable-image-${{ matrix.image }} + path: image-digests/ + if-no-files-found: error + retention-days: 30 + + promote-staging: + name: deploy-and-verify-staging + needs: [validate-source, publish-images] + runs-on: ubuntu-24.04 + environment: staging + permissions: + contents: read + packages: read + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ needs.validate-source.outputs.commit_sha }} + fetch-depth: 0 + - uses: actions/download-artifact@v4 + with: + pattern: immutable-image-* + path: immutable-image-artifacts + merge-multiple: true + - name: Build release bundle from the validated revision + env: + COMMIT_SHA: ${{ needs.validate-source.outputs.commit_sha }} + run: | + set -euo pipefail + [[ -n "${{ vars.STAGING_DEPLOY_ROOT }}" ]] || { echo "STAGING_DEPLOY_ROOT repository variable is required." >&2; exit 2; } + mkdir -p release-payload + git archive --format=tar "$COMMIT_SHA" | tar -xf - -C release-payload + for image in bff bff-migrations gateway cold-archive-writer; do + test -s "immutable-image-artifacts/$image.txt" + done + cat > release-payload/release.env <&2 + exit 2 + fi + install -d -m 700 ~/.ssh + printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + printf '%s\n' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts + chmod 600 ~/.ssh/known_hosts + - name: Transfer and promote immutable staging release + env: + STAGING_HOST: ${{ vars.STAGING_HOST }} + STAGING_USER: ${{ vars.STAGING_USER }} + STAGING_DEPLOY_ROOT: ${{ vars.STAGING_DEPLOY_ROOT }} + STAGING_DEPLOY_ENV_FILE: ${{ vars.STAGING_DEPLOY_ENV_FILE }} + COMMIT_SHA: ${{ needs.validate-source.outputs.commit_sha }} + run: | + set -euo pipefail + for value in STAGING_HOST STAGING_USER STAGING_DEPLOY_ROOT STAGING_DEPLOY_ENV_FILE; do test -n "${!value}" || { echo "$value repository variable is required." >&2; exit 2; }; done + target="${STAGING_USER}@${STAGING_HOST}" + remote_dir="${STAGING_DEPLOY_ROOT}/releases/${COMMIT_SHA}" + ssh -o BatchMode=yes "$target" "set -euo pipefail; mkdir -p '$remote_dir'" + scp -o BatchMode=yes release.tar.gz "$target:$remote_dir/release.tar.gz" + ssh -o BatchMode=yes "$target" "set -euo pipefail; cd '$remote_dir'; tar -xzf release.tar.gz; rm -f release.tar.gz; chmod 0750 scripts/promote-compose-release.sh; BIS_DEPLOY_ROOT='$STAGING_DEPLOY_ROOT' BIS_DEPLOY_ENV_FILE='$STAGING_DEPLOY_ENV_FILE' BIS_PROMOTION_ENV=staging BIS_RELEASE_SHA='$COMMIT_SHA' ./scripts/promote-compose-release.sh" + - name: Verify physical-device session persistence and synthetic SSE-KMS upload + env: + BIS_ENV: staging + BIS_STAGING_CONFIRMATION: I_APPROVE_NON_PRODUCTION_MOBILE_KMS_TESTS + STAGING_BFF_URL: ${{ vars.STAGING_BFF_URL }} + STAGING_MOBILE_ACCESS_TOKEN: ${{ secrets.STAGING_MOBILE_ACCESS_TOKEN }} + STAGING_KYC_RECORD_ID: ${{ vars.STAGING_KYC_RECORD_ID }} + STAGING_DEVICE_ATTESTATION_JSON: ${{ secrets.STAGING_DEVICE_ATTESTATION_JSON }} + STAGING_EXPECTED_KMS_KEY_ID: ${{ vars.STAGING_EXPECTED_KMS_KEY_ID }} + STAGING_DATA_RETENTION_CONFIRMED: synthetic-only-approved + BIS_STAGING_REPORT_PATH: artifacts/mobile-kms-staging/report.json + run: | + set -euo pipefail + test -n "$STAGING_DEVICE_ATTESTATION_JSON" || { echo "STAGING_DEVICE_ATTESTATION_JSON is required from the approved device-lab run." >&2; exit 2; } + umask 077 + attestation_file="$(mktemp)" + trap 'rm -f "$attestation_file"' EXIT + printf '%s' "$STAGING_DEVICE_ATTESTATION_JSON" > "$attestation_file" + STAGING_DEVICE_ATTESTATION_FILE="$attestation_file" node scripts/run-staging-mobile-kms-verification.mjs + - name: Verify staging OBS-001 metrics and alert rules + env: + BIS_ENV: staging + OBS_PROMETHEUS_URL: ${{ vars.STAGING_PROMETHEUS_URL }} + OBS_GATEWAY_METRICS_URL: ${{ vars.STAGING_GATEWAY_METRICS_URL }} + OBS_GATEWAY_KEY: ${{ secrets.STAGING_GATEWAY_METRICS_KEY }} + OBS_ALERTMANAGER_URL: ${{ vars.STAGING_ALERTMANAGER_URL }} + run: node scripts/verify-obs001-promotion.mjs + - uses: actions/upload-artifact@v4 + if: always() + with: + name: staging-mobile-kms-and-obs001-evidence + path: artifacts/ + if-no-files-found: warn + retention-days: 90 + + promote-production: + name: deploy-and-verify-production + needs: [validate-source, promote-staging] + runs-on: ubuntu-24.04 + environment: production + permissions: + contents: read + packages: read + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ needs.validate-source.outputs.commit_sha }} + fetch-depth: 0 + - uses: actions/download-artifact@v4 + with: + pattern: immutable-image-* + path: immutable-image-artifacts + merge-multiple: true + - name: Build production release bundle from the staging-validated revision + env: + COMMIT_SHA: ${{ needs.validate-source.outputs.commit_sha }} + run: | + set -euo pipefail + [[ -n "${{ vars.PRODUCTION_DEPLOY_ROOT }}" ]] || { echo "PRODUCTION_DEPLOY_ROOT repository variable is required." >&2; exit 2; } + mkdir -p release-payload + git archive --format=tar "$COMMIT_SHA" | tar -xf - -C release-payload + for image in bff bff-migrations gateway cold-archive-writer; do + test -s "immutable-image-artifacts/$image.txt" + done + cat > release-payload/release.env <&2 + exit 2 + fi + install -d -m 700 ~/.ssh + printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + printf '%s\n' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts + chmod 600 ~/.ssh/known_hosts + - name: Transfer and promote immutable production release + env: + PRODUCTION_HOST: ${{ vars.PRODUCTION_HOST }} + PRODUCTION_USER: ${{ vars.PRODUCTION_USER }} + PRODUCTION_DEPLOY_ROOT: ${{ vars.PRODUCTION_DEPLOY_ROOT }} + PRODUCTION_DEPLOY_ENV_FILE: ${{ vars.PRODUCTION_DEPLOY_ENV_FILE }} + COMMIT_SHA: ${{ needs.validate-source.outputs.commit_sha }} + run: | + set -euo pipefail + for value in PRODUCTION_HOST PRODUCTION_USER PRODUCTION_DEPLOY_ROOT PRODUCTION_DEPLOY_ENV_FILE; do test -n "${!value}" || { echo "$value repository variable is required." >&2; exit 2; }; done + target="${PRODUCTION_USER}@${PRODUCTION_HOST}" + remote_dir="${PRODUCTION_DEPLOY_ROOT}/releases/${COMMIT_SHA}" + ssh -o BatchMode=yes "$target" "set -euo pipefail; mkdir -p '$remote_dir'" + scp -o BatchMode=yes release.tar.gz "$target:$remote_dir/release.tar.gz" + ssh -o BatchMode=yes "$target" "set -euo pipefail; cd '$remote_dir'; tar -xzf release.tar.gz; rm -f release.tar.gz; chmod 0750 scripts/promote-compose-release.sh; BIS_DEPLOY_ROOT='$PRODUCTION_DEPLOY_ROOT' BIS_DEPLOY_ENV_FILE='$PRODUCTION_DEPLOY_ENV_FILE' BIS_PROMOTION_ENV=production BIS_RELEASE_SHA='$COMMIT_SHA' ./scripts/promote-compose-release.sh" + - name: Verify production OBS-001 metrics and alert rules + env: + BIS_ENV: production + OBS_PROMETHEUS_URL: ${{ vars.PRODUCTION_PROMETHEUS_URL }} + OBS_GATEWAY_METRICS_URL: ${{ vars.PRODUCTION_GATEWAY_METRICS_URL }} + OBS_GATEWAY_KEY: ${{ secrets.PRODUCTION_GATEWAY_METRICS_KEY }} + OBS_ALERTMANAGER_URL: ${{ vars.PRODUCTION_ALERTMANAGER_URL }} + run: node scripts/verify-obs001-promotion.mjs diff --git a/.github/workflows/publish-codeql-rust-model-pack.yml b/.github/workflows/publish-codeql-rust-model-pack.yml new file mode 100644 index 00000000..1a6bbeb8 --- /dev/null +++ b/.github/workflows/publish-codeql-rust-model-pack.yml @@ -0,0 +1,32 @@ +name: Publish BIS CodeQL Rust Model Pack + +on: + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + publish: + name: Validate and publish trusted-endpoint model pack + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v7 + + - name: Install CodeQL CLI + uses: github/codeql-action/init@v4 + with: + languages: rust + + - name: Validate model-pack manifest and extensions + working-directory: .github/codeql/bis-rust-models + run: | + codeql pack install + test -f qlpack.yml + test -f models/trusted-endpoint.yml + + - name: Publish immutable model-pack version + working-directory: .github/codeql/bis-rust-models + run: codeql pack publish diff --git a/.gitignore b/.gitignore index 745fd865..c8f31fe2 100644 --- a/.gitignore +++ b/.gitignore @@ -114,11 +114,21 @@ client/public/__manus__/version.json e2e/auth.json playwright-report/ test-results/ -services/*/target/ -services/event-emitter/target/ -services/aml-engine/target/ -services/event-processor/target/ +# Rust compiler outputs for every service +**/target/ +# Local audit and validation output +audit/ .project-config.json __pycache__/ *.pyc -.project-config.json + +# Rendered on-premises secrets, private PKI, and local storage state +infra/onprem/bis-onprem.env +infra/onprem/runtime/ +infra/onprem/pki/ +infra/onprem/secrets/ +infra/onprem/*.key +infra/onprem/*.crt +infra/onprem/*.pem +!infra/onprem/templates/ +!infra/onprem/bis-onprem.env.example diff --git a/.manus/db/db-query-error-1774377385693.json b/.manus/db/db-query-error-1774377385693.json deleted file mode 100644 index 2383bc07..00000000 --- a/.manus/db/db-query-error-1774377385693.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "query": "\nDO $$ BEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'alert_rule_metric') THEN\n CREATE TYPE alert_rule_metric AS ENUM('risk_score','sanctions_confidence','pep_confidence','adverse_media_count','duplicate_identity_score','velocity_hourly','velocity_daily','credit_score');\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'alert_rule_operator') THEN\n CREATE TYPE alert_rule_operator AS ENUM('gt','gte','lt','lte','eq','neq');\n END IF;\nEND $$;\n\nCREATE TABLE IF NOT EXISTS alert_rules (\n id serial PRIMARY KEY,\n name varchar(255) NOT NULL,\n description text,\n metric alert_rule_metric NOT NULL,\n operator alert_rule_operator NOT NULL DEFAULT 'gte',\n threshold real NOT NULL,\n severity severity NOT NULL DEFAULT 'high',\n enabled boolean NOT NULL DEFAULT true,\n \"autoEscalate\" boolean NOT NULL DEFAULT false,\n \"notifyOwner\" boolean NOT NULL DEFAULT true,\n \"createdBy\" varchar(255),\n \"createdAt\" timestamp DEFAULT now() NOT NULL,\n \"updatedAt\" timestamp DEFAULT now() NOT NULL\n);\n", - "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway04.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 2t5rixfHAYLQmtB.9f3d6c4c4f9f --database NigNpTxRrpwoLqf7dXXspm --execute \nDO $$ BEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'alert_rule_metric') THEN\n CREATE TYPE alert_rule_metric AS ENUM('risk_score','sanctions_confidence','pep_confidence','adverse_media_count','duplicate_identity_score','velocity_hourly','velocity_daily','credit_score');\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'alert_rule_operator') THEN\n CREATE TYPE alert_rule_operator AS ENUM('gt','gte','lt','lte','eq','neq');\n END IF;\nEND $$;\n\nCREATE TABLE IF NOT EXISTS alert_rules (\n id serial PRIMARY KEY,\n name varchar(255) NOT NULL,\n description text,\n metric alert_rule_metric NOT NULL,\n operator alert_rule_operator NOT NULL DEFAULT 'gte',\n threshold real NOT NULL,\n severity severity NOT NULL DEFAULT 'high',\n enabled boolean NOT NULL DEFAULT true,\n \"autoEscalate\" boolean NOT NULL DEFAULT false,\n \"notifyOwner\" boolean NOT NULL DEFAULT true,\n \"createdBy\" varchar(255),\n \"createdAt\" timestamp DEFAULT now() NOT NULL,\n \"updatedAt\" timestamp DEFAULT now() NOT NULL\n);\n", - "returncode": 1, - "logs": [ - "ERROR 1064 (42000) at line 2: You have an error in your SQL syntax; check the manual that corresponds to your TiDB version for the right syntax to use line 1 column 11 near \"BEGIN", - " IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'alert_rule_metric') THEN", - " CREATE TYPE alert_rule_metric AS ENUM('risk_score','sanctions_confidence','pep_confidence','adverse_media_count','duplicate_identity_score','velocity_hourly','velocity_daily','credit_score')\"" - ] -} \ No newline at end of file diff --git a/Dockerfile.bff b/Dockerfile.bff index 8035c395..a52436d4 100644 --- a/Dockerfile.bff +++ b/Dockerfile.bff @@ -18,6 +18,13 @@ COPY . . # Build Vite frontend + esbuild server bundle RUN pnpm run build +# ─── Canonical migration image ─────────────────────────────────────────────── +# This target retains the checked-in migration runner and its pinned tooling. It +# is published separately and run once by the promotion workflow before the BFF +# is rolled forward; the production runtime image stays lean. +FROM builder AS migrations +CMD ["pnpm", "db:migrate"] + # ─── Production image ───────────────────────────────────────────────────────── FROM node:22-alpine AS runner diff --git a/HONEST_AUDIT.md b/HONEST_AUDIT.md deleted file mode 100644 index 020e6b11..00000000 --- a/HONEST_AUDIT.md +++ /dev/null @@ -1,233 +0,0 @@ -# BIS Platform — Honest Implementation Audit -**Date:** 2026-08-12 -**Auditor:** Manus (automated, full codebase scan) -**Scope:** All services under `/home/ubuntu/bis-pwa/` - ---- - -## Executive Summary - -**Overall Completion Score: 68 / 100** (reassessed Aug 12; historical sections below are retained for traceability) - -> **Why the score is 68, not higher:** The original 57-point finding set materially improved: case-manager update/timeline handlers now use PostgreSQL, collection-site and MVR screens use real tRPC paths, both Rust services persist their core outputs, and production decision paths no longer substitute plausible sandbox identities, clear sanctions results, credit scores, payments, biometrics, or authorization allows. However, a score above 68 would be misleading: live provider credentials and services were not available for end-to-end verification; the local PostgreSQL service was unavailable during validation; and the broad Node suite still contains pre-existing path- and database-dependent failures unrelated to this remediation. - -### Aug 12 Reassessment — Silent Mockware Remediation - -The active production routes were re-scanned for fabricated success values, deterministic provider outputs, silent in-memory fallbacks, and fail-open controls. The following classes now **fail closed** with explicit unavailable/forbidden results rather than presenting synthetic data as authoritative: - -- Biometric liveness, enrollment, face matching, anti-spoofing, OCR, and document comparison. -- NIN, BVN, CAC, sanctions, PEP, credit, NIP beneficiary-name, criminal-record, Mojaloop-compliance, stablecoin quote/history, and verifier-service outcomes. -- KYC scheduled re-runs, QuickCheck, adverse-media screening, payment velocity control, Temporal workflow starts, and Permify authorization (including insider-threat tenant isolation). - -The verification services retain real provider chains where configured. Where no authoritative provider responds, the supported result is now an explicit failure/unavailability state—not a `clear`, `verified`, `approved`, `matched`, funded, or completed outcome. Regression tests now cover default-deny Permify, fail-closed velocity decisions, unavailable verifier providers, and the gateway verification engine's synthetic-data prohibition. - -**Validation evidence:** TypeScript check passes; focused Vitest authorization/velocity suites pass (107 tests); gateway root and verification-engine Go packages pass; verifier Go service tests pass. The full Node suite was not green because 95 existing tests rely on the old hardcoded `/home/ubuntu/bis` path and an unavailable local database; those failures were not hidden or relabeled as passing. - ---- - -## Scoring Rubric - -| Category | Weight | Score | Notes | -|---|---|---|---| -| Node.js tRPC layer (DB persistence) | 25% | 21/25 | 540 procedures, all DB-backed; 3 gaps remain (MVR, goAML sim, BatchMonitor) | -| Keycloak auth (Node.js) | 10% | 8/10 | Real JWKS verification via `jose`; demo fallback in non-production is a risk | -| Keycloak auth (Go gateway) | 5% | 5/5 | Dev-token bypass **removed** Jun 29 ✅ | -| Go microservices (gateway, case-manager) | 15% | 6/15 | Gateway: real; case-manager: ALL 6 sub-repos are stubs (HTTP handlers never call INSERT) | -| Python microservices | 15% | 10/15 | risk-engine: no DB persistence for scores; biometric-engine: Redis-only enrollment | -| Rust microservices | 15% | 10/15 | event-processor: in-memory event log; screening-engine: simulate mode; aml-engine: Redis DLQ (good) | -| Automated test suites | 10% | 6/10 | Node.js: **972 tests** (all pass, +29 new Jun 29); Rust: 52; Python: 81; Go: passes | -| Frontend (no mock data in production paths) | 5% | 2/5 | MVRCheckPage: mock fallback result; DrugScreeningPage: hardcoded collection sites | - ---- - -## Detailed Findings - -### 1. Node.js tRPC Layer — 21/25 - -**What is real:** -- 540 tRPC procedures across `server/routers.ts` and feature routers -- All procedures use Drizzle ORM against a real PostgreSQL database -- 177+ tables/enums in `drizzle/schema.ts` -- Circuit breaker wraps all external API calls -- Push subscriptions persisted to `pushSubscriptions` table -- Risk scores from risk-engine are persisted to `investigations` table -- goAML submissions call real NFIU API when `GOAML_API_KEY` is set - -**Gaps:** -1. **MVR API not integrated** — `screening.create` stores the request but does not call any real MVR/FRSC API. `MVRCheckPage.tsx:177` uses a `mockResult` fallback when `record.result` is null (which it always is). -2. **Drug collection sites hardcoded** — `DrugScreeningPage.tsx:112` defines `NIGERIA_COLLECTION_SITES` as a static array. A `collection_sites` DB table and `trpc.collectionSites.list` procedure were added Jun 29 — the frontend just needs to be wired. -3. **goAML simulated reference** — When `GOAML_API_KEY` is not set, `goaml.ts:27` returns a fake `NFIU-*` reference number. - ---- - -### 2. Keycloak Auth (Node.js) — 8/10 - -**What is real (`server/keycloak.ts`):** -- `verifyKeycloakToken()` uses `jose.jwtVerify` with a live JWKS endpoint -- Validates `issuer` and `audience` (client ID) -- `extractRoles()` merges realm roles and client-specific roles -- Full PKCE/confidential client token exchange - -**Gaps:** -1. **Demo fallback in non-production** — `context.ts:97–103` injects `DEMO_USER` when `NODE_ENV !== 'production'`. If staging runs with `NODE_ENV=development`, unauthenticated users get admin access. -2. **No Keycloak logout** — No endpoint revokes Keycloak tokens or calls the end-session endpoint. - ---- - -### 3. Keycloak Auth (Go Gateway) — 5/5 - -**What is real (`services/gateway/keycloak/oidc.go`):** -- Uses `github.com/coreos/go-oidc/v3` for OIDC discovery and token verification -- `Init()` fetches the JWKS from `{KEYCLOAK_URL}/realms/{realm}` on startup -- `Middleware()` validates Bearer tokens on all non-health routes -- **Dev-token bypass removed Jun 29** ✅ - ---- - -### 4. Go Microservices — 6/15 - -#### Gateway (`services/gateway/`) -**What is real:** -- API token validation via `apitoken` middleware backed by PostgreSQL `api_tokens` table -- Verify engine calls real NIMC/NIBSS/CAC APIs with Youverify fallback -- Kafka event publishing for all verification events -- Redis-backed rate limiting for stablecoin endpoints -- mTLS middleware with certificate CN/SAN validation - -**Gap:** -1. **mTLS rate limiter is in-memory** — `mtls.go:158` uses an in-memory `tokenBucket` map. Multi-replica deployments will have inconsistent rate limiting. - -#### Case Manager (`services/case-manager/`) -**What is real:** -- `CaseRepository` uses real PostgreSQL queries -- `GenericRepo` wraps `*sql.DB` - -**Gaps:** -1. **6 stub sub-repositories** — `NewPartyRepository`, `NewDocumentRepository`, `NewTimelineRepository`, `NewStakeholderRepository`, `NewCommentRepository` are stubs. HTTP handlers that use them have not been verified to call `INSERT`/`UPDATE`. -2. **Build timeout** — Service takes >2 minutes to compile in sandbox (Temporal + Kafka deps). - ---- - -### 5. Python Microservices — 10/15 - -#### Risk Engine (`services/risk-engine/`) -- 31 tests pass -- Composite risk scoring algorithm — real -- **Gap:** Does not write scored results to PostgreSQL. Pure compute service; caller (Node.js) persists. - -#### Biometric Engine (`services/biometric-engine/`) -- 21 tests pass -- Face embedding extraction with ArcFace — real -- **Gap:** Enrollment templates stored in Redis only (24h TTL). If Redis is flushed, all subjects must re-enroll. `biometric_templates` PostgreSQL table added Jun 29 — biometric-engine needs to write to it. - -#### Lex Matcher (`services/lex-matcher/`) -- 29 tests pass -- Pure compute service — no state to persist. **No gaps.** - -#### Risk Scoring (`services/risk-scoring/`) -- Pure compute service — no state to persist. **No gaps.** - ---- - -### 6. Rust Microservices — 10/15 - -#### Event Processor (`services/event-processor/`) -- 52 tests pass (after `baggage` field fix Jun 29 ✅) -- W3C traceparent parsing — real -- **Gap:** `AppState` holds event history in a `Vec` (in-memory). Lost on restart. - -#### Screening Engine (`services/screening-engine/`) -- Redis-backed result caching — real -- Kafka producer publishes results — real -- **Gap:** `simulate` mode returns `ScreeningOutcome::Clear` for all checks. No `"simulated": true` flag in response. - -#### AML Engine (`services/aml-engine/`) -- Redis-backed DLQ with `restore_from_redis()` on startup — real -- Exponential backoff replay — real -- **Gap:** SDN list loaded from local file at startup. Should be fetched from OFAC API or a `sanctions_entries` DB table. - ---- - -### 7. Automated Test Suites — 6/10 - -| Service | Tests | Status | -|---|---|---| -| Node.js tRPC (vitest) | **972 passing** (+29 new Jun 29) | ✅ All pass | -| Rust event-processor | **52 passing** | ✅ All pass | -| Python risk-engine | **31 passing** | ✅ All pass | -| Python biometric-engine | **21 passing** | ✅ All pass | -| Python lex-matcher | **29 passing** | ✅ All pass | -| Go gateway (unit) | Passes (verify, mtls, stablecoin, insider, dapr) | ✅ Passes | -| Go case-manager (unit) | Passes (repository package) | ✅ Passes | -| Go gateway (integration) | Not run — requires live Kafka/Redis | ⚠️ Skipped | -| Rust screening-engine | Build requires `librdkafka-dev` + `cmake` | ⚠️ Not run | -| Rust aml-engine | Build requires `librdkafka-dev` + `cmake` | ⚠️ Not run | -| End-to-end integration | None exist | ❌ Missing | - ---- - -### 8. Frontend Mock Data — 2/5 - -| Page | Issue | -|---|---| -| `MVRCheckPage.tsx:177` | `mockResult` fallback used when backend returns no result (always) | -| `DrugScreeningPage.tsx:112` | `NIGERIA_COLLECTION_SITES` is a hardcoded static array — should call `trpc.collectionSites.list` | -| `Dashboard.tsx` | `TICKER_SEED` used as initial state; real alerts replace it on load — acceptable | -| `BiometricEnrollmentPage.tsx:568` | `Math.random()` for UI animation only — not data | -| `BatchMonitor.tsx:75` | `Math.random()` for simulated queue jitter — should use real TigerBeetle/DB | - ---- - -## What Was Fixed in This Session (Jun 29 2026) - -| Fix | File | Change | -|---|---|---| -| Gateway dev-token bypass | `services/gateway/keycloak/oidc.go` | Removed `strings.HasPrefix(token, "dev-")` bypass ✅ | -| Biometric templates table | `drizzle/schema.ts` | Added `biometricTemplates` table, migrated to DB ✅ | -| Collection sites table | `drizzle/schema.ts` | Added `collectionSites` table, migrated to DB ✅ | -| Event log table | `drizzle/schema.ts` | Added `eventLog` table, migrated to DB ✅ | -| tRPC routers | `server/routers.ts` | Added `collectionSitesRouter`, `biometricTemplatesRouter`, `eventLogRouter` (12 new procedures) ✅ | -| event-processor | `services/event-processor/src/traceparent.rs` | Fixed missing `baggage` field in `TraceContext` ✅ | -| Vitest tests | `server/` | 29 new tests — 972 total passing ✅ | - ---- - -## Prioritised Remaining Work - -1. **[DONE ✅] Gateway dev-token bypass** — Removed Jun 29. -2. **[DONE ✅] Biometric enrollment durability** — `biometric_templates` table + tRPC procedures added Jun 29. -3. **[DONE ✅] Drug collection sites** — `collection_sites` table + tRPC procedures added Jun 29. Frontend wiring still needed. -4. **[HIGH] Wire DrugScreeningPage** — Call `trpc.collectionSites.list` instead of hardcoded array. -5. **[HIGH] MVR API integration** — Implement real FRSC/MVR API call in `screening.create`. Remove `mockResult` fallback. -6. **[HIGH] case-manager stub repos** — Replace all 6 stub repositories with real `pgx` PostgreSQL queries. -7. **[HIGH] event-processor persistence** — Replace `Vec` with `sqlx` PostgreSQL writes. -8. **[MEDIUM] screening-engine simulate flag** — Add `"simulated": true` to response body. -9. **[MEDIUM] mTLS rate limiter → Redis** — Replace in-memory `tokenBucket` with Redis sliding window. -10. **[MEDIUM] biometric-engine → DB** — Write enrollment templates to `biometric_templates` table (not just Redis). -11. **[MEDIUM] risk-engine → DB** — Write scored results to a `risk_scores` table directly. -12. **[MEDIUM] AML SDN list → DB/API** — Replace file-based loading with `sanctions_entries` table or OFAC API. -13. **[LOW] Keycloak logout** — Implement end-session endpoint. -14. **[LOW] BatchMonitor** — Remove `Math.random()` queue jitter. -15. **[LOW] JWKS refresh** — Add background goroutine to refresh Keycloak JWKS in gateway. -16. **[LOW] screening-engine tests** — Fix rdkafka CGO build in CI. -17. **[LOW] aml-engine tests** — Fix rdkafka CGO build in CI. - ---- - -## What Is Genuinely Production-Ready - -- All 540 tRPC procedures persist to PostgreSQL -- Keycloak OIDC auth is real (JWKS verification) in both Node.js and Go gateway -- Risk scoring pipeline: Node.js → risk-engine → DB (end-to-end real) -- AML engine: Redis-backed DLQ with replay -- Screening engine: real NIMC/NIBSS/CAC API calls (when not in sandbox mode) -- goAML NFIU submissions: real API call (when `GOAML_API_KEY` set) -- Push notifications: DB-persisted subscriptions, FCM + Web Push delivery -- Audit logging: all mutations write to `auditLogs` table -- Circuit breakers: all external API calls wrapped -- mTLS: certificate CN/SAN validation in gateway -- **972 automated tests passing** across Node.js, Python, Rust, and Go - ---- - -*This audit was produced by automated codebase scanning. Line numbers are accurate as of 2026-06-29.* diff --git a/README.md b/README.md index 8d4fd793..7053534c 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ BIS is a full-stack compliance intelligence platform built for Nigerian AML/CFT │ BIS Platform │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ -│ │ React PWA │ │ BIS BFF │ │ PostgreSQL (TiDB) │ │ +│ │ React PWA │ │ BIS BFF │ │ PostgreSQL │ │ │ │ (Vite 7) │◄─┤ (Express) ├──┤ 46 tables, 25 migs │ │ │ │ Tailwind 4 │ │ tRPC 11 │ │ Drizzle ORM │ │ │ │ shadcn/ui │ │ Node 22 │ └─────────────────────────┘ │ @@ -66,14 +66,14 @@ BIS is a full-stack compliance intelligence platform built for Nigerian AML/CFT - Go 1.21+ - Python 3.11+ - Rust 1.75+ (for event processor) -- PostgreSQL 15+ (or TiDB Serverless) +- PostgreSQL 15+ - Docker + Docker Compose (optional, for full stack) ### 1. Clone and install ```bash -git clone https://github.com/your-org/bis-pwa.git -cd bis-pwa +git clone https://github.com/munisp/bis.git +cd bis pnpm install ``` @@ -358,7 +358,7 @@ Local LLM adapter for on-premise AI inference via Ollama. ### Project Structure ``` -bis-pwa/ +bis/ ├── client/ # React PWA (Vite 7, Tailwind 4, shadcn/ui) │ ├── src/ │ │ ├── pages/ # 53 page components diff --git a/bis-mobile/.eslintrc.cjs b/bis-mobile/.eslintrc.cjs new file mode 100644 index 00000000..95b4534d --- /dev/null +++ b/bis-mobile/.eslintrc.cjs @@ -0,0 +1,9 @@ +module.exports = { + root: true, + extends: ["expo"], + ignorePatterns: ["node_modules/", ".expo/", "android/", "ios/"], + rules: { + "no-console": "error", + "@typescript-eslint/no-explicit-any": "warn", + }, +}; diff --git a/bis-mobile/app.json b/bis-mobile/app.json index 53db45af..a86b30b8 100644 --- a/bis-mobile/app.json +++ b/bis-mobile/app.json @@ -37,8 +37,7 @@ }, "web": { "bundler": "metro", - "output": "static", - "favicon": "./assets/favicon.png" + "output": "static" }, "plugins": [ "expo-router", diff --git a/bis-mobile/app/(tabs)/alerts.tsx b/bis-mobile/app/(tabs)/alerts.tsx index 02e075d0..1e20f1bf 100644 --- a/bis-mobile/app/(tabs)/alerts.tsx +++ b/bis-mobile/app/(tabs)/alerts.tsx @@ -15,7 +15,7 @@ const SEV_COLOR: Record = { export default function AlertsScreen() { const utils = trpc.useUtils(); const { data, isLoading, refetch, isRefetching } = trpc.alerts.list.useQuery( - { page: 1, limit: 30, resolved: false }, + { unreadOnly: true, limit: 30 }, { staleTime: 30_000 } ); @@ -23,19 +23,20 @@ export default function AlertsScreen() { onSuccess: () => utils.alerts.list.invalidate(), }); - const alerts = (data as any)?.alerts ?? []; + const alerts = data ?? []; + type AlertRow = (typeof alerts)[number]; return ( {isLoading ? ( ) : ( - data={alerts} - keyExtractor={(item: any) => String(item.id)} + keyExtractor={(item) => String(item.id)} refreshControl={} contentContainerStyle={styles.list} - renderItem={({ item }: { item: any }) => { + renderItem={({ item }) => { const sc = SEV_COLOR[item.severity] ?? COLORS.muted; return ( @@ -45,7 +46,7 @@ export default function AlertsScreen() { {!item.acknowledged && ( acknowledgeMutation.mutate({ alertId: item.id })} + onPress={() => acknowledgeMutation.mutate({ id: item.id })} style={styles.ackButton} > diff --git a/bis-mobile/app/(tabs)/aml.tsx b/bis-mobile/app/(tabs)/aml.tsx index 6ed34c70..0ce48b4e 100644 --- a/bis-mobile/app/(tabs)/aml.tsx +++ b/bis-mobile/app/(tabs)/aml.tsx @@ -1,19 +1,5 @@ -/** - * AML Transactions tab — BIS Mobile (Expo) - * Lists AML-screened transactions with risk scores and sanctions hits. - */ import React, { useState } from "react"; -import { - View, - Text, - FlatList, - TextInput, - TouchableOpacity, - ActivityIndicator, - StyleSheet, - RefreshControl, - Alert, -} from "react-native"; +import { View, Text, FlatList, TextInput, ActivityIndicator, StyleSheet, RefreshControl } from "react-native"; import { trpc } from "@/lib/trpc"; const RISK_COLORS: Record = { @@ -23,83 +9,56 @@ const RISK_COLORS: Record = { low: "#22c55e", }; +const PAGE_SIZE = 20; + export default function AMLScreen() { const [search, setSearch] = useState(""); - const [page, setPage] = useState(1); - - const { data, isLoading, refetch, isFetching } = trpc.aml.list.useQuery({ - search, - page, - limit: 20, - }); - - const screenMutation = trpc.aml.screenWithEngine.useMutation({ - onSuccess: () => { - refetch(); - Alert.alert("Success", "Transaction screened successfully"); - }, - onError: (err) => Alert.alert("Error", err.message), + const [offset, setOffset] = useState(0); + const { data, isLoading, refetch, isFetching } = trpc.aml.transactions.list.useQuery({ + search: search.trim() || undefined, + offset, + limit: PAGE_SIZE, }); - const items = (data as { data?: unknown[] })?.data ?? []; - const total = (data as { total?: number })?.total ?? 0; - - const renderItem = ({ item }: { item: unknown }) => { - const tx = item as Record; - const riskLevel = (tx.riskLevel as string) ?? "low"; - return ( - - - {(tx.transactionRef as string) ?? "—"} - - {riskLevel.toUpperCase()} - - - - Amount: {tx.currency as string} {Number(tx.amount ?? 0).toLocaleString()} - - - Sender: {(tx.senderName as string) ?? "—"} → Receiver: {(tx.receiverName as string) ?? "—"} - - {(tx.sanctionsHit as boolean) && ( - ⚠ SANCTIONS HIT - )} - - {tx.createdAt ? new Date(tx.createdAt as string).toLocaleString() : "—"} - - - ); - }; + const items = data?.items ?? []; + const total = data?.total ?? 0; return ( AML Transactions - {total} total records - + {total} authorised record{total === 1 ? "" : "s"} { setSearch(t); setPage(1); }} + onChangeText={(value) => { setSearch(value); setOffset(0); }} + accessibilityLabel="Search AML transactions" /> - - {isLoading ? ( - - ) : ( + {isLoading ? : ( String(i)} - renderItem={renderItem} - refreshControl={ - - } - ListEmptyComponent={ - No AML transactions found - } - onEndReached={() => { - if (items.length < total) setPage((p) => p + 1); + keyExtractor={(item) => String(item.id)} + renderItem={({ item: tx }) => { + const riskLevel = tx.amlRiskLevel ?? "low"; + return ( + + + {tx.txRef} + + {riskLevel.toUpperCase()} + + + Amount: {tx.currency} {Number(tx.amount ?? 0).toLocaleString()} + Originator: {tx.originatorName ?? "—"} + Beneficiary: {tx.beneficiaryName ?? "—"} + {tx.createdAt ? new Date(tx.createdAt).toLocaleString() : "—"} + + ); }} + refreshControl={ void refetch()} tintColor="#3b82f6" />} + ListEmptyComponent={No authorised AML transactions found} + onEndReached={() => { if (offset + items.length < total) setOffset((value) => value + PAGE_SIZE); }} onEndReachedThreshold={0.5} /> )} @@ -111,27 +70,13 @@ const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#0f172a", padding: 16 }, title: { fontSize: 22, fontWeight: "700", color: "#f8fafc", marginBottom: 4 }, subtitle: { fontSize: 13, color: "#94a3b8", marginBottom: 12 }, - search: { - backgroundColor: "#1e293b", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 8, - marginBottom: 12, - fontSize: 14, - }, - card: { - backgroundColor: "#1e293b", - borderRadius: 10, - padding: 14, - marginBottom: 10, - }, + search: { backgroundColor: "#1e293b", color: "#f8fafc", borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, marginBottom: 12, fontSize: 14 }, + card: { backgroundColor: "#1e293b", borderRadius: 10, padding: 14, marginBottom: 10 }, cardHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }, ref: { fontSize: 14, fontWeight: "600", color: "#f8fafc" }, badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, badgeText: { fontSize: 10, fontWeight: "700", color: "#fff" }, detail: { fontSize: 12, color: "#94a3b8", marginTop: 2 }, - sanctionsHit: { fontSize: 12, fontWeight: "700", color: "#ef4444", marginTop: 4 }, timestamp: { fontSize: 11, color: "#475569", marginTop: 6 }, empty: { textAlign: "center", color: "#64748b", marginTop: 48 }, }); diff --git a/bis-mobile/app/(tabs)/biometric.tsx b/bis-mobile/app/(tabs)/biometric.tsx index 3a9ddfbc..739412ce 100644 --- a/bis-mobile/app/(tabs)/biometric.tsx +++ b/bis-mobile/app/(tabs)/biometric.tsx @@ -36,7 +36,7 @@ export default function BiometricScreen() { const [page, setPage] = useState(1); const [showEnrollModal, setShowEnrollModal] = useState(false); const [form, setForm] = useState({ - subjectId: "", + subjectRef: "", subjectName: "", modality: "face", imageData: "", @@ -52,26 +52,13 @@ export default function BiometricScreen() { const enrollMutation = trpc.biometric.enroll.useMutation({ onSuccess: () => { setShowEnrollModal(false); - setForm({ subjectId: "", subjectName: "", modality: "face", imageData: "" }); + setForm({ subjectRef: "", subjectName: "", modality: "face", imageData: "" }); utils.biometric.list.invalidate(); Alert.alert("Enrolled", "Biometric enrollment successful"); }, onError: (err) => Alert.alert("Error", err.message), }); - const verifyMutation = trpc.biometric.verify.useMutation({ - onSuccess: (result) => { - const r = result as Record; - Alert.alert( - r.match ? "Match Found" : "No Match", - r.match - ? `Identity verified with ${Number(r.confidence ?? 0).toFixed(1)}% confidence` - : "Biometric verification failed — no match found" - ); - }, - onError: (err) => Alert.alert("Error", err.message), - }); - const deleteMutation = trpc.biometric.delete.useMutation({ onSuccess: () => utils.biometric.list.invalidate(), onError: (err) => Alert.alert("Error", err.message), @@ -98,7 +85,7 @@ export default function BiometricScreen() { {(rec.subjectName as string) ?? "—"} - ID: {(rec.subjectId as string) ?? "—"} + ID: {(rec.subjectRef as string) ?? "—"} {status.toUpperCase()} @@ -108,33 +95,8 @@ export default function BiometricScreen() { {rec.qualityScore !== undefined && ( Quality Score: {Number(rec.qualityScore).toFixed(1)}% )} - {rec.enrolledAt && ( - - Enrolled: {new Date(rec.enrolledAt as string).toLocaleString()} - - )} + Enrollment timestamp unavailable in this review view - {status === "enrolled" && ( - - Alert.alert("Verify", "Run biometric verification for this subject?", [ - { text: "Cancel", style: "cancel" }, - { - text: "Verify", - onPress: () => - verifyMutation.mutate({ - subjectId: rec.subjectId as string, - modality, - imageData: "live_capture_placeholder", - }), - }, - ]) - } - > - Verify - - )} @@ -210,8 +172,8 @@ export default function BiometricScreen() { style={styles.input} placeholder="Subject ID (NIN/BVN) *" placeholderTextColor="#64748b" - value={form.subjectId} - onChangeText={(t) => setForm((f) => ({ ...f, subjectId: t }))} + value={form.subjectRef} + onChangeText={(t) => setForm((f) => ({ ...f, subjectRef: t }))} /> { - if (!form.subjectId.trim() || !form.subjectName.trim()) - return Alert.alert("Error", "Subject ID and name are required"); + if (!form.subjectRef.trim() || !form.imageData.trim()) + return Alert.alert("Error", "Subject ID and captured image data are required"); enrollMutation.mutate({ - subjectId: form.subjectId, - subjectName: form.subjectName, - modality: form.modality, - imageData: `mock_capture_${Date.now()}`, + subjectRef: form.subjectRef, + imageBase64: form.imageData, }); }} > @@ -295,7 +255,7 @@ const styles = StyleSheet.create({ modalityIcon: { width: 44, height: 44, borderRadius: 22, backgroundColor: "#818cf822", alignItems: "center", justifyContent: "center" }, cardInfo: { flex: 1 }, subjectName: { fontSize: 14, fontWeight: "600", color: "#f8fafc" }, - subjectId: { fontSize: 12, color: "#64748b", marginTop: 2 }, + subjectRef: { fontSize: 12, color: "#64748b", marginTop: 2 }, badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, badgeText: { fontSize: 10, fontWeight: "700", color: "#fff" }, detail: { fontSize: 12, color: "#94a3b8", marginTop: 2 }, diff --git a/bis-mobile/app/(tabs)/cases.tsx b/bis-mobile/app/(tabs)/cases.tsx index c80beb43..c4318a06 100644 --- a/bis-mobile/app/(tabs)/cases.tsx +++ b/bis-mobile/app/(tabs)/cases.tsx @@ -1,287 +1,70 @@ -/** - * Cases tab — BIS Mobile (Expo) - * Investigation case management with CRUD, escalation, and notes. - */ import React, { useState } from "react"; -import { - View, - Text, - FlatList, - TextInput, - TouchableOpacity, - ActivityIndicator, - StyleSheet, - RefreshControl, - Alert, - Modal, - ScrollView, -} from "react-native"; +import { View, Text, FlatList, TextInput, TouchableOpacity, ActivityIndicator, StyleSheet, RefreshControl, Alert, Modal } from "react-native"; import { trpc } from "@/lib/trpc"; -const STATUS_COLORS: Record = { - open: "#3b82f6", - under_review: "#eab308", - escalated: "#f97316", - closed: "#22c55e", - dismissed: "#64748b", -}; +const STATUS_COLORS: Record = { open: "#3b82f6", under_review: "#eab308", escalated: "#f97316", closed: "#22c55e", dismissed: "#64748b" }; +const PAGE_SIZE = 20; export default function CasesScreen() { const [search, setSearch] = useState(""); const [page, setPage] = useState(1); const [showCreateModal, setShowCreateModal] = useState(false); const [newTitle, setNewTitle] = useState(""); - const [newDesc, setNewDesc] = useState(""); + const [newSummary, setNewSummary] = useState(""); const [noteText, setNoteText] = useState(""); - const [selectedCaseId, setSelectedCaseId] = useState(null); - + const [selectedCaseRef, setSelectedCaseRef] = useState(null); const utils = trpc.useUtils(); - const { data, isLoading, refetch, isFetching } = trpc.cases.list.useQuery({ - search, - page, - limit: 20, - }); - + const { data, isLoading, refetch, isFetching } = trpc.cases.list.useQuery({ search: search.trim() || undefined, page, pageSize: PAGE_SIZE }); const createMutation = trpc.cases.create.useMutation({ - onSuccess: () => { - setShowCreateModal(false); - setNewTitle(""); - setNewDesc(""); - utils.cases.list.invalidate(); - }, - onError: (err) => Alert.alert("Error", err.message), + onSuccess: () => { setShowCreateModal(false); setNewTitle(""); setNewSummary(""); void utils.cases.list.invalidate(); }, + onError: () => Alert.alert("Unable to create case", "No case was created. Check the information and try again."), }); - - const escalateMutation = trpc.cases.escalate.useMutation({ - onSuccess: () => { - utils.cases.list.invalidate(); - Alert.alert("Escalated", "Case has been escalated"); - }, - onError: (err) => Alert.alert("Error", err.message), + const reviewMutation = trpc.cases.update.useMutation({ + onSuccess: () => { void utils.cases.list.invalidate(); Alert.alert("Case moved to review", "The case is now under review."); }, + onError: () => Alert.alert("Unable to update case", "The case status was not changed."), }); - - const addNoteMutation = trpc.cases.addNote.useMutation({ - onSuccess: () => { - setNoteText(""); - setSelectedCaseId(null); - Alert.alert("Note Added", "Note has been added to the case"); - }, - onError: (err) => Alert.alert("Error", err.message), + const addCommentMutation = trpc.cases.addComment.useMutation({ + onSuccess: () => { setNoteText(""); setSelectedCaseRef(null); Alert.alert("Note added", "The case comment was saved."); }, + onError: () => Alert.alert("Unable to add note", "The comment was not saved."), }); - const items = (data as { data?: unknown[] })?.data ?? []; - const total = (data as { total?: number })?.total ?? 0; - - const renderItem = ({ item }: { item: unknown }) => { - const c = item as Record; - const status = (c.status as string) ?? "open"; - return ( - - - {(c.title as string) ?? "—"} - - {status.replace("_", " ").toUpperCase()} - - - Ref: {(c.caseRef as string) ?? "—"} - Priority: {(c.priority as string) ?? "—"} - {c.description && ( - {c.description as string} - )} - - {c.createdAt ? new Date(c.createdAt as string).toLocaleString() : "—"} - - - { - setSelectedCaseId(c.id as number); - }} - > - Add Note - - {status !== "escalated" && status !== "closed" && ( - - Alert.alert("Escalate", "Escalate this case?", [ - { text: "Cancel", style: "cancel" }, - { text: "Escalate", onPress: () => escalateMutation.mutate({ id: c.id as number }) }, - ]) - } - > - Escalate - - )} - - - ); - }; + const cases = data?.cases ?? []; + const total = data?.total ?? 0; return ( - - - Cases - {total} total cases - - setShowCreateModal(true)}> - + New - - - - { setSearch(t); setPage(1); }} - /> - - {isLoading ? ( - - ) : ( + Cases{total} authorised case{total === 1 ? "" : "s"} setShowCreateModal(true)}>+ New + { setSearch(value); setPage(1); }} accessibilityLabel="Search cases" /> + {isLoading ? : ( String(i)} - renderItem={renderItem} - refreshControl={ - - } - ListEmptyComponent={ - No cases found - } - onEndReached={() => { - if (items.length < total) setPage((p) => p + 1); + data={cases} + keyExtractor={(item) => item.ref} + refreshControl={ void refetch()} tintColor="#3b82f6" />} + renderItem={({ item: caseItem }) => { + const status = caseItem.status ?? "open"; + return + {caseItem.title}{status.replace("_", " ").toUpperCase()} + Ref: {caseItem.ref}Priority: {caseItem.priority ?? "—"} + {caseItem.summary ? {caseItem.summary} : null} + {caseItem.createdAt ? new Date(caseItem.createdAt).toLocaleString() : "—"} + + setSelectedCaseRef(caseItem.ref)}>Add note + {status !== "under_review" && status !== "closed" ? Alert.alert("Move to review", "Move this case to under review?", [{ text: "Cancel", style: "cancel" }, { text: "Confirm", onPress: () => reviewMutation.mutate({ ref: caseItem.ref, status: "under_review" }) }])}>Review : null} + + ; }} + ListEmptyComponent={No cases found} + onEndReached={() => { if (cases.length >= PAGE_SIZE && page * PAGE_SIZE < total) setPage((value) => value + 1); }} onEndReachedThreshold={0.5} /> )} - - {/* Create Case Modal */} - - - - New Case - - - - setShowCreateModal(false)} - > - Cancel - - { - if (!newTitle.trim()) return Alert.alert("Error", "Title is required"); - createMutation.mutate({ title: newTitle, description: newDesc }); - }} - > - - {createMutation.isPending ? "Creating…" : "Create"} - - - - - - - - {/* Add Note Modal */} - - - - Add Note - - - { setSelectedCaseId(null); setNoteText(""); }} - > - Cancel - - { - if (!noteText.trim()) return Alert.alert("Error", "Note cannot be empty"); - addNoteMutation.mutate({ id: selectedCaseId!, note: noteText }); - }} - > - - {addNoteMutation.isPending ? "Saving…" : "Save Note"} - - - - - - + setShowCreateModal(false)}>New case setShowCreateModal(false)}>Cancel { if (!newTitle.trim()) { Alert.alert("Title required", "Enter a title before creating the case."); return; } createMutation.mutate({ title: newTitle.trim(), type: "other", summary: newSummary.trim() || undefined }); }}>{createMutation.isPending ? "Creating…" : "Create"} + setSelectedCaseRef(null)}>Add note { setSelectedCaseRef(null); setNoteText(""); }}>Cancel { if (!selectedCaseRef || !noteText.trim()) { Alert.alert("Note required", "Enter a note before saving."); return; } addCommentMutation.mutate({ caseRef: selectedCaseRef, content: noteText.trim() }); }}>{addCommentMutation.isPending ? "Saving…" : "Save note"} ); } const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: "#0f172a", padding: 16 }, - header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, - title: { fontSize: 22, fontWeight: "700", color: "#f8fafc" }, - subtitle: { fontSize: 13, color: "#94a3b8" }, - createBtn: { backgroundColor: "#3b82f6", borderRadius: 8, paddingHorizontal: 14, paddingVertical: 8 }, - createBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, - search: { - backgroundColor: "#1e293b", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 8, - marginBottom: 12, - fontSize: 14, - }, - card: { backgroundColor: "#1e293b", borderRadius: 10, padding: 14, marginBottom: 10 }, - cardHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }, - caseTitle: { fontSize: 14, fontWeight: "600", color: "#f8fafc", flex: 1, marginRight: 8 }, - badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, - badgeText: { fontSize: 10, fontWeight: "700", color: "#fff" }, - detail: { fontSize: 12, color: "#94a3b8", marginTop: 2 }, - desc: { fontSize: 12, color: "#64748b", marginTop: 4 }, - timestamp: { fontSize: 11, color: "#475569", marginTop: 6 }, - actions: { flexDirection: "row", gap: 8, marginTop: 10 }, - actionBtn: { backgroundColor: "#334155", borderRadius: 6, paddingHorizontal: 10, paddingVertical: 6 }, - actionBtnText: { fontSize: 12, color: "#f8fafc", fontWeight: "600" }, - empty: { textAlign: "center", color: "#64748b", marginTop: 48 }, - modalOverlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.7)", justifyContent: "flex-end" }, - modalContent: { backgroundColor: "#1e293b", borderTopLeftRadius: 16, borderTopRightRadius: 16, padding: 20 }, - modalTitle: { fontSize: 18, fontWeight: "700", color: "#f8fafc", marginBottom: 16 }, - input: { - backgroundColor: "#0f172a", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 10, - marginBottom: 12, - fontSize: 14, - }, - modalActions: { flexDirection: "row", gap: 12, marginTop: 4 }, - modalBtn: { flex: 1, borderRadius: 8, paddingVertical: 12, alignItems: "center" }, - modalBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, + container: { flex: 1, backgroundColor: "#0f172a", padding: 16 }, header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, title: { fontSize: 22, fontWeight: "700", color: "#f8fafc" }, subtitle: { fontSize: 13, color: "#94a3b8" }, createBtn: { backgroundColor: "#3b82f6", borderRadius: 8, paddingHorizontal: 14, paddingVertical: 8 }, createBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, search: { backgroundColor: "#1e293b", color: "#f8fafc", borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, marginBottom: 12, fontSize: 14 }, card: { backgroundColor: "#1e293b", borderRadius: 10, padding: 14, marginBottom: 10 }, cardHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }, caseTitle: { fontSize: 14, fontWeight: "600", color: "#f8fafc", flex: 1, marginRight: 8 }, badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, badgeText: { fontSize: 10, fontWeight: "700", color: "#fff" }, detail: { fontSize: 12, color: "#94a3b8", marginTop: 2 }, desc: { fontSize: 12, color: "#64748b", marginTop: 4 }, timestamp: { fontSize: 11, color: "#475569", marginTop: 6 }, actions: { flexDirection: "row", gap: 8, marginTop: 10 }, actionBtn: { backgroundColor: "#334155", borderRadius: 6, paddingHorizontal: 10, paddingVertical: 6 }, reviewBtn: { backgroundColor: "#b45309" }, actionBtnText: { fontSize: 12, color: "#f8fafc", fontWeight: "600" }, empty: { textAlign: "center", color: "#64748b", marginTop: 48 }, modalOverlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.7)", justifyContent: "flex-end" }, modalContent: { backgroundColor: "#1e293b", borderTopLeftRadius: 16, borderTopRightRadius: 16, padding: 20 }, modalTitle: { fontSize: 18, fontWeight: "700", color: "#f8fafc", marginBottom: 16 }, input: { backgroundColor: "#0f172a", color: "#f8fafc", borderRadius: 8, paddingHorizontal: 12, paddingVertical: 10, marginBottom: 12, fontSize: 14 }, modalActions: { flexDirection: "row", gap: 12, marginTop: 4 }, modalBtn: { flex: 1, borderRadius: 8, paddingVertical: 12, alignItems: "center" }, cancelBtn: { backgroundColor: "#334155" }, submitBtn: { backgroundColor: "#3b82f6" }, modalBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, }); diff --git a/bis-mobile/app/(tabs)/documents.tsx b/bis-mobile/app/(tabs)/documents.tsx index 11d14336..1aae4de2 100644 --- a/bis-mobile/app/(tabs)/documents.tsx +++ b/bis-mobile/app/(tabs)/documents.tsx @@ -1,8 +1,4 @@ -/** - * Documents tab — BIS Mobile (Expo) - * Document vault with search, upload, and download. - */ -import React, { useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { View, Text, @@ -13,9 +9,13 @@ import { StyleSheet, RefreshControl, Alert, + Linking, + Modal, + Pressable, } from "react-native"; import { trpc } from "@/lib/trpc"; +const PAGE_SIZE = 20; const TYPE_COLORS: Record = { pdf: "#ef4444", docx: "#3b82f6", @@ -24,77 +24,135 @@ const TYPE_COLORS: Record = { other: "#64748b", }; +type VaultDocument = { + id: number; + filename: string; + category: string | null; + description: string | null; + sizeBytes: number | null; + createdAt: string | Date | null; + url: string | null; +}; + +type VaultListResponse = { + documents: VaultDocument[]; + total: number; + limit: number; + offset: number; +}; + +function documentType(filename: string): string { + const ext = filename.split(".").pop()?.toLowerCase() ?? "other"; + if (ext === "pdf") return "pdf"; + if (["doc", "docx"].includes(ext)) return "docx"; + if (["xls", "xlsx", "csv"].includes(ext)) return "xlsx"; + if (["jpg", "jpeg", "png", "gif", "webp"].includes(ext)) return "image"; + return "other"; +} + +function formatBytes(sizeBytes: number | null): string { + if (!sizeBytes || sizeBytes <= 0) return "Size unavailable"; + if (sizeBytes < 1024) return `${sizeBytes} B`; + if (sizeBytes < 1024 * 1024) return `${Math.round(sizeBytes / 1024)} KB`; + return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`; +} + export default function DocumentsScreen() { + const [searchDraft, setSearchDraft] = useState(""); const [search, setSearch] = useState(""); - const [page, setPage] = useState(1); - + const [page, setPage] = useState(0); + const [pendingDelete, setPendingDelete] = useState(null); + const [deleteReason, setDeleteReason] = useState(""); const utils = trpc.useUtils(); - const { data, isLoading, refetch, isFetching } = trpc.documentVault.list.useQuery({ - search, - page, - limit: 20, + useEffect(() => { + const timer = setTimeout(() => { + const nextSearch = searchDraft.trim(); + setSearch(nextSearch); + setPage(0); + }, 300); + return () => clearTimeout(timer); + }, [searchDraft]); + + const listInput = useMemo(() => ({ + search: search || undefined, + limit: PAGE_SIZE, + offset: page * PAGE_SIZE, + }), [page, search]); + + const query = trpc.documentVault.list.useQuery(listInput, { + retry: 1, + refetchOnWindowFocus: false, }); + const data = query.data as VaultListResponse | undefined; + const items = data?.documents ?? []; + const total = data?.total ?? 0; + const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE)); const deleteMutation = trpc.documentVault.delete.useMutation({ - onSuccess: () => utils.documentVault.list.invalidate(), - onError: (err) => Alert.alert("Error", err.message), + onSuccess: async () => { + setPendingDelete(null); + setDeleteReason(""); + await utils.documentVault.list.invalidate(); + Alert.alert("Document deleted", "The document has been removed and its custody event was recorded."); + }, + onError: () => Alert.alert("Unable to delete document", "The document was not changed. Check your permission and try again."), }); - const items = (data as { data?: unknown[] })?.data ?? []; - const total = (data as { total?: number })?.total ?? 0; + const openDocument = async (document: VaultDocument) => { + if (!document.url) return; + try { + const url = new URL(document.url); + if (url.protocol !== "https:") throw new Error("unsafe document URL"); + const supported = await Linking.canOpenURL(url.toString()); + if (!supported) throw new Error("device cannot open document URL"); + await Linking.openURL(url.toString()); + } catch { + Alert.alert("Unable to open document", "The secure document link is unavailable. Refresh the vault and try again."); + } + }; - const getDocType = (filename: string): string => { - const ext = filename?.split(".").pop()?.toLowerCase() ?? "other"; - if (["pdf"].includes(ext)) return "pdf"; - if (["doc", "docx"].includes(ext)) return "docx"; - if (["xls", "xlsx", "csv"].includes(ext)) return "xlsx"; - if (["jpg", "jpeg", "png", "gif", "webp"].includes(ext)) return "image"; - return "other"; + const confirmDelete = () => { + if (!pendingDelete) return; + const reason = deleteReason.trim(); + if (reason.length < 1 || reason.length > 500) { + Alert.alert("A deletion reason is required", "Enter a concise reason of up to 500 characters for the custody record."); + return; + } + deleteMutation.mutate({ id: pendingDelete.id, reason }); }; - const renderItem = ({ item }: { item: unknown }) => { - const doc = item as Record; - const docType = getDocType((doc.filename as string) ?? ""); + const renderItem = ({ item }: { item: VaultDocument }) => { + const type = documentType(item.filename); return ( - + - - {docType.toUpperCase()} + + {type.toUpperCase()} - {(doc.filename as string) ?? "—"} + {item.filename} - Category: {(doc.category as string) ?? "—"} - {doc.description && ( - {doc.description as string} - )} - - Size: {doc.fileSize ? `${Math.round(Number(doc.fileSize) / 1024)} KB` : "—"} - - - {doc.createdAt ? new Date(doc.createdAt as string).toLocaleString() : "—"} - + Category: {item.category ?? "Uncategorised"} + {item.description ? {item.description} : null} + {formatBytes(item.sizeBytes)} + {item.createdAt ? new Date(item.createdAt).toLocaleString() : "Date unavailable"} - {doc.fileUrl && ( - Alert.alert("Download", `Opening: ${doc.filename}`)} - > - Download - - )} - Alert.alert("Delete", "Delete this document?", [ - { text: "Cancel", style: "cancel" }, - { - text: "Delete", - style: "destructive", - onPress: () => deleteMutation.mutate({ id: doc.id as number }), - }, - ]) - } + disabled={!item.url} + accessibilityRole="button" + accessibilityLabel={`Open ${item.filename}`} + accessibilityHint="Opens the authorized secure document link" + style={[styles.actionBtn, styles.openButton, !item.url && styles.disabledButton]} + onPress={() => void openDocument(item)} + > + {item.url ? "Open securely" : "Link unavailable"} + + { setPendingDelete(item); setDeleteReason(""); }} > Delete @@ -103,62 +161,96 @@ export default function DocumentsScreen() { ); }; + if (query.error && !query.isFetching) { + return ( + + Document Vault + + Unable to load documents + Your documents were not displayed. No changes were made. + void query.refetch()}> + Retry + + + + ); + } + return ( Document Vault - {total} total documents - + {total} document{total === 1 ? "" : "s"} · Page {page + 1} of {pageCount} { setSearch(t); setPage(1); }} + placeholder="Search filename or description" + placeholderTextColor="#94a3b8" + value={searchDraft} + onChangeText={setSearchDraft} + returnKeyType="search" + accessibilityLabel="Search documents" + accessibilityHint="Filters authorized document metadata" /> - - {isLoading ? ( - - ) : ( + {query.isLoading ? : ( String(i)} + keyExtractor={(item) => String(item.id)} renderItem={renderItem} - refreshControl={ - + contentContainerStyle={items.length === 0 ? styles.emptyList : undefined} + refreshControl={ void query.refetch()} tintColor="#60a5fa" />} + ListEmptyComponent={No documents foundTry a different search or add a document from the linked case workflow.} + ListFooterComponent={ + + setPage((current) => Math.max(0, current - 1))}>Previous + = pageCount || query.isFetching} style={[styles.pageButton, (page + 1 >= pageCount || query.isFetching) && styles.disabledButton]} onPress={() => setPage((current) => current + 1)}>Next + } - ListEmptyComponent={No documents found} - onEndReached={() => { - if (items.length < total) setPage((p) => p + 1); - }} - onEndReachedThreshold={0.5} /> )} + setPendingDelete(null)}> + setPendingDelete(null)}> + event.stopPropagation()}> + Delete document + This action records a custody event. Explain why {pendingDelete?.filename ?? "this document"} should be removed. + + + setPendingDelete(null)}>Cancel + {deleteMutation.isPending ? "Deleting…" : "Confirm delete"} + + + + ); } const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: "#0f172a", padding: 16 }, - title: { fontSize: 22, fontWeight: "700", color: "#f8fafc", marginBottom: 4 }, + container: { flex: 1, backgroundColor: "#0b1220", padding: 16 }, + title: { fontSize: 24, fontWeight: "700", color: "#f8fafc", marginBottom: 4 }, subtitle: { fontSize: 13, color: "#94a3b8", marginBottom: 12 }, - search: { - backgroundColor: "#1e293b", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 8, - marginBottom: 12, - fontSize: 14, - }, - card: { backgroundColor: "#1e293b", borderRadius: 10, padding: 14, marginBottom: 10 }, + search: { backgroundColor: "#172033", borderColor: "#334155", borderWidth: 1, color: "#f8fafc", borderRadius: 10, paddingHorizontal: 12, paddingVertical: 10, marginBottom: 12, fontSize: 15 }, + loader: { marginTop: 32 }, + card: { backgroundColor: "#172033", borderRadius: 12, borderWidth: 1, borderColor: "#26334a", padding: 14, marginBottom: 10 }, cardHeader: { flexDirection: "row", alignItems: "center", marginBottom: 8, gap: 8 }, - typeTag: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, + typeTag: { borderRadius: 5, paddingHorizontal: 6, paddingVertical: 3 }, typeTagText: { fontSize: 10, fontWeight: "700", color: "#fff" }, - filename: { fontSize: 13, fontWeight: "600", color: "#f8fafc", flex: 1 }, - detail: { fontSize: 12, color: "#94a3b8", marginTop: 2 }, - timestamp: { fontSize: 11, color: "#475569", marginTop: 6 }, - actions: { flexDirection: "row", gap: 8, marginTop: 10 }, - actionBtn: { borderRadius: 6, paddingHorizontal: 10, paddingVertical: 6 }, - actionBtnText: { fontSize: 12, color: "#fff", fontWeight: "600" }, - empty: { textAlign: "center", color: "#64748b", marginTop: 48 }, + filename: { fontSize: 14, fontWeight: "600", color: "#f8fafc", flex: 1 }, + detail: { fontSize: 12, color: "#cbd5e1", marginTop: 3 }, + timestamp: { fontSize: 11, color: "#94a3b8", marginTop: 7 }, + actions: { flexDirection: "row", gap: 8, marginTop: 12 }, + actionBtn: { borderRadius: 8, paddingHorizontal: 12, paddingVertical: 9, minHeight: 40, justifyContent: "center" }, + openButton: { backgroundColor: "#2563eb" }, + deleteButton: { backgroundColor: "#b91c1c" }, + cancelButton: { backgroundColor: "#475569" }, + disabledButton: { opacity: 0.45 }, + actionBtnText: { fontSize: 13, color: "#fff", fontWeight: "700" }, + statePanel: { alignItems: "center", backgroundColor: "#172033", borderRadius: 12, borderWidth: 1, borderColor: "#334155", padding: 22, marginTop: 24 }, + stateTitle: { color: "#f8fafc", fontSize: 16, fontWeight: "700", marginBottom: 6 }, + stateText: { color: "#cbd5e1", fontSize: 13, lineHeight: 19, textAlign: "center" }, + emptyList: { flexGrow: 1 }, + pagination: { flexDirection: "row", justifyContent: "space-between", gap: 12, paddingVertical: 16 }, + pageButton: { flex: 1, alignItems: "center", backgroundColor: "#334155", borderRadius: 8, paddingVertical: 10, minHeight: 42 }, + modalBackdrop: { flex: 1, backgroundColor: "rgba(2, 6, 23, 0.78)", justifyContent: "center", padding: 20 }, + modalCard: { backgroundColor: "#172033", borderColor: "#334155", borderWidth: 1, borderRadius: 14, padding: 18 }, + modalTitle: { color: "#f8fafc", fontSize: 18, fontWeight: "700", marginBottom: 8 }, + reasonInput: { minHeight: 86, marginTop: 14, backgroundColor: "#0b1220", borderColor: "#475569", borderWidth: 1, borderRadius: 8, color: "#f8fafc", padding: 10, textAlignVertical: "top" }, }); diff --git a/bis-mobile/app/(tabs)/goaml.tsx b/bis-mobile/app/(tabs)/goaml.tsx index d3323525..6acfa380 100644 --- a/bis-mobile/app/(tabs)/goaml.tsx +++ b/bis-mobile/app/(tabs)/goaml.tsx @@ -1,265 +1,16 @@ -/** - * goAML STR Reports tab — BIS Mobile (Expo) - * Suspicious Transaction Reports with full CRUD and submission workflow. - */ import React, { useState } from "react"; -import { - View, - Text, - FlatList, - TextInput, - TouchableOpacity, - ActivityIndicator, - StyleSheet, - RefreshControl, - Alert, - Modal, -} from "react-native"; +import { ActivityIndicator, FlatList, RefreshControl, StyleSheet, Text, View } from "react-native"; import { trpc } from "@/lib/trpc"; -const STATUS_COLORS: Record = { - draft: "#64748b", - pending_review: "#eab308", - submitted: "#3b82f6", - accepted: "#22c55e", - rejected: "#ef4444", -}; +const LIMIT = 20; -export default function GoAMLScreen() { - const [search, setSearch] = useState(""); - const [page, setPage] = useState(1); - const [showCreateModal, setShowCreateModal] = useState(false); - const [form, setForm] = useState({ reportType: "STR", subject: "", description: "", amount: "" }); - - const utils = trpc.useUtils(); - - const { data, isLoading, refetch, isFetching } = trpc.goaml.list.useQuery({ - search, - page, - limit: 20, - }); - - const createMutation = trpc.goaml.create.useMutation({ - onSuccess: () => { - setShowCreateModal(false); - setForm({ reportType: "STR", subject: "", description: "", amount: "" }); - utils.goaml.list.invalidate(); - }, - onError: (err) => Alert.alert("Error", err.message), - }); - - const submitMutation = trpc.goaml.submit.useMutation({ - onSuccess: () => { - utils.goaml.list.invalidate(); - Alert.alert("Submitted", "Report submitted to goAML"); - }, - onError: (err) => Alert.alert("Error", err.message), - }); - - const deleteMutation = trpc.goaml.delete.useMutation({ - onSuccess: () => utils.goaml.list.invalidate(), - onError: (err) => Alert.alert("Error", err.message), - }); - - const items = (data as { data?: unknown[] })?.data ?? []; - const total = (data as { total?: number })?.total ?? 0; - - const renderItem = ({ item }: { item: unknown }) => { - const r = item as Record; - const status = (r.status as string) ?? "draft"; - return ( - - - {(r.reportRef as string) ?? "—"} - - {status.replace("_", " ").toUpperCase()} - - - Type: {(r.reportType as string) ?? "STR"} - Subject: {(r.subject as string) ?? "—"} - {r.amount && ( - Amount: {(r.currency as string) ?? "NGN"} {Number(r.amount).toLocaleString()} - )} - - {r.createdAt ? new Date(r.createdAt as string).toLocaleString() : "—"} - - - {status === "draft" && ( - - Alert.alert("Submit", "Submit this report to goAML?", [ - { text: "Cancel", style: "cancel" }, - { text: "Submit", onPress: () => submitMutation.mutate({ id: r.id as number }) }, - ]) - } - > - Submit - - )} - {status === "draft" && ( - - Alert.alert("Delete", "Delete this report?", [ - { text: "Cancel", style: "cancel" }, - { - text: "Delete", - style: "destructive", - onPress: () => deleteMutation.mutate({ id: r.id as number }), - }, - ]) - } - > - Delete - - )} - - - ); - }; - - return ( - - - - goAML Reports - {total} total reports - - setShowCreateModal(true)}> - + New STR - - - - { setSearch(t); setPage(1); }} - /> - - {isLoading ? ( - - ) : ( - String(i)} - renderItem={renderItem} - refreshControl={ - - } - ListEmptyComponent={ - No goAML reports found - } - onEndReached={() => { - if (items.length < total) setPage((p) => p + 1); - }} - onEndReachedThreshold={0.5} - /> - )} - - {/* Create Report Modal */} - - - - New STR Report - setForm((f) => ({ ...f, subject: t }))} - /> - setForm((f) => ({ ...f, amount: t }))} - keyboardType="numeric" - /> - setForm((f) => ({ ...f, description: t }))} - multiline - /> - - setShowCreateModal(false)} - > - Cancel - - { - if (!form.subject.trim() || !form.description.trim()) - return Alert.alert("Error", "Subject and description are required"); - createMutation.mutate({ - reportType: form.reportType, - subject: form.subject, - description: form.description, - amount: form.amount ? Number(form.amount) : undefined, - currency: "NGN", - }); - }} - > - - {createMutation.isPending ? "Creating…" : "Create Draft"} - - - - - - - - ); +export default function GoamlScreen() { + const [offset, setOffset] = useState(0); + const { data, isLoading, isFetching, refetch } = trpc.goaml.list.useQuery({ offset, limit: LIMIT }); + const filings = data ?? []; + return + goAML filings + {isLoading ? : String(item.id)} refreshControl={ void refetch()} tintColor="#38bdf8" />} renderItem={({ item }) => {item.filingRef}{item.subjectName}{item.transactionCurrency} {Number(item.transactionAmount ?? 0).toLocaleString()}{item.status.replace("_", " ").toUpperCase()}} ListEmptyComponent={No authorised goAML filings} onEndReached={() => { if (filings.length === LIMIT) setOffset((value) => value + LIMIT); }} onEndReachedThreshold={0.5} />} + ; } - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: "#0f172a", padding: 16 }, - header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, - title: { fontSize: 22, fontWeight: "700", color: "#f8fafc" }, - subtitle: { fontSize: 13, color: "#94a3b8" }, - createBtn: { backgroundColor: "#3b82f6", borderRadius: 8, paddingHorizontal: 14, paddingVertical: 8 }, - createBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, - search: { - backgroundColor: "#1e293b", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 8, - marginBottom: 12, - fontSize: 14, - }, - card: { backgroundColor: "#1e293b", borderRadius: 10, padding: 14, marginBottom: 10 }, - cardHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }, - reportRef: { fontSize: 14, fontWeight: "600", color: "#f8fafc" }, - badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, - badgeText: { fontSize: 10, fontWeight: "700", color: "#fff" }, - detail: { fontSize: 12, color: "#94a3b8", marginTop: 2 }, - timestamp: { fontSize: 11, color: "#475569", marginTop: 6 }, - actions: { flexDirection: "row", gap: 8, marginTop: 10 }, - actionBtn: { borderRadius: 6, paddingHorizontal: 10, paddingVertical: 6 }, - actionBtnText: { fontSize: 12, color: "#fff", fontWeight: "600" }, - empty: { textAlign: "center", color: "#64748b", marginTop: 48 }, - modalOverlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.7)", justifyContent: "flex-end" }, - modalContent: { backgroundColor: "#1e293b", borderTopLeftRadius: 16, borderTopRightRadius: 16, padding: 20 }, - modalTitle: { fontSize: 18, fontWeight: "700", color: "#f8fafc", marginBottom: 16 }, - input: { - backgroundColor: "#0f172a", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 10, - marginBottom: 12, - fontSize: 14, - }, - modalActions: { flexDirection: "row", gap: 12, marginTop: 4 }, - modalBtn: { flex: 1, borderRadius: 8, paddingVertical: 12, alignItems: "center" }, - modalBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, -}); +const styles = StyleSheet.create({ container:{flex:1,backgroundColor:"#0f172a",padding:16},title:{fontSize:22,fontWeight:"700",color:"#f8fafc",marginBottom:16},card:{backgroundColor:"#1e293b",padding:14,borderRadius:10,marginBottom:10},ref:{color:"#f8fafc",fontWeight:"700"},detail:{color:"#94a3b8",marginTop:3},status:{color:"#38bdf8",fontSize:11,marginTop:8,fontWeight:"700"},empty:{color:"#64748b",textAlign:"center",marginTop:48} }); diff --git a/bis-mobile/app/(tabs)/index.tsx b/bis-mobile/app/(tabs)/index.tsx index e06fa738..89942957 100644 --- a/bis-mobile/app/(tabs)/index.tsx +++ b/bis-mobile/app/(tabs)/index.tsx @@ -72,12 +72,12 @@ export default function DashboardScreen() { const router = useRouter(); const { user } = useAuth(); - const { data: summary, isLoading, refetch, isRefetching } = trpc.dashboard.summary.useQuery(undefined, { + const { data: summary, isLoading, refetch, isRefetching } = trpc.dashboard.stats.useQuery(undefined, { staleTime: 30_000, }); const { data: recentAlerts } = trpc.alerts.list.useQuery( - { page: 1, limit: 5, resolved: false }, + { unreadOnly: true, limit: 5 }, { staleTime: 30_000 } ); @@ -120,14 +120,14 @@ export default function DashboardScreen() { /> router.push("/(tabs)/alerts")} /> router.push("/(tabs)/kyc")} @@ -149,10 +149,10 @@ export default function DashboardScreen() { View all - {recentAlerts?.alerts?.length === 0 && ( + {recentAlerts?.length === 0 && ( No open alerts )} - {(recentAlerts?.alerts ?? []).map((alert: any) => ( + {(recentAlerts ?? []).map((alert) => ( ))} diff --git a/bis-mobile/app/(tabs)/investigations.tsx b/bis-mobile/app/(tabs)/investigations.tsx index 68de0b73..467665d1 100644 --- a/bis-mobile/app/(tabs)/investigations.tsx +++ b/bis-mobile/app/(tabs)/investigations.tsx @@ -33,40 +33,18 @@ function riskColor(score: number) { return COLORS.low; } -function InvestigationCard({ item, onPress }: { item: any; onPress: () => void }) { - const rc = riskColor(item.riskScore ?? 0); - return ( - - - {item.ref} - - {(item.riskScore ?? 0).toFixed(0)} - - - {item.subjectName} - - {item.status?.toUpperCase()} - · - {item.priority} - · - {item.country} - - - ); -} - export default function InvestigationsScreen() { const router = useRouter(); const [search, setSearch] = useState(""); const [page, setPage] = useState(1); const { data, isLoading, refetch, isRefetching } = trpc.investigations.list.useQuery( - { page, limit: 20, search: search || undefined }, + { offset: (page - 1) * 20, limit: 20, search: search || undefined }, { staleTime: 30_000 } ); - const investigations = (data as any)?.investigations ?? []; - const total = (data as any)?.total ?? 0; + const investigations = data?.items ?? []; + const total = data?.total ?? 0; return ( @@ -93,13 +71,32 @@ export default function InvestigationsScreen() { ) : ( String(item.id)} - renderItem={({ item }) => ( - router.push(`/investigation/${item.id}`)} - /> - )} + keyExtractor={(item) => String(item.id)} + renderItem={({ item }) => { + const risk = riskColor(item.riskScore ?? 0); + return ( + router.push(`/investigation/${item.id}`)} + activeOpacity={0.7} + > + + {item.ref} + + {(item.riskScore ?? 0).toFixed(0)} + + + {item.subjectName} + + {item.status?.toUpperCase()} + · + {item.priority} + · + {item.country} + + + ); + }} contentContainerStyle={styles.list} refreshControl={ diff --git a/bis-mobile/app/(tabs)/kyc.tsx b/bis-mobile/app/(tabs)/kyc.tsx index 696d82d9..f49c2a47 100644 --- a/bis-mobile/app/(tabs)/kyc.tsx +++ b/bis-mobile/app/(tabs)/kyc.tsx @@ -10,17 +10,17 @@ const COLORS = { }; const STATUS_COLOR: Record = { - verified: COLORS.success, pending: COLORS.warning, failed: COLORS.error, in_progress: COLORS.primary, + passed: COLORS.success, pending: COLORS.warning, failed: COLORS.error, processing: COLORS.primary, }; export default function KYCScreen() { const router = useRouter(); const { data, isLoading, refetch, isRefetching } = trpc.kyc.list.useQuery( - { page: 1, limit: 20 }, + { limit: 20 }, { staleTime: 30_000 } ); - const records = (data as any)?.records ?? []; + const records = data?.items ?? []; return ( @@ -41,10 +41,10 @@ export default function KYCScreen() { ) : ( String(item.id)} + keyExtractor={(item) => String(item.id)} refreshControl={} contentContainerStyle={styles.list} - renderItem={({ item }: { item: any }) => { + renderItem={({ item }) => { const sc = STATUS_COLOR[item.status] ?? COLORS.muted; return ( diff --git a/bis-mobile/app/(tabs)/more.tsx b/bis-mobile/app/(tabs)/more.tsx index d30414b4..6b426ab1 100644 --- a/bis-mobile/app/(tabs)/more.tsx +++ b/bis-mobile/app/(tabs)/more.tsx @@ -93,12 +93,12 @@ const MENU_ITEMS: MenuItem[] = [ export default function MoreScreen() { const router = useRouter(); - const { data: healthData } = trpc.system.allServicesHealth.useQuery(undefined, { + const { data: healthData } = trpc.lookup.allServicesHealth.useQuery(undefined, { refetchInterval: 60_000, }); const services = (healthData as unknown[]) ?? []; - const healthyCount = services.filter((s) => (s as Record).status === "healthy").length; + const healthyCount = services.filter((s) => (s as Record).status === "ok").length; const totalCount = services.length; return ( diff --git a/bis-mobile/app/(tabs)/payments.tsx b/bis-mobile/app/(tabs)/payments.tsx index ba2398ed..7ad421aa 100644 --- a/bis-mobile/app/(tabs)/payments.tsx +++ b/bis-mobile/app/(tabs)/payments.tsx @@ -1,264 +1,10 @@ -/** - * Payment Rails tab — BIS Mobile (Expo) - * TigerBeetle-backed payment transfers with ledger view and account balances. - */ -import React, { useState } from "react"; -import { - View, - Text, - FlatList, - TextInput, - TouchableOpacity, - ActivityIndicator, - StyleSheet, - RefreshControl, - Alert, - Modal, -} from "react-native"; +import React from "react"; +import { ActivityIndicator, FlatList, RefreshControl, StyleSheet, Text, View } from "react-native"; import { trpc } from "@/lib/trpc"; -const STATUS_COLORS: Record = { - pending: "#eab308", - processing: "#3b82f6", - completed: "#22c55e", - failed: "#ef4444", - reversed: "#f97316", -}; - export default function PaymentsScreen() { - const [search, setSearch] = useState(""); - const [page, setPage] = useState(1); - const [showCreateModal, setShowCreateModal] = useState(false); - const [form, setForm] = useState({ - originatorAccount: "", - beneficiaryAccount: "", - amount: "", - currency: "NGN", - narration: "", - purposeCode: "TRANSFER", - }); - - const utils = trpc.useUtils(); - - const { data, isLoading, refetch, isFetching } = trpc.paymentRails.listTransfers.useQuery({ - search, - page, - limit: 20, - }); - - const { data: queueData } = trpc.paymentRails.getQueueStats.useQuery(); - - const createMutation = trpc.paymentRails.initiateTransfer.useMutation({ - onSuccess: () => { - setShowCreateModal(false); - setForm({ originatorAccount: "", beneficiaryAccount: "", amount: "", currency: "NGN", narration: "", purposeCode: "TRANSFER" }); - utils.paymentRails.listTransfers.invalidate(); - Alert.alert("Transfer Initiated", "Payment transfer has been queued"); - }, - onError: (err) => Alert.alert("Error", err.message), - }); - - const items = (data as { data?: unknown[] })?.data ?? []; - const total = (data as { total?: number })?.total ?? 0; - const queueStats = queueData as Record | undefined; - - const renderItem = ({ item }: { item: unknown }) => { - const tx = item as Record; - const status = (tx.status as string) ?? "pending"; - return ( - - - {(tx.txRef as string) ?? "—"} - - {status.toUpperCase()} - - - - {(tx.currency as string) ?? "NGN"} {Number(tx.amount ?? 0).toLocaleString()} - - From: {(tx.originatorName as string) ?? (tx.originatorAccount as string) ?? "—"} - To: {(tx.beneficiaryName as string) ?? (tx.beneficiaryAccount as string) ?? "—"} - {tx.narration && Narration: {tx.narration as string}} - {tx.purposeCode && Purpose: {tx.purposeCode as string}} - - {tx.createdAt ? new Date(tx.createdAt as string).toLocaleString() : "—"} - - - ); - }; - - return ( - - - - Payment Rails - {total} total transfers - - setShowCreateModal(true)}> - + Transfer - - - - {/* Queue Stats */} - {queueStats && ( - - - {String(queueStats.pending ?? 0)} - Pending - - - {String(queueStats.processing ?? 0)} - Processing - - - {String(queueStats.completed ?? 0)} - Completed - - - {String(queueStats.failed ?? 0)} - Failed - - - )} - - { setSearch(t); setPage(1); }} - /> - - {isLoading ? ( - - ) : ( - String(i)} - renderItem={renderItem} - refreshControl={ - - } - ListEmptyComponent={No payment transfers found} - onEndReached={() => { - if (items.length < total) setPage((p) => p + 1); - }} - onEndReachedThreshold={0.5} - /> - )} - - {/* Initiate Transfer Modal */} - - - - Initiate Transfer - setForm((f) => ({ ...f, originatorAccount: t }))} - keyboardType="numeric" - /> - setForm((f) => ({ ...f, beneficiaryAccount: t }))} - keyboardType="numeric" - /> - setForm((f) => ({ ...f, amount: t }))} - keyboardType="numeric" - /> - setForm((f) => ({ ...f, narration: t }))} - /> - - setShowCreateModal(false)} - > - Cancel - - { - if (!form.originatorAccount || !form.beneficiaryAccount || !form.amount) - return Alert.alert("Error", "Account numbers and amount are required"); - createMutation.mutate({ - originatorAccount: form.originatorAccount, - beneficiaryAccount: form.beneficiaryAccount, - amount: Number(form.amount), - currency: form.currency, - narration: form.narration, - purposeCode: form.purposeCode, - }); - }} - > - - {createMutation.isPending ? "Initiating…" : "Initiate"} - - - - - - - - ); + const { data, isLoading, isFetching, refetch } = trpc.paymentRails.listTransfers.useQuery({ status: "all", limit: 30 }); + const transfers = data?.items ?? []; + return Payment transfers{isLoading ? : item.txRef} refreshControl={ void refetch()} tintColor="#22c55e" />} renderItem={({ item }) => {item.txRef}{item.currency} {Number(item.amount ?? 0).toLocaleString()}{item.beneficiaryName ?? "Beneficiary withheld"}{item.status.toUpperCase()}} ListEmptyComponent={No authorised transfer records} />}; } - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: "#0f172a", padding: 16 }, - header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, - title: { fontSize: 22, fontWeight: "700", color: "#f8fafc" }, - subtitle: { fontSize: 13, color: "#94a3b8" }, - createBtn: { backgroundColor: "#3b82f6", borderRadius: 8, paddingHorizontal: 14, paddingVertical: 8 }, - createBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, - statsRow: { flexDirection: "row", gap: 8, marginBottom: 12 }, - statCard: { flex: 1, backgroundColor: "#1e293b", borderRadius: 8, padding: 10, alignItems: "center" }, - statValue: { fontSize: 18, fontWeight: "700", color: "#f8fafc" }, - statLabel: { fontSize: 10, color: "#94a3b8", marginTop: 2 }, - search: { - backgroundColor: "#1e293b", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 8, - marginBottom: 12, - fontSize: 14, - }, - card: { backgroundColor: "#1e293b", borderRadius: 10, padding: 14, marginBottom: 10 }, - cardHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }, - txRef: { fontSize: 13, fontWeight: "600", color: "#f8fafc" }, - amount: { fontSize: 16, fontWeight: "700", color: "#f8fafc", marginBottom: 4 }, - badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, - badgeText: { fontSize: 10, fontWeight: "700", color: "#fff" }, - detail: { fontSize: 12, color: "#94a3b8", marginTop: 2 }, - timestamp: { fontSize: 11, color: "#475569", marginTop: 6 }, - empty: { textAlign: "center", color: "#64748b", marginTop: 48 }, - modalOverlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.7)", justifyContent: "flex-end" }, - modalContent: { backgroundColor: "#1e293b", borderTopLeftRadius: 16, borderTopRightRadius: 16, padding: 20 }, - modalTitle: { fontSize: 18, fontWeight: "700", color: "#f8fafc", marginBottom: 16 }, - input: { - backgroundColor: "#0f172a", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 10, - marginBottom: 12, - fontSize: 14, - }, - modalActions: { flexDirection: "row", gap: 12, marginTop: 4 }, - modalBtn: { flex: 1, borderRadius: 8, paddingVertical: 12, alignItems: "center" }, - modalBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, -}); +const styles=StyleSheet.create({container:{flex:1,backgroundColor:"#0f172a",padding:16},title:{fontSize:22,fontWeight:"700",color:"#f8fafc",marginBottom:16},card:{backgroundColor:"#1e293b",padding:14,borderRadius:10,marginBottom:10},ref:{color:"#f8fafc",fontWeight:"700"},detail:{color:"#94a3b8",marginTop:3},status:{color:"#22c55e",fontSize:11,marginTop:8,fontWeight:"700"},empty:{color:"#64748b",textAlign:"center",marginTop:48}}); diff --git a/bis-mobile/app/(tabs)/profile.tsx b/bis-mobile/app/(tabs)/profile.tsx index 62b98b23..ebc4a203 100644 --- a/bis-mobile/app/(tabs)/profile.tsx +++ b/bis-mobile/app/(tabs)/profile.tsx @@ -55,11 +55,7 @@ export default function ProfileScreen() { Account Details - - {/* App info */} diff --git a/bis-mobile/app/(tabs)/reports.tsx b/bis-mobile/app/(tabs)/reports.tsx index f1ddc61a..a93b7858 100644 --- a/bis-mobile/app/(tabs)/reports.tsx +++ b/bis-mobile/app/(tabs)/reports.tsx @@ -1,307 +1,13 @@ -/** - * Regulatory Reports tab — BIS Mobile (Expo) - * CBN/NFIU regulatory report submissions with CRUD and filing workflow. - */ import React, { useState } from "react"; -import { - View, - Text, - FlatList, - TextInput, - TouchableOpacity, - ActivityIndicator, - StyleSheet, - RefreshControl, - Alert, - Modal, -} from "react-native"; +import { ActivityIndicator, Alert, FlatList, RefreshControl, StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { trpc } from "@/lib/trpc"; - -const STATUS_COLORS: Record = { - draft: "#64748b", - pending_review: "#eab308", - approved: "#22c55e", - submitted: "#3b82f6", - rejected: "#ef4444", -}; - -const REPORT_TYPES = ["CTR", "STR", "ANNUAL", "QUARTERLY", "MONTHLY", "ADHOC"]; - +const LIMIT = 20; export default function ReportsScreen() { - const [search, setSearch] = useState(""); - const [page, setPage] = useState(1); - const [showCreateModal, setShowCreateModal] = useState(false); - const [form, setForm] = useState({ - reportType: "CTR", - title: "", - description: "", - periodStart: "", - periodEnd: "", - }); - + const [offset, setOffset] = useState(0); const utils = trpc.useUtils(); - - const { data, isLoading, refetch, isFetching } = trpc.regulatoryReports.list.useQuery({ - search, - page, - limit: 20, - }); - - const createMutation = trpc.regulatoryReports.create.useMutation({ - onSuccess: () => { - setShowCreateModal(false); - setForm({ reportType: "CTR", title: "", description: "", periodStart: "", periodEnd: "" }); - utils.regulatoryReports.list.invalidate(); - }, - onError: (err) => Alert.alert("Error", err.message), - }); - - const submitMutation = trpc.regulatoryReports.submit.useMutation({ - onSuccess: () => { - utils.regulatoryReports.list.invalidate(); - Alert.alert("Submitted", "Report submitted to regulator"); - }, - onError: (err) => Alert.alert("Error", err.message), - }); - - const deleteMutation = trpc.regulatoryReports.delete.useMutation({ - onSuccess: () => utils.regulatoryReports.list.invalidate(), - onError: (err) => Alert.alert("Error", err.message), - }); - - const items = (data as { data?: unknown[] })?.data ?? []; - const total = (data as { total?: number })?.total ?? 0; - - const renderItem = ({ item }: { item: unknown }) => { - const r = item as Record; - const status = (r.status as string) ?? "draft"; - return ( - - - - {(r.title as string) ?? "—"} - {(r.reportType as string) ?? "—"} - - - {status.replace("_", " ").toUpperCase()} - - - Ref: {(r.reportRef as string) ?? "—"} - {r.periodStart && r.periodEnd && ( - - Period: {new Date(r.periodStart as string).toLocaleDateString()} — {new Date(r.periodEnd as string).toLocaleDateString()} - - )} - {r.description && ( - {r.description as string} - )} - - {r.createdAt ? new Date(r.createdAt as string).toLocaleString() : "—"} - - - {(status === "draft" || status === "approved") && ( - - Alert.alert("Submit Report", "Submit this regulatory report?", [ - { text: "Cancel", style: "cancel" }, - { text: "Submit", onPress: () => submitMutation.mutate({ id: r.id as number }) }, - ]) - } - > - Submit - - )} - {status === "draft" && ( - - Alert.alert("Delete", "Delete this report?", [ - { text: "Cancel", style: "cancel" }, - { - text: "Delete", - style: "destructive", - onPress: () => deleteMutation.mutate({ id: r.id as number }), - }, - ]) - } - > - Delete - - )} - - - ); - }; - - return ( - - - - Regulatory Reports - {total} total reports - - setShowCreateModal(true)}> - + New Report - - - - { setSearch(t); setPage(1); }} - /> - - {isLoading ? ( - - ) : ( - String(i)} - renderItem={renderItem} - refreshControl={ - - } - ListEmptyComponent={No regulatory reports found} - onEndReached={() => { - if (items.length < total) setPage((p) => p + 1); - }} - onEndReachedThreshold={0.5} - /> - )} - - {/* Create Report Modal */} - - - - New Regulatory Report - - {/* Report Type Selector */} - Report Type - - {REPORT_TYPES.map((t) => ( - setForm((f) => ({ ...f, reportType: t }))} - > - - {t} - - - ))} - - - setForm((f) => ({ ...f, title: t }))} - /> - setForm((f) => ({ ...f, periodStart: t }))} - /> - setForm((f) => ({ ...f, periodEnd: t }))} - /> - setForm((f) => ({ ...f, description: t }))} - multiline - /> - - setShowCreateModal(false)} - > - Cancel - - { - if (!form.title.trim()) return Alert.alert("Error", "Title is required"); - createMutation.mutate({ - reportType: form.reportType, - title: form.title, - description: form.description, - periodStart: form.periodStart || undefined, - periodEnd: form.periodEnd || undefined, - }); - }} - > - - {createMutation.isPending ? "Creating…" : "Create Draft"} - - - - - - - - ); + const { data, isLoading, isFetching, refetch } = trpc.regulatoryReports.list.useQuery({ offset, limit: LIMIT }); + const transition = trpc.regulatoryReports.transition.useMutation({ onSuccess: () => void utils.regulatoryReports.list.invalidate(), onError: () => Alert.alert("Unable to submit", "The report status was not changed.") }); + const reports = data?.items ?? []; + return Regulatory reports{isLoading ? : String(item.id)} refreshControl={ void refetch()} tintColor="#3b82f6" />} renderItem={({ item }) => {item.reportRef}{item.type}{item.status.replace("_", " ").toUpperCase()}{item.status === "draft" || item.status === "reviewed" ? Alert.alert("Submit report", "Submit this report?", [{ text: "Cancel", style: "cancel" }, { text: "Submit", onPress: () => transition.mutate({ id: item.id, status: "submitted" }) }])}>{transition.isPending ? "Submitting…" : "Submit"} : null}} ListEmptyComponent={No authorised reports} onEndReached={() => { if (offset + reports.length < (data?.total ?? 0)) setOffset((value) => value + LIMIT); }} onEndReachedThreshold={0.5} />}; } - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: "#0f172a", padding: 16 }, - header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, - title: { fontSize: 22, fontWeight: "700", color: "#f8fafc" }, - subtitle: { fontSize: 13, color: "#94a3b8" }, - createBtn: { backgroundColor: "#3b82f6", borderRadius: 8, paddingHorizontal: 14, paddingVertical: 8 }, - createBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, - search: { - backgroundColor: "#1e293b", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 8, - marginBottom: 12, - fontSize: 14, - }, - card: { backgroundColor: "#1e293b", borderRadius: 10, padding: 14, marginBottom: 10 }, - cardHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 6 }, - reportTitle: { fontSize: 14, fontWeight: "600", color: "#f8fafc" }, - reportType: { fontSize: 11, color: "#3b82f6", marginTop: 2 }, - badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, - badgeText: { fontSize: 10, fontWeight: "700", color: "#fff" }, - detail: { fontSize: 12, color: "#94a3b8", marginTop: 2 }, - timestamp: { fontSize: 11, color: "#475569", marginTop: 6 }, - actions: { flexDirection: "row", gap: 8, marginTop: 10 }, - actionBtn: { borderRadius: 6, paddingHorizontal: 10, paddingVertical: 6 }, - actionBtnText: { fontSize: 12, color: "#fff", fontWeight: "600" }, - empty: { textAlign: "center", color: "#64748b", marginTop: 48 }, - modalOverlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.7)", justifyContent: "flex-end" }, - modalContent: { backgroundColor: "#1e293b", borderTopLeftRadius: 16, borderTopRightRadius: 16, padding: 20 }, - modalTitle: { fontSize: 18, fontWeight: "700", color: "#f8fafc", marginBottom: 16 }, - label: { fontSize: 12, color: "#94a3b8", marginBottom: 8 }, - typeRow: { flexDirection: "row", flexWrap: "wrap", gap: 8, marginBottom: 12 }, - typeChip: { borderRadius: 6, paddingHorizontal: 10, paddingVertical: 6, backgroundColor: "#334155" }, - typeChipActive: { backgroundColor: "#3b82f6" }, - typeChipText: { fontSize: 12, color: "#94a3b8", fontWeight: "600" }, - typeChipTextActive: { color: "#fff" }, - input: { - backgroundColor: "#0f172a", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 10, - marginBottom: 12, - fontSize: 14, - }, - modalActions: { flexDirection: "row", gap: 12, marginTop: 4 }, - modalBtn: { flex: 1, borderRadius: 8, paddingVertical: 12, alignItems: "center" }, - modalBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, -}); +const styles = StyleSheet.create({container:{flex:1,backgroundColor:"#0f172a",padding:16},title:{fontSize:22,fontWeight:"700",color:"#f8fafc",marginBottom:16},card:{backgroundColor:"#1e293b",padding:14,borderRadius:10,marginBottom:10},ref:{color:"#f8fafc",fontWeight:"700"},detail:{color:"#94a3b8",marginTop:4},status:{color:"#3b82f6",fontSize:11,marginTop:8,fontWeight:"700"},button:{backgroundColor:"#2563eb",borderRadius:6,padding:9,marginTop:10,alignSelf:"flex-start"},buttonText:{color:"#fff",fontWeight:"700",fontSize:12},empty:{color:"#64748b",textAlign:"center",marginTop:48}}); diff --git a/bis-mobile/app/(tabs)/sar.tsx b/bis-mobile/app/(tabs)/sar.tsx index 303ffce6..1789bfc2 100644 --- a/bis-mobile/app/(tabs)/sar.tsx +++ b/bis-mobile/app/(tabs)/sar.tsx @@ -1,262 +1,13 @@ -/** - * SAR Filings tab — BIS Mobile (Expo) - * Suspicious Activity Reports with full CRUD and submission workflow. - */ import React, { useState } from "react"; -import { - View, - Text, - FlatList, - TextInput, - TouchableOpacity, - ActivityIndicator, - StyleSheet, - RefreshControl, - Alert, - Modal, -} from "react-native"; +import { ActivityIndicator, Alert, FlatList, RefreshControl, StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { trpc } from "@/lib/trpc"; - -const STATUS_COLORS: Record = { - draft: "#64748b", - pending: "#eab308", - submitted: "#3b82f6", - accepted: "#22c55e", - rejected: "#ef4444", -}; - +const LIMIT = 20; export default function SARScreen() { - const [search, setSearch] = useState(""); - const [page, setPage] = useState(1); - const [showCreateModal, setShowCreateModal] = useState(false); - const [form, setForm] = useState({ subject: "", description: "", amount: "", currency: "NGN" }); - + const [offset, setOffset] = useState(0); const utils = trpc.useUtils(); - - const { data, isLoading, refetch, isFetching } = trpc.sar.list.useQuery({ - search, - page, - limit: 20, - }); - - const createMutation = trpc.sar.create.useMutation({ - onSuccess: () => { - setShowCreateModal(false); - setForm({ subject: "", description: "", amount: "", currency: "NGN" }); - utils.sar.list.invalidate(); - }, - onError: (err) => Alert.alert("Error", err.message), - }); - - const submitMutation = trpc.sar.submit.useMutation({ - onSuccess: () => { - utils.sar.list.invalidate(); - Alert.alert("Submitted", "SAR submitted successfully"); - }, - onError: (err) => Alert.alert("Error", err.message), - }); - - const deleteMutation = trpc.sar.delete.useMutation({ - onSuccess: () => utils.sar.list.invalidate(), - onError: (err) => Alert.alert("Error", err.message), - }); - - const items = (data as { data?: unknown[] })?.data ?? []; - const total = (data as { total?: number })?.total ?? 0; - - const renderItem = ({ item }: { item: unknown }) => { - const r = item as Record; - const status = (r.status as string) ?? "draft"; - return ( - - - {(r.sarRef as string) ?? "—"} - - {status.toUpperCase()} - - - Subject: {(r.subject as string) ?? "—"} - {r.amount && ( - - Amount: {(r.currency as string) ?? "NGN"} {Number(r.amount).toLocaleString()} - - )} - - {r.createdAt ? new Date(r.createdAt as string).toLocaleString() : "—"} - - - {status === "draft" && ( - - Alert.alert("Submit SAR", "Submit this SAR?", [ - { text: "Cancel", style: "cancel" }, - { text: "Submit", onPress: () => submitMutation.mutate({ id: r.id as number }) }, - ]) - } - > - Submit - - )} - {status === "draft" && ( - - Alert.alert("Delete", "Delete this SAR?", [ - { text: "Cancel", style: "cancel" }, - { - text: "Delete", - style: "destructive", - onPress: () => deleteMutation.mutate({ id: r.id as number }), - }, - ]) - } - > - Delete - - )} - - - ); - }; - - return ( - - - - SAR Filings - {total} total SARs - - setShowCreateModal(true)}> - + New SAR - - - - { setSearch(t); setPage(1); }} - /> - - {isLoading ? ( - - ) : ( - String(i)} - renderItem={renderItem} - refreshControl={ - - } - ListEmptyComponent={No SAR filings found} - onEndReached={() => { - if (items.length < total) setPage((p) => p + 1); - }} - onEndReachedThreshold={0.5} - /> - )} - - - - - New SAR Filing - setForm((f) => ({ ...f, subject: t }))} - /> - setForm((f) => ({ ...f, amount: t }))} - keyboardType="numeric" - /> - setForm((f) => ({ ...f, description: t }))} - multiline - /> - - setShowCreateModal(false)} - > - Cancel - - { - if (!form.subject.trim() || !form.description.trim()) - return Alert.alert("Error", "Subject and description are required"); - createMutation.mutate({ - subject: form.subject, - description: form.description, - amount: form.amount ? Number(form.amount) : undefined, - currency: form.currency, - }); - }} - > - - {createMutation.isPending ? "Creating…" : "Create Draft"} - - - - - - - - ); + const { data, isLoading, isFetching, refetch } = trpc.sar.list.useQuery({ offset, limit: LIMIT }); + const submit = trpc.sar.submitForReview.useMutation({ onSuccess: () => void utils.sar.list.invalidate(), onError: () => Alert.alert("Unable to submit", "The SAR was not submitted for review.") }); + const filings = data?.items ?? []; + return SAR filings{isLoading ? : String(item.id)} refreshControl={ void refetch()} tintColor="#3b82f6" />} renderItem={({ item }) => {item.sarRef}{item.subjectName}{item.suspiciousCurrency} {Number(item.suspiciousAmount ?? 0).toLocaleString()}{item.status.replace("_", " ").toUpperCase()}{item.status === "draft" ? Alert.alert("Submit SAR", "Submit this SAR for review?", [{ text:"Cancel", style:"cancel" }, { text:"Submit", onPress: () => submit.mutate({ id:item.id }) }])}>{submit.isPending ? "Submitting…" : "Submit for review"} : null}} ListEmptyComponent={No authorised SAR filings} onEndReached={() => { if (offset + filings.length < (data?.total ?? 0)) setOffset((value) => value + LIMIT); }} onEndReachedThreshold={0.5} />}; } - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: "#0f172a", padding: 16 }, - header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, - title: { fontSize: 22, fontWeight: "700", color: "#f8fafc" }, - subtitle: { fontSize: 13, color: "#94a3b8" }, - createBtn: { backgroundColor: "#3b82f6", borderRadius: 8, paddingHorizontal: 14, paddingVertical: 8 }, - createBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, - search: { - backgroundColor: "#1e293b", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 8, - marginBottom: 12, - fontSize: 14, - }, - card: { backgroundColor: "#1e293b", borderRadius: 10, padding: 14, marginBottom: 10 }, - cardHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }, - sarRef: { fontSize: 14, fontWeight: "600", color: "#f8fafc" }, - badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, - badgeText: { fontSize: 10, fontWeight: "700", color: "#fff" }, - detail: { fontSize: 12, color: "#94a3b8", marginTop: 2 }, - timestamp: { fontSize: 11, color: "#475569", marginTop: 6 }, - actions: { flexDirection: "row", gap: 8, marginTop: 10 }, - actionBtn: { borderRadius: 6, paddingHorizontal: 10, paddingVertical: 6 }, - actionBtnText: { fontSize: 12, color: "#fff", fontWeight: "600" }, - empty: { textAlign: "center", color: "#64748b", marginTop: 48 }, - modalOverlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.7)", justifyContent: "flex-end" }, - modalContent: { backgroundColor: "#1e293b", borderTopLeftRadius: 16, borderTopRightRadius: 16, padding: 20 }, - modalTitle: { fontSize: 18, fontWeight: "700", color: "#f8fafc", marginBottom: 16 }, - input: { - backgroundColor: "#0f172a", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 10, - marginBottom: 12, - fontSize: 14, - }, - modalActions: { flexDirection: "row", gap: 12, marginTop: 4 }, - modalBtn: { flex: 1, borderRadius: 8, paddingVertical: 12, alignItems: "center" }, - modalBtnText: { color: "#fff", fontWeight: "600", fontSize: 14 }, -}); +const styles=StyleSheet.create({container:{flex:1,backgroundColor:"#0f172a",padding:16},title:{fontSize:22,fontWeight:"700",color:"#f8fafc",marginBottom:16},card:{backgroundColor:"#1e293b",padding:14,borderRadius:10,marginBottom:10},ref:{color:"#f8fafc",fontWeight:"700"},detail:{color:"#94a3b8",marginTop:3},status:{color:"#3b82f6",fontSize:11,marginTop:8,fontWeight:"700"},button:{backgroundColor:"#2563eb",borderRadius:6,padding:9,marginTop:10,alignSelf:"flex-start"},buttonText:{color:"#fff",fontWeight:"700",fontSize:12},empty:{color:"#64748b",textAlign:"center",marginTop:48}}); diff --git a/bis-mobile/app/(tabs)/screening.tsx b/bis-mobile/app/(tabs)/screening.tsx index 35a62474..3052a4e6 100644 --- a/bis-mobile/app/(tabs)/screening.tsx +++ b/bis-mobile/app/(tabs)/screening.tsx @@ -1,149 +1,11 @@ -/** - * Screening Records tab — BIS Mobile (Expo) - * Background checks: drug, MVR, criminal, employment, education. - */ import React, { useState } from "react"; -import { - View, - Text, - FlatList, - TextInput, - ActivityIndicator, - StyleSheet, - RefreshControl, - TouchableOpacity, - Alert, -} from "react-native"; +import { ActivityIndicator, FlatList, RefreshControl, StyleSheet, Text, View } from "react-native"; import { trpc } from "@/lib/trpc"; - -const STATUS_COLORS: Record = { - completed: "#22c55e", - pending: "#eab308", - failed: "#ef4444", - processing: "#3b82f6", -}; - +const LIMIT = 20; export default function ScreeningScreen() { - const [search, setSearch] = useState(""); - const [page, setPage] = useState(1); - - const { data, isLoading, refetch, isFetching } = trpc.screening.list.useQuery({ - search, - page, - limit: 20, - }); - - const deleteMutation = trpc.screening.delete.useMutation({ - onSuccess: () => refetch(), - onError: (err) => Alert.alert("Error", err.message), - }); - - const items = (data as { data?: unknown[] })?.data ?? []; - const total = (data as { total?: number })?.total ?? 0; - - const renderItem = ({ item }: { item: unknown }) => { - const rec = item as Record; - const status = (rec.status as string) ?? "pending"; - return ( - - - {(rec.subjectName as string) ?? "—"} - - {status.toUpperCase()} - - - Type: {(rec.screeningType as string) ?? "—"} - Ref: {(rec.requestRef as string) ?? "—"} - {rec.result && ( - - Result: {(rec.result as string).substring(0, 80)} - {(rec.result as string).length > 80 ? "…" : ""} - - )} - - {rec.createdAt ? new Date(rec.createdAt as string).toLocaleString() : "—"} - - - Alert.alert("Delete", "Delete this screening record?", [ - { text: "Cancel", style: "cancel" }, - { - text: "Delete", - style: "destructive", - onPress: () => deleteMutation.mutate({ id: rec.id as number }), - }, - ]) - } - > - Delete - - - ); - }; - - return ( - - Screening Records - {total} total records - - { setSearch(t); setPage(1); }} - /> - - {isLoading ? ( - - ) : ( - String(i)} - renderItem={renderItem} - refreshControl={ - - } - ListEmptyComponent={ - No screening records found - } - onEndReached={() => { - if (items.length < total) setPage((p) => p + 1); - }} - onEndReachedThreshold={0.5} - /> - )} - - ); + const [offset, setOffset] = useState(0); + const { data, isLoading, isFetching, refetch } = trpc.screening.list.useQuery({ offset, limit: LIMIT }); + const records = data?.records ?? []; + return Screening records{isLoading ? : String(item.id)} refreshControl={ void refetch()} tintColor="#3b82f6" />} renderItem={({ item }) => {item.subjectName ?? "Subject withheld"}Type: {item.type}Reference: {item.requestRef}{item.status.toUpperCase()}} ListEmptyComponent={No authorised screening records} onEndReached={() => { if (offset + records.length < (data?.total ?? 0)) setOffset((value) => value + LIMIT); }} onEndReachedThreshold={0.5} />}; } - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: "#0f172a", padding: 16 }, - title: { fontSize: 22, fontWeight: "700", color: "#f8fafc", marginBottom: 4 }, - subtitle: { fontSize: 13, color: "#94a3b8", marginBottom: 12 }, - search: { - backgroundColor: "#1e293b", - color: "#f8fafc", - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 8, - marginBottom: 12, - fontSize: 14, - }, - card: { - backgroundColor: "#1e293b", - borderRadius: 10, - padding: 14, - marginBottom: 10, - }, - cardHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }, - name: { fontSize: 14, fontWeight: "600", color: "#f8fafc", flex: 1 }, - badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, - badgeText: { fontSize: 10, fontWeight: "700", color: "#fff" }, - detail: { fontSize: 12, color: "#94a3b8", marginTop: 2 }, - timestamp: { fontSize: 11, color: "#475569", marginTop: 6 }, - deleteBtn: { marginTop: 8, alignSelf: "flex-end" }, - deleteBtnText: { fontSize: 12, color: "#ef4444" }, - empty: { textAlign: "center", color: "#64748b", marginTop: 48 }, -}); +const styles=StyleSheet.create({container:{flex:1,backgroundColor:"#0f172a",padding:16},title:{fontSize:22,fontWeight:"700",color:"#f8fafc",marginBottom:16},card:{backgroundColor:"#1e293b",padding:14,borderRadius:10,marginBottom:10},name:{color:"#f8fafc",fontWeight:"700"},detail:{color:"#94a3b8",marginTop:3},status:{color:"#3b82f6",fontSize:11,marginTop:8,fontWeight:"700"},empty:{color:"#64748b",textAlign:"center",marginTop:48}}); diff --git a/bis-mobile/app/_layout.tsx b/bis-mobile/app/_layout.tsx index f6583079..f28f0efb 100644 --- a/bis-mobile/app/_layout.tsx +++ b/bis-mobile/app/_layout.tsx @@ -1,48 +1,42 @@ import { DarkTheme, ThemeProvider } from "@react-navigation/native"; -import { useFonts } from "expo-font"; import { Stack } from "expo-router"; import * as SplashScreen from "expo-splash-screen"; import { StatusBar } from "expo-status-bar"; import { useEffect } from "react"; import "react-native-reanimated"; -import { TRPCProvider } from "@/lib/trpc"; import { usePushNotifications } from "@/hooks/usePushNotifications"; +import { TRPCProvider } from "@/lib/trpc"; -// Prevent the splash screen from auto-hiding before asset loading is complete. -SplashScreen.preventAutoHideAsync(); - -export default function RootLayout() { - const [loaded] = useFonts({ - SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"), - }); +// Prevent the splash screen from auto-hiding until the root navigator is ready. +void SplashScreen.preventAutoHideAsync(); - useEffect(() => { - if (loaded) { - SplashScreen.hideAsync(); - } - }, [loaded]); +function RootNavigator() { + usePushNotifications(); - if (!loaded) { - return null; - } + return ( + + + + + + + + + + + + + ); +} - // Activate push notification listener + deep-link routing - usePushNotifications(); +export default function RootLayout() { + useEffect(() => { + void SplashScreen.hideAsync(); + }, []); return ( - - - - - - - - - - - - + ); } diff --git a/bis-mobile/app/alerts/[id].tsx b/bis-mobile/app/alerts/[id].tsx index 1ea0bd70..05720f44 100644 --- a/bis-mobile/app/alerts/[id].tsx +++ b/bis-mobile/app/alerts/[id].tsx @@ -39,7 +39,7 @@ export default function AlertDetailScreen() { { status: "active", limit: 50 }, { enabled: escalateOpen } ); - const agents: any[] = Array.isArray(agentsData) ? agentsData : (agentsData as any)?.agents ?? []; + const agents = agentsData ?? []; const acknowledgeMutation = trpc.alerts.acknowledge.useMutation({ onSuccess: () => utils.alerts.getById.invalidate({ id: alertId }), @@ -82,9 +82,9 @@ export default function AlertDetailScreen() { ); } - const sc = SEV_COLOR[(alert as any).severity] ?? COLORS.muted; - const isResolved = (alert as any).resolved; - const isAcknowledged = (alert as any).acknowledged; + const sc = SEV_COLOR[alert.severity] ?? COLORS.muted; + const isResolved = alert.resolved; + const isAcknowledged = alert.acknowledged; return ( <> @@ -93,7 +93,7 @@ export default function AlertDetailScreen() { - {(alert as any).severity?.toUpperCase()} ALERT + {alert.severity?.toUpperCase()} ALERT {isResolved && ( @@ -104,13 +104,13 @@ export default function AlertDetailScreen() { {/* Title */} - {(alert as any).title} + {alert.title} {/* Body */} - {(alert as any).body && ( + {alert.body && ( Details - {(alert as any).body} + {alert.body} )} @@ -118,22 +118,22 @@ export default function AlertDetailScreen() { Metadata - - - - + + + + {/* Linked investigation */} - {(alert as any).subjectRef && ( + {alert.subjectRef && ( router.push(`/investigation/${(alert as any).subjectRef}` as any)} + onPress={() => router.push({ pathname: "/investigation/[id]", params: { id: alert.subjectRef } })} > - View Investigation {(alert as any).subjectRef} + View Investigation {alert.subjectRef} )} @@ -206,9 +206,9 @@ export default function AlertDetailScreen() { {/* Agent list */} String(a.id)} + keyExtractor={(agent) => String(agent.id)} style={styles.agentList} - renderItem={({ item }: { item: any }) => { + renderItem={({ item }) => { const isChosen = selectedAgentId === String(item.id); return ( = { + blink: "blink", + turn_left: "head_turn_left", + turn_right: "head_turn_right", + smile: "smile", + nod: "nod", +}; + // ── Step indicator ──────────────────────────────────────────────────────────── function StepIndicator({ current, total }: { current: number; total: number }) { return ( @@ -152,7 +163,7 @@ function LivenessStep({ } // ── Selfie capture step ─────────────────────────────────────────────────────── -function SelfieStep({ onCapture }: { onCapture: (uri: string) => void }) { +function SelfieStep({ onCapture }: { onCapture: (capture: { uri: string; base64: string }) => void }) { const cameraRef = useRef(null); const [capturing, setCapturing] = useState(false); @@ -161,12 +172,12 @@ function SelfieStep({ onCapture }: { onCapture: (uri: string) => void }) { setCapturing(true); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); try { - const photo = await cameraRef.current.takePictureAsync({ quality: 0.8, base64: false }); - if (photo?.uri) { + const photo = await cameraRef.current.takePictureAsync({ quality: 0.8, base64: true }); + if (photo?.uri && photo.base64) { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); - onCapture(photo.uri); + onCapture({ uri: photo.uri, base64: photo.base64 }); } - } catch (err) { + } catch { Alert.alert("Capture Failed", "Could not capture photo. Please try again."); } finally { setCapturing(false); @@ -198,7 +209,7 @@ function SelfieStep({ onCapture }: { onCapture: (uri: string) => void }) { } // ── Document capture step ───────────────────────────────────────────────────── -function DocumentStep({ onCapture }: { onCapture: (uri: string) => void }) { +function DocumentStep({ onCapture }: { onCapture: (capture: { uri: string; base64: string }) => void }) { const cameraRef = useRef(null); const [capturing, setCapturing] = useState(false); @@ -207,10 +218,10 @@ function DocumentStep({ onCapture }: { onCapture: (uri: string) => void }) { setCapturing(true); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); try { - const photo = await cameraRef.current.takePictureAsync({ quality: 0.9, base64: false }); - if (photo?.uri) { + const photo = await cameraRef.current.takePictureAsync({ quality: 0.9, base64: true }); + if (photo?.uri && photo.base64) { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); - onCapture(photo.uri); + onCapture({ uri: photo.uri, base64: photo.base64 }); } } catch { Alert.alert("Capture Failed", "Could not capture document. Please try again."); @@ -223,9 +234,10 @@ function DocumentStep({ onCapture }: { onCapture: (uri: string) => void }) { const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, quality: 0.9, + base64: true, }); - if (!result.canceled && result.assets[0]) { - onCapture(result.assets[0].uri); + if (!result.canceled && result.assets[0]?.base64) { + onCapture({ uri: result.assets[0].uri, base64: result.assets[0].base64 }); } }; @@ -265,11 +277,15 @@ function ConfirmStep({ documentUri, onEnroll, isEnrolling, + subjectRef, + onSubjectRefChange, }: { selfieUri: string; documentUri: string; onEnroll: () => void; isEnrolling: boolean; + subjectRef: string; + onSubjectRefChange: (value: string) => void; }) { return ( @@ -300,6 +316,16 @@ function ConfirmStep({ ))} + + ("permissions"); const [selfieUri, setSelfieUri] = useState(null); + const [selfieBase64, setSelfieBase64] = useState(null); const [documentUri, setDocumentUri] = useState(null); - - const { data: challengesData } = trpc.biometric.getChallenges.useQuery(); - const challenges: LivenessChallenge[] = (challengesData as any)?.challenges ?? [ - { id: "blink", type: "blink", instruction: "Blink both eyes slowly twice" }, - { id: "turn_left", type: "head_turn_left", instruction: "Turn your head slowly to the left" }, - { id: "turn_right", type: "head_turn_right", instruction: "Turn your head slowly to the right" }, - ]; + const [documentBase64, setDocumentBase64] = useState(null); + const [subjectRef, setSubjectRef] = useState(""); + + const { + data: challengesData, + isLoading: challengesLoading, + error: challengesError, + refetch: refetchChallenges, + } = trpc.biometric.getChallenges.useQuery(); + const challenges: LivenessChallenge[] = (challengesData?.challenges ?? []).flatMap((challenge) => { + const type = CHALLENGE_DISPLAY_TYPES[challenge.id]; + return type ? [{ id: challenge.id, type, instruction: challenge.label }] : []; + }); const enrollMutation = trpc.biometric.fullEnrollment.useMutation({ onSuccess: () => { @@ -340,14 +373,19 @@ export default function BiometricEnrollmentScreen() { }); const handleEnroll = useCallback(() => { - if (!selfieUri || !documentUri) return; + if (!selfieUri || !selfieBase64 || !documentUri || !documentBase64 || !subjectRef.trim()) { + Alert.alert("Subject reference required", "Enter the authorised subject reference before enrollment."); + return; + } enrollMutation.mutate({ - selfieBase64: selfieUri, // In production: convert to base64 - documentBase64: documentUri, - documentType: "nin", - challengeResults: challenges.map(c => ({ challengeId: c.id, passed: true })), + enrollImageBase64: selfieBase64, + livenessImageBase64: selfieBase64, + subjectRef: subjectRef.trim(), + documentImageBase64: documentBase64, + documentType: "NIN_SLIP", + challenge: "blink", }); - }, [selfieUri, documentUri, challenges, enrollMutation]); + }, [selfieUri, selfieBase64, documentUri, documentBase64, subjectRef, enrollMutation]); const STEPS: Step[] = ["permissions", "liveness", "selfie", "document", "confirm", "done"]; const stepIndex = STEPS.indexOf(step); @@ -370,23 +408,41 @@ export default function BiometricEnrollmentScreen() { setStep("liveness")} /> )} {step === "liveness" && ( - setStep("selfie")} - /> + challengesLoading ? ( + + + Loading authorised liveness challenges… + + ) : challengesError || challenges.length === 0 ? ( + + + Liveness Check Unavailable + Authorised liveness challenges could not be loaded. Please retry before capturing biometric data. + refetchChallenges()}> + Retry + + + ) : ( + setStep("selfie")} + /> + ) )} {step === "selfie" && ( { - setSelfieUri(uri); + onCapture={(capture) => { + setSelfieUri(capture.uri); + setSelfieBase64(capture.base64); setStep("document"); }} /> )} {step === "document" && ( { - setDocumentUri(uri); + onCapture={(capture) => { + setDocumentUri(capture.uri); + setDocumentBase64(capture.base64); setStep("confirm"); }} /> @@ -397,6 +453,8 @@ export default function BiometricEnrollmentScreen() { documentUri={documentUri} onEnroll={handleEnroll} isEnrolling={enrollMutation.isPending} + subjectRef={subjectRef} + onSubjectRefChange={setSubjectRef} /> )} {step === "done" && ( @@ -459,6 +517,16 @@ const styles = StyleSheet.create({ justifyContent: "center", marginBottom: 20, }, + subjectRefInput: { + backgroundColor: COLORS.card, + borderColor: COLORS.border, + borderWidth: 1, + borderRadius: 10, + color: COLORS.text, + marginBottom: 14, + paddingHorizontal: 14, + paddingVertical: 12, + }, primaryButton: { backgroundColor: COLORS.primary, paddingVertical: 14, diff --git a/bis-mobile/app/kyc/camera.tsx b/bis-mobile/app/kyc/camera.tsx index 39bec7b9..904c7bf8 100644 --- a/bis-mobile/app/kyc/camera.tsx +++ b/bis-mobile/app/kyc/camera.tsx @@ -9,6 +9,7 @@ import { useState, useRef } from "react"; import { View, Text, + TextInput, StyleSheet, TouchableOpacity, Alert, @@ -36,6 +37,11 @@ const COLORS = { type DocType = "nin" | "passport" | "drivers_licence"; +type OcrResult = { + confidence: number | null; + reviewStatus: "pending_human_review"; +}; + const DOC_TYPES: { value: DocType; label: string; icon: keyof typeof Ionicons.glyphMap }[] = [ { value: "nin", label: "NIN Slip", icon: "card-outline" }, { value: "passport", label: "Passport", icon: "book-outline" }, @@ -48,13 +54,15 @@ export default function DocumentCameraScreen() { const [permission, requestPermission] = useCameraPermissions(); const [docType, setDocType] = useState("nin"); const [capturedUri, setCapturedUri] = useState(null); + const [capturedBase64, setCapturedBase64] = useState(null); + const [subjectRef, setSubjectRef] = useState(""); const [capturing, setCapturing] = useState(false); - const [ocrResult, setOcrResult] = useState | null>(null); + const [ocrResult, setOcrResult] = useState(null); const ocrMutation = trpc.biometric.ocrDocument.useMutation({ - onSuccess: (data: any) => { + onSuccess: (data) => { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); - setOcrResult(data?.fields ?? {}); + setOcrResult({ confidence: data.confidence, reviewStatus: data.reviewStatus }); }, onError: (err) => { Alert.alert("OCR Failed", err.message); @@ -79,9 +87,10 @@ export default function DocumentCameraScreen() { setCapturing(true); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); try { - const photo = await cameraRef.current.takePictureAsync({ quality: 0.9 }); - if (photo?.uri) { + const photo = await cameraRef.current.takePictureAsync({ quality: 0.9, base64: true }); + if (photo?.uri && photo.base64) { setCapturedUri(photo.uri); + setCapturedBase64(photo.base64); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } } catch { @@ -95,22 +104,29 @@ export default function DocumentCameraScreen() { const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, quality: 0.9, + base64: true, }); - if (!result.canceled && result.assets[0]) { + if (!result.canceled && result.assets[0]?.base64) { setCapturedUri(result.assets[0].uri); + setCapturedBase64(result.assets[0].base64); } }; const handleRunOCR = () => { - if (!capturedUri) return; + if (!capturedUri || !capturedBase64 || !subjectRef.trim()) { + Alert.alert("Subject reference required", "Enter the authorised subject reference before OCR."); + return; + } ocrMutation.mutate({ - imageBase64: capturedUri, - documentType: docType, + imageBase64: capturedBase64, + subjectRef: subjectRef.trim(), + documentType: docType === "nin" ? "NIN_SLIP" : docType === "passport" ? "PASSPORT" : "DRIVERS_LICENSE", }); }; const handleRetake = () => { setCapturedUri(null); + setCapturedBase64(null); setOcrResult(null); }; @@ -177,16 +193,30 @@ export default function DocumentCameraScreen() { {/* OCR result */} {ocrResult && ( - Extracted Fields - {Object.entries(ocrResult).map(([key, value]) => ( - - {key.replace(/_/g, " ").toUpperCase()} - {String(value)} - - ))} + OCR Review Submitted + + CONFIDENCE + + {ocrResult.confidence === null ? "Unavailable" : `${Math.round(ocrResult.confidence * 100)}%`} + + + + STATUS + {ocrResult.reviewStatus.replace(/_/g, " ")} + )} + + {/* Actions */} @@ -332,5 +362,15 @@ const styles = StyleSheet.create({ ocrRow: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 6, borderBottomWidth: 1, borderBottomColor: COLORS.border }, ocrKey: { fontSize: 11, color: COLORS.muted, fontWeight: "600" }, ocrValue: { fontSize: 12, color: COLORS.text, fontWeight: "500", maxWidth: "60%", textAlign: "right" }, + subjectRefInput: { + backgroundColor: COLORS.card, + borderColor: COLORS.border, + borderWidth: 1, + borderRadius: 10, + color: COLORS.text, + marginBottom: 14, + paddingHorizontal: 14, + paddingVertical: 12, + }, actionRow: { flexDirection: "row", gap: 12, justifyContent: "center" }, }); diff --git a/bis-mobile/hooks/usePushNotifications.ts b/bis-mobile/hooks/usePushNotifications.ts index 08d0b9f6..85859a6f 100644 --- a/bis-mobile/hooks/usePushNotifications.ts +++ b/bis-mobile/hooks/usePushNotifications.ts @@ -23,7 +23,7 @@ import { Platform } from "react-native"; import * as Notifications from "expo-notifications"; import * as Device from "expo-device"; import Constants from "expo-constants"; -import { useRouter } from "expo-router"; +import { useRouter, type Href } from "expo-router"; import { trpc } from "@/lib/trpc"; // Configure how foreground notifications are presented @@ -45,18 +45,20 @@ type NotificationData = { // ─── Deep-link resolver ─────────────────────────────────────────────────────── -function resolveRoute(data: NotificationData): string { +function resolveRoute(data: NotificationData): Href { const { type, id, ref } = data; const target = id ?? ref; switch (type) { case "alert": - return target ? `/alerts/${target}` : "/(tabs)/alerts"; + return target + ? { pathname: "/alerts/[id]", params: { id: String(target) } } + : "/(tabs)/alerts"; case "investigation": - return target ? `/investigation/${target}` : "/(tabs)/investigations"; case "field_task": - // Field tasks link to the parent investigation if ref is provided - return target ? `/investigation/${target}` : "/(tabs)/investigations"; + return target + ? { pathname: "/investigation/[id]", params: { id: String(target) } } + : "/(tabs)/investigations"; case "kyc": return "/(tabs)/kyc"; default: @@ -68,14 +70,14 @@ function resolveRoute(data: NotificationData): string { export function usePushNotifications() { const router = useRouter(); - const notificationListener = useRef(null); - const responseListener = useRef(null); - const registerToken = trpc.users.registerPushToken.useMutation(); + const notificationListener = useRef(null); + const responseListener = useRef(null); + const { mutate: registerPushToken } = trpc.users.registerPushToken.useMutation(); useEffect(() => { registerForPushNotificationsAsync().then((token) => { if (token) { - registerToken.mutate({ token }); + registerPushToken({ token }); } }); @@ -93,7 +95,7 @@ export function usePushNotifications() { const route = resolveRoute(data); // Small delay to ensure the navigator is mounted setTimeout(() => { - router.push(route as any); + router.push(route); }, 300); } ); @@ -104,7 +106,7 @@ export function usePushNotifications() { const data = response.notification.request.content.data as NotificationData; const route = resolveRoute(data); setTimeout(() => { - router.push(route as any); + router.push(route); }, 500); }); @@ -112,7 +114,7 @@ export function usePushNotifications() { notificationListener.current?.remove(); responseListener.current?.remove(); }; - }, [router]); + }, [router, registerPushToken]); } // ─── Permission + token registration ───────────────────────────────────────── @@ -142,7 +144,6 @@ async function registerForPushNotificationsAsync(): Promise { } if (finalStatus !== "granted") { - console.warn("[BIS] Push notification permission not granted"); return null; } @@ -151,15 +152,13 @@ async function registerForPushNotificationsAsync(): Promise { Constants.easConfig?.projectId; if (!projectId) { - console.warn("[BIS] No EAS project ID found — skipping push token registration"); return null; } try { const tokenData = await Notifications.getExpoPushTokenAsync({ projectId }); return tokenData.data; - } catch (err) { - console.warn("[BIS] Failed to get push token:", err); + } catch { return null; } } diff --git a/bis-mobile/lib/trpc.ts b/bis-mobile/lib/trpc.ts index 84bfc605..bd9d3862 100644 --- a/bis-mobile/lib/trpc.ts +++ b/bis-mobile/lib/trpc.ts @@ -66,9 +66,6 @@ export const trpcClient = trpc.createClient({ // ── Provider component ──────────────────────────────────────────────────────── export function TRPCProvider({ children }: { children: React.ReactNode }) { - return React.createElement( - trpc.Provider, - { client: trpcClient, queryClient }, - React.createElement(QueryClientProvider, { client: queryClient }, children) - ); + const content = React.createElement(QueryClientProvider, { client: queryClient }, children); + return React.createElement(trpc.Provider, { client: trpcClient, queryClient, children: content }); } diff --git a/bis-mobile/package.json b/bis-mobile/package.json index 4dc06e8b..d55fa55a 100644 --- a/bis-mobile/package.json +++ b/bis-mobile/package.json @@ -12,6 +12,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@babel/runtime": "7.29.2", "@expo/vector-icons": "^14.0.2", "@react-native-async-storage/async-storage": "1.23.1", "@react-navigation/native": "^6.1.17", @@ -21,14 +22,14 @@ "expo": "~51.0.28", "expo-camera": "~15.0.16", "expo-constants": "~16.0.2", - "expo-device": "^55.0.10", + "expo-device": "~6.0.2", "expo-file-system": "~17.0.1", "expo-font": "~12.0.9", "expo-haptics": "~13.0.1", "expo-image": "~1.13.0", - "expo-image-picker": "~15.0.7", + "expo-image-picker": "~15.1.0", "expo-linking": "~6.3.1", - "expo-notifications": "^55.0.13", + "expo-notifications": "~0.28.19", "expo-router": "~3.5.23", "expo-secure-store": "~13.0.2", "expo-splash-screen": "~0.27.5", @@ -39,7 +40,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.74.5", - "react-native-gesture-handler": "~2.17.1", + "react-native-gesture-handler": "~2.16.1", "react-native-reanimated": "~3.10.1", "react-native-safe-area-context": "4.10.5", "react-native-screens": "3.31.1", @@ -51,7 +52,7 @@ "@types/react": "~18.2.45", "@types/react-native": "~0.73.0", "eslint": "^8.57.0", - "eslint-config-expo": "~8.0.1", + "eslint-config-expo": "~7.1.2", "typescript": "~5.3.3" }, "private": true diff --git a/bis-mobile/pnpm-lock.yaml b/bis-mobile/pnpm-lock.yaml index 19c972ba..c2932402 100644 --- a/bis-mobile/pnpm-lock.yaml +++ b/bis-mobile/pnpm-lock.yaml @@ -8,15 +8,18 @@ importers: .: dependencies: + '@babel/runtime': + specifier: 7.29.2 + version: 7.29.2 '@expo/vector-icons': specifier: ^14.0.2 - version: 14.1.0(expo-font@12.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + version: 14.1.0(expo-font@12.0.10(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) '@react-native-async-storage/async-storage': specifier: 1.23.1 - version: 1.23.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0)) + version: 1.23.1(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1)) '@react-navigation/native': specifier: ^6.1.17 - version: 6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + version: 6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) '@tanstack/react-query': specifier: ^5.56.2 version: 5.95.2(react@18.2.0) @@ -28,58 +31,58 @@ importers: version: 11.15.0(@tanstack/react-query@5.95.2(react@18.2.0))(@trpc/client@11.15.0(@trpc/server@11.15.0(typescript@5.3.3))(typescript@5.3.3))(@trpc/server@11.15.0(typescript@5.3.3))(react@18.2.0)(typescript@5.3.3) expo: specifier: ~51.0.28 - version: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + version: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) expo-camera: specifier: ~15.0.16 - version: 15.0.16(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 15.0.16(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) expo-constants: specifier: ~16.0.2 - version: 16.0.2(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 16.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) expo-device: - specifier: ^55.0.10 - version: 55.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + specifier: ~6.0.2 + version: 6.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) expo-file-system: specifier: ~17.0.1 - version: 17.0.1(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 17.0.1(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) expo-font: specifier: ~12.0.9 - version: 12.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 12.0.10(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) expo-haptics: specifier: ~13.0.1 - version: 13.0.1(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 13.0.1(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) expo-image: specifier: ~1.13.0 - version: 1.13.0(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 1.13.0(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) expo-image-picker: - specifier: ~15.0.7 - version: 15.0.7(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + specifier: ~15.1.0 + version: 15.1.0(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) expo-linking: specifier: ~6.3.1 - version: 6.3.1(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 6.3.1(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) expo-notifications: - specifier: ^55.0.13 - version: 55.0.13(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)(typescript@5.3.3) + specifier: ~0.28.19 + version: 0.28.19(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) expo-router: specifier: ~3.5.23 - version: 3.5.24(7e9c78a356a2cc7044ab01920b075952) + version: 3.5.24(b51aa3c20ae172a8588f75896dca1775) expo-secure-store: specifier: ~13.0.2 - version: 13.0.2(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 13.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) expo-splash-screen: specifier: ~0.27.5 - version: 0.27.7(expo-modules-autolinking@1.11.3)(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 0.27.7(expo-modules-autolinking@1.11.3)(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) expo-status-bar: specifier: ~1.12.1 version: 1.12.1 expo-symbols: specifier: ~0.1.0 - version: 0.1.5(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 0.1.5(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) expo-system-ui: specifier: ~3.0.7 - version: 3.0.7(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 3.0.7(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) expo-web-browser: specifier: ~13.0.3 - version: 13.0.3(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + version: 13.0.3(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) react: specifier: 18.2.0 version: 18.2.0 @@ -88,19 +91,19 @@ importers: version: 18.2.0(react@18.2.0) react-native: specifier: 0.74.5 - version: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + version: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) react-native-gesture-handler: - specifier: ~2.17.1 - version: 2.17.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + specifier: ~2.16.1 + version: 2.16.2(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) react-native-reanimated: specifier: ~3.10.1 - version: 3.10.1(@babel/core@7.29.0)(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + version: 3.10.1(@babel/core@7.29.0(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) react-native-safe-area-context: specifier: 4.10.5 - version: 4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + version: 4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) react-native-screens: specifier: 3.31.1 - version: 3.31.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + version: 3.31.1(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) react-native-web: specifier: ~0.19.10 version: 0.19.13(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -110,19 +113,19 @@ importers: devDependencies: '@babel/core': specifier: ^7.24.0 - version: 7.29.0 + version: 7.29.0(supports-color@8.1.1) '@types/react': specifier: ~18.2.45 version: 18.2.79 '@types/react-native': specifier: ~0.73.0 - version: 0.73.0(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + version: 0.73.0(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) eslint: specifier: ^8.57.0 - version: 8.57.1 + version: 8.57.1(supports-color@8.1.1) eslint-config-expo: - specifier: ~8.0.1 - version: 8.0.1(eslint@8.57.1)(typescript@5.3.3) + specifier: ~7.1.2 + version: 7.1.2(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3) typescript: specifier: ~5.3.3 version: 5.3.3 @@ -887,7 +890,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {'0': node >=0.10.0} + engines: {node: '>=0.10.0'} '@expo/cli@0.18.31': resolution: {integrity: sha512-v9llw9fT3Uv+TCM6Xllo54t672CuYtinEQZ2LPJ2EJsCwuTc4Cd2gXQaouuIVD21VoeGQnr5JtJuWbF97sBKzQ==} @@ -896,21 +899,12 @@ packages: '@expo/code-signing-certificates@0.0.5': resolution: {integrity: sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw==} - '@expo/config-plugins@55.0.7': - resolution: {integrity: sha512-XZUoDWrsHEkH3yasnDSJABM/UxP5a1ixzRwU/M+BToyn/f0nTrSJJe/Ay/FpxkI4JSNz2n0e06I23b2bleXKVA==} - '@expo/config-plugins@8.0.11': resolution: {integrity: sha512-oALE1HwnLFthrobAcC9ocnR9KXLzfWEjgIe4CPe+rDsfC6GDs8dGYCXfRFoCEzoLN4TGYs9RdZ8r0KoCcNrm2A==} '@expo/config-types@51.0.3': resolution: {integrity: sha512-hMfuq++b8VySb+m9uNNrlpbvGxYc8OcFCUX9yTmi9tlx6A4k8SDabWFBgmnr4ao3wEArvWrtUQIfQCVtPRdpKA==} - '@expo/config-types@55.0.5': - resolution: {integrity: sha512-sCmSUZG4mZ/ySXvfyyBdhjivz8Q539X1NondwDdYG7s3SBsk+wsgPJzYsqgAG/P9+l0xWjUD2F+kQ1cAJ6NNLg==} - - '@expo/config@55.0.10': - resolution: {integrity: sha512-qCHxo9H1ZoeW+y0QeMtVZ3JfGmumpGrgUFX60wLWMarraoQZSe47ZUm9kJSn3iyoPjUtUNanO3eXQg+K8k4rag==} - '@expo/config@9.0.4': resolution: {integrity: sha512-g5ns5u1JSKudHYhjo1zaSfkJ/iZIcWmUmIQptMJZ6ag1C0ShL2sj8qdfU8MmAMuKLOgcIfSaiWlQnm4X3VJVkg==} @@ -920,16 +914,9 @@ packages: '@expo/env@0.3.0': resolution: {integrity: sha512-OtB9XVHWaXidLbHvrVDeeXa09yvTl3+IQN884sO6PhIi2/StXfgSH/9zC7IvzrDB8kW3EBJ1PPLuCUJ2hxAT7Q==} - '@expo/env@2.1.1': - resolution: {integrity: sha512-rVvHC4I6xlPcg+mAO09ydUi2Wjv1ZytpLmHOSzvXzBAz9mMrJggqCe4s4dubjJvi/Ino/xQCLhbaLCnTtLpikg==} - engines: {node: '>=20.12.0'} - '@expo/image-utils@0.5.1': resolution: {integrity: sha512-U/GsFfFox88lXULmFJ9Shfl2aQGcwoKPF7fawSCLixIKtMCpsI+1r0h+5i0nQnmt9tHuzXZDL8+Dg1z6OhkI9A==} - '@expo/image-utils@0.8.12': - resolution: {integrity: sha512-3KguH7kyKqq7pNwLb9j6BBdD/bjmNwXZG/HPWT6GWIXbwrvAJt2JNyYTP5agWJ8jbbuys1yuCzmkX+TU6rmI7A==} - '@expo/json-file@10.0.12': resolution: {integrity: sha512-inbDycp1rMAelAofg7h/mMzIe+Owx6F7pur3XdQ3EPTy00tme+4P6FWgHKUcjN8dBSrnbRNpSyh5/shzHyVCyQ==} @@ -954,22 +941,11 @@ packages: '@expo/plist@0.1.3': resolution: {integrity: sha512-GW/7hVlAylYg1tUrEASclw1MMk9FP4ZwyFAY/SUTJIhPDQHtfOlXREyWV3hhrHdX/K+pS73GNgdfT6E/e+kBbg==} - '@expo/plist@0.5.2': - resolution: {integrity: sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g==} - '@expo/prebuild-config@7.0.9': resolution: {integrity: sha512-9i6Cg7jInpnGEHN0jxnW0P+0BexnePiBzmbUvzSbRXpdXihYUX2AKMu73jgzxn5P1hXOSkzNS7umaY+BZ+aBag==} peerDependencies: expo-modules-autolinking: '>=0.8.1' - '@expo/require-utils@55.0.3': - resolution: {integrity: sha512-TS1m5tW45q4zoaTlt6DwmdYHxvFTIxoLrTHKOFrIirHIqIXnHCzpceg8wumiBi+ZXSaGY2gobTbfv+WVhJY6Fw==} - peerDependencies: - typescript: ^5.0.0 || ^5.0.0-0 - peerDependenciesMeta: - typescript: - optional: true - '@expo/rudder-sdk-node@1.1.1': resolution: {integrity: sha512-uy/hS/awclDJ1S88w9UGpc6Nm9XnNUjzOAAib1A3PVAnGQIwebg8DpFqOthFBTlZxeuV/BKbZ5jmTbtNZkp1WQ==} engines: {node: '>=12'} @@ -1022,6 +998,9 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@ide/backoff@1.0.0': + resolution: {integrity: sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1233,6 +1212,7 @@ packages: '@react-navigation/bottom-tabs@6.5.20': resolution: {integrity: sha512-ow6Z06iS4VqBO8d7FP+HsGjJLWt2xTWIvuWjpoCvsM/uQXzCRDIjBv9HaKcXbF0yTW7IMir0oDAbU5PFzEDdgA==} + deprecated: This version is no longer supported peerDependencies: '@react-navigation/native': ^6.0.0 react: '*' @@ -1242,11 +1222,13 @@ packages: '@react-navigation/core@6.4.17': resolution: {integrity: sha512-Nd76EpomzChWAosGqWOYE3ItayhDzIEzzZsT7PfGcRFDgW5miHV2t4MZcq9YIK4tzxZjVVpYbIynOOQQd1e0Cg==} + deprecated: This version is no longer supported peerDependencies: react: '*' '@react-navigation/elements@1.3.31': resolution: {integrity: sha512-bUzP4Awlljx5RKEExw8WYtif8EuQni2glDaieYROKTnaxsu9kEIA515sXQgUDZU4Ob12VoL7+z70uO3qrlfXcQ==} + deprecated: This version is no longer supported peerDependencies: '@react-navigation/native': ^6.0.0 react: '*' @@ -1264,12 +1246,14 @@ packages: '@react-navigation/native@6.1.18': resolution: {integrity: sha512-mIT9MiL/vMm4eirLcmw2h6h/Nm5FICtnYSdohq4vTLA2FF/6PNhByM7s8ffqoVfE5L0uAa6Xda1B7oddolUiGg==} + deprecated: This version is no longer supported peerDependencies: react: '*' react-native: '*' '@react-navigation/routers@6.1.9': resolution: {integrity: sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==} + deprecated: This version is no longer supported '@remix-run/node@2.17.4': resolution: {integrity: sha512-9A29JaYiGHDEmaiQuD1IlO/TrQxnnkj98GpytihU+Nz6yTt6RwzzyMMqTAoasRd1dPD4OeSaSqbwkcim/eE76Q==} @@ -1428,67 +1412,67 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.57.2': - resolution: {integrity: sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/eslint-plugin@7.18.0': + resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: - '@typescript-eslint/parser': ^8.57.2 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/parser@8.57.2': - resolution: {integrity: sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==} - 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.0.0' + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@typescript-eslint/project-service@8.57.2': - resolution: {integrity: sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/parser@7.18.0': + resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/scope-manager@8.57.2': - resolution: {integrity: sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@typescript-eslint/tsconfig-utils@8.57.2': - resolution: {integrity: sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/scope-manager@7.18.0': + resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} + engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/type-utils@8.57.2': - resolution: {integrity: sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/type-utils@7.18.0': + resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@typescript-eslint/types@8.57.2': - resolution: {integrity: sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@7.18.0': + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} + engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/typescript-estree@8.57.2': - resolution: {integrity: sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@7.18.0': + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@typescript-eslint/utils@8.57.2': - resolution: {integrity: sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/utils@7.18.0': + resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.56.0 - '@typescript-eslint/visitor-keys@8.57.2': - resolution: {integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@7.18.0': + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} + engines: {node: ^18.18.0 || >=20.0.0} '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} @@ -1609,11 +1593,12 @@ packages: '@xmldom/xmldom@0.7.13': resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} engines: {node: '>=10.0.0'} - deprecated: this version is no longer supported, please update to at least 0.8.* + deprecated: this version has critical issues, please update to the latest version '@xmldom/xmldom@0.8.11': resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version '@zxing/text-encoding@0.9.0': resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} @@ -1759,6 +1744,9 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + assert@2.1.0: + resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} + ast-types@0.15.2: resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} engines: {node: '>=4'} @@ -1828,10 +1816,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1871,10 +1855,6 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} - engines: {node: 18 || 20 || >=22} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2362,8 +2342,8 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-expo@8.0.1: - resolution: {integrity: sha512-r9PSgkuZk5Q5ALbk1yowYwEIj0oqO/ikRO9TNhpx2DzSOdK65y3urgFI04WYvQzMr9q1fnA62wr9iGfrsmF5pQ==} + eslint-config-expo@7.1.2: + resolution: {integrity: sha512-WxrDVNklN43Op0v3fglQfzL2bC7vqacUq9oVwJcGCUEDzdM7kGOR6pfEJiz3i3dQv3cFjHtct0CFEExep5c/dA==} peerDependencies: eslint: '>=8.10' @@ -2404,11 +2384,11 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-expo@0.1.4: - resolution: {integrity: sha512-YA7yiMacQbLJySuyJA0Eb5V65obqp6fVOWtw1JdYDRWC5MeToPrnNvhGDpk01Bv3Vm4ownuzUfvi89MXi1d6cg==} + eslint-plugin-expo@0.0.1: + resolution: {integrity: sha512-dNri81vunJ3T+N1YWWxjLU6ux6KiukwZ4ECXCOPp8hG7M4kuvPAb9YQSIM63AT0pbtfYH/a6htikhaQcRPjhRA==} engines: {node: '>=18.0.0'} peerDependencies: - eslint: '>=8.10' + eslint: '>=8' eslint-plugin-import@2.32.0: resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} @@ -2440,10 +2420,6 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2491,8 +2467,8 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - expo-application@55.0.10: - resolution: {integrity: sha512-5ccf+S6hsQz+doi907TOJxKzV5AKgAgw004z4FoDWSoGhfab0LUPg6uyvOspuU4cbNvqw8EAy08hZbVO8nKc9Q==} + expo-application@5.9.1: + resolution: {integrity: sha512-uAfLBNZNahnDZLRU41ZFmNSKtetHUT9Ua557/q189ua0AWV7pQjoVAx49E4953feuvqc9swtU3ScZ/hN1XO/FQ==} peerDependencies: expo: '*' @@ -2511,14 +2487,8 @@ packages: peerDependencies: expo: '*' - expo-constants@55.0.9: - resolution: {integrity: sha512-iBiXjZeuU5S/8docQeNzsVvtDy4w0zlmXBpFEi1ypwugceEpdQQab65TVRbusXAcwpNVxCPMpNlDssYp0Pli2g==} - peerDependencies: - expo: '*' - react-native: '*' - - expo-device@55.0.10: - resolution: {integrity: sha512-jvPImQg5LN7LNL57sGY6WaJxrWrPKXueUiDctZ7brLyiZfioS6wUieLwOTTkHUdtSSBtHkTvUg675w0sOzwlTg==} + expo-device@6.0.2: + resolution: {integrity: sha512-sCt91CuTmAuMXX4SlFOn4lIos2UIr8vb0jDstDDZXys6kErcj0uynC7bQAMreU5uRUTKMAl4MAMpKt9ufCXPBw==} peerDependencies: expo: '*' @@ -2542,8 +2512,8 @@ packages: peerDependencies: expo: '*' - expo-image-picker@15.0.7: - resolution: {integrity: sha512-u8qiPZNfDb+ap6PJ8pq2iTO7JKX+ikAUQ0K0c7gXGliKLxoXgDdDmXxz9/6QdICTshJBJlBvI0MwY5NWu7A/uw==} + expo-image-picker@15.1.0: + resolution: {integrity: sha512-6cE10S7d0qG7+pcHtsYukRKuFL44ssOM0/v+5JIBzRwxpIgGA6qokr8PxasFEpxOA9NpN0gwry33jmq1qLXJTg==} peerDependencies: expo: '*' @@ -2567,12 +2537,10 @@ packages: expo-modules-core@1.12.26: resolution: {integrity: sha512-y8yDWjOi+rQRdO+HY+LnUlz8qzHerUaw/LUjKPU/mX8PRXP4UUPEEp5fjAwBU44xjNmYSHWZDwet4IBBE+yQUA==} - expo-notifications@55.0.13: - resolution: {integrity: sha512-vbtSBcMkYtNTO+6WKdeOzysOqvtmiq/sQrUKJpYcB75m9hBFcAfI2klpXdUiGg5kMr/ygBmFENSolQt1B9QY8A==} + expo-notifications@0.28.19: + resolution: {integrity: sha512-rKKTnVQQ9XNQyTNwKmI9OlchhVu0XOZfRpImMqPFCJg6IwECM1izdas2SLCbE/GApg2Tw3U5R2fd26OnCtUU/w==} peerDependencies: expo: '*' - react: '*' - react-native: '*' expo-router@3.5.24: resolution: {integrity: sha512-wFi+PIUrOntF5cgg0PgBMlkxEZlWedIv5dWnPFEzN6Tr3A3bpsqdDLgOEIwvwd+pxn5DLzykTmg9EkQ1pPGspw==} @@ -2828,10 +2796,6 @@ packages: resolution: {integrity: sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==} engines: {node: '>=6'} - getenv@2.0.0: - resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} - engines: {node: '>=6'} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2845,10 +2809,6 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} - engines: {node: 18 || 20 || >=22} - glob@7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -2973,10 +2933,6 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - image-size@1.2.1: resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} engines: {node: '>=16.x'} @@ -3131,6 +3087,10 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} + is-nan@1.3.2: + resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} + engines: {node: '>= 0.4'} + is-negative-zero@2.0.3: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} @@ -3475,10 +3435,6 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.7: - resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} - engines: {node: 20 || >=22} - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -3626,10 +3582,6 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - minimatch@10.2.4: - resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} - engines: {node: 18 || 20 || >=22} - minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -3792,6 +3744,10 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -3957,10 +3913,6 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4115,8 +4067,8 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-native-gesture-handler@2.17.1: - resolution: {integrity: sha512-pWfniN6NuVKUq40KACuD3NCMe+bWNQCpD3cmxL6aLSCTwPKYmf7l4Lp0/E/almpjvxhybJZtFLU0w4tmxnIKaA==} + react-native-gesture-handler@2.16.2: + resolution: {integrity: sha512-vGFlrDKlmyI+BT+FemqVxmvO7nqxU33cgXVsn6IKAFishvlG3oV2Ds67D5nPkHMea8T+s1IcuMm0bF8ntZtAyg==} peerDependencies: react: '*' react-native: '*' @@ -4714,11 +4666,11 @@ packages: resolution: {integrity: sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==} engines: {node: '>=0.10.0'} - ts-api-utils@2.5.0: - resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} - engines: {node: '>=18.12'} + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} peerDependencies: - typescript: '>=4.8.4' + typescript: '>=4.2.0' ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -4887,10 +4839,12 @@ packages: uuid@7.0.3: resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true valid-url@1.0.9: @@ -5116,20 +5070,20 @@ snapshots: '@babel/compat-data@7.29.0': {} - '@babel/core@7.29.0': + '@babel/core@7.29.0(supports-color@8.1.1)': dependencies: '@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/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helpers': 7.29.2 '@babel/parser': 7.29.2 '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -5164,32 +5118,32 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@8.1.1) '@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 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) + '@babel/traverse': 7.29.0(supports-color@8.1.1) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@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.0)': + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.11 transitivePeerDependencies: @@ -5201,26 +5155,26 @@ snapshots: '@babel/helper-globals@7.28.0': {} - '@babel/helper-member-expression-to-functions@7.28.5': + '@babel/helper-member-expression-to-functions@7.28.5(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.28.6': + '@babel/helper-module-imports@7.28.6(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1) '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -5230,27 +5184,27 @@ snapshots: '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-wrap-function': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/helper-wrap-function': 7.28.6(supports-color@8.1.1) + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@8.1.1) '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + '@babel/helper-skip-transparent-expression-wrappers@7.27.1(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -5261,10 +5215,10 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helper-wrap-function@7.28.6': + '@babel/helper-wrap-function@7.28.6(supports-color@8.1.1)': dependencies: '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -5285,717 +5239,717 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@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(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@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(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@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) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.29.0)': + '@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0(supports-color@8.1.1)) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.29.0)': + '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.29.0)': + '@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.29.0)': + '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.29.0)': + '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.29.0)': + '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: '@babel/compat-data': 7.29.0 - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.29.0)': + '@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.29.0)': + '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@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(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-export-default-from@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-export-default-from@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-flow@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-flow@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@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 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@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 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 '@babel/template': 7.28.6 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@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 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-module-imports': 7.28.6 + '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-runtime@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-runtime@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@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) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.29.2(@babel/core@7.29.0)': + '@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: '@babel/compat-data': 7.29.0 - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@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.17(@babel/core@7.29.0) - babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0(supports-color@8.1.1)) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) core-js-compat: 3.49.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-flow@7.27.1(@babel/core@7.29.0)': + '@babel/preset-flow@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 '@babel/types': 7.29.0 esutils: 2.0.3 - '@babel/preset-react@7.28.5(@babel/core@7.29.0)': + '@babel/preset-react@7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@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-development': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@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) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/register@7.28.6(@babel/core@7.29.0)': + '@babel/register@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 @@ -6010,7 +5964,7 @@ snapshots: '@babel/parser': 7.29.2 '@babel/types': 7.29.0 - '@babel/traverse@7.29.0': + '@babel/traverse@7.29.0(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 @@ -6018,7 +5972,7 @@ snapshots: '@babel/parser': 7.29.2 '@babel/template': 7.28.6 '@babel/types': 7.29.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -6047,17 +6001,17 @@ snapshots: tslib: 2.8.1 optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1(supports-color@8.1.1))': dependencies: - eslint: 8.57.1 + eslint: 8.57.1(supports-color@8.1.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/eslintrc@2.1.4': + '@eslint/eslintrc@2.1.4(supports-color@8.1.1)': dependencies: ajv: 6.14.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -6074,25 +6028,25 @@ snapshots: dependencies: uuid: 8.3.2 - '@expo/cli@0.18.31(expo-modules-autolinking@1.11.3)': + '@expo/cli@0.18.31(expo-modules-autolinking@1.11.3)(supports-color@8.1.1)': dependencies: '@babel/runtime': 7.29.2 '@expo/code-signing-certificates': 0.0.5 - '@expo/config': 9.0.4 - '@expo/config-plugins': 8.0.11 - '@expo/devcert': 1.2.1 - '@expo/env': 0.3.0 + '@expo/config': 9.0.4(supports-color@8.1.1) + '@expo/config-plugins': 8.0.11(supports-color@8.1.1) + '@expo/devcert': 1.2.1(supports-color@8.1.1) + '@expo/env': 0.3.0(supports-color@8.1.1) '@expo/image-utils': 0.5.1 '@expo/json-file': 8.3.3 - '@expo/metro-config': 0.18.11 + '@expo/metro-config': 0.18.11(supports-color@8.1.1) '@expo/osascript': 2.4.2 '@expo/package-manager': 1.10.3 '@expo/plist': 0.1.3 - '@expo/prebuild-config': 7.0.9(expo-modules-autolinking@1.11.3) + '@expo/prebuild-config': 7.0.9(expo-modules-autolinking@1.11.3)(supports-color@8.1.1) '@expo/rudder-sdk-node': 1.1.1 '@expo/spawn-async': 1.7.2 '@expo/xcpretty': 4.4.1 - '@react-native/dev-middleware': 0.74.85 + '@react-native/dev-middleware': 0.74.85(supports-color@8.1.1) '@urql/core': 2.3.6(graphql@15.8.0) '@urql/exchange-retry': 0.3.0(graphql@15.8.0) accepts: 1.3.8 @@ -6103,8 +6057,8 @@ snapshots: cacache: 18.0.4 chalk: 4.1.2 ci-info: 3.9.0 - connect: 3.7.0 - debug: 4.4.3 + connect: 3.7.0(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) env-editor: 0.4.2 fast-glob: 3.3.3 find-yarn-workspace-root: 2.0.0 @@ -6115,7 +6069,7 @@ snapshots: glob: 7.2.3 graphql: 15.8.0 graphql-tag: 2.12.6(graphql@15.8.0) - https-proxy-agent: 5.0.1 + https-proxy-agent: 5.0.1(supports-color@8.1.1) internal-ip: 4.3.0 is-docker: 2.2.1 is-wsl: 2.2.0 @@ -6140,7 +6094,7 @@ snapshots: resolve-from: 5.0.0 resolve.exports: 2.0.3 semver: 7.7.4 - send: 0.18.0 + send: 0.18.0(supports-color@8.1.1) slugify: 1.6.8 source-map-support: 0.5.21 stacktrace-parser: 0.1.11 @@ -6165,32 +6119,14 @@ snapshots: node-forge: 1.4.0 nullthrows: 1.1.1 - '@expo/config-plugins@55.0.7': - dependencies: - '@expo/config-types': 55.0.5 - '@expo/json-file': 10.0.12 - '@expo/plist': 0.5.2 - '@expo/sdk-runtime-versions': 1.0.0 - chalk: 4.1.2 - debug: 4.4.3 - getenv: 2.0.0 - glob: 13.0.6 - resolve-from: 5.0.0 - semver: 7.7.4 - slugify: 1.6.8 - xcode: 3.0.1 - xml2js: 0.6.0 - transitivePeerDependencies: - - supports-color - - '@expo/config-plugins@8.0.11': + '@expo/config-plugins@8.0.11(supports-color@8.1.1)': dependencies: '@expo/config-types': 51.0.3 '@expo/json-file': 8.3.3 '@expo/plist': 0.1.3 '@expo/sdk-runtime-versions': 1.0.0 chalk: 4.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) find-up: 5.0.0 getenv: 1.0.0 glob: 7.1.6 @@ -6205,29 +6141,10 @@ snapshots: '@expo/config-types@51.0.3': {} - '@expo/config-types@55.0.5': {} - - '@expo/config@55.0.10(typescript@5.3.3)': - dependencies: - '@expo/config-plugins': 55.0.7 - '@expo/config-types': 55.0.5 - '@expo/json-file': 10.0.12 - '@expo/require-utils': 55.0.3(typescript@5.3.3) - deepmerge: 4.3.1 - getenv: 2.0.0 - glob: 13.0.6 - resolve-from: 5.0.0 - resolve-workspace-root: 2.0.1 - semver: 7.7.4 - slugify: 1.6.8 - transitivePeerDependencies: - - supports-color - - typescript - - '@expo/config@9.0.4': + '@expo/config@9.0.4(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.10.4 - '@expo/config-plugins': 8.0.11 + '@expo/config-plugins': 8.0.11(supports-color@8.1.1) '@expo/config-types': 51.0.3 '@expo/json-file': 8.3.3 getenv: 1.0.0 @@ -6240,31 +6157,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/devcert@1.2.1': + '@expo/devcert@1.2.1(supports-color@8.1.1)': dependencies: '@expo/sudo-prompt': 9.3.2 - debug: 3.2.7 + debug: 3.2.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@expo/env@0.3.0': + '@expo/env@0.3.0(supports-color@8.1.1)': dependencies: chalk: 4.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) dotenv: 16.4.7 dotenv-expand: 11.0.7 getenv: 1.0.0 transitivePeerDependencies: - supports-color - '@expo/env@2.1.1': - dependencies: - chalk: 4.1.2 - debug: 4.4.3 - getenv: 2.0.0 - transitivePeerDependencies: - - supports-color - '@expo/image-utils@0.5.1': dependencies: '@expo/spawn-async': 1.7.2 @@ -6280,16 +6189,6 @@ snapshots: transitivePeerDependencies: - encoding - '@expo/image-utils@0.8.12': - dependencies: - '@expo/spawn-async': 1.7.2 - chalk: 4.1.2 - getenv: 2.0.0 - jimp-compact: 0.16.1 - parse-png: 2.1.0 - resolve-from: 5.0.0 - semver: 7.7.4 - '@expo/json-file@10.0.12': dependencies: '@babel/code-frame': 7.29.0 @@ -6301,18 +6200,18 @@ snapshots: json5: 2.2.3 write-file-atomic: 2.4.3 - '@expo/metro-config@0.18.11': + '@expo/metro-config@0.18.11(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/generator': 7.29.1 '@babel/parser': 7.29.2 '@babel/types': 7.29.0 - '@expo/config': 9.0.4 - '@expo/env': 0.3.0 + '@expo/config': 9.0.4(supports-color@8.1.1) + '@expo/env': 0.3.0(supports-color@8.1.1) '@expo/json-file': 8.3.3 '@expo/spawn-async': 1.7.2 chalk: 4.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) find-yarn-workspace-root: 2.0.0 fs-extra: 9.1.0 getenv: 1.0.0 @@ -6324,9 +6223,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/metro-runtime@3.2.3(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))': + '@expo/metro-runtime@3.2.3(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))': dependencies: - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) '@expo/osascript@2.4.2': dependencies: @@ -6347,21 +6246,15 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 14.0.0 - '@expo/plist@0.5.2': - dependencies: - '@xmldom/xmldom': 0.8.11 - base64-js: 1.5.1 - xmlbuilder: 15.1.1 - - '@expo/prebuild-config@7.0.9(expo-modules-autolinking@1.11.3)': + '@expo/prebuild-config@7.0.9(expo-modules-autolinking@1.11.3)(supports-color@8.1.1)': dependencies: - '@expo/config': 9.0.4 - '@expo/config-plugins': 8.0.11 + '@expo/config': 9.0.4(supports-color@8.1.1) + '@expo/config-plugins': 8.0.11(supports-color@8.1.1) '@expo/config-types': 51.0.3 '@expo/image-utils': 0.5.1 '@expo/json-file': 8.3.3 '@react-native/normalize-colors': 0.74.85 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) expo-modules-autolinking: 1.11.3 fs-extra: 9.1.0 resolve-from: 5.0.0 @@ -6371,16 +6264,6 @@ snapshots: - encoding - supports-color - '@expo/require-utils@55.0.3(typescript@5.3.3)': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/core': 7.29.0 - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - optionalDependencies: - typescript: 5.3.3 - transitivePeerDependencies: - - supports-color - '@expo/rudder-sdk-node@1.1.1': dependencies: '@expo/bunyan': 4.0.1 @@ -6395,11 +6278,11 @@ snapshots: '@expo/sdk-runtime-versions@1.0.0': {} - '@expo/server@0.4.4(typescript@5.3.3)': + '@expo/server@0.4.4(supports-color@8.1.1)(typescript@5.3.3)': dependencies: '@remix-run/node': 2.17.4(typescript@5.3.3) abort-controller: 3.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) source-map-support: 0.5.21 transitivePeerDependencies: - supports-color @@ -6411,11 +6294,11 @@ snapshots: '@expo/sudo-prompt@9.3.2': {} - '@expo/vector-icons@14.1.0(expo-font@12.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)': + '@expo/vector-icons@14.1.0(expo-font@12.0.10(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)': dependencies: - expo-font: 12.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + expo-font: 12.0.10(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) react: 18.2.0 - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) '@expo/xcpretty@4.4.1': dependencies: @@ -6433,10 +6316,10 @@ snapshots: dependencies: '@hapi/hoek': 9.3.0 - '@humanwhocodes/config-array@0.13.0': + '@humanwhocodes/config-array@0.13.0(supports-color@8.1.1)': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -6445,6 +6328,8 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@ide/backoff@1.0.0': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -6566,10 +6451,10 @@ snapshots: '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) react: 18.2.0 - '@react-native-async-storage/async-storage@1.23.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))': + '@react-native-async-storage/async-storage@1.23.1(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))': dependencies: merge-options: 3.0.4 - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) '@react-native-community/cli-clean@13.6.9': dependencies: @@ -6591,9 +6476,9 @@ snapshots: transitivePeerDependencies: - encoding - '@react-native-community/cli-debugger-ui@13.6.9': + '@react-native-community/cli-debugger-ui@13.6.9(supports-color@8.1.1)': dependencies: - serve-static: 1.16.3 + serve-static: 1.16.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -6656,16 +6541,16 @@ snapshots: transitivePeerDependencies: - encoding - '@react-native-community/cli-server-api@13.6.9': + '@react-native-community/cli-server-api@13.6.9(supports-color@8.1.1)': dependencies: - '@react-native-community/cli-debugger-ui': 13.6.9 + '@react-native-community/cli-debugger-ui': 13.6.9(supports-color@8.1.1) '@react-native-community/cli-tools': 13.6.9 - compression: 1.8.1 - connect: 3.7.0 + compression: 1.8.1(supports-color@8.1.1) + connect: 3.7.0(supports-color@8.1.1) errorhandler: 1.5.2 nocache: 3.0.4 pretty-format: 26.6.2 - serve-static: 1.16.3 + serve-static: 1.16.3(supports-color@8.1.1) ws: 6.2.3 transitivePeerDependencies: - bufferutil @@ -6693,14 +6578,14 @@ snapshots: dependencies: joi: 17.13.3 - '@react-native-community/cli@13.6.9': + '@react-native-community/cli@13.6.9(supports-color@8.1.1)': dependencies: '@react-native-community/cli-clean': 13.6.9 '@react-native-community/cli-config': 13.6.9 - '@react-native-community/cli-debugger-ui': 13.6.9 + '@react-native-community/cli-debugger-ui': 13.6.9(supports-color@8.1.1) '@react-native-community/cli-doctor': 13.6.9 '@react-native-community/cli-hermes': 13.6.9 - '@react-native-community/cli-server-api': 13.6.9 + '@react-native-community/cli-server-api': 13.6.9(supports-color@8.1.1) '@react-native-community/cli-tools': 13.6.9 '@react-native-community/cli-types': 13.6.9 chalk: 4.1.2 @@ -6720,85 +6605,85 @@ snapshots: '@react-native/assets-registry@0.74.87': {} - '@react-native/babel-plugin-codegen@0.74.87(@babel/preset-env@7.29.2(@babel/core@7.29.0))': + '@react-native/babel-plugin-codegen@0.74.87(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@react-native/codegen': 0.74.87(@babel/preset-env@7.29.2(@babel/core@7.29.0)) + '@react-native/codegen': 0.74.87(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - '@babel/preset-env' - supports-color - '@react-native/babel-preset@0.74.87(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))': - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.29.0) - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.29.0) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.29.0) - '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@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-flow': 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-arrow-functions': 7.27.1(@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-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-flow-strip-types': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-literals': 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-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-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-runtime': 7.29.0(@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-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-preset@0.74.87(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-export-default-from': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/template': 7.28.6 - '@react-native/babel-plugin-codegen': 0.74.87(@babel/preset-env@7.29.2(@babel/core@7.29.0)) - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.0) + '@react-native/babel-plugin-codegen': 0.74.87(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.0(supports-color@8.1.1)) react-refresh: 0.14.2 transitivePeerDependencies: - '@babel/preset-env' - supports-color - '@react-native/codegen@0.74.87(@babel/preset-env@7.29.2(@babel/core@7.29.0))': + '@react-native/codegen@0.74.87(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: '@babel/parser': 7.29.2 - '@babel/preset-env': 7.29.2(@babel/core@7.29.0) + '@babel/preset-env': 7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) glob: 7.2.3 hermes-parser: 0.19.1 invariant: 2.2.4 - jscodeshift: 0.14.0(@babel/preset-env@7.29.2(@babel/core@7.29.0)) + jscodeshift: 0.14.0(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) mkdirp: 0.5.6 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - '@react-native/community-cli-plugin@0.74.87(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))': + '@react-native/community-cli-plugin@0.74.87(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@react-native-community/cli-server-api': 13.6.9 + '@react-native-community/cli-server-api': 13.6.9(supports-color@8.1.1) '@react-native-community/cli-tools': 13.6.9 - '@react-native/dev-middleware': 0.74.87 - '@react-native/metro-babel-transformer': 0.74.87(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0)) + '@react-native/dev-middleware': 0.74.87(supports-color@8.1.1) + '@react-native/metro-babel-transformer': 0.74.87(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) chalk: 4.1.2 execa: 5.1.1 - metro: 0.80.12 - metro-config: 0.80.12 + metro: 0.80.12(supports-color@8.1.1) + metro-config: 0.80.12(supports-color@8.1.1) metro-core: 0.80.12 node-fetch: 2.7.0 querystring: 0.2.1 @@ -6815,19 +6700,19 @@ snapshots: '@react-native/debugger-frontend@0.74.87': {} - '@react-native/dev-middleware@0.74.85': + '@react-native/dev-middleware@0.74.85(supports-color@8.1.1)': dependencies: '@isaacs/ttlcache': 1.4.1 '@react-native/debugger-frontend': 0.74.85 - '@rnx-kit/chromium-edge-launcher': 1.0.0 - chrome-launcher: 0.15.2 - connect: 3.7.0 - debug: 2.6.9 + '@rnx-kit/chromium-edge-launcher': 1.0.0(supports-color@8.1.1) + chrome-launcher: 0.15.2(supports-color@8.1.1) + connect: 3.7.0(supports-color@8.1.1) + debug: 2.6.9(supports-color@8.1.1) node-fetch: 2.7.0 nullthrows: 1.1.1 open: 7.4.2 selfsigned: 2.4.1 - serve-static: 1.16.3 + serve-static: 1.16.3(supports-color@8.1.1) temp-dir: 2.0.0 ws: 6.2.3 transitivePeerDependencies: @@ -6836,19 +6721,19 @@ snapshots: - supports-color - utf-8-validate - '@react-native/dev-middleware@0.74.87': + '@react-native/dev-middleware@0.74.87(supports-color@8.1.1)': dependencies: '@isaacs/ttlcache': 1.4.1 '@react-native/debugger-frontend': 0.74.87 - '@rnx-kit/chromium-edge-launcher': 1.0.0 - chrome-launcher: 0.15.2 - connect: 3.7.0 - debug: 2.6.9 + '@rnx-kit/chromium-edge-launcher': 1.0.0(supports-color@8.1.1) + chrome-launcher: 0.15.2(supports-color@8.1.1) + connect: 3.7.0(supports-color@8.1.1) + debug: 2.6.9(supports-color@8.1.1) node-fetch: 2.7.0 nullthrows: 1.1.1 open: 7.4.2 selfsigned: 2.4.1 - serve-static: 1.16.3 + serve-static: 1.16.3(supports-color@8.1.1) temp-dir: 2.0.0 ws: 6.2.3 transitivePeerDependencies: @@ -6861,10 +6746,10 @@ snapshots: '@react-native/js-polyfills@0.74.87': {} - '@react-native/metro-babel-transformer@0.74.87(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))': + '@react-native/metro-babel-transformer@0.74.87(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@react-native/babel-preset': 0.74.87(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0)) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@react-native/babel-preset': 0.74.87(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) hermes-parser: 0.19.1 nullthrows: 1.1.1 transitivePeerDependencies: @@ -6877,24 +6762,24 @@ snapshots: '@react-native/normalize-colors@0.74.89': {} - '@react-native/virtualized-lists@0.74.87(@types/react@18.2.79)(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)': + '@react-native/virtualized-lists@0.74.87(@types/react@18.2.79)(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 18.2.0 - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) optionalDependencies: '@types/react': 18.2.79 - '@react-navigation/bottom-tabs@6.5.20(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)': - dependencies: - '@react-navigation/elements': 1.3.31(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + ? '@react-navigation/bottom-tabs@6.5.20(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)' + : dependencies: + '@react-navigation/elements': 1.3.31(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) + '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) color: 4.2.3 react: 18.2.0 - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) - react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) + react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) + react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) warn-once: 0.1.1 '@react-navigation/core@6.4.17(react@18.2.0)': @@ -6907,31 +6792,31 @@ snapshots: react-is: 16.13.1 use-latest-callback: 0.2.6(react@18.2.0) - '@react-navigation/elements@1.3.31(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)': + '@react-navigation/elements@1.3.31(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)': dependencies: - '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) react: 18.2.0 - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) - react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) + react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) - '@react-navigation/native-stack@6.9.26(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)': - dependencies: - '@react-navigation/elements': 1.3.31(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + ? '@react-navigation/native-stack@6.9.26(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)' + : dependencies: + '@react-navigation/elements': 1.3.31(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) + '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) react: 18.2.0 - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) - react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) + react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) + react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) warn-once: 0.1.1 - '@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)': + '@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)': dependencies: '@react-navigation/core': 6.4.17(react@18.2.0) escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 nanoid: 3.3.11 react: 18.2.0 - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) '@react-navigation/routers@6.1.9': dependencies: @@ -6991,12 +6876,12 @@ snapshots: dependencies: web-streams-polyfill: 3.3.3 - '@rnx-kit/chromium-edge-launcher@1.0.0': + '@rnx-kit/chromium-edge-launcher@1.0.0(supports-color@8.1.1)': dependencies: '@types/node': 18.19.130 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 - lighthouse-logger: 1.4.2 + lighthouse-logger: 1.4.2(supports-color@8.1.1) mkdirp: 1.0.4 rimraf: 3.0.2 transitivePeerDependencies: @@ -7093,9 +6978,9 @@ snapshots: '@types/prop-types@15.7.15': {} - '@types/react-native@0.73.0(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0)': + '@types/react-native@0.73.0(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1)': dependencies: - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) transitivePeerDependencies: - '@babel/core' - '@babel/preset-env' @@ -7127,96 +7012,86 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.3.3))(eslint@8.57.1)(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3))(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.57.2(eslint@8.57.1)(typescript@5.3.3) - '@typescript-eslint/scope-manager': 8.57.2 - '@typescript-eslint/type-utils': 8.57.2(eslint@8.57.1)(typescript@5.3.3) - '@typescript-eslint/utils': 8.57.2(eslint@8.57.1)(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 8.57.2 - eslint: 8.57.1 - ignore: 7.0.5 + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 7.18.0 + eslint: 8.57.1(supports-color@8.1.1) + graphemer: 1.4.0 + ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@5.3.3) - typescript: 5.3.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.3.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.57.2 - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 8.57.2 - debug: 4.4.3 - eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.57.2(typescript@5.3.3)': + '@typescript-eslint/parser@7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.3.3) - '@typescript-eslint/types': 8.57.2 - debug: 4.4.3 + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(supports-color@8.1.1)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.4.3(supports-color@8.1.1) + eslint: 8.57.1(supports-color@8.1.1) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.57.2': + '@typescript-eslint/scope-manager@7.18.0': dependencies: - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/visitor-keys': 8.57.2 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/tsconfig-utils@8.57.2(typescript@5.3.3)': + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3)': dependencies: - typescript: 5.3.3 - - '@typescript-eslint/type-utils@8.57.2(eslint@8.57.1)(typescript@5.3.3)': - dependencies: - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.3.3) - '@typescript-eslint/utils': 8.57.2(eslint@8.57.1)(typescript@5.3.3) - debug: 4.4.3 - eslint: 8.57.1 - ts-api-utils: 2.5.0(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 7.18.0(supports-color@8.1.1)(typescript@5.3.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3) + debug: 4.4.3(supports-color@8.1.1) + eslint: 8.57.1(supports-color@8.1.1) + ts-api-utils: 1.4.3(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.57.2': {} + '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/typescript-estree@8.57.2(typescript@5.3.3)': + '@typescript-eslint/typescript-estree@7.18.0(supports-color@8.1.1)(typescript@5.3.3)': dependencies: - '@typescript-eslint/project-service': 8.57.2(typescript@5.3.3) - '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.3.3) - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/visitor-keys': 8.57.2 - debug: 4.4.3 - minimatch: 10.2.4 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.4.3(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.9 semver: 7.7.4 - tinyglobby: 0.2.15 - ts-api-utils: 2.5.0(typescript@5.3.3) + ts-api-utils: 1.4.3(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.57.2(eslint@8.57.1)(typescript@5.3.3)': + '@typescript-eslint/utils@7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.57.2 - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.3.3) - eslint: 8.57.1 - typescript: 5.3.3 + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1(supports-color@8.1.1)) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(supports-color@8.1.1)(typescript@5.3.3) + eslint: 8.57.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color + - typescript - '@typescript-eslint/visitor-keys@8.57.2': + '@typescript-eslint/visitor-keys@7.18.0': dependencies: - '@typescript-eslint/types': 8.57.2 - eslint-visitor-keys: 5.0.1 + '@typescript-eslint/types': 7.18.0 + eslint-visitor-keys: 3.4.3 '@ungap/structured-clone@1.3.0': {} @@ -7315,9 +7190,9 @@ snapshots: acorn@8.16.0: {} - agent-base@6.0.2: + agent-base@6.0.2(supports-color@8.1.1): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -7467,6 +7342,14 @@ snapshots: asap@2.0.6: {} + assert@2.1.0: + dependencies: + call-bind: 1.0.8 + is-nan: 1.3.2 + object-is: 1.1.6 + object.assign: 4.1.7 + util: 0.12.5 + ast-types@0.15.2: dependencies: tslib: 2.8.1 @@ -7485,39 +7368,39 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - babel-core@7.0.0-bridge.0(@babel/core@7.29.0): + babel-core@7.0.0-bridge.0(@babel/core@7.29.0(supports-color@8.1.1)): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: '@babel/compat-data': 7.29.0 - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0): + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -7533,21 +7416,21 @@ snapshots: babel-plugin-react-native-web@0.19.13: {} - babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.0): + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.0(supports-color@8.1.1)): dependencies: - '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) transitivePeerDependencies: - '@babel/core' - babel-preset-expo@11.0.15(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0)): + babel-preset-expo@11.0.15(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - '@babel/preset-react': 7.28.5(@babel/core@7.29.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - '@react-native/babel-preset': 0.74.87(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0)) + '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/preset-react': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@react-native/babel-preset': 0.74.87(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) babel-plugin-react-compiler: 0.0.0-experimental-592953e-20240517 babel-plugin-react-native-web: 0.19.13 react-refresh: 0.14.2 @@ -7560,8 +7443,6 @@ snapshots: balanced-match@1.0.2: {} - balanced-match@4.0.4: {} - base64-js@1.5.1: {} baseline-browser-mapping@2.10.10: {} @@ -7603,10 +7484,6 @@ snapshots: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.5: - dependencies: - balanced-match: 4.0.4 - braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -7708,12 +7585,12 @@ snapshots: chownr@2.0.0: {} - chrome-launcher@0.15.2: + chrome-launcher@0.15.2(supports-color@8.1.1): dependencies: '@types/node': 25.5.0 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 - lighthouse-logger: 1.4.2 + lighthouse-logger: 1.4.2(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -7801,11 +7678,11 @@ snapshots: dependencies: mime-db: 1.54.0 - compression@1.8.1: + compression@1.8.1(supports-color@8.1.1): dependencies: bytes: 3.1.2 compressible: 2.0.18 - debug: 2.6.9 + debug: 2.6.9(supports-color@8.1.1) negotiator: 0.6.4 on-headers: 1.1.0 safe-buffer: 5.2.1 @@ -7815,10 +7692,10 @@ snapshots: concat-map@0.0.1: {} - connect@3.7.0: + connect@3.7.0(supports-color@8.1.1): dependencies: - debug: 2.6.9 - finalhandler: 1.1.2 + debug: 2.6.9(supports-color@8.1.1) + finalhandler: 1.1.2(supports-color@8.1.1) parseurl: 1.3.3 utils-merge: 1.0.1 transitivePeerDependencies: @@ -7903,17 +7780,23 @@ snapshots: dayjs@1.11.20: {} - debug@2.6.9: + debug@2.6.9(supports-color@8.1.1): dependencies: ms: 2.0.0 + optionalDependencies: + supports-color: 8.1.1 - debug@3.2.7: + debug@3.2.7(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 - debug@4.4.3: + debug@4.4.3(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 decamelize@1.2.0: {} @@ -8140,77 +8023,77 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-expo@8.0.1(eslint@8.57.1)(typescript@5.3.3): + eslint-config-expo@7.1.2(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.3.3))(eslint@8.57.1)(typescript@5.3.3) - '@typescript-eslint/parser': 8.57.2(eslint@8.57.1)(typescript@5.3.3) - eslint: 8.57.1 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) - eslint-plugin-expo: 0.1.4(eslint@8.57.1)(typescript@5.3.3) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.3.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) - eslint-plugin-react: 7.37.5(eslint@8.57.1) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) + '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3))(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3) + eslint: 8.57.1(supports-color@8.1.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1) + eslint-plugin-expo: 0.0.1(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1) + eslint-plugin-react: 7.37.5(eslint@8.57.1(supports-color@8.1.1)) + eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1(supports-color@8.1.1)) transitivePeerDependencies: - eslint-import-resolver-webpack - eslint-plugin-import-x - supports-color - typescript - eslint-import-resolver-node@0.3.9: + eslint-import-resolver-node@0.3.9(supports-color@8.1.1): dependencies: - debug: 3.2.7 + debug: 3.2.7(supports-color@8.1.1) is-core-module: 2.16.1 resolve: 1.22.11 transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.3 - eslint: 8.57.1 + debug: 4.4.3(supports-color@8.1.1) + eslint: 8.57.1(supports-color@8.1.1) get-tsconfig: 4.13.7 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.3.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - debug: 3.2.7 + debug: 3.2.7(supports-color@8.1.1) optionalDependencies: - '@typescript-eslint/parser': 8.57.2(eslint@8.57.1)(typescript@5.3.3) - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3) + eslint: 8.57.1(supports-color@8.1.1) + eslint-import-resolver-node: 0.3.9(supports-color@8.1.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - eslint-plugin-expo@0.1.4(eslint@8.57.1)(typescript@5.3.3): + eslint-plugin-expo@0.0.1(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3): dependencies: - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/utils': 8.57.2(eslint@8.57.1)(typescript@5.3.3) - eslint: 8.57.1 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3) + eslint: 8.57.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.3.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 - debug: 3.2.7 + debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint: 8.57.1(supports-color@8.1.1) + eslint-import-resolver-node: 0.3.9(supports-color@8.1.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -8222,17 +8105,17 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.57.2(eslint@8.57.1)(typescript@5.3.3) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.3.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): + eslint-plugin-react-hooks@4.6.2(eslint@8.57.1(supports-color@8.1.1)): dependencies: - eslint: 8.57.1 + eslint: 8.57.1(supports-color@8.1.1) - eslint-plugin-react@7.37.5(eslint@8.57.1): + eslint-plugin-react@7.37.5(eslint@8.57.1(supports-color@8.1.1)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -8240,7 +8123,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.3.1 - eslint: 8.57.1 + eslint: 8.57.1(supports-color@8.1.1) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -8261,22 +8144,20 @@ snapshots: eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@5.0.1: {} - - eslint@8.57.1: + eslint@8.57.1(supports-color@8.1.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1(supports-color@8.1.1)) '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 + '@eslint/eslintrc': 2.1.4(supports-color@8.1.1) '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/config-array': 0.13.0(supports-color@8.1.1) '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 '@ungap/structured-clone': 1.3.0 ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -8352,80 +8233,70 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - expo-application@55.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-application@5.9.1(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) - expo-asset@10.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-asset@10.0.10(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - expo-constants: 16.0.2(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) + expo-constants: 16.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) invariant: 2.2.4 md5-file: 3.2.3 transitivePeerDependencies: - supports-color - expo-camera@15.0.16(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-camera@15.0.16(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) invariant: 2.2.4 - expo-constants@16.0.2(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-constants@16.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@expo/config': 9.0.4 - '@expo/env': 0.3.0 - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + '@expo/config': 9.0.4(supports-color@8.1.1) + '@expo/env': 0.3.0(supports-color@8.1.1) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) transitivePeerDependencies: - supports-color - expo-constants@55.0.9(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(typescript@5.3.3): - dependencies: - '@expo/config': 55.0.10(typescript@5.3.3) - '@expo/env': 2.1.1 - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) - transitivePeerDependencies: - - supports-color - - typescript - - expo-device@55.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-device@6.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) ua-parser-js: 0.7.41 - expo-file-system@17.0.1(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-file-system@17.0.1(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) - expo-font@12.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-font@12.0.10(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) fontfaceobserver: 2.3.0 - expo-haptics@13.0.1(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-haptics@13.0.1(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) - expo-image-loader@4.7.0(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-image-loader@4.7.0(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) - expo-image-picker@15.0.7(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-image-picker@15.1.0(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - expo-image-loader: 4.7.0(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) + expo-image-loader: 4.7.0(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) - expo-image@1.13.0(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-image@1.13.0(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) - expo-keep-awake@13.0.2(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-keep-awake@13.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) - expo-linking@6.3.1(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-linking@6.3.1(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - expo-constants: 16.0.2(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + expo-constants: 16.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) invariant: 2.2.4 transitivePeerDependencies: - expo @@ -8445,39 +8316,40 @@ snapshots: dependencies: invariant: 2.2.4 - expo-notifications@55.0.13(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)(typescript@5.3.3): + expo-notifications@0.28.19(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@expo/image-utils': 0.8.12 + '@expo/image-utils': 0.5.1 + '@ide/backoff': 1.0.0 abort-controller: 3.0.0 + assert: 2.1.0 badgin: 1.2.3 - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - expo-application: 55.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) - expo-constants: 55.0.9(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(typescript@5.3.3) - react: 18.2.0 - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) + expo-application: 5.9.1(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) + expo-constants: 16.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) + fs-extra: 9.1.0 transitivePeerDependencies: + - encoding - supports-color - - typescript - expo-router@3.5.24(7e9c78a356a2cc7044ab01920b075952): + expo-router@3.5.24(b51aa3c20ae172a8588f75896dca1775): dependencies: - '@expo/metro-runtime': 3.2.3(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0)) - '@expo/server': 0.4.4(typescript@5.3.3) + '@expo/metro-runtime': 3.2.3(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1)) + '@expo/server': 0.4.4(supports-color@8.1.1)(typescript@5.3.3) '@radix-ui/react-slot': 1.0.1(react@18.2.0) - '@react-navigation/bottom-tabs': 6.5.20(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - '@react-navigation/native-stack': 6.9.26(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - expo-constants: 16.0.2(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) - expo-linking: 6.3.1(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) - expo-splash-screen: 0.27.7(expo-modules-autolinking@1.11.3)(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + '@react-navigation/bottom-tabs': 6.5.20(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) + '@react-navigation/native': 6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) + '@react-navigation/native-stack': 6.9.26(@react-navigation/native@6.1.18(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) + expo-constants: 16.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) + expo-linking: 6.3.1(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) + expo-splash-screen: 0.27.7(expo-modules-autolinking@1.11.3)(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) expo-status-bar: 1.12.1 react-native-helmet-async: 2.0.4(react@18.2.0) - react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) + react-native-screens: 3.31.1(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) schema-utils: 4.3.3 optionalDependencies: - react-native-reanimated: 3.10.1(@babel/core@7.29.0)(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + react-native-reanimated: 3.10.1(@babel/core@7.29.0(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) transitivePeerDependencies: - encoding - expo-modules-autolinking @@ -8486,14 +8358,14 @@ snapshots: - supports-color - typescript - expo-secure-store@13.0.2(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-secure-store@13.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) - expo-splash-screen@0.27.7(expo-modules-autolinking@1.11.3)(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-splash-screen@0.27.7(expo-modules-autolinking@1.11.3)(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@expo/prebuild-config': 7.0.9(expo-modules-autolinking@1.11.3) - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + '@expo/prebuild-config': 7.0.9(expo-modules-autolinking@1.11.3)(supports-color@8.1.1) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) transitivePeerDependencies: - encoding - expo-modules-autolinking @@ -8501,36 +8373,36 @@ snapshots: expo-status-bar@1.12.1: {} - expo-symbols@0.1.5(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-symbols@0.1.5(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) sf-symbols-typescript: 2.2.0 - expo-system-ui@3.0.7(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-system-ui@3.0.7(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1): dependencies: '@react-native/normalize-colors': 0.74.85 - debug: 4.4.3 - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + debug: 4.4.3(supports-color@8.1.1) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) transitivePeerDependencies: - supports-color - expo-web-browser@13.0.3(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)): + expo-web-browser@13.0.3(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)): dependencies: - expo: 51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + expo: 51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1) - expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0): + expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1): dependencies: '@babel/runtime': 7.29.2 - '@expo/cli': 0.18.31(expo-modules-autolinking@1.11.3) - '@expo/config': 9.0.4 - '@expo/config-plugins': 8.0.11 - '@expo/metro-config': 0.18.11 - '@expo/vector-icons': 14.1.0(expo-font@12.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) - babel-preset-expo: 11.0.15(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0)) - expo-asset: 10.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) - expo-file-system: 17.0.1(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) - expo-font: 12.0.10(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) - expo-keep-awake: 13.0.2(expo@51.0.39(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)) + '@expo/cli': 0.18.31(expo-modules-autolinking@1.11.3)(supports-color@8.1.1) + '@expo/config': 9.0.4(supports-color@8.1.1) + '@expo/config-plugins': 8.0.11(supports-color@8.1.1) + '@expo/metro-config': 0.18.11(supports-color@8.1.1) + '@expo/vector-icons': 14.1.0(expo-font@12.0.10(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) + babel-preset-expo: 11.0.15(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + expo-asset: 10.0.10(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1))(supports-color@8.1.1) + expo-file-system: 17.0.1(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) + expo-font: 12.0.10(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) + expo-keep-awake: 13.0.2(expo@51.0.39(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1)) expo-modules-autolinking: 1.11.3 expo-modules-core: 1.12.26 fbemitter: 3.0.0 @@ -8613,9 +8485,9 @@ snapshots: filter-obj@1.1.0: {} - finalhandler@1.1.2: + finalhandler@1.1.2(supports-color@8.1.1): dependencies: - debug: 2.6.9 + debug: 2.6.9(supports-color@8.1.1) encodeurl: 1.0.2 escape-html: 1.0.3 on-finished: 2.3.0 @@ -8772,8 +8644,6 @@ snapshots: getenv@1.0.0: {} - getenv@2.0.0: {} - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -8791,12 +8661,6 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@13.0.6: - dependencies: - minimatch: 10.2.4 - minipass: 7.1.3 - path-scurry: 2.0.2 - glob@7.1.6: dependencies: fs.realpath: 1.0.0 @@ -8914,10 +8778,10 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - https-proxy-agent@5.0.1: + https-proxy-agent@5.0.1(supports-color@8.1.1): dependencies: - agent-base: 6.0.2 - debug: 4.4.3 + agent-base: 6.0.2(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8929,8 +8793,6 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.5: {} - image-size@1.2.1: dependencies: queue: 6.0.2 @@ -9077,6 +8939,11 @@ snapshots: is-map@2.0.3: {} + is-nan@1.3.2: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + is-negative-zero@2.0.3: {} is-number-object@1.1.1: @@ -9257,19 +9124,19 @@ snapshots: jsc-safe-url@0.2.4: {} - jscodeshift@0.14.0(@babel/preset-env@7.29.2(@babel/core@7.29.0)): + jscodeshift@0.14.0(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/parser': 7.29.2 - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/preset-env': 7.29.2(@babel/core@7.29.0) - '@babel/preset-flow': 7.27.1(@babel/core@7.29.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - '@babel/register': 7.28.6(@babel/core@7.29.0) - babel-core: 7.0.0-bridge.0(@babel/core@7.29.0) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/preset-env': 7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/preset-flow': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/register': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + babel-core: 7.0.0-bridge.0(@babel/core@7.29.0(supports-color@8.1.1)) chalk: 4.1.2 flow-parser: 0.306.1 graceful-fs: 4.2.11 @@ -9345,9 +9212,9 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lighthouse-logger@1.4.2: + lighthouse-logger@1.4.2(supports-color@8.1.1): dependencies: - debug: 2.6.9 + debug: 2.6.9(supports-color@8.1.1) marky: 1.3.0 transitivePeerDependencies: - supports-color @@ -9433,8 +9300,6 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.7: {} - lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -9488,9 +9353,9 @@ snapshots: merge2@1.4.1: {} - metro-babel-transformer@0.80.12: + metro-babel-transformer@0.80.12(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) flow-enums-runtime: 0.0.6 hermes-parser: 0.23.1 nullthrows: 1.1.1 @@ -9507,13 +9372,13 @@ snapshots: flow-enums-runtime: 0.0.6 metro-core: 0.80.12 - metro-config@0.80.12: + metro-config@0.80.12(supports-color@8.1.1): dependencies: - connect: 3.7.0 + connect: 3.7.0(supports-color@8.1.1) cosmiconfig: 5.2.1 flow-enums-runtime: 0.0.6 jest-validate: 29.7.0 - metro: 0.80.12 + metro: 0.80.12(supports-color@8.1.1) metro-cache: 0.80.12 metro-core: 0.80.12 metro-runtime: 0.80.12 @@ -9528,10 +9393,10 @@ snapshots: lodash.throttle: 4.1.1 metro-resolver: 0.80.12 - metro-file-map@0.80.12: + metro-file-map@0.80.12(supports-color@8.1.1): dependencies: anymatch: 3.1.3 - debug: 2.6.9 + debug: 2.6.9(supports-color@8.1.1) fb-watchman: 2.0.2 flow-enums-runtime: 0.0.6 graceful-fs: 4.2.11 @@ -9560,13 +9425,13 @@ snapshots: '@babel/runtime': 7.29.2 flow-enums-runtime: 0.0.6 - metro-source-map@0.80.12: + metro-source-map@0.80.12(supports-color@8.1.1): dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-symbolicate: 0.80.12 + metro-symbolicate: 0.80.12(supports-color@8.1.1) nullthrows: 1.1.1 ob1: 0.80.12 source-map: 0.5.7 @@ -9574,11 +9439,11 @@ snapshots: transitivePeerDependencies: - supports-color - metro-symbolicate@0.80.12: + metro-symbolicate@0.80.12(supports-color@8.1.1): dependencies: flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-source-map: 0.80.12 + metro-source-map: 0.80.12(supports-color@8.1.1) nullthrows: 1.1.1 source-map: 0.5.7 through2: 2.0.5 @@ -9586,51 +9451,51 @@ snapshots: transitivePeerDependencies: - supports-color - metro-transform-plugins@0.80.12: + metro-transform-plugins@0.80.12(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/generator': 7.29.1 '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) flow-enums-runtime: 0.0.6 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - metro-transform-worker@0.80.12: + metro-transform-worker@0.80.12(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/generator': 7.29.1 '@babel/parser': 7.29.2 '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 - metro: 0.80.12 - metro-babel-transformer: 0.80.12 + metro: 0.80.12(supports-color@8.1.1) + metro-babel-transformer: 0.80.12(supports-color@8.1.1) metro-cache: 0.80.12 metro-cache-key: 0.80.12 metro-minify-terser: 0.80.12 - metro-source-map: 0.80.12 - metro-transform-plugins: 0.80.12 + metro-source-map: 0.80.12(supports-color@8.1.1) + metro-transform-plugins: 0.80.12(supports-color@8.1.1) nullthrows: 1.1.1 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - metro@0.80.12: + metro@0.80.12(supports-color@8.1.1): dependencies: '@babel/code-frame': 7.29.0 - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/generator': 7.29.1 '@babel/parser': 7.29.2 '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 accepts: 1.3.8 chalk: 4.1.2 ci-info: 2.0.0 - connect: 3.7.0 - debug: 2.6.9 + connect: 3.7.0(supports-color@8.1.1) + debug: 2.6.9(supports-color@8.1.1) denodeify: 1.2.1 error-stack-parser: 2.1.4 flow-enums-runtime: 0.0.6 @@ -9641,18 +9506,18 @@ snapshots: jest-worker: 29.7.0 jsc-safe-url: 0.2.4 lodash.throttle: 4.1.1 - metro-babel-transformer: 0.80.12 + metro-babel-transformer: 0.80.12(supports-color@8.1.1) metro-cache: 0.80.12 metro-cache-key: 0.80.12 - metro-config: 0.80.12 + metro-config: 0.80.12(supports-color@8.1.1) metro-core: 0.80.12 - metro-file-map: 0.80.12 + metro-file-map: 0.80.12(supports-color@8.1.1) metro-resolver: 0.80.12 metro-runtime: 0.80.12 - metro-source-map: 0.80.12 - metro-symbolicate: 0.80.12 - metro-transform-plugins: 0.80.12 - metro-transform-worker: 0.80.12 + metro-source-map: 0.80.12(supports-color@8.1.1) + metro-symbolicate: 0.80.12(supports-color@8.1.1) + metro-transform-plugins: 0.80.12(supports-color@8.1.1) + metro-transform-worker: 0.80.12(supports-color@8.1.1) mime-types: 2.1.35 nullthrows: 1.1.1 serialize-error: 2.1.0 @@ -9687,10 +9552,6 @@ snapshots: mimic-fn@2.1.0: {} - minimatch@10.2.4: - dependencies: - brace-expansion: 5.0.5 - minimatch@3.1.5: dependencies: brace-expansion: 1.1.12 @@ -9821,6 +9682,11 @@ snapshots: object-inspect@1.13.4: {} + object-is@1.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + object-keys@1.1.1: {} object.assign@4.1.7: @@ -10003,11 +9869,6 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.3 - path-scurry@2.0.2: - dependencies: - lru-cache: 11.2.7 - minipass: 7.1.3 - path-type@4.0.0: {} picocolors@1.1.1: {} @@ -10152,14 +10013,15 @@ snapshots: react-is@18.3.1: {} - react-native-gesture-handler@2.17.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0): + react-native-gesture-handler@2.16.2(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0): dependencies: '@egjs/hammerjs': 2.0.17 hoist-non-react-statics: 3.3.2 invariant: 2.2.4 + lodash: 4.17.23 prop-types: 15.8.1 react: 18.2.0 - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) react-native-helmet-async@2.0.4(react@18.2.0): dependencies: @@ -10168,32 +10030,32 @@ snapshots: react-fast-compare: 3.2.2 shallowequal: 1.1.0 - react-native-reanimated@3.10.1(@babel/core@7.29.0)(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0): + react-native-reanimated@3.10.1(@babel/core@7.29.0(supports-color@8.1.1))(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0)(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-arrow-functions': 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-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/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) convert-source-map: 2.0.0 invariant: 2.2.4 react: 18.2.0 - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) transitivePeerDependencies: - supports-color - react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0): + react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0): dependencies: react: 18.2.0 - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) - react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0): + react-native-screens@3.31.1(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0): dependencies: react: 18.2.0 react-freeze: 1.0.4(react@18.2.0) - react-native: 0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0) + react-native: 0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1) warn-once: 0.1.1 react-native-web@0.19.13(react-dom@18.2.0(react@18.2.0))(react@18.2.0): @@ -10211,19 +10073,19 @@ snapshots: transitivePeerDependencies: - encoding - react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0): + react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1): dependencies: '@jest/create-cache-key-function': 29.7.0 - '@react-native-community/cli': 13.6.9 + '@react-native-community/cli': 13.6.9(supports-color@8.1.1) '@react-native-community/cli-platform-android': 13.6.9 '@react-native-community/cli-platform-ios': 13.6.9 '@react-native/assets-registry': 0.74.87 - '@react-native/codegen': 0.74.87(@babel/preset-env@7.29.2(@babel/core@7.29.0)) - '@react-native/community-cli-plugin': 0.74.87(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0)) + '@react-native/codegen': 0.74.87(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + '@react-native/community-cli-plugin': 0.74.87(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) '@react-native/gradle-plugin': 0.74.87 '@react-native/js-polyfills': 0.74.87 '@react-native/normalize-colors': 0.74.87 - '@react-native/virtualized-lists': 0.74.87(@types/react@18.2.79)(react-native@0.74.5(@babel/core@7.29.0)(@babel/preset-env@7.29.2(@babel/core@7.29.0))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + '@react-native/virtualized-lists': 0.74.87(@types/react@18.2.79)(react-native@0.74.5(@babel/core@7.29.0(supports-color@8.1.1))(@babel/preset-env@7.29.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.2.79)(react@18.2.0)(supports-color@8.1.1))(react@18.2.0) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 @@ -10236,7 +10098,7 @@ snapshots: jsc-android: 250231.0.0 memoize-one: 5.2.1 metro-runtime: 0.80.12 - metro-source-map: 0.80.12 + metro-source-map: 0.80.12(supports-color@8.1.1) mkdirp: 0.5.6 nullthrows: 1.1.1 pretty-format: 26.6.2 @@ -10461,9 +10323,9 @@ snapshots: semver@7.7.4: {} - send@0.18.0: + send@0.18.0(supports-color@8.1.1): dependencies: - debug: 2.6.9 + debug: 2.6.9(supports-color@8.1.1) depd: 2.0.0 destroy: 1.2.0 encodeurl: 1.0.2 @@ -10479,9 +10341,9 @@ snapshots: transitivePeerDependencies: - supports-color - send@0.19.2: + send@0.19.2(supports-color@8.1.1): dependencies: - debug: 2.6.9 + debug: 2.6.9(supports-color@8.1.1) depd: 2.0.0 destroy: 1.2.0 encodeurl: 2.0.0 @@ -10499,12 +10361,12 @@ snapshots: serialize-error@2.1.0: {} - serve-static@1.16.3: + serve-static@1.16.3(supports-color@8.1.1): dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.19.2 + send: 0.19.2(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -10874,7 +10736,7 @@ snapshots: trim-right@1.0.1: {} - ts-api-utils@2.5.0(typescript@5.3.3): + ts-api-utils@1.4.3(typescript@5.3.3): dependencies: typescript: 5.3.3 diff --git a/bis-mobile/tsconfig.json b/bis-mobile/tsconfig.json index 9e9ed52f..8458bedf 100644 --- a/bis-mobile/tsconfig.json +++ b/bis-mobile/tsconfig.json @@ -2,13 +2,23 @@ "extends": "expo/tsconfig.base", "compilerOptions": { "strict": true, + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "skipLibCheck": true, + "baseUrl": ".", "paths": { - "@/*": ["./*"] + "@/*": ["./*"], + "@shared/*": ["../shared/*"] } }, "include": [ - "**/*.ts", - "**/*.tsx", + "app/**/*.ts", + "app/**/*.tsx", + "hooks/**/*.ts", + "hooks/**/*.tsx", + "lib/**/*.ts", + "lib/**/*.tsx", ".expo/types/**/*.d.ts", "expo-env.d.ts" ] diff --git a/charts/bis/Chart.yaml b/charts/bis/Chart.yaml new file mode 100644 index 00000000..046cc7de --- /dev/null +++ b/charts/bis/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: bis +description: Hardened BIS control-plane workloads +version: 0.1.0 +appVersion: "2.0" +type: application diff --git a/charts/bis/templates/_helpers.tpl b/charts/bis/templates/_helpers.tpl new file mode 100644 index 00000000..735441ca --- /dev/null +++ b/charts/bis/templates/_helpers.tpl @@ -0,0 +1,12 @@ +{{- define "bis.name" -}}bis{{- end -}} +{{- define "bis.labels" -}} +app.kubernetes.io/name: {{ include "bis.name" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} +{{- define "bis.image" -}} +{{- $repository := required "image repository is required" .repository -}} +{{- $digest := required "an immutable image digest is required" .digest -}} +{{- if not (hasPrefix "sha256:" $digest) -}}{{ fail "image digest must begin with sha256:" }}{{- end -}} +{{ printf "%s@%s" $repository $digest }} +{{- end -}} diff --git a/charts/bis/templates/workloads.yaml b/charts/bis/templates/workloads.yaml new file mode 100644 index 00000000..4397578a --- /dev/null +++ b/charts/bis/templates/workloads.yaml @@ -0,0 +1,133 @@ +{{- if .Values.rbac.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.rbac.serviceAccounts.bff }} + namespace: {{ .Values.namespace }} +automountServiceAccountToken: false +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.rbac.serviceAccounts.gateway }} + namespace: {{ .Values.namespace }} +automountServiceAccountToken: false +--- +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: bis-bff + namespace: {{ .Values.namespace }} + labels: {{- include "bis.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount.bff }} + revisionHistoryLimit: 3 + progressDeadlineSeconds: 600 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: {{ .Values.rollingUpdate.maxUnavailable }} + maxSurge: {{ .Values.rollingUpdate.maxSurge }} + selector: + matchLabels: {app.kubernetes.io/component: bff, app.kubernetes.io/instance: {{ .Release.Name }}} + template: + metadata: + labels: {app.kubernetes.io/name: bis, app.kubernetes.io/component: bff, app.kubernetes.io/instance: {{ .Release.Name }}} + spec: + serviceAccountName: {{ .Values.rbac.serviceAccounts.bff }} + automountServiceAccountToken: false + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: bff + image: {{ include "bis.image" .Values.images.bff }} + imagePullPolicy: IfNotPresent + securityContext: {{- toYaml .Values.containerSecurityContext | nindent 12 }} + ports: [{name: http, containerPort: {{ .Values.service.bff.targetPort }}, protocol: TCP}] + envFrom: [{secretRef: {name: {{ required "existingSecrets.bff is required" .Values.existingSecrets.bff }}}}] + readinessProbe: {httpGet: {path: /api/health, port: http}, initialDelaySeconds: 10, timeoutSeconds: 3, periodSeconds: 10, failureThreshold: 3} + livenessProbe: {httpGet: {path: /api/health, port: http}, initialDelaySeconds: 30, timeoutSeconds: 3, periodSeconds: 20, failureThreshold: 3} + startupProbe: {httpGet: {path: /api/health, port: http}, failureThreshold: 30, periodSeconds: 5} + resources: {{- toYaml .Values.resources.bff | nindent 12 }} + volumeMounts: [{name: tmp, mountPath: /tmp}] + volumes: [{name: tmp, emptyDir: {sizeLimit: 64Mi}}] +--- +apiVersion: v1 +kind: Service +metadata: + name: bis-bff + namespace: {{ .Values.namespace }} + labels: {{- include "bis.labels" . | nindent 4 }} +spec: + selector: {app.kubernetes.io/component: bff, app.kubernetes.io/instance: {{ .Release.Name }}} + ports: [{name: http, port: {{ .Values.service.bff.port }}, targetPort: http, protocol: TCP}] +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: {name: bis-bff, namespace: {{ .Values.namespace }}} +spec: + minAvailable: 2 + selector: {matchLabels: {app.kubernetes.io/component: bff, app.kubernetes.io/instance: {{ .Release.Name }}}} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: bis-gateway + namespace: {{ .Values.namespace }} + labels: {{- include "bis.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount.gateway }} + revisionHistoryLimit: 3 + progressDeadlineSeconds: 600 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: {{ .Values.rollingUpdate.maxUnavailable }} + maxSurge: {{ .Values.rollingUpdate.maxSurge }} + selector: + matchLabels: {app.kubernetes.io/component: gateway, app.kubernetes.io/instance: {{ .Release.Name }}} + template: + metadata: + labels: {app.kubernetes.io/name: bis, app.kubernetes.io/component: gateway, app.kubernetes.io/instance: {{ .Release.Name }}} + spec: + serviceAccountName: {{ .Values.rbac.serviceAccounts.gateway }} + automountServiceAccountToken: false + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: gateway + image: {{ include "bis.image" .Values.images.gateway }} + imagePullPolicy: IfNotPresent + securityContext: {{- toYaml .Values.containerSecurityContext | nindent 12 }} + ports: [{name: http, containerPort: {{ .Values.service.gateway.targetPort }}, protocol: TCP}] + envFrom: [{secretRef: {name: {{ required "existingSecrets.gateway is required" .Values.existingSecrets.gateway }}}}] + readinessProbe: {httpGet: {path: /health, port: http}, initialDelaySeconds: 10, timeoutSeconds: 3, periodSeconds: 10, failureThreshold: 3} + livenessProbe: {httpGet: {path: /health, port: http}, initialDelaySeconds: 30, timeoutSeconds: 3, periodSeconds: 20, failureThreshold: 3} + startupProbe: {httpGet: {path: /health, port: http}, failureThreshold: 30, periodSeconds: 5} + resources: {{- toYaml .Values.resources.gateway | nindent 12 }} + volumeMounts: [{name: tmp, mountPath: /tmp}] + volumes: [{name: tmp, emptyDir: {sizeLimit: 64Mi}}] +--- +apiVersion: v1 +kind: Service +metadata: + name: bis-gateway + namespace: {{ .Values.namespace }} + labels: {{- include "bis.labels" . | nindent 4 }} +spec: + selector: {app.kubernetes.io/component: gateway, app.kubernetes.io/instance: {{ .Release.Name }}} + ports: [{name: http, port: {{ .Values.service.gateway.port }}, targetPort: http, protocol: TCP}] +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: {name: bis-gateway, namespace: {{ .Values.namespace }}} +spec: + minAvailable: 2 + selector: {matchLabels: {app.kubernetes.io/component: gateway, app.kubernetes.io/instance: {{ .Release.Name }}}} diff --git a/charts/bis/values.yaml b/charts/bis/values.yaml new file mode 100644 index 00000000..a6bcc857 --- /dev/null +++ b/charts/bis/values.yaml @@ -0,0 +1,50 @@ +namespace: bis-production +replicaCount: + bff: 3 + gateway: 3 +imagePullSecrets: [] +rbac: + create: true + serviceAccounts: + bff: bis-bff + gateway: bis-gateway +service: + bff: + port: 3000 + targetPort: 3001 + gateway: + port: 8081 + targetPort: 8081 +images: + bff: + repository: ghcr.io/munisp/bis-bff + digest: "" + gateway: + repository: ghcr.io/munisp/bis-gateway + digest: "" +existingSecrets: + bff: bis-bff-runtime + gateway: bis-gateway-runtime +podSecurityContext: + runAsNonRoot: true + runAsUser: 10001 + runAsGroup: 10001 + fsGroup: 10001 +containerSecurityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + seccompProfile: + type: RuntimeDefault +resources: + bff: + requests: {cpu: 250m, memory: 512Mi} + limits: {cpu: "1", memory: 1Gi} + gateway: + requests: {cpu: 250m, memory: 256Mi} + limits: {cpu: "1", memory: 512Mi} +rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 +terminationGracePeriodSeconds: 45 diff --git a/client/index.html b/client/index.html index c87bc9a5..a45b8659 100644 --- a/client/index.html +++ b/client/index.html @@ -10,7 +10,7 @@ BIS — Background Intelligence System - + @@ -24,33 +24,5 @@
- - diff --git a/client/public/sw.js b/client/public/sw.js index 2a22a5a2..baaef817 100644 --- a/client/public/sw.js +++ b/client/public/sw.js @@ -3,14 +3,17 @@ * Provides offline capability for LEX field agents with low bandwidth * Features: Cache-first for static assets, network-first for API, IndexedDB queue for mutations * - * Cache-busting: On each deployment the server injects a new BUILD_VERSION meta tag. - * The SW reads this on activate and clears all old caches when the version changes. + * Cache-busting: The client registers this worker through a versioned script URL. + * Activation clears prior BIS caches for a new build and navigation responses are + * never stored, so every online page load obtains a current HTML shell. */ // ─── Version tracking ───────────────────────────────────────────────────────── -// This constant is updated on each deployment by the CI pipeline or manually. -// Changing it forces all old caches to be cleared on the next SW activation. -const SW_VERSION = '2.0.0'; +const SAFE_BUILD_VERSION = /^[A-Za-z0-9._-]{7,128}$/; +const requestedBuildVersion = new URL(self.location.href).searchParams.get('build')?.trim(); +const SW_VERSION = SAFE_BUILD_VERSION.test(requestedBuildVersion ?? '') + ? requestedBuildVersion + : 'development'; const CACHE_VERSION = `bis-v${SW_VERSION}`; const STATIC_CACHE = `${CACHE_VERSION}-static`; const API_CACHE = `${CACHE_VERSION}-api`; @@ -19,9 +22,6 @@ const OFFLINE_QUEUE_STORE = 'pending-submissions'; // Static assets to pre-cache const PRECACHE_URLS = [ - '/', - '/lex/submit', - '/quickcheck', '/manifest.json', ]; @@ -33,12 +33,9 @@ const API_CACHE_ROUTES = [ // ─── Install ──────────────────────────────────────────────────────────────── self.addEventListener('install', (event) => { - console.log(`[SW] Installing version ${SW_VERSION}`); event.waitUntil( caches.open(STATIC_CACHE).then((cache) => { - return cache.addAll(PRECACHE_URLS).catch((err) => { - console.warn('[SW] Pre-cache failed for some URLs:', err); - }); + return cache.addAll(PRECACHE_URLS).catch(() => undefined); }).then(() => { // Force immediate activation — don't wait for old SW to be unloaded return self.skipWaiting(); @@ -48,17 +45,13 @@ self.addEventListener('install', (event) => { // ─── Activate ──────────────────────────────────────────────────────────────── self.addEventListener('activate', (event) => { - console.log(`[SW] Activating version ${SW_VERSION} — clearing old caches`); event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames // Delete any BIS cache that doesn't match the current version .filter((name) => name.startsWith('bis-') && name !== STATIC_CACHE && name !== API_CACHE) - .map((name) => { - console.log(`[SW] Deleting stale cache: ${name}`); - return caches.delete(name); - }) + .map((name) => caches.delete(name)) ); }).then(() => { // Take control of all open pages immediately @@ -91,10 +84,14 @@ self.addEventListener('fetch', (event) => { return; } - // HTML navigation: ALWAYS network-first, no caching — must get fresh index.html + // HTML navigation: always network-only. The authenticated entry shell must + // never be retained in Cache Storage or replayed after a deployment. if (request.mode === 'navigate' || url.pathname === '/') { event.respondWith( - fetch(request, { cache: 'no-store' }).catch(() => caches.match('/')) + fetch(request, { cache: 'no-store' }).catch(() => new Response( + 'Offline

Connection unavailable

Please reconnect and reload.

', + { status: 503, headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' } }, + )) ); return; } diff --git a/client/src/App.tsx b/client/src/App.tsx index 18d18748..d3f73ff3 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -21,6 +21,7 @@ const Settings = lazy(() => import("@/pages/Settings")); const NotFound = lazy(() => import("@/pages/NotFound")); const Forbidden = lazy(() => import("@/pages/Forbidden")); const SearchResults = lazy(() => import("@/pages/SearchResults")); +const ConsumerDiscoveryPage = lazy(() => import("@/pages/ConsumerDiscoveryPage")); const VerifyKYC = lazy(() => import("@/pages/VerifyKYC")); const BiometricEnrollmentPage = lazy(() => import("@/pages/bis/BiometricEnrollmentPage")); @@ -66,6 +67,7 @@ const CaseDetailPage = lazy(() => import("@/pages/bis/CaseDetailPa const StakeholderPortalPage = lazy(() => import("@/pages/bis/StakeholderPortalPage")); const StakeholderPortalLandingPage = lazy(() => import("@/pages/StakeholderPortalLandingPage")); const OllamaManagementPage = lazy(() => import("@/pages/bis/OllamaManagementPage")); +const PiiForensicAuditPage = lazy(() => import("@/pages/bis/PiiForensicAuditPage")); // LEX — Law Enforcement Extension const LexAdminPage = lazy(() => import("@/pages/lex/LexAdminPage")); @@ -223,6 +225,7 @@ function Router() { + {/* NG Background Screening */} @@ -234,6 +237,7 @@ function Router() { {/* Fallback */} + diff --git a/client/src/components/BISLayout.tsx b/client/src/components/BISLayout.tsx index 44e197b6..36520705 100644 --- a/client/src/components/BISLayout.tsx +++ b/client/src/components/BISLayout.tsx @@ -151,6 +151,7 @@ const BASE_NAV_GROUPS: NavGroup[] = [ defaultOpen: true, items: [ { label: 'QuickCheck', href: '/quickcheck', icon: }, + { label: 'Consumer Discovery', href: '/consumer-discovery', icon: }, { label: 'goAML STR Wizard', href: '/goaml', icon: }, { label: 'Lakehouse Analytics', href: '/lakehouse', icon: }, { label: 'Duplicate ID Check', href: '/duplicate-check', icon: }, @@ -557,9 +558,8 @@ export default function BISLayout({ children, title, subtitle, actions }: BISLay async function setupPush() { try { - // Register the service worker - const registration = await navigator.serviceWorker.register('/sw.js', { scope: '/' }); - await navigator.serviceWorker.ready; + // Reuse the version-aware root registration created by the app entry. + const registration = await navigator.serviceWorker.ready; // Only request permission if not already granted/denied if (Notification.permission === 'default') { diff --git a/client/src/hooks/useEventStream.ts b/client/src/hooks/useEventStream.ts index 36a14606..54bb640a 100644 --- a/client/src/hooks/useEventStream.ts +++ b/client/src/hooks/useEventStream.ts @@ -3,7 +3,7 @@ * * Usage: * const { lastEvent, connected } = useEventStream({ - * onEvent: (e) => console.log(e), + * onEvent: (e) => logger.info(e), * eventTypes: ["ALERT_TRIGGERED", "KYC_COMPLETED"], * }); * diff --git a/client/src/lib/fieldOfflineQueue.ts b/client/src/lib/fieldOfflineQueue.ts new file mode 100644 index 00000000..21ee0951 --- /dev/null +++ b/client/src/lib/fieldOfflineQueue.ts @@ -0,0 +1,101 @@ +export type FieldDispatchInput = { + agentId: string; + agentName: string; + taskType: 'address_verification' | 'biometric_capture' | 'document_collection' | 'surveillance' | 'interview'; + priority: 'low' | 'medium' | 'high' | 'critical'; + subjectName?: string; + address?: string; + state?: string; + lga?: string; + gpsLat?: number; + gpsLng?: number; + deadline?: string; + instructions?: string; + idempotencyKey: string; +}; + +type StoredOperation = { id: string; nonce: ArrayBuffer; ciphertext: ArrayBuffer; attempts: number; nextAttemptAt: number; createdAt: number }; +const DB_NAME = 'bis-field-offline-v1'; +const STORE_NAME = 'operations'; +const KEY_NAME = 'aes-gcm'; + +function openDb(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, 1); + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME, { keyPath: 'id' }); + if (!db.objectStoreNames.contains('keys')) db.createObjectStore('keys'); + }; + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error ?? new Error('Cannot open encrypted field queue')); + }); +} + +async function keyFor(db: IDBDatabase): Promise { + const existing = await new Promise((resolve, reject) => { + const request = db.transaction('keys', 'readonly').objectStore('keys').get(KEY_NAME); + request.onsuccess = () => resolve(request.result as CryptoKey | undefined); + request.onerror = () => reject(request.error); + }); + if (existing) return existing; + const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']); + await new Promise((resolve, reject) => { + const request = db.transaction('keys', 'readwrite').objectStore('keys').put(key, KEY_NAME); + request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); + }); + return key; +} + +async function all(db: IDBDatabase): Promise { + return new Promise((resolve, reject) => { + const request = db.transaction(STORE_NAME, 'readonly').objectStore(STORE_NAME).getAll(); + request.onsuccess = () => resolve(request.result as StoredOperation[]); request.onerror = () => reject(request.error); + }); +} + +function retryAt(attempts: number): number { return Date.now() + Math.min(60_000, 1_000 * 2 ** Math.min(6, attempts)); } + +export class EncryptedFieldOfflineQueue { + async enqueue(payload: Omit): Promise { + if (!globalThis.crypto?.subtle || !globalThis.crypto.randomUUID) throw new Error('This browser cannot securely store offline field operations'); + const db = await openDb(); + const id = crypto.randomUUID(); + const operation: FieldDispatchInput = { ...payload, idempotencyKey: id }; + const nonce = crypto.getRandomValues(new Uint8Array(12)); + const key = await keyFor(db); + const encoded = new TextEncoder().encode(JSON.stringify(operation)); + const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: nonce, additionalData: new TextEncoder().encode(`bis-field-op|${id}`) }, key, encoded); + await new Promise((resolve, reject) => { + const request = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).put({ id, nonce: nonce.buffer, ciphertext, attempts: 0, nextAttemptAt: Date.now(), createdAt: Date.now() } satisfies StoredOperation); + request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); + }); + db.close(); + return id; + } + + async drain(executor: (operation: FieldDispatchInput) => Promise): Promise<{ delivered: number; pending: number }> { + if (!navigator.onLine) return { delivered: 0, pending: (await this.pendingCount()) }; + const db = await openDb(); + const key = await keyFor(db); + let delivered = 0; + for (const stored of (await all(db)).sort((a, b) => a.createdAt - b.createdAt)) { + if (stored.nextAttemptAt > Date.now()) continue; + try { + const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: new Uint8Array(stored.nonce), additionalData: new TextEncoder().encode(`bis-field-op|${stored.id}`) }, key, stored.ciphertext); + await executor(JSON.parse(new TextDecoder().decode(plaintext)) as FieldDispatchInput); + await new Promise((resolve, reject) => { const request = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).delete(stored.id); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); + delivered++; + } catch { + const next = { ...stored, attempts: stored.attempts + 1, nextAttemptAt: retryAt(stored.attempts + 1) }; + await new Promise((resolve, reject) => { const request = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).put(next); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); + } + } + db.close(); + return { delivered, pending: await this.pendingCount() }; + } + + async pendingCount(): Promise { const db = await openDb(); const count = (await all(db)).length; db.close(); return count; } +} + +export const encryptedFieldOfflineQueue = new EncryptedFieldOfflineQueue(); diff --git a/client/src/lib/safeClientLogger.ts b/client/src/lib/safeClientLogger.ts new file mode 100644 index 00000000..92d3eda9 --- /dev/null +++ b/client/src/lib/safeClientLogger.ts @@ -0,0 +1,72 @@ +import * as Sentry from "@sentry/react"; +import { TRPCClientError } from "@trpc/client"; + +type ErrorSource = "query" | "mutation" | "transport" | "ui"; +type ErrorCategory = "unauthenticated" | "forbidden" | "offline" | "rate_limited" | "timeout" | "server" | "unknown"; + +export type SafeClientFailure = Readonly<{ + source: ErrorSource; + category: ErrorCategory; + trpcCode?: string; + httpStatus?: number; +}>; + +function classifyTrpcCode(code: string | undefined): ErrorCategory { + switch (code) { + case "UNAUTHORIZED": + return "unauthenticated"; + case "FORBIDDEN": + return "forbidden"; + case "TOO_MANY_REQUESTS": + return "rate_limited"; + case "TIMEOUT": + return "timeout"; + case "INTERNAL_SERVER_ERROR": + case "SERVICE_UNAVAILABLE": + return "server"; + default: + return "unknown"; + } +} + +export function toSafeClientFailure(error: unknown, source: ErrorSource): SafeClientFailure { + if (error instanceof TRPCClientError) { + const trpcCode = error.data?.code; + return { + source, + category: classifyTrpcCode(trpcCode), + ...(trpcCode ? { trpcCode } : {}), + ...(typeof error.data?.httpStatus === "number" ? { httpStatus: error.data.httpStatus } : {}), + }; + } + + if (typeof navigator !== "undefined" && !navigator.onLine) { + return { source, category: "offline" }; + } + + return { source, category: "unknown" }; +} + +/** + * Reports operation metadata only. Raw error messages, request payloads, URLs, + * stack traces, identifiers, and server responses are deliberately excluded. + */ +export function reportClientFailure(error: unknown, source: ErrorSource): SafeClientFailure { + const failure = toSafeClientFailure(error, source); + + Sentry.withScope((scope) => { + scope.setLevel(failure.category === "server" ? "error" : "warning"); + scope.setTag("client.source", failure.source); + scope.setTag("client.category", failure.category); + if (failure.trpcCode) scope.setTag("trpc.code", failure.trpcCode); + if (failure.httpStatus) scope.setTag("http.status", String(failure.httpStatus)); + scope.setExtra("operation", "redacted"); + Sentry.captureMessage("BIS client operation failed"); + }); + + return failure; +} + +export function isUnauthenticatedFailure(failure: SafeClientFailure): boolean { + return failure.category === "unauthenticated"; +} diff --git a/client/src/lib/serviceWorker.ts b/client/src/lib/serviceWorker.ts new file mode 100644 index 00000000..be2f706c --- /dev/null +++ b/client/src/lib/serviceWorker.ts @@ -0,0 +1,86 @@ +declare const __BIS_BUILD_VERSION__: string; + +const VERSION_PATTERN = /^[A-Za-z0-9._-]{7,128}$/; +const VERSION_STORAGE_KEY = "bis.service-worker.reloaded-version"; + +/** + * The version is supplied at build time from the deployed revision. It must be + * safe to include in a service-worker script URL and cache name; unknown + * values fail closed to a stable, non-empty development label. + */ +export const BIS_BUILD_VERSION = VERSION_PATTERN.test(__BIS_BUILD_VERSION__) + ? __BIS_BUILD_VERSION__ + : "development"; + +function canUseServiceWorker(): boolean { + return typeof window !== "undefined" && "serviceWorker" in navigator; +} + +function reloadForActivatedVersion(version: string): void { + try { + if (window.sessionStorage.getItem(VERSION_STORAGE_KEY) === version) return; + window.sessionStorage.setItem(VERSION_STORAGE_KEY, version); + } catch { + // Storage may be unavailable in a hardened browser context. A reload still + // guarantees that the newly activated worker controls a freshly loaded shell. + } + + window.location.reload(); +} + +/** + * Registers the root PWA service worker once per page load. + * + * The versioned script URL prevents an intermediary cache from returning an + * earlier worker after a deployment. `updateViaCache: "none"` also instructs + * compliant browsers to bypass HTTP cache entries while checking worker updates. + * An active client reloads once when a different worker takes control, after the + * worker has cleared its own versioned `bis-*` caches. Initial installation never + * reloads the page. + */ +export function registerBISServiceWorker(): void { + if (!canUseServiceWorker()) return; + + const hadControllerAtRegistration = + navigator.serviceWorker.controller !== null; + let deploymentUpdateObserved = false; + + navigator.serviceWorker.addEventListener("controllerchange", () => { + if (!hadControllerAtRegistration || !deploymentUpdateObserved) return; + reloadForActivatedVersion(BIS_BUILD_VERSION); + }); + + const register = () => { + const scriptUrl = `/sw.js?build=${encodeURIComponent(BIS_BUILD_VERSION)}`; + void navigator.serviceWorker + .register(scriptUrl, { + scope: "/", + updateViaCache: "none", + }) + .then(registration => { + const observeInstallingWorker = () => { + deploymentUpdateObserved = hadControllerAtRegistration; + }; + + registration.addEventListener("updatefound", observeInstallingWorker); + if (registration.waiting) { + deploymentUpdateObserved = hadControllerAtRegistration; + registration.waiting.postMessage({ type: "SKIP_WAITING" }); + } + + // Fetch the versioned worker URL without trusting a browser's stale + // HTTP-cache entry. Failure is non-fatal because offline capability is + // progressive enhancement, not an authentication or authorization path. + void registration.update().catch(() => undefined); + }) + .catch(() => { + // Offline capability is optional. Do not expose runtime details or PII. + }); + }; + + if (document.readyState === "complete") { + register(); + } else { + window.addEventListener("load", register, { once: true }); + } +} diff --git a/client/src/main.tsx b/client/src/main.tsx index 70ca590a..5406ca01 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,6 +1,6 @@ import { trpc } from "@/lib/trpc"; import "./sentry.client.config"; -import { UNAUTHED_ERR_MSG } from '@shared/const'; +import { UNAUTHED_ERR_MSG } from "@shared/const"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { httpBatchLink, TRPCClientError } from "@trpc/client"; import { createRoot } from "react-dom/client"; @@ -8,72 +8,76 @@ import superjson from "superjson"; import App from "./App"; import { OfflineBanner } from "./components/OfflineBanner"; import { getLoginUrl } from "./const"; +import { isUnauthenticatedFailure, reportClientFailure } from "./lib/safeClientLogger"; +import { registerBISServiceWorker } from "./lib/serviceWorker"; import { toast } from "sonner"; import "./index.css"; const queryClient = new QueryClient(); +let authenticationRedirectInFlight = false; -const redirectToLoginIfUnauthorized = (error: unknown) => { - if (!(error instanceof TRPCClientError)) return; - if (typeof window === "undefined") return; +function redirectToLoginIfUnauthorized(error: unknown, source: "query" | "mutation") { + const failure = reportClientFailure(error, source); + const isUnauthenticated = isUnauthenticatedFailure(failure) + || (error instanceof TRPCClientError && error.message === UNAUTHED_ERR_MSG); - const isUnauthorized = error.message === UNAUTHED_ERR_MSG; + if (!isUnauthenticated || typeof window === "undefined" || authenticationRedirectInFlight) return false; - if (!isUnauthorized) return; + const oauthPortalUrl = import.meta.env.VITE_OAUTH_PORTAL_URL; + const appId = import.meta.env.VITE_APP_ID; + if (!oauthPortalUrl || !appId) { + toast.error("Your session has ended", { + description: "Sign in again to continue.", + duration: 5000, + }); + return true; + } - // Demo mode: server always returns a demo user, so UNAUTHORIZED should not - // occur. Log it but do NOT redirect to Manus OAuth to keep the demo open. - console.warn("[BIS Demo] Unauthorized API call — skipping OAuth redirect"); -}; + authenticationRedirectInFlight = true; + window.location.replace(getLoginUrl()); + return true; +} -queryClient.getQueryCache().subscribe(event => { - if (event.type === "updated" && event.action.type === "error") { - const error = event.query.state.error; - redirectToLoginIfUnauthorized(error); - console.error("[API Query Error]", error); - } +queryClient.getQueryCache().subscribe((event) => { + if (event.type !== "updated" || event.action.type !== "error") return; + redirectToLoginIfUnauthorized(event.query.state.error, "query"); }); -queryClient.getMutationCache().subscribe(event => { - if (event.type === "updated" && event.action.type === "error") { - const error = event.mutation.state.error; - redirectToLoginIfUnauthorized(error); - // Show demo mode toast for read-only errors - if (error instanceof TRPCClientError && error.message.includes("demo mode")) { - toast.warning("Demo Mode — Read Only", { - description: "Sign in with a real account to make changes.", - duration: 4000, - }); - } - console.error("[API Mutation Error]", error); - } +queryClient.getMutationCache().subscribe((event) => { + if (event.type !== "updated" || event.action.type !== "error") return; + const error = event.mutation.state.error; + if (redirectToLoginIfUnauthorized(error, "mutation")) return; + + const failure = reportClientFailure(error, "mutation"); + const description = failure.category === "offline" + ? "Check your connection and try again." + : "No changes were confirmed. Please try again."; + toast.error("We could not complete that action", { description, duration: 5000 }); }); -// ── CSRF Token ──────────────────────────────────────────────────────────────── -// Fetch a CSRF token from the server on app load and inject it into all -// state-changing tRPC requests via the X-CSRF-Token header. -// The server validates this header on POST/PUT/PATCH/DELETE requests. -let _csrfToken: string | null = null; +let csrfToken: string | null = null; async function fetchCsrfToken(): Promise { try { - const res = await fetch("/api/csrf-token", { + const response = await fetch("/api/csrf-token", { credentials: "include", cache: "no-store", }); - if (!res.ok) return null; - const data = await res.json(); - _csrfToken = data?.csrfToken ?? null; - return _csrfToken; + if (!response.ok) { + reportClientFailure(null, "transport"); + return null; + } + const data = await response.json(); + csrfToken = typeof data?.csrfToken === "string" ? data.csrfToken : null; + return csrfToken; } catch { - // Non-fatal — CSRF validation is defence-in-depth; app still works - console.warn("[BIS] Failed to fetch CSRF token"); + reportClientFailure(null, "transport"); return null; } } -// Pre-fetch CSRF token before first mutation (non-blocking) -fetchCsrfToken(); +void fetchCsrfToken(); +registerBISServiceWorker(); const trpcClient = trpc.createClient({ links: [ @@ -81,11 +85,8 @@ const trpcClient = trpc.createClient({ url: "/api/trpc", transformer: superjson, async headers() { - // Ensure we have a token before any request (lazy fetch if needed) - if (!_csrfToken) { - await fetchCsrfToken(); - } - return _csrfToken ? { "X-CSRF-Token": _csrfToken } : {}; + if (!csrfToken) await fetchCsrfToken(); + return csrfToken ? { "X-CSRF-Token": csrfToken } : {}; }, fetch(input, init) { return globalThis.fetch(input, { diff --git a/client/src/pages/ConsumerDiscoveryPage.tsx b/client/src/pages/ConsumerDiscoveryPage.tsx new file mode 100644 index 00000000..8eb70b1b --- /dev/null +++ b/client/src/pages/ConsumerDiscoveryPage.tsx @@ -0,0 +1,194 @@ +import { useMemo, useState } from "react"; +import BISLayout from "@/components/BISLayout"; +import { trpc } from "@/lib/trpc"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { AlertTriangle, FileSearch, MapPin, Search, ShieldCheck, Users } from "lucide-react"; +import { toast } from "sonner"; + +type SearchKind = "name" | "phone" | "email" | "address"; +type Purpose = "self" | "personal_safety" | "fraud_prevention" | "account_security" | "compliance_investigation" | "legal_authority"; +type Mode = "consumer" | "institutional"; + +const PURPOSES: Array<{ value: Purpose; label: string; consumer: boolean }> = [ + { value: "self", label: "Find my own record", consumer: true }, + { value: "personal_safety", label: "Personal safety", consumer: true }, + { value: "fraud_prevention", label: "Fraud prevention", consumer: true }, + { value: "account_security", label: "Account security", consumer: false }, + { value: "compliance_investigation", label: "Compliance investigation", consumer: false }, + { value: "legal_authority", label: "Legal authority", consumer: false }, +]; + +function formatPurpose(purpose: Purpose): string { + return PURPOSES.find(item => item.value === purpose)?.label ?? purpose; +} + +export default function ConsumerDiscoveryPage() { + const [mode, setMode] = useState("consumer"); + const [kind, setKind] = useState("name"); + const [term, setTerm] = useState(""); + const [purpose, setPurpose] = useState("personal_safety"); + const [consentConfirmed, setConsentConfirmed] = useState(false); + const [submitted, setSubmitted] = useState<{ mode: Mode; kind: SearchKind; term: string; purpose: Purpose } | null>(null); + + const input = useMemo<{ + mode: Mode; + purpose: Purpose; + consentConfirmed: true; + countryCode: "NG"; + name?: string; + phone?: string; + email?: string; + address?: string; + }>(() => { + if (!submitted) return { mode, purpose, consentConfirmed: true, countryCode: "NG", name: "not-submitted" }; + const key = submitted.kind; + return { + mode: submitted.mode, + purpose: submitted.purpose, + consentConfirmed: true, + countryCode: "NG", + [key]: submitted.term, + }; + }, [submitted, mode, purpose]); + + const query = trpc.consumerIntelligence.search.useQuery(input, { + enabled: submitted !== null, + retry: false, + }); + const grantConsent = trpc.consumerGovernance.consent.grant.useMutation(); + + const submit = async (event: React.FormEvent) => { + event.preventDefault(); + if (term.trim().length < 2) { + toast.error("Enter at least two characters to search the synthetic Nigeria dataset."); + return; + } + if (!consentConfirmed) { + toast.error("Confirm a permitted purpose and your consent acknowledgement before searching."); + return; + } + if (mode === "consumer" && !PURPOSES.find(item => item.value === purpose)?.consumer) { + toast.error("This purpose is available only to authorized institutional accounts."); + return; + } + try { + await grantConsent.mutateAsync({ + purpose, + legalBasis: mode === "consumer" ? "consent" : "legitimate_interest", + policyVersion: "NG-CONSUMER-2026-01", + scopes: ["consumer_discovery", "profile_detail", "provenance", "relationship_linkage"], + }); + setSubmitted({ mode, kind, term: term.trim(), purpose }); + } catch (error) { + toast.error(error instanceof Error ? error.message : "Could not record consent for this search."); + } + }; + + return ( + +
+
+
+
+
+ +
+
+
+
+
+

People and Contact Discovery

+

Search Nigeria-first profiles by name, phone, email, or address with purpose-bound access logging.

+
+
+ +
+
+ Access mode +
+ {(["consumer", "institutional"] as const).map(value => ( + + ))} +
+
+ +
+ + +
+ + + + + + +
+
+ + +
+ + {query.error &&
Search unavailable: {query.error.message}
} + + {query.data &&
+
+

Synthetic results

Purpose: {formatPurpose(query.data.declaredPurpose)} · Nigeria dataset

+ Synthetic only +
+ {query.data.results.length === 0 ?
No synthetic Nigeria profile matches this query.
: ( +
+ {query.data.results.map(profile => ( +
+

{profile.name}

{profile.profileRef}

+

{profile.contact.phone ?? "No phone"}

{profile.contact.email ?? "No email"}

+

{profile.provenance.notice}

+
+ ))} +
+ )} +
} +
+
+ ); +} diff --git a/client/src/pages/DeveloperPortal.tsx b/client/src/pages/DeveloperPortal.tsx index bba70c1c..204c9164 100644 --- a/client/src/pages/DeveloperPortal.tsx +++ b/client/src/pages/DeveloperPortal.tsx @@ -105,7 +105,7 @@ const result = await client.risk.score('Amaka Okonkwo', { bvn: '22345678901', includeFactors: true }); -console.log(\`Score: \${result.score}/100 — \${result.level}\`);`, +logger.info(\`Score: \${result.score}/100 — \${result.level}\`);`, full_due_diligence: `import { BISClient } from '@bis/sdk'; const client = new BISClient({ @@ -116,9 +116,9 @@ const report = await client.fullDueDiligence('Amaka Okonkwo', { bvn: '22345678901', priority: 'high' }); -console.log(\`Risk: \${report.riskScore}/100 — \${report.riskLevel}\`); -console.log(\`Clear: \${report.isClear}\`); -console.log(\`Investigation: \${report.investigationRef}\`);`, +logger.info(\`Risk: \${report.riskScore}/100 — \${report.riskLevel}\`); +logger.info(\`Clear: \${report.isClear}\`); +logger.info(\`Investigation: \${report.investigationRef}\`);`, kyc_verify: `import { BISClient } from '@bis/sdk'; const client = new BISClient({ @@ -130,7 +130,7 @@ const record = await client.kyc.verify({ fullName: 'Emeka Adeyemi', bvn: '22345678901' }); -console.log(\`Status: \${record.status}\`);`, +logger.info(\`Status: \${record.status}\`);`, }, go: { risk_score: `package main diff --git a/client/src/pages/Forbidden.tsx b/client/src/pages/Forbidden.tsx index e786e5ed..c5bbb208 100644 --- a/client/src/pages/Forbidden.tsx +++ b/client/src/pages/Forbidden.tsx @@ -8,10 +8,20 @@ import BISLayout from '@/components/BISLayout'; import { Button } from '@/components/ui/button'; import { ShieldOff, ArrowLeft, Home } from 'lucide-react'; +import { useEffect, useState } from 'react'; import { useLocation } from 'wouter'; function ForbiddenInner() { const [, navigate] = useLocation(); + const [serviceAvailable, setServiceAvailable] = useState(null); + + useEffect(() => { + const controller = new AbortController(); + void fetch('/api/health', { signal: controller.signal }) + .then((response) => setServiceAvailable(response.ok)) + .catch(() => setServiceAvailable(false)); + return () => controller.abort(); + }, []); return (
@@ -35,6 +45,12 @@ function ForbiddenInner() {

+ {serviceAvailable === false && ( +

+ Service status is temporarily unavailable; your access request remains protected. +

+ )} + {/* Actions */}
@@ -1176,6 +1176,16 @@ export default function PaymentRailsPage() { />
+
+ + setLookupBankCode(e.target.value.replace(/\D/g, "").slice(0, 6))} + className="bg-slate-800 border-slate-700 text-slate-100 text-xs h-8" + /> +
@@ -1253,10 +1263,11 @@ export default function PaymentRailsPage() {
diff --git a/client/src/pages/bis/BiometricEnrollmentPage.tsx b/client/src/pages/bis/BiometricEnrollmentPage.tsx index c408a972..1ed19265 100644 --- a/client/src/pages/bis/BiometricEnrollmentPage.tsx +++ b/client/src/pages/bis/BiometricEnrollmentPage.tsx @@ -228,11 +228,12 @@ interface CameraStepProps { challengeLabel?: string; onCapture: (imageB64: string) => void; onLivenessResult?: (result: { passed: boolean; score: number }) => void; + subjectRef: string; t: Record; isLiveness?: boolean; } -function CameraStep({ title, description, challenge, challengeLabel, onCapture, onLivenessResult, t, isLiveness }: CameraStepProps) { +function CameraStep({ title, description, challenge, challengeLabel, onCapture, onLivenessResult, subjectRef, t, isLiveness }: CameraStepProps) { const livenessCheck = trpc.biometric.checkLiveness.useMutation(); const videoRef = useRef(null); const canvasRef = useRef(null); @@ -324,7 +325,7 @@ function CameraStep({ title, description, challenge, challengeLabel, onCapture, setError(''); livenessCheck.mutate( - { imageBase64: captured, challenge: challenge ?? 'blink' }, + { imageBase64: captured, challenge: challenge ?? 'blink', subjectRef }, { onSuccess: (rawResult) => { const result = rawResult as { passed?: boolean; live?: boolean; score?: number; sandbox?: boolean }; @@ -345,7 +346,7 @@ function CameraStep({ title, description, challenge, challengeLabel, onCapture, }, } ); - }, [captured, isLiveness, challenge, livenessCheck, onCapture, onLivenessResult]); + }, [captured, isLiveness, challenge, livenessCheck, onCapture, onLivenessResult, subjectRef]); const handleRetake = useCallback(() => { setCaptured(null); @@ -1038,6 +1039,7 @@ function BiometricEnrollmentPageInner() { title="ArcFace Facial Enrollment" description="Capture a clear, well-lit photo of the subject's face. This will be used for future identity verification." onCapture={handleEnrollCapture} + subjectRef={state.subjectInfo.nin || state.subjectInfo.bvn || state.subjectInfo.fullName} t={t} /> @@ -1070,6 +1072,7 @@ function BiometricEnrollmentPageInner() { title="Document Scan" description="Hold the identity document flat and steady in front of the camera. Ensure all text is visible and not obscured." onCapture={handleDocumentCapture} + subjectRef={state.subjectInfo.nin || state.subjectInfo.bvn || state.subjectInfo.fullName} t={t} /> diff --git a/client/src/pages/bis/FieldAgentsPage.tsx b/client/src/pages/bis/FieldAgentsPage.tsx index e41965ee..d59bc57c 100644 --- a/client/src/pages/bis/FieldAgentsPage.tsx +++ b/client/src/pages/bis/FieldAgentsPage.tsx @@ -1,7 +1,7 @@ // FieldAgentsPage — live tRPC-backed field agent management // Design: Dark forensic intelligence theme, JetBrains Mono typography -import { useState, useRef, useMemo } from 'react'; +import { useEffect, useState, useRef, useMemo } from 'react'; import BISLayout from '@/components/BISLayout'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -14,6 +14,7 @@ import { MapView } from '@/components/Map'; import { trpc } from '@/lib/trpc'; import { FieldTaskDetailDrawer } from '@/components/FieldTaskDetailDrawer'; import { toast } from 'sonner'; +import { encryptedFieldOfflineQueue, type FieldDispatchInput } from '@/lib/fieldOfflineQueue'; // ─── Config ─────────────────────────────────────────────────────────────────── @@ -98,22 +99,45 @@ function DispatchTaskSheet({ const set = (k: string, v: string) => setForm(prev => ({ ...prev, [k]: v })); - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - dispatchMutation.mutate({ - agentId: agent.agentCode, - agentName: agent.name, - taskType: form.taskType as any, - priority: form.priority as any, - subjectName: form.subjectName || undefined, - address: form.address || undefined, - state: form.state || undefined, - lga: form.lga || undefined, - gpsLat: form.gpsLat ? parseFloat(form.gpsLat) : undefined, - gpsLng: form.gpsLng ? parseFloat(form.gpsLng) : undefined, - deadline: form.deadline || undefined, - instructions: form.instructions || undefined, + const payload = (): Omit => ({ + agentId: agent.agentCode, + agentName: agent.name, + taskType: form.taskType as FieldDispatchInput['taskType'], + priority: form.priority as FieldDispatchInput['priority'], + subjectName: form.subjectName || undefined, + address: form.address || undefined, + state: form.state || undefined, + lga: form.lga || undefined, + gpsLat: form.gpsLat ? parseFloat(form.gpsLat) : undefined, + gpsLng: form.gpsLng ? parseFloat(form.gpsLng) : undefined, + deadline: form.deadline || undefined, + instructions: form.instructions || undefined, + }); + + useEffect(() => { + const replay = () => void encryptedFieldOfflineQueue.drain(async operation => { + await dispatchMutation.mutateAsync(operation); + await utils.fieldAgents.list.invalidate(); + await utils.fieldTasks.list.invalidate(); }); + replay(); + window.addEventListener('online', replay); + return () => window.removeEventListener('online', replay); + }, [dispatchMutation, utils]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const input = payload(); + try { + const result = await dispatchMutation.mutateAsync({ ...input, idempotencyKey: crypto.randomUUID() }); + setTaskRef(result.taskRef); + setDispatched(true); + } catch { + const operationId = await encryptedFieldOfflineQueue.enqueue(input); + setTaskRef(`Securely queued operation ${operationId.slice(0, 8)}`); + setDispatched(true); + toast.info('Dispatch queued securely', { description: 'It will be delivered automatically when connectivity returns.' }); + } }; const inputCls = "h-8 text-xs font-mono bg-background border-border text-foreground placeholder:text-muted-foreground"; diff --git a/client/src/pages/bis/PiiForensicAuditPage.tsx b/client/src/pages/bis/PiiForensicAuditPage.tsx new file mode 100644 index 00000000..859f1813 --- /dev/null +++ b/client/src/pages/bis/PiiForensicAuditPage.tsx @@ -0,0 +1,91 @@ +import { useEffect, useMemo, useState } from "react"; +import { trpc } from "@/lib/trpc"; +import { AlertTriangle, ChevronLeft, ChevronRight, Download, Loader2, RefreshCw, ShieldCheck } from "lucide-react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; + +const MAX_FORENSIC_CURSOR_STACK = 100; + +type ForensicEvent = { + id: number; + createdAt: Date | string; + eventType: string; + detail: Record; + integrityHash: string; + integrityScheme: string; + incidentRef: string | null; + incidentStatus: string | null; +}; + +function isExpiredCursorError(error: { data?: { code?: string } | null; message?: string } | null): boolean { + return error?.data?.code === "BAD_REQUEST" && error.message === "PII forensic pagination cursor is invalid or expired."; +} + +function startVerifiedForensicExport(): void { + const anchor = document.createElement("a"); + anchor.href = "/api/pii-forensics/export.ndjson"; + anchor.download = "bis-pii-forensic-audit.ndjson"; + anchor.click(); +} + +export default function PiiForensicAuditPage() { + const [cursor, setCursor] = useState(); + const [pagesViewed, setPagesViewed] = useState(0); + const [pageStack, setPageStack] = useState>([]); + const [cursorExpired, setCursorExpired] = useState(false); + const query = trpc.piiKeyCustody.listForensics.useQuery( + { limit: 100, cursor }, + { + retry: false, + staleTime: 0, + gcTime: 0, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + }, + ); + + useEffect(() => { + if (!query.data) return; + setCursorExpired(false); + setPagesViewed((current) => current + 1); + }, [query.data]); + + const events = query.data?.events ?? []; + const nextCursor = query.data?.nextCursor ?? null; + const expired = isExpiredCursorError(query.error); + const canDownload = !query.isFetching && !expired; + const cursorHistoryAtLimit = pageStack.length >= MAX_FORENSIC_CURSOR_STACK; + const formattedEvents = useMemo(() => events.map((event) => ({ ...event, createdAt: new Date(event.createdAt).toLocaleString("en-NG") })), [events]); + + function advanceToNextPage(): void { + if (!nextCursor || query.isFetching || cursorHistoryAtLimit) return; + setPageStack((current) => current.length >= MAX_FORENSIC_CURSOR_STACK ? current : [...current, cursor]); + setCursor(nextCursor); + } + + function restartAfterExpiry(): void { + setCursor(undefined); + setPageStack([]); + setPagesViewed(0); + setCursorExpired(true); + } + + return
+
+
+
PII custody
+

Verified forensic audit

+

Each page is tenant-isolated, HMAC-verified, and bounded to 100 immutable forensic events. Event detail is constrained to an approved non-PII schema.

+
+ +
+ + {expired ?

The signed continuation cursor expired or is no longer valid. No page was returned. Restarting begins a new verified scan and discards local page-navigation state to avoid presenting a mixed scan.

: null} + {cursorExpired ?

A new cursor chain is active after expiration recovery.

: null} + {query.isLoading ?
: null} + {!query.isLoading && !query.error ? {formattedEvents.length ? formattedEvents.map((event) =>
event #{event.id}{event.createdAt}

{event.eventType}

{JSON.stringify(event.detail)}

) :

No verified forensic events are available for this tenant.

}
Viewed {pagesViewed} verified page{pagesViewed === 1 ? "" : "s"}
: null} + {cursorHistoryAtLimit ?

Interactive navigation retains at most {MAX_FORENSIC_CURSOR_STACK} opaque cursors. Restart the verified scan to begin a new bounded navigation chain.

: null} + {query.error && !expired ? The verified forensic audit page could not be loaded. No partial result is shown. {query.error.message} : null} +
; +} diff --git a/client/src/pages/ng-screening/NgAdverseActionsPage.tsx b/client/src/pages/ng-screening/NgAdverseActionsPage.tsx index e9dc0065..b1cf22d9 100644 --- a/client/src/pages/ng-screening/NgAdverseActionsPage.tsx +++ b/client/src/pages/ng-screening/NgAdverseActionsPage.tsx @@ -1,254 +1,78 @@ -import { useState } from 'react'; -import { trpc } from '@/lib/trpc'; -import { toast } from 'sonner'; -import { Card, CardContent } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; -import { Textarea } from '@/components/ui/textarea'; -import { Label } from '@/components/ui/label'; -import { Loader2, Gavel, AlertTriangle, CheckCircle, Clock, XCircle } from 'lucide-react'; -import { formatDistanceToNow } from 'date-fns'; +import { useState } from "react"; +import { trpc } from "@/lib/trpc"; +import { toast } from "sonner"; +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Textarea } from "@/components/ui/textarea"; +import { Label } from "@/components/ui/label"; +import { AlertTriangle, CheckCircle2, Clock3, Gavel, Loader2, PauseCircle, Send, ShieldCheck, XCircle } from "lucide-react"; +import { formatDistanceToNow } from "date-fns"; -const STATUS_CONFIG: Record = { - pre_adverse_sent: { label: 'Pre-Adverse Sent', color: 'text-amber-400 bg-amber-500/10 border-amber-500/30', icon: }, - dispute_received: { label: 'Dispute Received', color: 'text-blue-400 bg-blue-500/10 border-blue-500/30', icon: }, - final_adverse_sent:{ label: 'Final Adverse Sent',color: 'text-red-400 bg-red-500/10 border-red-500/30', icon: }, - withdrawn: { label: 'Withdrawn', color: 'text-emerald-400 bg-emerald-500/10 border-emerald-500/30', icon: }, - cleared: { label: 'Cleared', color: 'text-green-400 bg-green-500/10 border-green-500/30', icon: }, +const statusConfig: Record = { + pre_notice_queued: { label: "Pre-notice queued", className: "text-amber-700 bg-amber-50 border-amber-200", icon: }, + pre_notice_delivered: { label: "Pre-notice delivered", className: "text-sky-700 bg-sky-50 border-sky-200", icon: }, + waiting: { label: "Waiting period", className: "text-blue-700 bg-blue-50 border-blue-200", icon: }, + paused_for_dispute: { label: "Paused for dispute", className: "text-violet-700 bg-violet-50 border-violet-200", icon: }, + final_notice_queued: { label: "Final notice queued", className: "text-orange-700 bg-orange-50 border-orange-200", icon: }, + completed: { label: "Completed", className: "text-emerald-700 bg-emerald-50 border-emerald-200", icon: }, + undeliverable: { label: "Undeliverable", className: "text-red-700 bg-red-50 border-red-200", icon: }, + manual_delivery: { label: "Manual delivery required", className: "text-red-700 bg-red-50 border-red-200", icon: }, + canceled: { label: "Canceled", className: "text-slate-700 bg-slate-50 border-slate-200", icon: }, }; -type ResolveOutcome = 'final_adverse_sent' | 'withdrawn' | 'cleared'; +const statuses = ["all", ...Object.keys(statusConfig)]; export default function NgAdverseActionsPage() { - const [statusFilter, setStatusFilter] = useState('all'); - const [offset, setOffset] = useState(0); - const [selected, setSelected] = useState(null); - const [resolveOutcome, setResolveOutcome] = useState('cleared'); - const [resolveNote, setResolveNote] = useState(''); - const [disputeRef, setDisputeRef] = useState(null); - const [disputeNote, setDisputeNote] = useState(''); - const LIMIT = 25; - + const [status, setStatus] = useState("all"); + const [cancelCaseRef, setCancelCaseRef] = useState(null); + const [cancelReason, setCancelReason] = useState(""); const utils = trpc.useUtils(); - - const { data, isLoading } = trpc.ngScreening.adverseAction.list.useQuery({ - status: statusFilter !== 'all' ? statusFilter : undefined, - limit: LIMIT, - offset, - }); - const actions = data?.items ?? []; - const total = data?.total ?? 0; - - const resolveMut = trpc.ngScreening.adverseAction.resolve.useMutation({ - onSuccess: () => { - toast.success('Adverse action resolved'); - utils.ngScreening.adverseAction.list.invalidate(); - setSelected(null); - setResolveNote(''); - }, - onError: (e: any) => toast.error(e.message), - }); - - const disputeMut = trpc.ngScreening.adverseAction.dispute.useMutation({ + const cases = trpc.complianceWorkflow.list.useQuery(status === "all" ? undefined : { status }); + const cancel = trpc.complianceWorkflow.cancel.useMutation({ onSuccess: () => { - toast.success('Dispute recorded'); - utils.ngScreening.adverseAction.list.invalidate(); - setDisputeRef(null); - setDisputeNote(''); + toast.success("Adverse-action workflow canceled and queued deliveries invalidated."); + setCancelCaseRef(null); + setCancelReason(""); + void utils.complianceWorkflow.list.invalidate(); }, - onError: (e: any) => toast.error(e.message), + onError: (error) => toast.error(error.message), }); return ( -
-
+
+
-

Adverse Actions

-

- NDPR-compliant pre-adverse notice, dispute period, and final adverse action workflow -

+
Compliance operations
+

Adverse-action control queue

+

This queue contains workflow references and statuses only. It deliberately does not display candidate PII, report content, raw provider data, or notice text.

- + + {statuses.map((value) => {value === "all" ? "All statuses" : statusConfig[value]?.label ?? value})} -
+ - {isLoading ? ( -
- -
- ) : actions.length === 0 ? ( -
- -

No adverse actions found

-

Adverse actions are initiated when a screening result requires follow-up

-
- ) : ( -
- {actions.map((row: any) => { - const action = row.adverse ?? row; - const statusKey = action.status ?? 'pre_adverse_sent'; - const cfg = STATUS_CONFIG[statusKey] ?? STATUS_CONFIG.pre_adverse_sent; - const canResolve = !['final_adverse_sent','withdrawn','cleared'].includes(statusKey); - return ( - - -
-
-
- - {cfg.icon} - {cfg.label} - - - {action.adverseRef ?? `#${action.id}`} - -
-

- Order: {row.orderRef ?? action.orderId} -

- {(row.candidateFirstName || row.candidateLastName) && ( -

- {row.candidateFirstName} {row.candidateLastName} - {row.candidateEmail && ` · ${row.candidateEmail}`} -

- )} - {action.reason && ( -

{action.reason}

- )} -
-
- {action.preAdverseDeadline && ( - - Deadline: {new Date(action.preAdverseDeadline).toLocaleDateString('en-NG')} - - )} - {action.createdAt ? formatDistanceToNow(new Date(action.createdAt), { addSuffix: true }) : ''} -
-
- {canResolve && ( -
- - {statusKey === 'pre_adverse_sent' && ( - - )} -
- )} -
-
- ); - })} -
- )} - - {/* Pagination */} - {total > LIMIT && ( -
- Showing {offset + 1}–{Math.min(offset + LIMIT, total)} of {total} -
- - -
-
- )} +

Production activation is fail-closed: authorized staff require tenant-scoped policy approval, counsel-approved templates, active encryption keyrings, and a valid signed candidate consent. Notice dispatch remains held until an approved channel adapter is configured.

- {/* Resolve dialog */} - { if (!open) { setSelected(null); setResolveNote(''); } }}> - - - Resolve Adverse Action - -
-

- Under NDPR, the candidate has the right to dispute findings before a final adverse decision is issued. -

-
- - -
-
- -